From 3b2e5bded2a86f4bccb29d93e0fddd1548596a90 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 20 Apr 2014 15:19:30 +0000 Subject: [PATCH] * SusiMail: - Don't store encoding class names in config - New susimail.debug setting in config - Use DataHelper to load config file - Close any open POP3 socket when session is unbound - Don't keep returning user to compose page (ticket #1252) - Add javascript capture of back button from compose page --- .../src/src/i2p/susi/util/Config.java | 33 +++-- .../src/src/i2p/susi/webmail/WebMail.java | 134 ++++++++++++++---- .../webmail/encoding/EncodingFactory.java | 12 +- .../i2p/susi/webmail/pop3/POP3MailBox.java | 2 +- apps/susimail/src/susimail.properties | 2 - 5 files changed, 132 insertions(+), 51 deletions(-) diff --git a/apps/susimail/src/src/i2p/susi/util/Config.java b/apps/susimail/src/src/i2p/susi/util/Config.java index ff1a9a51ce..9752f05a41 100644 --- a/apps/susimail/src/src/i2p/susi/util/Config.java +++ b/apps/susimail/src/src/i2p/susi/util/Config.java @@ -31,14 +31,18 @@ import java.io.IOException; import java.util.Properties; import net.i2p.I2PAppContext; +import net.i2p.data.DataHelper; /** + * Warning - static - not for use by multiple applications or prefixes + * * @author susi */ public class Config { private static Properties properties, config; private static String configPrefix; + /** * * @param name @@ -59,10 +63,11 @@ public class Config { if( result != null ) return result; - result = config.getProperty( name ); - - if( result != null ) - return result; + if( config != null ) { + result = config.getProperty( name ); + if( result != null ) + return result; + } result = properties.getProperty( name ); @@ -84,25 +89,24 @@ public class Config { */ public static void reloadConfiguration() { + // DEBUG level logging won't work here since we haven't loaded the config yet... properties = new Properties(); - config = new Properties(); try { properties.load( Config.class.getResourceAsStream( "/susimail.properties" ) ); } catch (Exception e) { - Debug.debug( Debug.DEBUG, "Could not open WEB-INF/classes/susimail.properties (possibly in jar), reason: " + e.getMessage() ); + Debug.debug(Debug.ERROR, "Could not open WEB-INF/classes/susimail.properties (possibly in jar), reason: " + e); } - FileInputStream fis = null; try { File cfg = new File(I2PAppContext.getGlobalContext().getConfigDir(), "susimail.config"); - fis = new FileInputStream(cfg); - config.load( fis ); + if (cfg.exists()) { + config = new Properties(); + DataHelper.loadProps(config, cfg); + } } catch (Exception e) { - Debug.debug( Debug.DEBUG, "Could not open susimail.config, reason: " + e.getMessage() ); - } finally { - if (fis != null) - try { fis.close(); } catch (IOException ioe) {} + Debug.debug(Debug.ERROR, "Could not open susimail.config, reason: " + e); } } + /** * * @param name @@ -134,8 +138,9 @@ public class Config { } return result; } + /** - * + * Static! Not for use by multiple applications! * @param prefix */ public static void setPrefix( String prefix ) diff --git a/apps/susimail/src/src/i2p/susi/webmail/WebMail.java b/apps/susimail/src/src/i2p/susi/webmail/WebMail.java index cfa227d68d..5f47fea23d 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/WebMail.java +++ b/apps/susimail/src/src/i2p/susi/webmail/WebMail.java @@ -59,6 +59,8 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; @@ -71,7 +73,7 @@ public class WebMail extends HttpServlet /* * set to true, if its a release build */ - private static final boolean RELEASE = true; + private static final boolean RELEASE; /* * increase version number for every release */ @@ -168,6 +170,7 @@ public class WebMail extends HttpServlet private static final String CONFIG_COMPOSER_ROWS = "composer.rows"; private static final String CONFIG_BCC_TO_SELF = "composer.bcc.to.self"; + private static final String CONFIG_DEBUG = "debug"; private static final String RC_PROP_THEME = "routerconsole.theme"; private static final String RC_PROP_UNIVERSAL_THEMING = "routerconsole.universal.theme"; @@ -177,6 +180,12 @@ public class WebMail extends HttpServlet private static final String spacer = " "; private static final String thSpacer = "<th> </th>\n"; + + static { + Config.setPrefix( "susimail" ); + RELEASE = !Boolean.parseBoolean(Config.getProperty(CONFIG_DEBUG)); + Debug.setLevel( RELEASE ? Debug.ERROR : Debug.DEBUG ); + } /** * sorts Mail objects by id field @@ -313,7 +322,7 @@ public class WebMail extends HttpServlet * data structure to hold any persistent data (to store them in session dictionary) * @author susi */ - private static class SessionObject { + private static class SessionObject implements HttpSessionBindingListener { boolean pageChanged, markAll, clear, invert; int state, smtpPort; POP3MailBox mailbox; @@ -335,11 +344,22 @@ public class WebMail extends HttpServlet state = STATE_AUTH; bccToSelf = Boolean.parseBoolean(Config.getProperty( CONFIG_BCC_TO_SELF, "true" )); } - } - - static { - Debug.setLevel( RELEASE ? Debug.ERROR : Debug.DEBUG ); - Config.setPrefix( "susimail" ); + + /** @since 0.9.13 */ + public void valueBound(HttpSessionBindingEvent event) {} + + /** + * Close the POP3 socket if still open + * @since 0.9.13 + */ + public void valueUnbound(HttpSessionBindingEvent event) { + Debug.debug(Debug.DEBUG, "Session unbound: " + event.getSession().getId()); + POP3MailBox mbox = mailbox; + if (mbox != null) { + mbox.close(); + mailbox = null; + } + } } /** @@ -351,8 +371,15 @@ public class WebMail extends HttpServlet */ private static String button( String name, String label ) { - return "<input type=\"submit\" class=\"" + name + "\" name=\"" + name + "\" value=\"" + label + "\">"; + StringBuilder buf = new StringBuilder(128); + buf.append("<input type=\"submit\" class=\"").append(name).append("\" name=\"") + .append(name).append("\" value=\"").append(label).append('"'); + if (name.equals(SEND) || name.equals(CANCEL) || name.equals(DELETE_ATTACHMENT)) + buf.append(" onclick=\"cancelPopup()\""); + buf.append('>'); + return buf.toString(); } + /** * returns html string of a disabled form button with name and label * @@ -624,16 +651,17 @@ public class WebMail extends HttpServlet } } if( doContinue ) { - sessionObject.mailbox = new POP3MailBox( host, pop3PortNo, user, pass ); - if( sessionObject.mailbox.isConnected() ) { + POP3MailBox mailbox = new POP3MailBox( host, pop3PortNo, user, pass ); + if (mailbox.isConnected() ) { + sessionObject.mailbox = mailbox; sessionObject.user = user; sessionObject.pass = pass; sessionObject.host = host; sessionObject.smtpPort = smtpPortNo; sessionObject.state = STATE_LIST; - sessionObject.mailCache = new MailCache( sessionObject.mailbox ); + sessionObject.mailCache = new MailCache(mailbox); sessionObject.folder = new Folder<String>(); - sessionObject.folder.setElements( sessionObject.mailbox.getUIDLs() ); + sessionObject.folder.setElements(mailbox.getUIDLs() ); sessionObject.folder.addSorter( SORT_ID, new IDSorter( sessionObject.mailCache ) ); sessionObject.folder.addSorter( SORT_SENDER, new SenderSorter( sessionObject.mailCache ) ); sessionObject.folder.addSorter( SORT_SUBJECT, new SubjectSorter( sessionObject.mailCache ) ); @@ -645,8 +673,8 @@ public class WebMail extends HttpServlet Debug.debug(Debug.DEBUG, "CONNECTED, YAY"); } else { - sessionObject.error += sessionObject.mailbox.lastError(); - sessionObject.mailbox.close(); + sessionObject.error += mailbox.lastError(); + mailbox.close(); sessionObject.mailbox = null; Debug.debug(Debug.DEBUG, "NOT CONNECTED, BOO"); } @@ -666,8 +694,9 @@ public class WebMail extends HttpServlet HttpSession session = request.getSession(); session.removeAttribute( "sessionObject" ); session.invalidate(); - if( sessionObject.mailbox != null ) { - sessionObject.mailbox.close(); + POP3MailBox mailbox = sessionObject.mailbox; + if (mailbox != null) { + mailbox.close(); sessionObject.mailbox = null; } sessionObject.info += _("User logged out.") + "<br>"; @@ -699,12 +728,39 @@ public class WebMail extends HttpServlet * compose dialog */ if( sessionObject.state == STATE_NEW ) { - if (buttonPressed( request, CANCEL ) || - buttonPressed( request, LIST )) { // LIST button not shown but we could be lost - sessionObject.state = STATE_LIST; - } else if( buttonPressed( request, SEND ) ) { + // We have to make sure to get the state right even if + // the user hit the back button previously + if( buttonPressed( request, SEND ) ) { if( sendMail( sessionObject, request ) ) sessionObject.state = STATE_LIST; + } else if (buttonPressed( request, CANCEL ) || + buttonPressed( request, SHOW ) || // A param, not a button, but we could be lost + buttonPressed( request, PREVPAGE ) || // All these buttons are not shown but we could be lost + buttonPressed( request, NEXTPAGE ) || + buttonPressed( request, FIRSTPAGE ) || + buttonPressed( request, LASTPAGE ) || + buttonPressed( request, SETPAGESIZE ) || + buttonPressed( request, MARKALL ) || + buttonPressed( request, CLEAR ) || + buttonPressed( request, INVERT ) || + buttonPressed( request, SORT_ID ) || + buttonPressed( request, SORT_SENDER ) || + buttonPressed( request, SORT_SUBJECT ) || + buttonPressed( request, SORT_DATE ) || + buttonPressed( request, SORT_SIZE ) || + buttonPressed( request, REFRESH ) || + buttonPressed( request, LIST )) { + sessionObject.state = STATE_LIST; + sessionObject.sentMail = null; + if( sessionObject.attachments != null ) + sessionObject.attachments.clear(); + } 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(); } } /* @@ -1358,13 +1414,25 @@ public class WebMail extends HttpServlet out.println( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n<html>\n" + "<head>\n" + "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n" + - "<title>susimail - " + subtitle + "</title>\n" + + "<title>" + _("SusiMail") + " - " + subtitle + "</title>\n" + "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + sessionObject.themePath + "susimail.css\">\n" ); if (sessionObject.isMobile ) { out.println( "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=2.0, user-scalable=yes\" />\n" + "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + sessionObject.themePath + "mobile.css\" />\n" ); } - // TODO javascript here + if (sessionObject.state == STATE_NEW) { + // TODO cancel if to and body are empty + out.println( + "<script type = \"text/javascript\">" + + "window.onbeforeunload = function () {" + + "return \"" + _("Message has not been sent. Do you want to discard it?") + "\";" + + "};" + + "function cancelPopup() {" + + "window.onbeforeunload = null;" + + "};" + + "</script>" + ); + } out.println( "</head>\n<body>\n" + "<div class=\"page\"><p><img src=\"" + sessionObject.imgPath + "susimail.png\" alt=\"Susimail\"><br> </p>\n" + "<form method=\"POST\" enctype=\"multipart/form-data\" action=\"" + myself + "\" accept-charset=\"UTF-8\">" ); @@ -1519,12 +1587,14 @@ public class WebMail extends HttpServlet if( qp == null ) { ok = false; - sessionObject.error += _("Internal error") + ": Quoted printable encoder not available."; + // can't happen, don't translate + sessionObject.error += "Internal error: Quoted printable encoder not available."; } if( hl == null ) { ok = false; - sessionObject.error += _("Internal error") + ": Header line encoder not available."; + // can't happen, don't translate + sessionObject.error += "Internal error: Header line encoder not available."; } if( ok ) { @@ -1565,17 +1635,18 @@ public class WebMail extends HttpServlet body.append( "\r\n--" + boundary + "--\r\n" ); } + // TODO set to the StringBuilder instead so SMTP can replace() in place sessionObject.sentMail = body.toString(); - SMTPClient relay = new SMTPClient(); - if( ok ) { + SMTPClient relay = new SMTPClient(); if( relay.sendMail( sessionObject.host, sessionObject.smtpPort, sessionObject.user, sessionObject.pass, - sender, recipients.toArray(), body.toString() ) ) { + sender, recipients.toArray(), sessionObject.sentMail ) ) { sessionObject.info += _("Mail sent."); + sessionObject.sentMail = null; if( sessionObject.attachments != null ) sessionObject.attachments.clear(); } @@ -1648,7 +1719,8 @@ public class WebMail extends HttpServlet "<tr><td align=\"right\">" + _("Subject:") + "</td><td align=\"left\"><input type=\"text\" size=\"80\" name=\"" + NEW_SUBJECT + "\" value=\"" + subject + "\"></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>" ); + "<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") + "\" onclick=\"cancelPopup()\"></td></tr>" ); if( sessionObject.attachments != null && !sessionObject.attachments.isEmpty() ) { boolean wroteHeader = false; @@ -1676,7 +1748,7 @@ public class WebMail extends HttpServlet String pop3 = Config.getProperty( CONFIG_PORTS_POP3, "" + DEFAULT_POP3PORT ); String smtp = Config.getProperty( CONFIG_PORTS_SMTP, "" + DEFAULT_SMTPPORT ); - out.println( "<table cellspacing=\"0\" cellpadding=\"5\">\n" + + out.println( "<table cellspacing=\"3\" cellpadding=\"5\">\n" + // current postman hq length limits 16/12, new postman version 32/32 "<tr><td align=\"right\" width=\"30%\">" + _("User") + "</td><td width=\"40%\" align=\"left\"><input type=\"text\" size=\"32\" name=\"" + USER + "\" value=\"" + "\"> @mail.i2p</td></tr>\n" + "<tr><td align=\"right\" width=\"30%\">" + _("Password") + "</td><td width=\"40%\" align=\"left\"><input type=\"password\" size=\"32\" name=\"pass\" value=\"" + "\"></td></tr>\n"); @@ -1689,7 +1761,9 @@ public class WebMail extends HttpServlet "<tr><td align=\"right\" width=\"30%\">" + _("SMTP Port") + "</td><td width=\"40%\" align=\"left\"><input type=\"text\" style=\"text-align: right;\" size=\"5\" name=\"" + SMTP +"\" value=\"" + smtp + "\"" + ( fixed ? " disabled" : "" ) + "></td></tr>\n"); } out.println( - "<tr><td></td><td align=\"left\">" + button( LOGIN, _("Login") ) + " <input class=\"cancel\" type=\"reset\" value=\"" + _("Reset") + "\"></td></tr>\n" + + "<tr><td colspan=\"2\"> </td></tr>\n" + + "<tr><td></td><td align=\"left\">" + button( LOGIN, _("Login") ) + spacer + " <input class=\"cancel\" type=\"reset\" value=\"" + _("Reset") + "\"></td></tr>\n" + + "<tr><td colspan=\"2\"> </td></tr>\n" + "<tr><td></td><td align=\"left\"><a href=\"http://hq.postman.i2p/?page_id=14\">" + _("Learn about I2P mail") + "</a></td></tr>\n" + "<tr><td></td><td align=\"left\"><a href=\"http://hq.postman.i2p/?page_id=16\">" + _("Create Account") + "</a></td></tr>\n" + "</table>"); diff --git a/apps/susimail/src/src/i2p/susi/webmail/encoding/EncodingFactory.java b/apps/susimail/src/src/i2p/susi/webmail/encoding/EncodingFactory.java index f42506ee34..c2ede6cc69 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/encoding/EncodingFactory.java +++ b/apps/susimail/src/src/i2p/susi/webmail/encoding/EncodingFactory.java @@ -26,7 +26,8 @@ package i2p.susi.webmail.encoding; import i2p.susi.debug.Debug; import i2p.susi.util.Config; -import java.util.Hashtable; +import java.util.HashMap; +import java.util.Map; import java.util.Set; /** @@ -36,12 +37,15 @@ import java.util.Set; public class EncodingFactory { private static final String CONFIG_ENCODING = "encodings"; + private static final String DEFAULT_ENCODINGS = "i2p.susi.webmail.encoding.HeaderLine;i2p.susi.webmail.encoding.QuotedPrintable;i2p.susi.webmail.encoding.Base64;i2p.susi.webmail.encoding.SevenBit;i2p.susi.webmail.encoding.EightBit;i2p.susi.webmail.encoding.HTML"; - private static final Hashtable<String, Encoding> encodings; + private static final Map<String, Encoding> encodings; static { - encodings = new Hashtable<String, Encoding>(); - String list = Config.getProperty( CONFIG_ENCODING ); + encodings = new HashMap<String, Encoding>(); + // Let's not give the user a chance to break things + //String list = Config.getProperty( CONFIG_ENCODING ); + String list = DEFAULT_ENCODINGS; if( list != null ) { String[] classNames = list.split( ";" ); for( int i = 0; i < classNames.length; i++ ) { 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 61fa9c5df0..976aa4cdf5 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/pop3/POP3MailBox.java +++ b/apps/susimail/src/src/i2p/susi/webmail/pop3/POP3MailBox.java @@ -391,7 +391,7 @@ public class POP3MailBox { lastError = _("Error opening mailbox") + ": " + e1; } catch (IOException e1) { - lastError = _("Error opening mailbox") + ": " + e1; + lastError = _("Error opening mailbox") + ": " + e1.getLocalizedMessage(); } } } diff --git a/apps/susimail/src/susimail.properties b/apps/susimail/src/susimail.properties index 6cd44eb4fb..7fcb76d657 100644 --- a/apps/susimail/src/susimail.properties +++ b/apps/susimail/src/susimail.properties @@ -1,5 +1,3 @@ -susimail.encodings=i2p.susi.webmail.encoding.HeaderLine;i2p.susi.webmail.encoding.QuotedPrintable;i2p.susi.webmail.encoding.Base64;i2p.susi.webmail.encoding.SevenBit;i2p.susi.webmail.encoding.EightBit;i2p.susi.webmail.encoding.HTML - susimail.host=localhost susimail.ports.fixed=true -- GitLab