* SusiMail:

- Use pipelining in SMTP
   - Rewrite SMTP response processing
   - Translate SMTP error messages
   - Right-justify msg size in folder view
   - String.compareTo() cleanup
This commit is contained in:
zzz
2014-04-20 01:26:11 +00:00
parent 552ab31559
commit b75ad1ca5a
2 changed files with 229 additions and 100 deletions

View File

@@ -415,10 +415,10 @@ public class WebMail extends HttpServlet
}
if( mailPart.multipart ) {
if( mailPart.type.compareTo( "multipart/alternative" ) == 0 ) {
if( mailPart.type.equals("multipart/alternative")) {
MailPart chosen = null;
for( MailPart subPart : mailPart.parts ) {
if( subPart.type != null && subPart.type.compareTo( "text/plain" ) == 0 )
if( subPart.type != null && subPart.type.equals("text/plain"))
chosen = subPart;
}
if( chosen != null ) {
@@ -454,7 +454,7 @@ public class WebMail extends HttpServlet
showBody = true;
}
if( showBody == false && mailPart.type != null ) {
if( mailPart.type.compareTo( "text/plain" ) == 0 ) {
if( mailPart.type.equals("text/plain")) {
showBody = true;
}
else
@@ -559,7 +559,7 @@ public class WebMail extends HttpServlet
String pop3Port = request.getParameter( POP3 );
String smtpPort = request.getParameter( SMTP );
String fixedPorts = Config.getProperty( CONFIG_PORTS_FIXED, "true" );
if( fixedPorts.compareToIgnoreCase( "false" ) != 0 ) {
if( !fixedPorts.equalsIgnoreCase("false")) {
host = Config.getProperty( CONFIG_HOST, DEFAULT_HOST );
pop3Port = Config.getProperty( CONFIG_PORTS_POP3, "" + DEFAULT_POP3PORT );
smtpPort = Config.getProperty( CONFIG_PORTS_SMTP, "" + DEFAULT_SMTPPORT );
@@ -885,7 +885,7 @@ public class WebMail extends HttpServlet
private static int getCheckedMessage(RequestWrapper request) {
for( Enumeration<String> e = request.getParameterNames(); e.hasMoreElements(); ) {
String parameter = e.nextElement();
if( parameter.startsWith( "check" ) && request.getParameter( parameter ).compareTo( "1" ) == 0 ) {
if( parameter.startsWith( "check" ) && request.getParameter( parameter ).equals("1")) {
String number = parameter.substring( 5 );
try {
int n = Integer.parseInt( number );
@@ -972,7 +972,7 @@ public class WebMail extends HttpServlet
else if( sessionObject.attachments != null && buttonPressed( request, DELETE_ATTACHMENT ) ) {
for( Enumeration<String> e = request.getParameterNames(); e.hasMoreElements(); ) {
String parameter = e.nextElement();
if( parameter.startsWith( "check" ) && request.getParameter( parameter ).compareTo( "1" ) == 0 ) {
if( parameter.startsWith( "check" ) && request.getParameter( parameter ).equals("1")) {
String number = parameter.substring( 5 );
try {
int n = Integer.parseInt( number );
@@ -1119,7 +1119,7 @@ public class WebMail extends HttpServlet
if( buttonPressed( request, REALLYDELETE ) ) {
for( Enumeration<String> e = request.getParameterNames(); e.hasMoreElements(); ) {
String parameter = e.nextElement();
if( parameter.startsWith( "check" ) && request.getParameter( parameter ).compareTo( "1" ) == 0 ) {
if( parameter.startsWith( "check" ) && request.getParameter( parameter ).equals("1")) {
String number = parameter.substring( 5 );
try {
int n = Integer.parseInt( number );
@@ -1170,11 +1170,11 @@ public class WebMail extends HttpServlet
{
String str = request.getParameter( sort_id );
if( str != null ) {
if( str.compareToIgnoreCase( "up" ) == 0 ) {
if( str.equalsIgnoreCase("up")) {
sessionObject.folder.setSortingDirection( Folder.UP );
sessionObject.folder.sortBy( sort_id );
}
if( str.compareToIgnoreCase( "down" ) == 0 ) {
if( str.equalsIgnoreCase("down")) {
sessionObject.folder.setSortingDirection( Folder.DOWN );
sessionObject.folder.sortBy( sort_id );
}
@@ -1471,7 +1471,7 @@ public class WebMail extends HttpServlet
String prop = Config.getProperty( CONFIG_SENDER_FIXED, "true" );
String domain = Config.getProperty( CONFIG_SENDER_DOMAIN, "mail.i2p" );
if( prop.compareToIgnoreCase( "false" ) != 0 ) {
if( !prop.equalsIgnoreCase("false")) {
from = "<" + sessionObject.user + "@" + domain + ">";
}
ArrayList<String> toList = new ArrayList<String>();
@@ -1503,7 +1503,7 @@ public class WebMail extends HttpServlet
String bccToSelf = request.getParameter( NEW_BCC_TO_SELF );
if( bccToSelf != null && bccToSelf.compareTo( "1" ) == 0 )
if( bccToSelf != null && bccToSelf.equals("1"))
recipients.add( sender );
if( toList.isEmpty() ) {
@@ -1619,7 +1619,7 @@ public class WebMail extends HttpServlet
String from = request.getParameter( NEW_FROM );
String fixed = Config.getProperty( CONFIG_SENDER_FIXED, "true" );
if( from == null || fixed.compareToIgnoreCase( "false" ) != 0 ) {
if( from == null || !fixed.equalsIgnoreCase("false")) {
String domain = Config.getProperty( CONFIG_SENDER_DOMAIN, "mail.i2p" );
from = "<" + sessionObject.user + "@" + domain + ">";
}
@@ -1637,12 +1637,12 @@ public class WebMail extends HttpServlet
out.println( "<table cellspacing=\"0\" cellpadding=\"5\">\n" +
"<tr><td colspan=\"2\" align=\"center\"><hr></td></tr>\n" +
"<tr><td align=\"right\">" + _("From:") + "</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_FROM + "\" value=\"" + from + "\" " + ( fixed.compareToIgnoreCase( "false" ) != 0 ? "disabled" : "" ) +"></td></tr>\n" +
"<tr><td align=\"right\">" + _("From:") + "</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_FROM + "\" value=\"" + from + "\" " + ( !fixed.equalsIgnoreCase("false") ? "disabled" : "" ) +"></td></tr>\n" +
"<tr><td align=\"right\">" + _("To:") + "</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_TO + "\" value=\"" + to + "\"></td></tr>\n" +
"<tr><td align=\"right\">" + _("Cc:") + "</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_CC + "\" value=\"" + cc + "\"></td></tr>\n" +
"<tr><td align=\"right\">" + _("Bcc:") + "</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_BCC + "\" value=\"" + bcc + "\"></td></tr>\n" +
"<tr><td align=\"right\">" + _("Subject:") + "</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_SUBJECT + "\" value=\"" + subject + "\"></td></tr>\n" +
"<tr><td>&nbsp;</td><td align=\"left\"><input type=\"checkbox\" class=\"optbox\" name=\"" + NEW_BCC_TO_SELF + "\" value=\"1\"" + ( bccToSelf.compareToIgnoreCase( "false" ) != 0 ? "checked" : "" )+ ">" + _("Bcc to self") + "</td></tr>\n" +
"<tr><td>&nbsp;</td><td align=\"left\"><input type=\"checkbox\" class=\"optbox\" name=\"" + NEW_BCC_TO_SELF + "\" value=\"1\"" + ( !bccToSelf.equalsIgnoreCase("false") ? "checked" : "" )+ ">" + _("Bcc to self") + "</td></tr>\n" +
"<tr><td colspan=\"2\" align=\"center\"><textarea cols=\"" + Config.getProperty( CONFIG_COMPOSER_COLS, 80 )+ "\" rows=\"" + Config.getProperty( CONFIG_COMPOSER_ROWS, 10 )+ "\" name=\"" + NEW_TEXT + "\">" + text + "</textarea>" +
"<tr><td colspan=\"2\" align=\"center\"><hr></td></tr>\n" +
"<tr><td align=\"right\">" + _("New Attachment:") + "</td><td align=\"left\"><input type=\"file\" size=\"50%\" name=\"" + NEW_FILENAME + "\" value=\"\"><input type=\"submit\" name=\"" + NEW_UPLOAD + "\" value=\"" + _("Upload File") + "\"></td></tr>" );
@@ -1668,7 +1668,7 @@ public class WebMail extends HttpServlet
private static void showLogin( PrintWriter out )
{
String fixedPorts = Config.getProperty( CONFIG_PORTS_FIXED, "true" );
boolean fixed = fixedPorts.compareToIgnoreCase( "false" ) != 0;
boolean fixed = !fixedPorts.equalsIgnoreCase("false");
String host = Config.getProperty( CONFIG_HOST, DEFAULT_HOST );
String pop3 = Config.getProperty( CONFIG_PORTS_POP3, "" + DEFAULT_POP3PORT );
String smtp = Config.getProperty( CONFIG_PORTS_SMTP, "" + DEFAULT_SMTPPORT );
@@ -1747,7 +1747,7 @@ public class WebMail extends HttpServlet
boolean idChecked = false;
String checkId = sessionObject.pageChanged ? null : request.getParameter( "check" + i );
if( checkId != null && checkId.compareTo( "1" ) == 0 )
if( checkId != null && checkId.equals("1"))
idChecked = true;
if( sessionObject.markAll )
@@ -1765,7 +1765,7 @@ public class WebMail extends HttpServlet
( idChecked ? "checked" : "" ) + ">" + "</td><td>" +
link + mail.shortSender + "</a></td><td>&nbsp;</td><td>" + link + mail.shortSubject + "</a></td><td>&nbsp;</td><td>" +
// don't let date get split across lines
mail.localFormattedDate.replace(" ", "&nbsp;") + "</td><td>&nbsp;</td><td>" +
mail.localFormattedDate.replace(" ", "&nbsp;") + "</td><td>&nbsp;</td><td align=\"right\">" +
DataHelper.formatSize2(mail.size) + "B</td></tr>" );
bg = 1 - bg;
i++;

View File

@@ -24,6 +24,7 @@
package i2p.susi.webmail.smtp;
import i2p.susi.debug.Debug;
import i2p.susi.webmail.Messages;
import i2p.susi.webmail.encoding.Encoding;
import i2p.susi.webmail.encoding.EncodingException;
import i2p.susi.webmail.encoding.EncodingFactory;
@@ -32,6 +33,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import net.i2p.data.DataHelper;
/**
* @author susi
@@ -42,6 +47,7 @@ public class SMTPClient {
private final byte buffer[];
public String error;
private String lastResponse;
private boolean supportsPipelining;
private static final Encoding base64;
@@ -72,61 +78,126 @@ public class SMTPClient {
* @since 0.9.13
*/
private int sendCmd(String cmd, boolean shouldWait)
{
if( socket == null )
return 0;
try {
if (cmd != null)
sendCmdNoWait(cmd);
if (!shouldWait)
return 100;
socket.getOutputStream().flush();
return getResult();
} catch (IOException e) {
error += "IOException occured.<br>";
return 0;
}
}
/**
* Does not flush, wait, or read
*
* @param cmd non-null
* @since 0.9.13
*/
private void sendCmdNoWait(String cmd) throws IOException
{
Debug.debug( Debug.DEBUG, "SMTP sendCmd(" + cmd +")" );
if( socket == null )
return 0;
int result = 0;
lastResponse = "";
try {
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
if( cmd != null ) {
cmd += "\r\n";
out.write( cmd.getBytes() );
}
if (!shouldWait)
return 100;
out.flush();
String str = "";
boolean doContinue = true;
while( doContinue ) {
if( in.available() > 0 ) {
int read = in.read( buffer );
str += new String( buffer, 0, read );
lastResponse += str;
while( true ) {
int i = str.indexOf( "\r\n" );
if( i == -1 )
break;
if( result == 0 ) {
try {
result = Integer.parseInt( str.substring( 0, 3 ) );
}
catch( NumberFormatException nfe ) {
result = 0;
doContinue = false;
break;
}
}
if( str.substring( 3, 4 ).compareTo( " " ) == 0 ) {
doContinue = false;
break;
}
str = str.substring( i + 2 );
}
throw new IOException("no socket");
OutputStream out = socket.getOutputStream();
cmd += "\r\n";
out.write( cmd.getBytes() );
}
/**
* Pipeline if supported
*
* @param cmds non-null
* @return number of successful commands
* @since 0.9.13
*/
private int sendCmds(List<SendExpect> cmds)
{
int rv = 0;
if (supportsPipelining) {
Debug.debug(Debug.DEBUG, "SMTP pipelining " + cmds.size() + " commands");
try {
for (SendExpect cmd : cmds) {
sendCmdNoWait(cmd.send);
}
socket.getOutputStream().flush();
} catch (IOException ioe) {
return 0;
}
for (SendExpect cmd : cmds) {
int r = getResult();
// stop only on EOF
if (r == 0)
break;
if (r == cmd.expect)
rv++;
}
} else {
for (SendExpect cmd : cmds) {
int r = sendCmd(cmd.send);
// stop at first error
if (r != cmd.expect)
break;
rv++;
}
}
catch (IOException e) {
Debug.debug(Debug.DEBUG, "SMTP success in " + rv + " of " + cmds.size() + " commands");
return rv;
}
/**
* @return result code or 0 for failure
* @since 0.9.13
*/
private int getResult() {
return getFullResult().result;
}
/**
* @return result code and string, all lines combined with \r separators,
* first 3 bytes are the ASCII return code or "000" for failure
* Result and Result.recv non null
* @since 0.9.13
*/
private Result getFullResult() {
int result = 0;
StringBuilder fullResponse = new StringBuilder(512);
try {
InputStream in = socket.getInputStream();
StringBuilder buf = new StringBuilder(128);
while (DataHelper.readLine(in, buf)) {
Debug.debug(Debug.DEBUG, "SMTP rcv \"" + buf.toString().trim() + '"');
int len = buf.length();
if (len < 4) {
result = 0;
break; // huh? no nnn\r?
}
if( result == 0 ) {
try {
String r = buf.substring(0, 3);
result = Integer.parseInt(r);
} catch ( NumberFormatException nfe ) {
break;
}
}
fullResponse.append(buf.substring(4));
if (buf.charAt(3) == ' ')
break;
buf.setLength(0);
}
} catch (IOException e) {
error += "IOException occured.<br>";
result = 0;
}
return result;
lastResponse = fullResponse.toString();
return new Result(result, lastResponse);
}
/**
@@ -139,60 +210,118 @@ public class SMTPClient {
try {
socket = new Socket( host, port );
}
catch (Exception e) {
error += "Cannot connect: " + e.getMessage() + "<br>";
} catch (Exception e) {
error += _("Cannot connect") + ": " + e.getMessage() + "<br>";
ok = false;
}
try {
if( ok && sendCmd( null ) == 220 &&
sendCmd( "EHLO localhost" ) == 250 &&
sendCmd( "AUTH LOGIN" ) == 334 &&
sendCmd( base64.encode( user ) ) == 334 &&
sendCmd( base64.encode( pass ) ) == 235 &&
sendCmd( "MAIL FROM: " + sender ) == 250 ) {
for( int i = 0; i < recipients.length; i++ ) {
if( sendCmd( "RCPT TO: " + recipients[i] ) != 250 ) {
ok = false;
}
}
if( ok ) {
if( sendCmd( "DATA" ) == 354 ) {
if( body.indexOf( "\r\n.\r\n" ) != -1 )
body = body.replaceAll( "\r\n.\r\n", "\r\n..\r\n" );
body += "\r\n.\r\n";
try {
socket.getOutputStream().write( body.getBytes() );
if( sendCmd( null ) == 250 ) {
mailSent = true;
}
}
catch (Exception e) {
ok = false;
error += "Error while sending mail: " + e.getMessage() + "<br>";
}
}
// SMTP ref: RFC 821
// Pipelining ref: RFC 2920
// AUTH ref: RFC 4954
if (ok) {
int result = sendCmd(null);
if (result != 220) {
error += _("Server refused connection") + " (" + result + ")<br>";
ok = false;
}
}
if (ok) {
sendCmdNoWait( "EHLO localhost" );
socket.getOutputStream().flush();
Result r = getFullResult();
if (r.result == 250) {
supportsPipelining = r.recv.contains("PIPELINING");
} else {
error += _("Server refused connection") + " (" + r.result + ")<br>";
ok = false;
}
}
if (ok) {
// RFC 4954 says AUTH must be the last but let's assume
// that includes the user/pass on following lines
List<SendExpect> cmds = new ArrayList<SendExpect>();
cmds.add(new SendExpect("AUTH LOGIN", 334));
cmds.add(new SendExpect(base64.encode(user), 334));
cmds.add(new SendExpect(base64.encode(pass), 235));
if (sendCmds(cmds) != 3) {
error += _("Login failed") + "<br>";
ok = false;
}
}
if (ok) {
List<SendExpect> cmds = new ArrayList<SendExpect>();
cmds.add(new SendExpect("MAIL FROM: " + sender, 250));
for( int i = 0; i < recipients.length; i++ ) {
cmds.add(new SendExpect("RCPT TO: " + recipients[i], 250));
}
cmds.add(new SendExpect("DATA", 354));
if (sendCmds(cmds) != cmds.size()) {
// TODO which recipient?
error += _("Mail rejected") + "<br>";
ok = false;
}
}
if (ok) {
if( body.indexOf( "\r\n.\r\n" ) != -1 )
body = body.replaceAll( "\r\n.\r\n", "\r\n..\r\n" );
socket.getOutputStream().write( body.getBytes() );
socket.getOutputStream().write("\r\n.\r\n".getBytes() );
int result = sendCmd(null);
if (result == 250)
mailSent = true;
else
error += _("Error sending mail") + " (" + result + ")<br>";
}
} catch (IOException e) {
error += _("Error sending mail") + ": " + e.getMessage() + "<br>";
} catch (EncodingException e) {
ok = false;
error += e.getMessage();
}
if( !mailSent && lastResponse.length() > 0 ) {
String[] lines = lastResponse.split( "\r\n" );
String[] lines = lastResponse.split( "\r" );
for( int i = 0; i < lines.length; i++ )
error += lines[i] + "<br>";
}
sendCmd("QUIT", false );
sendCmd("QUIT", false);
if( socket != null ) {
try {
socket.close();
}
catch (IOException e1) {
// ignore
}
} catch (IOException e1) {}
}
return mailSent;
}
/**
* A command to send and a result code to expect
* @since 0.9.13
*/
private static class SendExpect {
public final String send;
public final int expect;
public SendExpect(String s, int e) {
send = s;
expect = e;
}
}
/**
* A result string and code
* @since 0.9.13
*/
private static class Result {
public final int result;
public final String recv;
public Result(int r, String t) {
result = r;
recv = t;
}
}
/** translate */
private static String _(String s) {
return Messages.getString(s);
}
}