diff --git a/apps/susimail/src/src/i2p/susi/webmail/MailPart.java b/apps/susimail/src/src/i2p/susi/webmail/MailPart.java index 14d4075acf75a0c1482276b23c7831720b1413f3..22eeae2b4bacd4e9da1efb1e496b7a6cef55f1e7 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/MailPart.java +++ b/apps/susimail/src/src/i2p/susi/webmail/MailPart.java @@ -94,17 +94,18 @@ class MailPart { for( int i = 0; i < headerLines.length; i++ ) { - if( headerLines[i].toLowerCase(Locale.US).startsWith( "content-transfer-encoding: " ) ) { + String hlc = headerLines[i].toLowerCase(Locale.US); + if( hlc.startsWith( "content-transfer-encoding: " ) ) { x_encoding = getFirstAttribute( headerLines[i] ).toLowerCase(Locale.US); } - else if( headerLines[i].toLowerCase(Locale.US).startsWith( "content-disposition: " ) ) { + else if( hlc.startsWith( "content-disposition: " ) ) { x_disposition = getFirstAttribute( headerLines[i] ).toLowerCase(Locale.US); String str; str = getHeaderLineAttribute( headerLines[i], "filename" ); if( str != null ) x_name = str; } - else if( headerLines[i].toLowerCase(Locale.US).startsWith( "content-type: " ) ) { + else if( hlc.startsWith( "content-type: " ) ) { x_type = getFirstAttribute( headerLines[i] ).toLowerCase(Locale.US); /* * extract boundary, name and charset from content type @@ -124,10 +125,10 @@ class MailPart { if( str != null ) x_charset = str.toUpperCase(Locale.US); } - else if( headerLines[i].toLowerCase(Locale.US).startsWith( "content-description: " ) ) { + else if( hlc.startsWith( "content-description: " ) ) { x_description = getFirstAttribute( headerLines[i] ); } - else if( headerLines[i].toLowerCase(Locale.US).startsWith( "mime-version: " ) ) { + else if( hlc.startsWith( "mime-version: " ) ) { x_version = getFirstAttribute( headerLines[i] ); } } diff --git a/apps/susimail/src/src/i2p/susi/webmail/encoding/Base64.java b/apps/susimail/src/src/i2p/susi/webmail/encoding/Base64.java index 0572ec4bfd96a956b589eab8480ba1823df0aad0..be82a28a14217a9a2544ca9b48c769be5832da99 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/encoding/Base64.java +++ b/apps/susimail/src/src/i2p/susi/webmail/encoding/Base64.java @@ -31,16 +31,11 @@ import java.io.InputStream; import java.io.StringWriter; import java.io.Writer; -import net.i2p.data.DataHelper; - /** * @author susi */ public class Base64 extends Encoding { - /* (non-Javadoc) - * @see i2p.susi23.util.Encoding#getName() - */ public String getName() { return "base64"; } @@ -166,9 +161,6 @@ public class Base64 extends Encoding { return b; } - /** - * @see Base64#decode(String) - */ public ReadBuffer decode(byte[] in, int offset, int length) throws DecodingException { byte out[] = new byte[length * 3 / 4 + 1 ]; int written = 0; @@ -197,7 +189,7 @@ public class Base64 extends Encoding { length -= 4; } else { - System.err.println( "" ); + //System.err.println( "" ); throw new DecodingException( "Decoding base64 failed (trailing garbage)." ); } } diff --git a/apps/susimail/src/src/i2p/susi/webmail/encoding/EightBit.java b/apps/susimail/src/src/i2p/susi/webmail/encoding/EightBit.java index b385aadd8d7bcd2fa16a08c520e60a619c8d9ea8..e19b035b5686bd32d8fae9acc563ba5596665cdc 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/encoding/EightBit.java +++ b/apps/susimail/src/src/i2p/susi/webmail/encoding/EightBit.java @@ -25,30 +25,32 @@ package i2p.susi.webmail.encoding; import i2p.susi.util.ReadBuffer; -import net.i2p.data.DataHelper; - /** + * Decode only. See encode(). * @author susi */ public class EightBit extends Encoding { - /* (non-Javadoc) - * @see i2p.susi.webmail.encoding.Encoding#getName() - */ public String getName() { return "8bit"; } - /* (non-Javadoc) - * @see i2p.susi.webmail.encoding.Encoding#encode(byte[]) + /** + * TODO would be nice to implement this, as it is supported on the project server, + * but content must be CRLF terminated with a max of 998 chars per line. + * And you can't have leading dots either, we'd have to prevent or double-dot it. + * That would be expensive to check, using either a double read or + * pulling it all into memory. + * So it's prohibitive for attachments. We could do it for the message body, + * since it's in memory already, but that's not much of a win. + * ref: https://stackoverflow.com/questions/29510178/how-to-handle-1000-character-lines-in-8bit-mime + * + * @throws EncodingException always */ public String encode(byte[] in) throws EncodingException { throw new EncodingException("unsupported"); } - /* (non-Javadoc) - * @see i2p.susi.webmail.encoding.Encoding#decode(byte[], int, int) - */ public ReadBuffer decode(byte[] in, int offset, int length) throws DecodingException { return new ReadBuffer(in, offset, length); diff --git a/apps/susimail/src/src/i2p/susi/webmail/encoding/Encoding.java b/apps/susimail/src/src/i2p/susi/webmail/encoding/Encoding.java index 77123b062b0443ebe4c7490741c8c87e289c539f..a6a9d74798c79a331f68317124508802d2ba9bf6 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/encoding/Encoding.java +++ b/apps/susimail/src/src/i2p/susi/webmail/encoding/Encoding.java @@ -42,7 +42,12 @@ public abstract class Encoding { public abstract String getName(); /** - * Encode a byte array to a ASCII or ISO-8859-1 String + * Encode a byte array to a ASCII or ISO-8859-1 String. + * Output must be SMTP-safe: Line length of 998 or less, + * using SMTP-safe characters, + * followed by \r\n, and must not start with a '.' + * unless escaped by a 2nd dot. + * For some encodings, max line length is 76. * * @param in * @return Encoded string. @@ -51,7 +56,12 @@ public abstract class Encoding { public abstract String encode( byte in[] ) throws EncodingException; /** - * Encode a (UTF-8) String to a ASCII or ISO-8859-1 String + * Encode a (UTF-8) String to a ASCII or ISO-8859-1 String. + * Output must be SMTP-safe: Line length of 998 or less, + * using SMTP-safe characters, + * followed by \r\n, and must not start with a '.' + * unless escaped by a 2nd dot. + * For some encodings, max line length is 76. * * This implementation just converts the string to a byte array * and then calls encode(byte[]). @@ -67,6 +77,13 @@ public abstract class Encoding { } /** + * Encode an input stream of bytes to a ASCII or ISO-8859-1 String. + * Output must be SMTP-safe: Line length of 998 or less, + * using SMTP-safe characters, + * followed by \r\n, and must not start with a '.' + * unless escaped by a 2nd dot. + * For some encodings, max line length is 76. + * * This implementation just reads the whole stream into memory * and then calls encode(byte[]). * Subclasses should implement a more memory-efficient method diff --git a/apps/susimail/src/src/i2p/susi/webmail/encoding/HTML.java b/apps/susimail/src/src/i2p/susi/webmail/encoding/HTML.java index 1be6733ae995a596282aa2984279cf68e8c56652..0bfc3244974c27c2b1a50cc9a14e760407847f51 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/encoding/HTML.java +++ b/apps/susimail/src/src/i2p/susi/webmail/encoding/HTML.java @@ -30,23 +30,14 @@ import i2p.susi.util.ReadBuffer; */ public class HTML extends Encoding { - /* (non-Javadoc) - * @see i2p.susi.webmail.encoding.Encoding#getName() - */ public String getName() { return "HTML"; } - /* (non-Javadoc) - * @see i2p.susi.webmail.encoding.Encoding#encode(byte[]) - */ public String encode(byte[] in) throws EncodingException { throw new EncodingException("unsupported"); } - /* (non-Javadoc) - * @see i2p.susi.webmail.encoding.Encoding#encode(java.lang.String) - */ @Override public String encode(String str) throws EncodingException { @@ -56,9 +47,6 @@ public class HTML extends Encoding { .replaceAll( "\r{0,1}\n", "<br>\r\n" ); } - /* (non-Javadoc) - * @see i2p.susi.webmail.encoding.Encoding#decode(byte[], int, int) - */ public ReadBuffer decode(byte[] in, int offset, int length) throws DecodingException { throw new DecodingException("unsupported"); diff --git a/apps/susimail/src/src/i2p/susi/webmail/encoding/HeaderLine.java b/apps/susimail/src/src/i2p/susi/webmail/encoding/HeaderLine.java index 509d889d3603936574a2616ccfc3b43000e68251..560eaa330ecc0e0f77d900660f8d62ccdeba935b 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/encoding/HeaderLine.java +++ b/apps/susimail/src/src/i2p/susi/webmail/encoding/HeaderLine.java @@ -44,17 +44,13 @@ import net.i2p.data.DataHelper; */ public class HeaderLine extends Encoding { public static final String NAME = "HEADERLINE"; - /* (non-Javadoc) - * @see i2p.susi.webmail.encoding.Encoding#getName() - */ + public String getName() { return NAME; } private static final int BUFSIZE = 2; - /* (non-Javadoc) - * @see i2p.susi.webmail.encoding.Encoding#encode(byte[]) - */ + public String encode( byte in[] ) throws EncodingException { StringBuilder out = new StringBuilder(); int l = 0, buffered = 0, tmp[] = new int[BUFSIZE]; @@ -148,9 +144,6 @@ public class HeaderLine extends Encoding { return out.toString(); } - /* (non-Javadoc) - * @see i2p.susi.webmail.encoding.Encoding#decode(java.lang.String) - */ public ReadBuffer decode( byte in[], int offset, int length ) throws DecodingException { ByteArrayOutputStream out = new ByteArrayOutputStream(4096); int written = 0; diff --git a/apps/susimail/src/src/i2p/susi/webmail/encoding/QuotedPrintable.java b/apps/susimail/src/src/i2p/susi/webmail/encoding/QuotedPrintable.java index e4b769657da32b059b314dd6ba3c0b080bec9a8f..30ec5d81196012ff49f2a12e1ceccf28f7bca431 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/encoding/QuotedPrintable.java +++ b/apps/susimail/src/src/i2p/susi/webmail/encoding/QuotedPrintable.java @@ -32,26 +32,18 @@ import java.io.InputStream; import java.io.StringWriter; import java.io.Writer; -import net.i2p.data.DataHelper; - /** * ref: https://en.wikipedia.org/wiki/Quoted-printable * @author susi */ public class QuotedPrintable extends Encoding { - /* (non-Javadoc) - * @see i2p.susi.webmail.encoding.Encoding#getName() - */ public String getName() { return "quoted-printable"; } private static int BUFSIZE = 2; - /* (non-Javadoc) - * @see i2p.susi.webmail.encoding.Encoding#encode(byte[]) - */ public String encode( byte in[] ) throws EncodingException { try { StringWriter strBuf = new StringWriter(); @@ -142,9 +134,6 @@ public class QuotedPrintable extends Encoding { } } - /* (non-Javadoc) - * @see i2p.susi.webmail.encoding.Encoding#decode(byte[], int, int) - */ public ReadBuffer decode(byte[] in, int offset, int length) { byte[] out = new byte[length]; int written = 0; diff --git a/apps/susimail/src/src/i2p/susi/webmail/encoding/SevenBit.java b/apps/susimail/src/src/i2p/susi/webmail/encoding/SevenBit.java index 0cf84d063ffd4ebd69652f6de735aed9914c0f17..7bca4b47db13621dc5d45b731c65fc79e309d781 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/encoding/SevenBit.java +++ b/apps/susimail/src/src/i2p/susi/webmail/encoding/SevenBit.java @@ -25,29 +25,25 @@ package i2p.susi.webmail.encoding; import i2p.susi.util.ReadBuffer; -import net.i2p.data.DataHelper; - /** + * Decode only. * @author susi */ public class SevenBit extends Encoding { - /* (non-Javadoc) - * @see i2p.susi23.mail.encoding.Encoding#getName() - */ public String getName() { return "7bit"; } - /* (non-Javadoc) - * @see i2p.susi23.mail.encoding.Encoding#encode(byte[]) + /** + * @throws EncodingException always */ public String encode(byte[] in) throws EncodingException { throw new EncodingException("unsupported"); } - /* (non-Javadoc) - * @see i2p.susi23.mail.encoding.Encoding#decode(byte[], int, int) + /** + * @throws DecodingException on illegal characters */ public ReadBuffer decode(byte[] in, int offset, int length) throws DecodingException { diff --git a/apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java b/apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java index 2383625a852486e3c7c4d2cf00ecc9ef113a2275..cf40c05c58726d0fbf22295ad0cdec2358438e66 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java +++ b/apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java @@ -64,7 +64,8 @@ public class SMTPClient { private Socket socket; public String error; private String lastResponse; - private boolean supportsPipelining; + private boolean supportsPipelining, eightBitMime; + private long maxSize = DEFAULT_MAX_SIZE; private static final Encoding base64; @@ -258,12 +259,44 @@ public class SMTPClient { socket.setSoTimeout(60*1000); Result r = getFullResult(); if (r.result == 250) { - supportsPipelining = r.recv.contains("PIPELINING"); + String[] caps = DataHelper.split(r.recv, "\r"); + for (String c : caps) { + if (c.equals("PIPELINING")) { + supportsPipelining = true; + Debug.debug(Debug.DEBUG, "Server supports pipelining"); + } else if (c.startsWith("SIZE ")) { + try { + maxSize = Long.parseLong(c.substring(5)); + Debug.debug(Debug.DEBUG, "Server max size: " + maxSize); + } catch (NumberFormatException nfe) {} + } else if (c.equals("8BITMIME")) { + // unused, see encoding/EightBit.java + eightBitMime = true; + Debug.debug(Debug.DEBUG, "Server supports 8bitmime"); + } + } } else { error += _t("Server refused connection") + " (" + r + ")\n"; ok = false; } } + if (ok && maxSize < DEFAULT_MAX_SIZE) { + Debug.debug(Debug.DEBUG, "Rechecking with new max size"); + // recalculate whether we'll fit + // copied from WebMail + long total = body.length(); + if (attachments != null && !attachments.isEmpty()) { + for(Attachment a : attachments) { + total += a.getSize(); + } + } + long binaryMax = (long) ((maxSize * 57.0d / 78) - 32*1024); + if (total > binaryMax) { + ok = false; + error += _t("Email is too large, max is {0}", + DataHelper.formatSize2(binaryMax, false) + 'B') + '\n'; + } + } if (ok) { // RFC 4954 says AUTH must be the last but let's assume // that includes the user/pass on following lines