diff --git a/apps/susimail/src/src/i2p/susi/webmail/pop3/POP3MailBox.java b/apps/susimail/src/src/i2p/susi/webmail/pop3/POP3MailBox.java index 976aa4cdf56829ef206fc0813c50697959b8a4f7..74c46181cee1e9b5a640cc14470f3356df6378a4 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/pop3/POP3MailBox.java +++ b/apps/susimail/src/src/i2p/susi/webmail/pop3/POP3MailBox.java @@ -51,6 +51,7 @@ public class POP3MailBox { private int mails; private boolean connected; + private boolean supportsPipelining; /** ID to size */ private final HashMap<Integer, Integer> sizes; @@ -362,15 +363,17 @@ public class POP3MailBox { try { // pipeline 2 commands lastError = ""; - List<String> cmds = new ArrayList(2); + List<SendRecv> cmds = new ArrayList<SendRecv>(3); // TODO APOP (unsupported by postman) - cmds.add("USER " + user); - cmds.add("PASS " + pass); + cmds.add(new SendRecv(null, Mode.A1)); + // TODO CAPA + cmds.add(new SendRecv("USER " + user, Mode.A1)); + cmds.add(new SendRecv("PASS " + pass, Mode.A1)); // We can't pipleline the STAT because we must // enter the transaction state first. //cmds.add("STAT"); // wait for 3 +OK lines since the connect generates one - if (sendCmds(cmds, 3) && sendCmd1a("STAT")) { + if (sendCmds(cmds) && sendCmd1a("STAT")) { int i = lastLine.indexOf(" ", 5); mails = Integer.parseInt( @@ -431,7 +434,7 @@ public class POP3MailBox { } return result; } - + /** * Send commands to pop3 server all at once (and expect answers). * Sets lastError to the FIRST error. @@ -441,29 +444,70 @@ public class POP3MailBox { * @param rcvLines lines to receive * @return true if ALL received lines were successful (+OK) * @throws IOException - * @since 0.9.13 + * @since 0.9.13 */ - private boolean sendCmds(List<String> cmds, int rcvLines) throws IOException { + private boolean sendCmds(List<SendRecv> cmds) throws IOException { boolean result = true; - for (String cmd : cmds) { - sendCmd1aNoWait(cmd); - } + boolean pipe = supportsPipelining; + if (pipe) { + Debug.debug(Debug.DEBUG, "POP3 pipelining " + cmds.size() + " commands"); + for (SendRecv sr : cmds) { + String cmd = sr.send; + if (cmd != null) + sendCmd1aNoWait(cmd); + } + } // else we will do it below socket.getOutputStream().flush(); InputStream in = socket.getInputStream(); - for (int i = 0; i < rcvLines; i++) { + int i = 0; + for (SendRecv sr : cmds) { + if (!pipe) { + String cmd = sr.send; + if (cmd != null) { + sendCmd1aNoWait(cmd); + socket.getOutputStream().flush(); + } + } String foo = DataHelper.readLine(in); if (foo == null) { lastError = _("No response from server"); throw new IOException(lastError); } - //foo = foo.trim(); // readLine() doesn't strip \r + sr.response = foo.trim(); + i++; if (!foo.startsWith("+OK")) { - Debug.debug(Debug.DEBUG, "Fail after " + (i+1) + " of " + rcvLines + " responses: \"" + foo.trim() + '"'); + Debug.debug(Debug.DEBUG, "Fail after " + i + " of " + cmds.size() + " responses: \"" + foo.trim() + '"'); if (result) lastError = foo; // actually the first error, for better info to the user result = false; + sr.result = false; } else { - Debug.debug(Debug.DEBUG, "OK after " + (i+1) + " of " + rcvLines + " responses: \"" + foo.trim() + '"'); + Debug.debug(Debug.DEBUG, "OK after " + i + " of " + cmds.size() + " responses: \"" + foo.trim() + '"'); + switch (sr.mode) { + case A1: + sr.result = true; + break; + + case RB: + try { + sr.rb = getResultNa(); + sr.result = true; + } catch (IOException ioe) { + result = false; + sr.result = false; + } + break; + + case LS: + try { + sr.ls = getResultNl(); + sr.result = true; + } catch (IOException ioe) { + result = false; + sr.result = false; + } + break; + } } lastLine = foo; } @@ -474,7 +518,7 @@ public class POP3MailBox { * send command to pop3 server. Does NOT flush or read or wait. * Caller must sync. * - * @param cmd command to send + * @param cmd command to send non-null * @throws IOException * @since 0.9.13 */ @@ -532,32 +576,7 @@ public class POP3MailBox { private ReadBuffer sendCmdNa(String cmd) throws IOException { if (sendCmd1a(cmd)) { - InputStream input = socket.getInputStream(); - StringBuilder buf = new StringBuilder(512); - ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); - while (DataHelper.readLine(input, buf)) { - int len = buf.length(); - if (len == 0) - break; // huh? no \r? - if (len == 2 && buf.charAt(0) == '.' && buf.charAt(1) == '\r') - break; - String line; - // RFC 1939 sec. 3 de-byte-stuffing - if (buf.charAt(0) == '.') - line = buf.substring(1); - else - line = buf.toString(); - baos.write(DataHelper.getASCII(line)); - if (buf.charAt(len - 1) != '\r') - baos.write((byte) '\n'); - baos.write((byte) '\n'); - buf.setLength(0); - } - ReadBuffer readBuffer = new ReadBuffer(); - readBuffer.content = baos.toByteArray(); - readBuffer.offset = 0; - readBuffer.length = baos.size(); - return readBuffer; + return getResultNa(); } else { Debug.debug( Debug.DEBUG, "sendCmd1a returned false" ); return null; @@ -577,35 +596,87 @@ public class POP3MailBox { private List<String> sendCmdNl(String cmd) throws IOException { if (sendCmd1a(cmd)) { - List<String> rv = new ArrayList<String>(16); - long timeOut = 120*1000; - InputStream input = socket.getInputStream(); - long startTime = System.currentTimeMillis(); - StringBuilder buf = new StringBuilder(512); - while (DataHelper.readLine(input, buf)) { - int len = buf.length(); - if (len == 0) - break; // huh? no \r? - if (len == 2 && buf.charAt(0) == '.' && buf.charAt(1) == '\r') - break; - if( System.currentTimeMillis() - startTime > timeOut ) - throw new IOException( "Timeout while waiting on server response." ); - String line; - // RFC 1939 sec. 3 de-byte-stuffing - if (buf.charAt(0) == '.') - line = buf.substring(1); - else - line = buf.toString(); - rv.add(line); - buf.setLength(0); - } - return rv; + return getResultNl(); } else { Debug.debug( Debug.DEBUG, "sendCmd1a returned false" ); return null; } } + /** + * No total timeout (result could be large) + * Caller must sync. + * + * @return buffer non-null + * @throws IOException + */ + private ReadBuffer getResultNa() throws IOException + { + InputStream input = socket.getInputStream(); + StringBuilder buf = new StringBuilder(512); + ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); + while (DataHelper.readLine(input, buf)) { + int len = buf.length(); + if (len == 0) + break; // huh? no \r? + if (len == 2 && buf.charAt(0) == '.' && buf.charAt(1) == '\r') + break; + String line; + // RFC 1939 sec. 3 de-byte-stuffing + if (buf.charAt(0) == '.') + line = buf.substring(1); + else + line = buf.toString(); + baos.write(DataHelper.getASCII(line)); + if (buf.charAt(len - 1) != '\r') + baos.write((byte) '\n'); + baos.write((byte) '\n'); + buf.setLength(0); + } + ReadBuffer readBuffer = new ReadBuffer(); + readBuffer.content = baos.toByteArray(); + readBuffer.offset = 0; + readBuffer.length = baos.size(); + return readBuffer; + } + + /** + * Like getResultNa but returns a list of strings, one per line. + * Strings will have trailing \r but not \n. + * Total timeout 2 minutes. + * Caller must sync. + * + * @return the lines non-null + * @throws IOException on timeout + * @since 0.9.13 + */ + private List<String> getResultNl() throws IOException + { + List<String> rv = new ArrayList<String>(16); + long timeOut = 120*1000; + InputStream input = socket.getInputStream(); + long startTime = System.currentTimeMillis(); + StringBuilder buf = new StringBuilder(512); + while (DataHelper.readLine(input, buf)) { + int len = buf.length(); + if (len == 0) + break; // huh? no \r? + if (len == 2 && buf.charAt(0) == '.' && buf.charAt(1) == '\r') + break; + if( System.currentTimeMillis() - startTime > timeOut ) + throw new IOException( "Timeout while waiting on server response." ); + String line; + // RFC 1939 sec. 3 de-byte-stuffing + if (buf.charAt(0) == '.') + line = buf.substring(1); + else + line = buf.toString(); + rv.add(line); + buf.setLength(0); + } + return rv; + } + /** * Warning - forces a connection. * @@ -739,6 +810,34 @@ public class POP3MailBox { } } + private enum Mode { + /** no extra lines (sendCmd1a) */ + A1, + /** return extra lines in ReadBuffer (sendCmdNa) */ + RB, + /** return extra lines in List of Strings (sendCmdNl) */ + LS + } + + /** + * A command to send and a mode to receive and return the results + * @since 0.9.13 + */ + private static class SendRecv { + public final String send; + public final Mode mode; + public String response; + public boolean result; + public ReadBuffer rb; + public List<String> ls; + + /** @param s may be null */ + public SendRecv(String s, Mode m) { + send = s; + mode = m; + } + } + /** translate */ private static String _(String s) { return Messages.getString(s);