I want to implement a simple IMAP client. I can connect to server, receive its greeting but it seems I have a problem receiving the response after the login
command.
I know that in IMAP line ends with \r\n
or .\r\n
but it seems that reading one byte a time simply does not work here and I do not know why.
My code:
class NewClass {
public static String readAll(Socket socket, String end) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while (!sb.toString().endsWith(end))
{
char c = (char)reader.read();
sb.append(c);
}
return sb.toString();
}
public static void main(String[] args) throws IOException {
String host = "x.x.x.x";
int port = 143;
Socket socket = new Socket(host, port);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
// server's greeting
String initreply = readAll(socket, "\r\n");
System.out.println(initreply);
// log me in
String login_cmd = "A1 LOGIN user pass\r\n";
writer.print(login_cmd);
writer.flush();
String loginreply = readAll(socket, "\r\n");
System.out.println(loginreply);
}
}
There is nothing simply about IMAP, it is a very complex protocol to implement from scratch. Consider finding a pre-existing IMAP library instead.
That being said, it is clear that you did not read the IMAP specification, RFC 3501 (in particular, section 2.2 "Commands and Responses"), or you would know why your code is failing. Your code is not compliant with the spec.
In particular:
you are not sending a trailing
\r\n
at the end of yourLOGIN
command, so the server will not send any response.PrintWriter.print()
does not print a line break.PrintWriter.println()
does, but it only prints a LF, but IMAP uses CRLF instead. So you need to include\r\n
at the end of every command you send to the server.you are not reading the server response correctly at all. You have to read lines, taking the
*
and+
tokens into account, until you receive a line that starts with the sameA1
tag that you used to tag theLOGIN
command with. The logic would be much easier to implement if you useBufferedReader.readLine()
instead of reading characters individually into aStringBuilder
. And technically, you should be doing the reading asynchronously, such as in a background thread, as IMAP is an asynchronous protocol. There can be multiple commands in flight at one time, and there can be unsolicited data sent by the server in between command responses. The tags in the responses tell you which commands are being responded to, if any. Untagged responses that are prefixed with the*
token should be processed as-is regardless of which command generates them.i made this in python 2 and it worked well
You can always know where your program hangs by taking a thread dump and examining the stack trace of the target thread (hint: if needed you can
Thread.interrupt()
a job waiting in a socket read).Anyway, your program blocks because it's waiting for more input from the server, and the server has nothing to send. Always set a timeout before starting to read:
At this point your program will likely throw a
SocketTimeoutException
and you'll see a call toBufferedReader.read()
among the topmost frames. Assuming that IMAP messages are exactly one line long and terminated byCRLF
, the read routine may be:Hint: install a sniffer like Wireshark to better understand what's going on.