SusiMail: Don't store attachments of composed email in-memory,

encode them on-the fly (ticket #1668)
- Fix bug corrupting sent text and text attachments
  larger than about 1000 chars (output line length was not limited)
- Fix bug corrupting some sent text and text attachments
  containing '.'
- Fix handling of unimplemented encoders
- Error message improvements
- Add test code for encoders
This commit is contained in:
zzz
2017-12-05 15:02:23 +00:00
parent c299976165
commit 7f5f764aba
14 changed files with 359 additions and 135 deletions

View File

@@ -23,14 +23,24 @@
*/
package i2p.susi.webmail;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author user
* @since public since 0.9.33, was package private
*/
class Attachment {
public class Attachment {
private final String fileName, contentType, transferEncoding;
private final String data;
private final File data;
Attachment(String name, String type, String encoding, String data) {
/**
* @param type the content type
* @param encoding the transfer encoding, non-null
*/
Attachment(String name, String type, String encoding, File data) {
fileName = name;
contentType = type;
transferEncoding = encoding;
@@ -44,6 +54,9 @@ class Attachment {
return fileName;
}
/**
* @return non-null
*/
public String getTransferEncoding() {
return transferEncoding;
}
@@ -55,7 +68,15 @@ class Attachment {
/**
* @return Returns the data.
*/
public String getData() {
return data;
public InputStream getData() throws IOException {
return new FileInputStream(data);
}
/**
* Delete the data file
* @since 0.9.33
*/
public void deleteData() {
data.delete();
}
}

View File

@@ -42,6 +42,7 @@ import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
@@ -72,6 +73,7 @@ import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.servlet.RequestWrapper;
import net.i2p.servlet.util.ServletUtil;
import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.Translate;
/**
@@ -409,7 +411,7 @@ public class WebMail extends HttpServlet
String user, pass, host, error, info;
String replyTo, replyCC;
String subject, body, showUIDL;
public String sentMail;
public StringBuilder sentMail;
public ArrayList<Attachment> attachments;
public boolean reallyDelete;
String themePath, imgPath;
@@ -475,6 +477,16 @@ public class WebMail extends HttpServlet
return nonces.contains(nonce);
}
}
/** @since 0.9.33 */
public void clearAttachments() {
if (attachments != null) {
for (Attachment a : attachments) {
a.deleteData();
}
attachments.clear();
}
}
}
/**
@@ -914,15 +926,13 @@ public class WebMail extends HttpServlet
buttonPressed( request, LIST )) {
sessionObject.state = STATE_LIST;
sessionObject.sentMail = null;
if( sessionObject.attachments != null )
sessionObject.attachments.clear();
sessionObject.clearAttachments();
} else if (buttonPressed( request, PREV ) || // All these buttons are not shown but we could be lost
buttonPressed( request, NEXT ) ||
buttonPressed( request, DELETE )) {
sessionObject.state = STATE_SHOW;
sessionObject.sentMail = null;
if( sessionObject.attachments != null )
sessionObject.attachments.clear();
sessionObject.clearAttachments();
}
}
/*
@@ -1205,41 +1215,38 @@ public class WebMail extends HttpServlet
if( i != -1 )
filename = filename.substring( i + 1 );
if( filename != null && filename.length() > 0 ) {
InputStream in = request.getInputStream( NEW_FILENAME );
int l;
InputStream in = null;
OutputStream out = null;
I2PAppContext ctx = I2PAppContext.getGlobalContext();
File f = new File(ctx.getTempDir(), "susimail-attachment-" + ctx.random().nextLong());
try {
l = in.available();
if( l > 0 ) {
byte buf[] = new byte[l];
in.read( buf );
String contentType = request.getContentType( NEW_FILENAME );
Encoding encoding;
String encodeTo;
if( contentType.toLowerCase(Locale.US).startsWith( "text/" ) )
encodeTo = "quoted-printable";
else
encodeTo = "base64";
encoding = EncodingFactory.getEncoding( encodeTo );
try {
if( encoding != null ) {
String data = encoding.encode(buf);
if( sessionObject.attachments == null )
sessionObject.attachments = new ArrayList<Attachment>();
sessionObject.attachments.add(
new Attachment(filename, contentType, encodeTo, data)
);
}
else {
sessionObject.error += _t("No Encoding found for {0}", encodeTo) + '\n';
}
}
catch (EncodingException e1) {
sessionObject.error += _t("Could not encode data: {0}", e1.getMessage());
}
in = request.getInputStream( NEW_FILENAME );
if(in == null)
throw new IOException("no stream");
out = new SecureFileOutputStream(f);
DataHelper.copy(in, out);
String contentType = request.getContentType( NEW_FILENAME );
String encodeTo;
if( contentType.toLowerCase(Locale.US).startsWith( "text/" ) )
encodeTo = "quoted-printable";
else
encodeTo = "base64";
Encoding encoding = EncodingFactory.getEncoding(encodeTo);
if (encoding != null) {
if (sessionObject.attachments == null)
sessionObject.attachments = new ArrayList<Attachment>();
sessionObject.attachments.add(
new Attachment(filename, contentType, encodeTo, f)
);
} else {
sessionObject.error += _t("No Encoding found for {0}", encodeTo) + '\n';
}
}
catch (IOException e) {
} catch (IOException e) {
sessionObject.error += _t("Error reading uploaded file: {0}", e.getMessage()) + '\n';
f.delete();
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (out != null) try { out.close(); } catch (IOException ioe) {}
}
}
}
@@ -2021,7 +2028,7 @@ public class WebMail extends HttpServlet
}
if( ok ) {
StringBuilder body = new StringBuilder();
StringBuilder body = new StringBuilder(1024);
body.append( "From: " + from + "\r\n" );
Mail.appendRecipients( body, toList, "To: " );
Mail.appendRecipients( body, ccList, "To: " );
@@ -2042,6 +2049,7 @@ public class WebMail extends HttpServlet
body.append( "\r\nMIME-Version: 1.0\r\nContent-type: text/plain; charset=\"utf-8\"\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n" );
}
try {
// TODO pass the text separately to SMTP and let it pick the encoding
if( multipart )
body.append( "--" + boundary + "\r\nContent-type: text/plain; charset=\"utf-8\"\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n" );
body.append( qp.encode( text ) );
@@ -2050,28 +2058,18 @@ public class WebMail extends HttpServlet
sessionObject.error += e.getMessage();
}
if( multipart ) {
for( Attachment attachment : sessionObject.attachments ) {
body.append( "\r\n--" + boundary + "\r\nContent-type: " + attachment.getContentType() + "\r\nContent-Disposition: attachment; filename=\"" + attachment.getFileName() + "\"\r\nContent-Transfer-Encoding: " + attachment.getTransferEncoding() + "\r\n\r\n" );
body.append( attachment.getData() );
}
body.append( "\r\n--" + boundary + "--\r\n" );
}
// TODO set to the StringBuilder instead so SMTP can replace() in place
sessionObject.sentMail = body.toString();
// set to the StringBuilder so SMTP can replace() in place
sessionObject.sentMail = body;
if( ok ) {
SMTPClient relay = new SMTPClient();
if( relay.sendMail( sessionObject.host, sessionObject.smtpPort,
sessionObject.user, sessionObject.pass,
sender, recipients.toArray(), sessionObject.sentMail ) ) {
sender, recipients.toArray(), sessionObject.sentMail,
sessionObject.attachments, boundary)) {
sessionObject.info += _t("Mail sent.");
sessionObject.sentMail = null;
if( sessionObject.attachments != null )
sessionObject.attachments.clear();
sessionObject.clearAttachments();
}
else {
ok = false;

View File

@@ -28,13 +28,15 @@ import i2p.susi.util.ReadBuffer;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.Writer;
import net.i2p.data.DataHelper;
/**
* @author susi
*/
public class Base64 implements Encoding {
public class Base64 extends Encoding {
/* (non-Javadoc)
* @see i2p.susi23.util.Encoding#getName()
@@ -42,6 +44,7 @@ public class Base64 implements Encoding {
public String getName() {
return "base64";
}
/**
* @return Base64-encoded String.
* @throws EncodingException
@@ -49,30 +52,37 @@ public class Base64 implements Encoding {
public String encode( byte in[] ) throws EncodingException
{
try {
return encode( new ByteArrayInputStream( in ) );
StringWriter strBuf = new StringWriter();
encode(new ByteArrayInputStream(in), strBuf);
return strBuf.toString();
}catch (IOException e) {
throw new EncodingException( e.getMessage() );
throw new EncodingException("encode error", e);
}
}
/**
* @see Base64#encode(byte[])
*/
public String encode(String str) throws EncodingException {
try {
return encode( new ByteArrayInputStream( DataHelper.getUTF8(str) ) );
StringWriter strBuf = new StringWriter();
encode(new ByteArrayInputStream(DataHelper.getUTF8(str)), strBuf);
return strBuf.toString();
}catch (IOException e) {
throw new EncodingException( e.getMessage() );
throw new EncodingException("encode error", e);
}
}
/**
* More efficient than super
*
* @param in
* @see Base64#encode(String)
* @since public since 0.9.33 with new params
*/
private String encode( InputStream in ) throws IOException, EncodingException
@Override
public void encode(InputStream in, Writer strBuf) throws IOException
{
StringBuilder strBuf = new StringBuilder();
int buf[] = new int[3];
int out[] = new int[4];
int l = 0;
@@ -111,8 +121,6 @@ public class Base64 implements Encoding {
l -= 76;
}
}
return strBuf.toString();
}
/**

View File

@@ -30,7 +30,7 @@ import net.i2p.data.DataHelper;
/**
* @author susi
*/
public class EightBit implements Encoding {
public class EightBit extends Encoding {
/* (non-Javadoc)
* @see i2p.susi.webmail.encoding.Encoding#getName()
@@ -42,17 +42,15 @@ public class EightBit implements Encoding {
/* (non-Javadoc)
* @see i2p.susi.webmail.encoding.Encoding#encode(byte[])
*/
public String encode(byte[] in) {
// TODO Auto-generated method stub
return null;
public String encode(byte[] in) throws EncodingException {
throw new EncodingException("unsupported");
}
/* (non-Javadoc)
* @see i2p.susi.webmail.encoding.Encoding#encode(java.lang.String)
*/
public String encode(String str) {
// TODO Auto-generated method stub
return null;
public String encode(String str) throws EncodingException {
throw new EncodingException("unsupported");
}
/* (non-Javadoc)

View File

@@ -23,36 +23,58 @@
*/
package i2p.susi.webmail.encoding;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import i2p.susi.util.ReadBuffer;
import net.i2p.data.DataHelper;
/**
* Interface to encode/decode content transfer encodings like quoted-printable, base64 etc.
*
* @author susi
*/
public interface Encoding {
public String getName();
public abstract class Encoding {
public abstract String getName();
/**
*
* @param in
* @return Encoded string.
* @throws EncodingException
*/
public String encode( byte in[] ) throws EncodingException;
public abstract String encode( byte in[] ) throws EncodingException;
/**
*
* @param str
* @see Encoding#encode(byte[])
* @throws EncodingException
*/
public String encode( String str ) throws EncodingException;
public abstract String encode( String str ) throws EncodingException;
/**
* This implementation just reads the whole stream into memory
* and then calls encode(byte[]).
* Subclasses should implement a more memory-efficient method
* if large inputs are expected.
*
* @since 0.9.33
*/
public void encode(InputStream in, Writer out) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataHelper.copy(in, baos);
out.write(encode(baos.toByteArray()));
}
/**
*
* @param in
* @see Encoding#decode(byte[], int, int)
* @throws DecodingException
*/
public ReadBuffer decode( byte in[] ) throws DecodingException;
public abstract ReadBuffer decode( byte in[] ) throws DecodingException;
/**
*
* @param in
@@ -61,19 +83,19 @@ public interface Encoding {
* @return Output buffer containing decoded String.
* @throws DecodingException
*/
public ReadBuffer decode( byte in[], int offset, int length ) throws DecodingException;
public abstract ReadBuffer decode( byte in[], int offset, int length ) throws DecodingException;
/**
*
* @param str
* @see Encoding#decode(byte[], int, int)
* @throws DecodingException
*/
public ReadBuffer decode( String str ) throws DecodingException;
public abstract ReadBuffer decode( String str ) throws DecodingException;
/**
*
* @param in
* @see Encoding#decode(byte[], int, int)
* @throws DecodingException
*/
public ReadBuffer decode( ReadBuffer in ) throws DecodingException;
public abstract ReadBuffer decode( ReadBuffer in ) throws DecodingException;
}

View File

@@ -23,18 +23,32 @@
*/
package i2p.susi.webmail.encoding;
import java.io.IOException;
/**
* Converted from Exception to IOException in 0.9.33
* @author susi
*/
public class EncodingException extends Exception {
public class EncodingException extends IOException {
/**
* Comment for <code>serialVersionUID</code>
*/
private static final long serialVersionUID = 1L;
EncodingException( String msg )
/**
* @since public since 0.9.33, was package private
*/
public EncodingException( String msg )
{
super( msg );
}
/**
* @since 0.9.33
*/
public EncodingException(String msg, Exception cause)
{
super(msg, cause);
}
}

View File

@@ -25,11 +25,16 @@ package i2p.susi.webmail.encoding;
import i2p.susi.debug.Debug;
import i2p.susi.util.Config;
import i2p.susi.util.ReadBuffer;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import net.i2p.data.DataHelper;
import net.i2p.util.HexDump;
/**
* Manager class to handle content transfer encodings.
* @author susi
@@ -60,7 +65,10 @@ public class EncodingFactory {
}
}
}
// TEST
//main(null);
}
/**
* Retrieve instance of an encoder for a supported encoding (or null).
*
@@ -81,4 +89,45 @@ public class EncodingFactory {
{
return encodings.keySet();
}
/****
public static void main(String[] args) {
String text = "Subject: test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test \r\n" +
"From: UTF8 <smoerebroed@mail.i2p>\r\n" +
"To: UTF8 <lalala@mail.i2p>\r\n";
byte[] test = DataHelper.getUTF8(text);
for (String s : availableEncodings()) {
Encoding e = getEncoding(s);
try {
String enc = e.encode(test);
if (enc == null) {
System.out.println(s + "\tFAIL - null encode result");
continue;
}
ReadBuffer rb = e.decode(enc);
if (rb == null) {
System.out.println(s + "\tFAIL - null decode result");
continue;
}
byte[] result = rb.content;
if (DataHelper.eq(test, 0, result, rb.offset, test.length)) {
System.out.println(s + "\tPASS");
System.out.println("encoding:");
System.out.println('"' + enc + '"');
} else {
System.out.println(s + "\tFAIL");
System.out.println("expected:");
System.out.println(HexDump.dump(test));
System.out.println("got:");
System.out.println(HexDump.dump(result, rb.offset, rb.length));
System.out.println("encoding:");
System.out.println('"' + enc + '"');
}
} catch (Exception ex) {
System.out.println(s + "\tFAIL");
ex.printStackTrace();
}
}
}
****/
}

View File

@@ -28,7 +28,7 @@ import i2p.susi.util.ReadBuffer;
/**
* @author user
*/
public class HTML implements Encoding {
public class HTML extends Encoding {
/* (non-Javadoc)
* @see i2p.susi.webmail.encoding.Encoding#getName()
@@ -41,8 +41,7 @@ public class HTML implements Encoding {
* @see i2p.susi.webmail.encoding.Encoding#encode(byte[])
*/
public String encode(byte[] in) throws EncodingException {
// TODO Auto-generated method stub
return null;
throw new EncodingException("unsupported");
}
/* (non-Javadoc)
@@ -60,8 +59,7 @@ public class HTML implements Encoding {
* @see i2p.susi.webmail.encoding.Encoding#decode(byte[])
*/
public ReadBuffer decode(byte[] in) throws DecodingException {
// TODO Auto-generated method stub
return null;
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
@@ -69,24 +67,21 @@ public class HTML implements Encoding {
*/
public ReadBuffer decode(byte[] in, int offset, int length)
throws DecodingException {
// TODO Auto-generated method stub
return null;
throw new DecodingException("unsupported");
}
/* (non-Javadoc)
* @see i2p.susi.webmail.encoding.Encoding#decode(java.lang.String)
*/
public ReadBuffer decode(String str) throws DecodingException {
// TODO Auto-generated method stub
return null;
throw new DecodingException("unsupported");
}
/* (non-Javadoc)
* @see i2p.susi.webmail.encoding.Encoding#decode(i2p.susi.webmail.util.ReadBuffer)
*/
public ReadBuffer decode(ReadBuffer in) throws DecodingException {
// TODO Auto-generated method stub
return null;
throw new DecodingException("unsupported");
}
}

View File

@@ -42,7 +42,7 @@ import net.i2p.data.DataHelper;
*
* @author susi
*/
public class HeaderLine implements Encoding {
public class HeaderLine extends Encoding {
public static final String NAME = "HEADERLINE";
/* (non-Javadoc)
* @see i2p.susi.webmail.encoding.Encoding#getName()

View File

@@ -29,13 +29,16 @@ import i2p.susi.util.ReadBuffer;
import java.io.ByteArrayInputStream;
import java.io.IOException;
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 implements Encoding {
public class QuotedPrintable extends Encoding {
/* (non-Javadoc)
* @see i2p.susi.webmail.encoding.Encoding#getName()
@@ -49,24 +52,41 @@ public class QuotedPrintable implements Encoding {
public String encode(String text) throws EncodingException {
return encode( DataHelper.getUTF8(text) );
}
/**
*
* @param in
* @return
*/
private static int BUFSIZE = 2;
/* (non-Javadoc)
* @see i2p.susi.webmail.encoding.Encoding#encode(byte[])
*/
public String encode( byte in[] ) throws EncodingException {
StringBuilder out = new StringBuilder();
try {
StringWriter strBuf = new StringWriter();
encode(new ByteArrayInputStream(in), strBuf);
return strBuf.toString();
} catch (IOException e) {
throw new EncodingException("encode error", e);
}
}
/**
* More efficient than super
*
* @param in
* @see Base64#encode(String)
* @since since 0.9.33
*/
@Override
public void encode(InputStream in, Writer out) throws IOException
{
int buffered = 0, tmp[] = new int[BUFSIZE];
int read = in.length;
int index = 0;
int l = 0;
while( true ) {
while( read > 0 && buffered < BUFSIZE ) {
tmp[buffered++] = in[index++];
read--;
int read = 0;
int r;
while(buffered < BUFSIZE && (r = in.read()) >= 0) {
tmp[buffered++] = r;
read++;
}
if( read == 0 && buffered == 0 )
break;
@@ -76,31 +96,56 @@ public class QuotedPrintable implements Encoding {
for( int j = 1; j < BUFSIZE; j++ )
tmp[j-1] = tmp[j];
if( c > 32 && c < 127 && c != 61 ) {
if (c == 46 && l == 0) {
// leading '.' seems to get eaten by SMTP,
// even if more chars after it
String s = HexTable.table[46];
l = s.length();
out.append(s);
} else if (c > 32 && c < 127 && c != 61) {
out.append( (char)c );
l++;
}
else if( ( c == 32 || c == 9 ) ) {
if( buffered > 0 && ( tmp[0] == 10 || tmp[0] == 13 ) ) {
/*
* whitespace at end of line
*/
if (l >= 73) {
// soft line breaks
out.append("=\r\n");
l = 0;
}
out.append( c == 32 ? "=20" : "=09" );
l += 3;
}
else {
out.append( (char)c );
l++;
}
}
else if( c == 13 && buffered > 0 && tmp[0] == 10 ) {
out.append( "\r\n" );
l = 0;
buffered--;
for( int j = 1; j < BUFSIZE; j++ )
tmp[j-1] = tmp[j];
} else {
String s = HexTable.table[ c < 0 ? 256 + c : c ];
l += s.length();
if (l > 75) {
// soft line breaks
out.append("=\r\n");
l = s.length();
}
out.append(s);
}
else {
out.append( HexTable.table[ c < 0 ? 256 + c : c ] );
if (l >= 75) {
// soft line breaks
out.append("=\r\n");
l = 0;
}
}
return out.toString();
}
/* (non-Javadoc)

View File

@@ -30,7 +30,7 @@ import net.i2p.data.DataHelper;
/**
* @author susi
*/
public class SevenBit implements Encoding {
public class SevenBit extends Encoding {
/* (non-Javadoc)
* @see i2p.susi23.mail.encoding.Encoding#getName()
@@ -42,17 +42,15 @@ public class SevenBit implements Encoding {
/* (non-Javadoc)
* @see i2p.susi23.mail.encoding.Encoding#encode(byte[])
*/
public String encode(byte[] in) {
// TODO Auto-generated method stub
return null;
public String encode(byte[] in) throws EncodingException {
throw new EncodingException("unsupported");
}
/* (non-Javadoc)
* @see i2p.susi23.mail.encoding.Encoding#encode(java.lang.String)
*/
public String encode(String str) {
// TODO Auto-generated method stub
return null;
public String encode(String str) throws EncodingException {
throw new EncodingException("unsupported");
}
/* (non-Javadoc)

View File

@@ -24,14 +24,18 @@
package i2p.susi.webmail.smtp;
import i2p.susi.debug.Debug;
import i2p.susi.webmail.Attachment;
import i2p.susi.webmail.Messages;
import i2p.susi.webmail.encoding.Encoding;
import i2p.susi.webmail.encoding.EncodingException;
import i2p.susi.webmail.encoding.EncodingFactory;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
@@ -44,7 +48,6 @@ import net.i2p.data.DataHelper;
public class SMTPClient {
private Socket socket;
private final byte buffer[];
public String error;
private String lastResponse;
private boolean supportsPipelining;
@@ -57,7 +60,6 @@ public class SMTPClient {
public SMTPClient()
{
buffer = new byte[10240];
error = "";
lastResponse = "";
}
@@ -89,7 +91,7 @@ public class SMTPClient {
socket.getOutputStream().flush();
return getResult();
} catch (IOException e) {
error += "IOException occured.\n";
error += e + "\n";
return 0;
}
}
@@ -129,6 +131,7 @@ public class SMTPClient {
}
socket.getOutputStream().flush();
} catch (IOException ioe) {
error += ioe + "\n";
return 0;
}
for (SendExpect cmd : cmds) {
@@ -193,7 +196,7 @@ public class SMTPClient {
buf.setLength(0);
}
} catch (IOException e) {
error += "IOException occured.\n";
error += e + "\n";
result = 0;
}
lastResponse = fullResponse.toString();
@@ -201,17 +204,23 @@ public class SMTPClient {
}
/**
* @param body without the attachments
* @param attachments may be null
* @param boundary non-null if attachments is non-null
* @return success
*/
public boolean sendMail( String host, int port, String user, String pass, String sender, Object[] recipients, String body )
public boolean sendMail(String host, int port, String user, String pass, String sender,
Object[] recipients, StringBuilder body,
List<Attachment> attachments, String boundary)
{
boolean mailSent = false;
boolean ok = true;
Writer out = null;
try {
socket = new Socket( host, port );
} catch (IOException e) {
error += _t("Cannot connect") + ": " + e.getMessage() + '\n';
error += _t("Cannot connect") + " (" + host + ':' + port + ") : " + e.getMessage() + '\n';
ok = false;
}
try {
@@ -222,7 +231,10 @@ public class SMTPClient {
socket.setSoTimeout(120*1000);
int result = sendCmd(null);
if (result != 220) {
error += _t("Server refused connection") + " (" + result + ")\n";
if (result != 0)
error += _t("Server refused connection") + " (" + result + ")\n";
else
error += _t("Cannot connect") + " (" + host + ':' + port + ")\n";
ok = false;
}
}
@@ -234,7 +246,7 @@ public class SMTPClient {
if (r.result == 250) {
supportsPipelining = r.recv.contains("PIPELINING");
} else {
error += _t("Server refused connection") + " (" + r.result + ")\n";
error += _t("Server refused connection") + " (" + r + ")\n";
ok = false;
}
}
@@ -264,10 +276,46 @@ public class SMTPClient {
}
}
if (ok) {
if( body.indexOf( "\r\n.\r\n" ) != -1 )
body = body.replace( "\r\n.\r\n", "\r\n..\r\n" );
socket.getOutputStream().write(DataHelper.getUTF8(body));
socket.getOutputStream().write(DataHelper.getASCII("\r\n.\r\n"));
// in-memory replace, no copies
int oidx = 0;
while (true) {
int idx = body.indexOf("\r\n.\r\n", oidx);
if (idx < 0)
break;
body.replace(idx, idx + 5, "\r\n..\r\n");
oidx = idx;
}
//socket.getOutputStream().write(DataHelper.getUTF8(body));
//socket.getOutputStream().write(DataHelper.getASCII("\r\n.\r\n"));
// Do it this way so we don't double the memory
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "ISO-8859-1"));
out.write(body.toString());
// moved from WebMail so we don't bring the attachments into memory
// Also TODO use the 250 service extension responses to pick the best encoding
// and check the max total size
if (attachments != null && !attachments.isEmpty()) {
for(Attachment attachment : attachments) {
String encodeTo = attachment.getTransferEncoding();
Encoding encoding = EncodingFactory.getEncoding(encodeTo);
if (encoding == null)
throw new EncodingException( _t("No Encoding found for {0}", encodeTo));
out.write("\r\n--" + boundary +
"\r\nContent-type: " + attachment.getContentType() +
"\r\nContent-Disposition: attachment; filename=\"" + attachment.getFileName() +
"\"\r\nContent-Transfer-Encoding: " + attachment.getTransferEncoding() +
"\r\n\r\n");
InputStream in = null;
try {
in = attachment.getData();
encoding.encode(in, out);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
}
out.write( "\r\n--" + boundary + "--\r\n" );
}
out.write("\r\n.\r\n");
out.flush();
socket.setSoTimeout(0);
int result = sendCmd(null);
if (result == 250)
@@ -277,9 +325,6 @@ public class SMTPClient {
}
} catch (IOException e) {
error += _t("Error sending mail") + ": " + e.getMessage() + '\n';
} catch (EncodingException e) {
error += e.getMessage();
}
if( !mailSent && lastResponse.length() > 0 ) {
String[] lines = DataHelper.split(lastResponse, "\r");
@@ -291,6 +336,7 @@ public class SMTPClient {
try {
socket.close();
} catch (IOException e1) {}
if (out != null) try { out.close(); } catch (IOException ioe) {}
}
return mailSent;
}
@@ -317,14 +363,30 @@ public class SMTPClient {
public final int result;
public final String recv;
/** @param t non-null */
public Result(int r, String t) {
result = r;
recv = t;
}
/** @since 0.9.33 */
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(result);
if (recv.length() > 0)
buf.append(' ').append(recv);
return buf.toString();
}
}
/** translate */
private static String _t(String s) {
return Messages.getString(s);
}
/** translate */
private static String _t(String s, Object o) {
return Messages.getString(s, o);
}
}

View File

@@ -1,3 +1,17 @@
2017-12-05 zzz
* SusiMail:
- Don't store attachments of composed email in-memory (ticket #1668)
- Fix bug corrupting sent text and text attachments
larger than about 1000 chars
- Fix bug corrupting some sent text and text attachments
containing '.'
- Fix handling of unimplemented encoders
- Add test code for encoders
- Error message improvements
2017-12-04 zzz
* Servlet: Refactor RequestWrapper to use Servlet 3.0 API (ticket #2109)
2017-12-03 zzz
* i2ptunnel:
- Don't lose messages on refresh (ticket #2107)

View File

@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 12;
public final static long BUILD = 13;
/** for example "-test" */
public final static String EXTRA = "";