diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java index c1062438c16b6220b584dc43981df909d43eba80..9879e02c96185f05b22a1a70a63cef3d88fb97a1 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java @@ -113,10 +113,10 @@ public class ConfigClientsHelper extends HelperBase { renderForm(buf, ""+cur, ca.clientName, // urlify, enabled false, !ca.disabled, - // read only + // read only, preventDisable // dangerous, but allow editing the console args too //"webConsole".equals(ca.clientName) || "Web console".equals(ca.clientName), - false, + false, RouterConsoleRunner.class.getName().equals(ca.className), // description, edit ca.className + ((ca.args != null) ? " " + ca.args : ""), (""+cur).equals(_edit), // show edit button, show update button @@ -129,7 +129,7 @@ public class ConfigClientsHelper extends HelperBase { } if ("new".equals(_edit)) - renderForm(buf, "" + clients.size(), "", false, false, false, "", true, false, false, false, false, false); + renderForm(buf, "" + clients.size(), "", false, false, false, false, "", true, false, false, false, false, false); buf.append("</table>\n"); return buf.toString(); } @@ -150,7 +150,8 @@ public class ConfigClientsHelper extends HelperBase { String val = props.getProperty(name); boolean isRunning = WebAppStarter.isWebAppRunning(app); renderForm(buf, app, app, !"addressbook".equals(app), - "true".equals(val), RouterConsoleRunner.ROUTERCONSOLE.equals(app), app + ".war", + "true".equals(val), RouterConsoleRunner.ROUTERCONSOLE.equals(app), + RouterConsoleRunner.ROUTERCONSOLE.equals(app), app + ".war", false, false, false, isRunning, false, !isRunning); } } @@ -239,7 +240,7 @@ public class ConfigClientsHelper extends HelperBase { enableStop &= PluginStarter.isPluginRunning(app, _context); boolean enableStart = !PluginStarter.isPluginRunning(app, _context); renderForm(buf, app, app, false, - "true".equals(val), false, desc.toString(), false, false, + "true".equals(val), false, false, desc.toString(), false, false, updateURL != null, enableStop, true, enableStart); } } @@ -253,7 +254,7 @@ public class ConfigClientsHelper extends HelperBase { * ro trumps edit and showEditButton */ private void renderForm(StringBuilder buf, String index, String name, boolean urlify, - boolean enabled, boolean ro, String desc, boolean edit, + boolean enabled, boolean ro, boolean preventDisable, String desc, boolean edit, boolean showEditButton, boolean showUpdateButton, boolean showStopButton, boolean showDeleteButton, boolean showStartButton) { String escapeddesc = DataHelper.escapeHTML(desc); @@ -275,7 +276,7 @@ public class ConfigClientsHelper extends HelperBase { buf.append("</td><td align=\"center\" width=\"10%\"><input type=\"checkbox\" class=\"optbox\" name=\"").append(index).append(".enabled\" value=\"true\" "); if (enabled) { buf.append("checked=\"checked\" "); - if (ro) + if (ro || preventDisable) buf.append("disabled=\"disabled\" "); } buf.append("></td><td align=\"center\" width=\"15%\">"); diff --git a/apps/susimail/build.xml b/apps/susimail/build.xml index d429c8807c5c6fe64d5e9318b028ac0b51f419e9..4c79fdfd2a9a6e453076f5ef2fa0b5777fedcc78 100644 --- a/apps/susimail/build.xml +++ b/apps/susimail/build.xml @@ -13,8 +13,22 @@ <condition property="no.bundle"> <isfalse value="${require.gettext}" /> </condition> + <condition property="depend.available"> + <typefound name="depend" /> + </condition> + <target name="depend" if="depend.available"> + <depend + cache="../../build" + srcdir="./src/src" + destdir="./src/WEB-INF/classes" > + <!-- Depend on classes instead of jars where available --> + <classpath> + <pathelement location="../../core/java/build/obj" /> + </classpath> + </depend> + </target> - <target name="compile"> + <target name="compile" depends="depend" > <mkdir dir="./src/WEB-INF/classes" /> <javac srcdir="./src/src" diff --git a/apps/susimail/src/WEB-INF/web.xml b/apps/susimail/src/WEB-INF/web.xml index f8f4f9f352cdede731b897d2d5f5abc173a11691..d5fa807a2661f089edc4017b0d1c6d5e49e6ced4 100644 --- a/apps/susimail/src/WEB-INF/web.xml +++ b/apps/susimail/src/WEB-INF/web.xml @@ -13,7 +13,7 @@ <url-pattern>/susimail</url-pattern> </servlet-mapping> <session-config> - <session-timeout>15</session-timeout> + <session-timeout>1440</session-timeout> </session-config> <!-- diff --git a/apps/susimail/src/src/i2p/susi/util/Config.java b/apps/susimail/src/src/i2p/susi/util/Config.java index eeff33a0f51927439ef90aaba54597f3ca9cc881..ff1a9a51ce9f6e95265db4f1d1a72959371d5bdb 100644 --- a/apps/susimail/src/src/i2p/susi/util/Config.java +++ b/apps/susimail/src/src/i2p/susi/util/Config.java @@ -68,6 +68,16 @@ public class Config { return result; } + + /** + * Don't bother showing a reload config button if this returns false. + * @since 0.9.13 + */ + public static boolean hasConfigFile() { + File cfg = new File(I2PAppContext.getGlobalContext().getConfigDir(), "susimail.config"); + return cfg.exists(); + } + /** * * diff --git a/apps/susimail/src/src/i2p/susi/webmail/Mail.java b/apps/susimail/src/src/i2p/susi/webmail/Mail.java index 9bd67ed424d38442344b3800eaea86472326b2d6..03c595f668bf846eec12cdb5bb3e6beaf91d76b3 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/Mail.java +++ b/apps/susimail/src/src/i2p/susi/webmail/Mail.java @@ -37,6 +37,9 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Locale; +import java.util.TimeZone; + +import net.i2p.I2PAppContext; /** * data structure to hold a single message, mostly used with folder view and sorting @@ -51,8 +54,12 @@ public class Mail { public int id, size; public String sender, reply, subject, dateString, - formattedSender, formattedSubject, formattedDate, - shortSender, shortSubject, quotedDate, uidl; + formattedSender, formattedSubject, + formattedDate, // US Locale, UTC + localFormattedDate, // Current Locale, local time zone + shortSender, shortSubject, + quotedDate, // Current Locale, local time zone, longer format + uidl; public Date date; public ReadBuffer header, body; public MailPart part; @@ -68,6 +75,7 @@ public class Mail { formattedSender = unknown; formattedSubject = unknown; formattedDate = unknown; + localFormattedDate = unknown; shortSender = unknown; shortSubject = unknown; quotedDate = unknown; @@ -110,7 +118,7 @@ public class Mail { for( int i = 0; i < tokens.length; i++ ) { if( tokens[i].matches( "^[^@< \t]+@[^> \t]+$" ) ) return "<" + tokens[i] + ">"; - if( tokens[i].matches( "^<[^@< \t]+@[^> \t]+>$" ) ) + if( tokens[i].matches( "^<[^@< \t]+@[^> \t]+>$" ) ) return tokens[i]; } @@ -149,7 +157,16 @@ public class Mail { } public void parseHeaders() { - DateFormat dateFormatter = new SimpleDateFormat( Config.getProperty( DATEFORMAT, "mm/dd/yyyy HH:mm:ss" ) ); + DateFormat dateFormatter = new SimpleDateFormat("yyyy-mm-dd HH:mm"); + DateFormat localDateFormatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); + DateFormat longLocalDateFormatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); + // the router sets the JVM time zone to UTC but saves the original here so we can get it + String systemTimeZone = I2PAppContext.getGlobalContext().getProperty("i2p.systemTimeZone"); + if (systemTimeZone != null) { + TimeZone tz = TimeZone.getTimeZone(systemTimeZone); + localDateFormatter.setTimeZone(tz); + longLocalDateFormatter.setTimeZone(tz); + } DateFormat mailDateFormatter = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH ); error = ""; @@ -195,7 +212,9 @@ public class Mail { try { date = mailDateFormatter.parse( dateString ); formattedDate = dateFormatter.format( date ); - quotedDate = html.encode( dateString ); + localFormattedDate = localDateFormatter.format( date ); + //quotedDate = html.encode( dateString ); + quotedDate = longLocalDateFormatter.format(date); } catch (ParseException e) { date = null; @@ -211,16 +230,16 @@ public class Mail { shortSubject = html.encode( shortSubject ); } else if( line.toLowerCase(Locale.US).startsWith( "reply-to:" ) ) { - reply = Mail.getAddress( line.substring( 9 ).trim() ); + reply = getAddress( line.substring( 9 ).trim() ); } else if( line.startsWith( "To:" ) ) { ArrayList<String> list = new ArrayList<String>(); - Mail.getRecipientsFromList( list, line.substring( 3 ).trim(), true ); + getRecipientsFromList( list, line.substring( 3 ).trim(), true ); to = list.toArray(); } else if( line.startsWith( "Cc:" ) ) { ArrayList<String> list = new ArrayList<String>(); - Mail.getRecipientsFromList( list, line.substring( 3 ).trim(), true ); + getRecipientsFromList( list, line.substring( 3 ).trim(), true ); cc = list.toArray(); } } diff --git a/apps/susimail/src/src/i2p/susi/webmail/WebMail.java b/apps/susimail/src/src/i2p/susi/webmail/WebMail.java index a403751e358a3b8f119e7bed2115f4dbcc159179..05bfd816d90fa98bf1bbf24b96b78890be1fde62 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/WebMail.java +++ b/apps/susimail/src/src/i2p/susi/webmail/WebMail.java @@ -370,7 +370,9 @@ public class WebMail extends HttpServlet */ private static String sortHeader( String name, String label, String imgPath ) { - return "" + label + " <a href=\"" + myself + "?" + name + "=up\"><img src=\"" + imgPath + "3up.png\" border=\"0\" alt=\"^\"></a><a href=\"" + myself + "?" + name + "=down\"><img src=\"" + imgPath + "3down.png\" border=\"0\" alt=\"v\"></a>"; + return label + " <a href=\"" + myself + "?" + name + "=up\"><img src=\"" + + imgPath + "3up.png\" border=\"0\" alt=\"^\"></a><a href=\"" + myself + + "?" + name + "=down\"><img src=\"" + imgPath + "3down.png\" border=\"0\" alt=\"v\"></a>"; } /** * check, if a given button "was pressed" in the received http request @@ -758,7 +760,7 @@ public class WebMail extends HttpServlet sessionObject.subject = "Re: " + mail.formattedSubject; StringWriter text = new StringWriter(); PrintWriter pw = new PrintWriter( text ); - pw.println( _("On {0} {1} wrote:", mail.formattedDate, sessionObject.replyTo) ); + pw.println( _("On {0} {1} wrote:", mail.formattedDate + " UTC", sessionObject.replyTo) ); StringWriter text2 = new StringWriter(); PrintWriter pw2 = new PrintWriter( text2 ); showPart( pw2, mail.part, 0, TEXT_ONLY ); @@ -1273,7 +1275,7 @@ public class WebMail extends HttpServlet if( sessionObject.state == STATE_LIST ) { processFolderButtons( sessionObject, request ); for( Iterator<String> it = sessionObject.folder.currentPageIterator(); it != null && it.hasNext(); ) { - String uidl = (String)it.next(); + String uidl = it.next(); Mail mail = sessionObject.mailCache.getMail( uidl, MailCache.FETCH_HEADER ); if( mail != null && mail.error.length() > 0 ) { sessionObject.error += mail.error; @@ -1579,9 +1581,10 @@ public class WebMail extends HttpServlet { out.println( button( SEND, _("Send") ) + button( CANCEL, _("Cancel") ) + spacer + - (sessionObject.attachments != null && (!sessionObject.attachments.isEmpty()) ? button( DELETE_ATTACHMENT, _("Delete Attachment") ) : button2( DELETE_ATTACHMENT, _("Delete Attachment") ) ) + spacer + - button( RELOAD, _("Reload Config") ) + spacer + - button( LOGOUT, _("Logout") ) ); + (sessionObject.attachments != null && (!sessionObject.attachments.isEmpty()) ? button( DELETE_ATTACHMENT, _("Delete Attachment") ) : button2( DELETE_ATTACHMENT, _("Delete Attachment") ) ) + spacer); + if (Config.hasConfigFile()) + out.println(button( RELOAD, _("Reload Config") ) + spacer); + out.println(button( LOGOUT, _("Logout") ) ); String from = request.getParameter( NEW_FROM ); String fixed = Config.getProperty( CONFIG_SENDER_FIXED, "true" ); @@ -1672,18 +1675,21 @@ public class WebMail extends HttpServlet button( REPLYALL, _("Reply All") ) + button( FORWARD, _("Forward") ) + spacer + button( DELETE, _("Delete") ) + spacer + - button( REFRESH, _("Check Mail") ) + spacer + - button( RELOAD, _("Reload Config") ) + spacer + - button( LOGOUT, _("Logout") ) + "<table id=\"mailbox\" cellspacing=\"0\" cellpadding=\"5\">\n" + + button( REFRESH, _("Check Mail") ) + spacer); + if (Config.hasConfigFile()) + out.println(button( RELOAD, _("Reload Config") ) + spacer); + out.println(button( LOGOUT, _("Logout") ) + "<table id=\"mailbox\" cellspacing=\"0\" cellpadding=\"5\">\n" + "<tr><td colspan=\"8\"><hr></td></tr>\n<tr>" + thSpacer + "<th>" + sortHeader( SORT_SENDER, _("Sender"), sessionObject.imgPath ) + "</th>" + thSpacer + "<th>" + sortHeader( SORT_SUBJECT, _("Subject"), sessionObject.imgPath ) + "</th>" + - thSpacer + "<th>" + sortHeader( SORT_DATE, _("Date"), sessionObject.imgPath ) + sortHeader( SORT_ID, "", sessionObject.imgPath ) + "</th>" + + thSpacer + "<th>" + sortHeader( SORT_DATE, _("Date"), sessionObject.imgPath ) + + //sortHeader( SORT_ID, "", sessionObject.imgPath ) + + "</th>" + thSpacer + "<th>" + sortHeader( SORT_SIZE, _("Size"), sessionObject.imgPath ) + "</th></tr>" ); int bg = 0; int i = 0; for( Iterator<String> it = sessionObject.folder.currentPageIterator(); it != null && it.hasNext(); ) { - String uidl = (String)it.next(); + String uidl = it.next(); Mail mail = sessionObject.mailCache.getMail( uidl, MailCache.FETCH_HEADER ); String link = "<a href=\"" + myself + "?" + SHOW + "=" + i + "\">"; @@ -1705,7 +1711,9 @@ public class WebMail extends HttpServlet ", invert=" + sessionObject.invert + ", clear=" + sessionObject.clear ); out.println( "<tr class=\"list" + bg + "\"><td><input type=\"checkbox\" class=\"optbox\" name=\"check" + i + "\" value=\"1\"" + - ( idChecked ? "checked" : "" ) + ">" + ( RELEASE ? "" : "" + i ) + "</td><td>" + link + mail.shortSender + "</a></td><td> </td><td>" + link + mail.shortSubject + "</a></td><td> </td><td>" + mail.formattedDate + "</td><td> </td><td>" + ngettext("1 Byte", "{0} Bytes", mail.size) + "</td></tr>" ); + ( idChecked ? "checked" : "" ) + ">" + ( RELEASE ? "" : "" + i ) + "</td><td>" + + link + mail.shortSender + "</a></td><td> </td><td>" + link + mail.shortSubject + "</a></td><td> </td><td>" + + mail.localFormattedDate + "</td><td> </td><td>" + ngettext("1 Byte", "{0} Bytes", mail.size) + "</td></tr>" ); bg = 1 - bg; i++; } @@ -1754,15 +1762,19 @@ public class WebMail extends HttpServlet button( DELETE, _("Delete") ) + spacer + ( sessionObject.folder.isFirstElement( sessionObject.showUIDL ) ? button2( PREV, _("Previous") ) : button( PREV, _("Previous") ) ) + ( sessionObject.folder.isLastElement( sessionObject.showUIDL ) ? button2( NEXT, _("Next") ) : button( NEXT, _("Next") ) ) + spacer + - button( LIST, _("Back to Folder") ) + spacer + - button( RELOAD, _("Reload Config") ) + spacer + - button( LOGOUT, _("Logout") ) ); + button( LIST, _("Back to Folder") ) + spacer); + if (Config.hasConfigFile()) + out.println(button( RELOAD, _("Reload Config") ) + spacer); + out.println(button( LOGOUT, _("Logout") ) ); if( mail != null ) { out.println( "<table cellspacing=\"0\" cellpadding=\"5\">\n" + "<tr><td colspan=\"2\" align=\"center\"><hr></td></tr>\n" + - "<tr class=\"mailhead\"><td align=\"right\">" + _("From:") + "</td><td align=\"left\">" + quoteHTML( mail.formattedSender ) + "</td></tr>\n" + - "<tr class=\"mailhead\"><td align=\"right\">" + _("Date:") + "</td><td align=\"left\">" + mail.quotedDate + "</td></tr>\n" + - "<tr class=\"mailhead\"><td align=\"right\">" + _("Subject:") + "</td><td align=\"left\">" + quoteHTML( mail.formattedSubject ) + "</td></tr>\n" + + "<tr class=\"mailhead\"><td align=\"right\" valign=\"top\">" + _("From:") + + "</td><td align=\"left\">" + quoteHTML( mail.sender ) + "</td></tr>\n" + + "<tr class=\"mailhead\"><td align=\"right\" valign=\"top\">" + _("Date:") + + "</td><td align=\"left\">" + mail.quotedDate + "</td></tr>\n" + + "<tr class=\"mailhead\"><td align=\"right\" valign=\"top\">" + _("Subject:") + + "</td><td align=\"left\">" + quoteHTML( mail.formattedSubject ) + "</td></tr>\n" + "<tr><td colspan=\"2\" align=\"center\"><hr></td></tr>" ); if( mail.body != null ) { showPart( out, mail.part, 0, SHOW_HTML ); 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 2e6599c217082ea5ff029802a1ca1f9acc1b8b9c..1a5e5228de2997e140b3c5cdb7fd7c56e36b169e 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/encoding/HeaderLine.java +++ b/apps/susimail/src/src/i2p/susi/webmail/encoding/HeaderLine.java @@ -23,12 +23,14 @@ */ package i2p.susi.webmail.encoding; +import i2p.susi.debug.Debug; import i2p.susi.util.HexTable; import i2p.susi.util.ReadBuffer; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Locale; /** * Ref: @@ -170,7 +172,8 @@ public class HeaderLine implements Encoding { if( length > 0 ) { if( in[offset] == '?' ) { // System.err.println( "=? found at " + ( offset -1 ) ); - int f2 = offset + 1; + int f1 = offset; + int f2 = f1 + 1; // FIXME save charset here ticket #508 for( ; f2 < end && in[f2] != '?'; f2++ ); if( f2 < end ) { @@ -201,18 +204,31 @@ public class HeaderLine implements Encoding { try { // System.err.println( "decode(" + (f3 + 1) + "," + ( f4 - f3 - 1 ) + ")" ); tmp = e.decode( in, f3 + 1, f4 - f3 - 1 ); - // FIXME use saved charset here ticket #508 - for( int j = 0; j < tmp.length; j++ ) { - byte d = tmp.content[ tmp.offset + j ]; - out[written++] = ( d == '_' ? 32 : d ); + // get charset + String charset = new String(in, f1 + 1, f2 - f1 - 1, "ISO-8859-1"); + String clc = charset.toLowerCase(Locale.US); + if (clc.equals("utf-8") || clc.equals("utf8")) { + for( int j = 0; j < tmp.length; j++ ) { + byte d = tmp.content[ tmp.offset + j ]; + out[written++] = ( d == '_' ? 32 : d ); + } + } else { + // decode string + String decoded = new String(tmp.content, tmp.offset, tmp.length, charset); + // encode string + byte[] utf8 = decoded.getBytes("UTF-8"); + for( int j = 0; j < utf8.length; j++ ) { + byte d = utf8[j]; + out[written++] = ( d == '_' ? 32 : d ); + } } int distance = f4 + 2 - offset; offset += distance; length -= distance; lastCharWasQuoted = true; continue; - } - catch (Exception e1) { + } catch (Exception e1) { + Debug.debug(Debug.ERROR, e1.toString()); } } } 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 cc04e3fc2cca0bea6412f70ce80a7d9d010e14f4..6d63ff854c4e306bc1a6f62e540751cba6b5c808 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java +++ b/apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java @@ -38,19 +38,19 @@ import java.net.Socket; */ public class SMTPClient { - Socket socket; - byte buffer[]; + private Socket socket; + private final byte buffer[]; public String error; - String lastResponse; + private String lastResponse; - private static Encoding base64 = null; + private static final Encoding base64; static { base64 = EncodingFactory.getEncoding( "base64" ); } + public SMTPClient() { - socket = null; buffer = new byte[10240]; error = ""; lastResponse = ""; diff --git a/apps/susimail/src/susimail.properties b/apps/susimail/src/susimail.properties index a310bfb9cf2154ec664fa9465e0ec79f2de9a1ea..68070226b96fc38fce0ac23c49c5c3d1ec896c0b 100644 --- a/apps/susimail/src/susimail.properties +++ b/apps/susimail/src/susimail.properties @@ -11,10 +11,10 @@ susimail.fast.start=true susimail.sender.fixed=true susimail.sender.domain=mail.i2p -susimail.pager.pagesize=10 +susimail.pager.pagesize=40 susimail.composer.cols=80 susimail.composer.rows=15 susimail.composer.bcc.to.self=true -susimail.date.format=MM/dd/yyyy HH:mm:ss \ No newline at end of file +susimail.date.format=MM/dd/yyyy HH:mm:ss diff --git a/history.txt b/history.txt index 06219f40d1da00278a01695d8005f24e6842037c..87c670b42894eca984c5792019a09fba72846c33 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,15 @@ +2014-04-18 zzz + * configclients: Don't allow console disable + * SusiMail: + - Extend session expiration (ticket #1253) + - Handle non-UTF8 encoding on header lines (ticket #508) + - Display dates in current locale and time zone + - Display sender name on message view + - Remove sort-by-ID buttons + - Hide "reload config" button unless config file is present + - Increase default page size + - Add dependency tracking to build + 2014-04-17 zzz * i2psnark: Randomize announce list order and limit size * SSU: SessionRequest replay prevention (ticket #1212)