Changeset 8161f099


Ignore:
Timestamp:
Feb 8, 2018 2:46:41 PM (2 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
3d25a9f
Parents:
7da3de2
Message:

SusiMail?: Error handling fixes
More tolerant parsing of Date headers
Set a date if we don't get a valid Date header
Fix parsing long Base64 encoded headers
Fix page count after changing page size
Make attribute name parsing case-insensitive
Import mail method for debugging
Debug and log tweaks

Files:
1 added
8 edited

Legend:

Unmodified
Added
Removed
  • apps/susimail/src/src/i2p/susi/webmail/Mail.java

    r7da3de2 r8161f099  
    2929import i2p.susi.util.CountingInputStream;
    3030import i2p.susi.util.EOFOnMatchInputStream;
     31import i2p.susi.util.FileBuffer;
    3132import i2p.susi.util.MemoryBuffer;
    3233import i2p.susi.webmail.encoding.Encoding;
     
    4748import java.util.regex.Pattern;
    4849
     50import net.i2p.I2PAppContext;
    4951import net.i2p.data.DataHelper;
    5052import net.i2p.servlet.util.ServletUtil;
     53import net.i2p.util.RFC822Date;
    5154import net.i2p.util.SystemVersion;
    5255
     
    130133                if (closeIn)
    131134                        rb.readComplete(true);
     135                // set a date if we didn't get one in the headers
     136                if (date == null) {
     137                        long dateLong;
     138                        if (rb instanceof FileBuffer) {
     139                                dateLong = ((FileBuffer) rb).getFile().lastModified();
     140                        } else {
     141                                dateLong = I2PAppContext.getGlobalContext().clock().now();
     142                        }
     143                        setDate(dateLong);
     144                }
    132145                return rv;
    133146        }
     
    176189                        Debug.debug(Debug.ERROR, "Parse error", e);
    177190                } finally {
    178                         try { in.close(); } catch (IOException ioe) {}
     191                        if (in != null) try { in.close(); } catch (IOException ioe) {}
    179192                        rb.readComplete(success);
    180193                }
     
    324337        private static final DateFormat localDateFormatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
    325338        private static final DateFormat longLocalDateFormatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
    326         private static final DateFormat mailDateFormatter = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH );
    327339        static {
    328340                // the router sets the JVM time zone to UTC but saves the original here so we can get it
     
    330342                localDateFormatter.setTimeZone(tz);
    331343                longLocalDateFormatter.setTimeZone(tz);
     344        }
     345
     346        /**
     347         * @param dateLong non-negative
     348         * @since 0.9.34 pulled from parseHeaders()
     349         */
     350        private void setDate(long dateLong) {
     351                date = new Date(dateLong);
     352                synchronized(dateFormatter) {
     353                        formattedDate = dateFormatter.format( date );
     354                        localFormattedDate = localDateFormatter.format( date );
     355                        quotedDate = longLocalDateFormatter.format(date);
     356                }
    332357        }
    333358
     
    391416                                                else if (hlc.startsWith("date:")) {
    392417                                                        dateString = line.substring( 5 ).trim();
    393                                                         try {
    394                                                                 synchronized(mailDateFormatter) {
    395                                                                         date = mailDateFormatter.parse( dateString );
    396                                                                         formattedDate = dateFormatter.format( date );
    397                                                                         localFormattedDate = localDateFormatter.format( date );
    398                                                                         //quotedDate = html.encode( dateString );
    399                                                                         quotedDate = longLocalDateFormatter.format(date);
    400                                                                 }
    401                                                         }
    402                                                         catch (ParseException e) {
    403                                                                 date = null;
    404                                                                 e.printStackTrace();
    405                                                         }
     418                                                        long dateLong = RFC822Date.parse822Date(dateString);
     419                                                        if (dateLong > 0)
     420                                                                setDate(dateLong);
    406421                                                }
    407422                                                else if (hlc.startsWith("subject:")) {
  • apps/susimail/src/src/i2p/susi/webmail/MailCache.java

    r7da3de2 r8161f099  
    7373                PersistentMailCache pmc = null;
    7474                try {
    75                         pmc = new PersistentMailCache(host, port, user, pass, PersistentMailCache.DIR_FOLDER);
     75                        pmc = new PersistentMailCache(ctx, host, port, user, pass, PersistentMailCache.DIR_FOLDER);
    7676                        // TODO Drafts, Sent, Trash
    7777                } catch (IOException ioe) {
  • apps/susimail/src/src/i2p/susi/webmail/MailPart.java

    r7da3de2 r8161f099  
    203203                                        DataHelper.copy(eofin, dummy); 
    204204                                        if (!eofin.wasFound())
    205                                                 Debug.debug(Debug.DEBUG, "EOF hit before first boundary " + boundary);
     205                                                Debug.debug(Debug.DEBUG, "EOF hit before first boundary " + boundary + " UIDL: " + uidl);
    206206                                        if (readBoundaryTrailer(in)) {
    207207                                                if (!eofin.wasFound())
    208                                                         Debug.debug(Debug.DEBUG, "EOF hit before first part body " + boundary);
     208                                                        Debug.debug(Debug.DEBUG, "EOF hit before first part body " + boundary + " UIDL: " + uidl);
    209209                                                tmpEnd = (int) eofin.getRead();
    210210                                                break;
     
    221221                                        DataHelper.copy(eofin, DUMMY_OUTPUT); 
    222222                                        if (!eofin.wasFound())
    223                                                 Debug.debug(Debug.DEBUG, "EOF hit before end of body " + i + " boundary: " + boundary);
     223                                                Debug.debug(Debug.DEBUG, "EOF hit before end of body " + i + " boundary: " + boundary + " UIDL: " + uidl);
    224224                                }
    225225                                if (readBoundaryTrailer(in))
     
    352352        }
    353353
     354        /**
     355         *  @param attributeName must be lower case, will be matched case-insensitively
     356         *  @return as found, not necessarily lower case
     357         */
    354358        private static String getHeaderLineAttribute( String line, String attributeName )
    355359        {
     360                String lineLC = line.toLowerCase(Locale.US);
    356361                String result = null;
    357362                int h = 0;
    358363                int l = attributeName.length();
    359364                while( true ) {
    360                         int i = line.indexOf( attributeName, h );
     365                        int i = lineLC.indexOf(attributeName, h);
    361366                        // System.err.println( "i=" + i );
    362367                        if( i == -1 )
  • apps/susimail/src/src/i2p/susi/webmail/PersistentMailCache.java

    r7da3de2 r8161f099  
    55import i2p.susi.util.Buffer;
    66import i2p.susi.util.FileBuffer;
     7import i2p.susi.util.FixCRLFOutputStream;
    78import i2p.susi.util.GzipFileBuffer;
    89import i2p.susi.util.ReadBuffer;
     
    2021import java.util.Hashtable;
    2122import java.util.List;
     23import java.util.Locale;
    2224import java.util.concurrent.ConcurrentHashMap;
    2325import java.util.zip.GZIPInputStream;
     
    6163        private final Object _lock;
    6264        private final File _cacheDir;
     65        private final I2PAppContext _context;
    6366
    6467        private static final String DIR_SUSI = "susimail";
     
    6972        public static final String DIR_SENT = "Sent"; // MailDir-like
    7073        public static final String DIR_TRASH = "Trash"; // MailDir-like
     74        public static final String DIR_SPAM = "Bulk Mail"; // MailDir-like
     75        public static final String DIR_IMPORT = "import"; // Flat with .eml files, debug only for now
    7176        private static final String DIR_PREFIX = "s";
    7277        private static final String FILE_PREFIX = "mail-";
     
    8085         *  @param folder use DIR_FOLDER
    8186         */
    82         public PersistentMailCache(String host, int port, String user, String pass, String folder) throws IOException {
     87        public PersistentMailCache(I2PAppContext ctx, String host, int port, String user, String pass, String folder) throws IOException {
     88                _context = ctx;
    8389                _lock = getLock(host, port, user, pass);
    8490                synchronized(_lock) {
    8591                        _cacheDir = makeCacheDirs(host, port, user, pass, folder);
     92                        // Debugging only for now.
     93                        if (folder.equals(DIR_FOLDER))
     94                                importMail();
    8695                }
    8796        }
     
    210219         *   folder1 is the base.
    211220         */
    212         private static File makeCacheDirs(String host, int port, String user, String pass, String folder) throws IOException {
    213                 File f = new SecureDirectory(I2PAppContext.getGlobalContext().getConfigDir(), DIR_SUSI);
     221        private File makeCacheDirs(String host, int port, String user, String pass, String folder) throws IOException {
     222                File f = new SecureDirectory(_context.getConfigDir(), DIR_SUSI);
    214223                if (!f.exists() && !f.mkdir())
    215224                        throw new IOException("Cannot create " + f);
     
    308317                return mail;
    309318        }
     319
     320        /**
     321         *  For debugging. Import .eml files from the import/ directory
     322         *  @since 0.9.34
     323         */
     324        private void importMail() {
     325                File importDir = new File(_cacheDir.getParentFile(), DIR_IMPORT);
     326                if (importDir.exists() && importDir.isDirectory()) {
     327                        File[] files = importDir.listFiles();
     328                        if (files == null)
     329                                return;
     330                        for (int i = 0; i < files.length; i++) {
     331                                File f = files[i];
     332                                if (!f.isFile())
     333                                        continue;
     334                                if (!f.getName().toLowerCase(Locale.US).endsWith(".eml"))
     335                                        continue;
     336                                // Read in the headers to get the X-UIDL that Thunderbird stuck in there
     337                                String uidl = Long.toString(_context.random().nextLong());
     338                                InputStream in = null;
     339                                try {
     340                                        in = new FileInputStream(f);
     341                                        for (int j = 0; j < 20; j++) {
     342                                                String line = DataHelper.readLine(in);
     343                                                if (line.length() < 2)
     344                                                        break;
     345                                                if (line.startsWith("X-UIDL:")) {
     346                                                        uidl = line.substring(7).trim();
     347                                                        break;
     348                                                }
     349                                        }
     350                                } catch (IOException ioe) {
     351                                        Debug.debug(Debug.ERROR, "Import failed " + f, ioe);
     352                                        continue;
     353                                } finally {
     354                                        if (in != null)
     355                                                try { in.close(); } catch (IOException ioe) {}
     356                                }
     357                                if (uidl == null)
     358                                        uidl = Long.toString(_context.random().nextLong());
     359                                File to = getFullFile(uidl);
     360                                if (to.exists()) {
     361                                        Debug.debug(Debug.DEBUG, "Already have " + f + " as UIDL " + uidl);
     362                                        f.delete();
     363                                        continue;
     364                                }
     365                                in = null;
     366                                OutputStream out = null;
     367                                try {
     368                                        in = new FileInputStream(f);
     369                                        GzipFileBuffer gb = new GzipFileBuffer(to);
     370                                        // Thunderbird exports aren't CRLF terminated
     371                                        out = new FixCRLFOutputStream(gb.getOutputStream());
     372                                        DataHelper.copy(in, out);
     373                                } catch (IOException ioe) {
     374                                        Debug.debug(Debug.ERROR, "Import failed " + f, ioe);
     375                                        continue;
     376                                } finally {
     377                                        if (in != null)
     378                                                try { in.close(); } catch (IOException ioe) {}
     379                                        if (out != null)
     380                                                try { out.close(); } catch (IOException ioe) {}
     381                                }
     382                                f.delete();
     383                                Debug.debug(Debug.DEBUG, "Imported " + f + " as UIDL " + uidl);
     384                        }
     385                }
     386        }
    310387}
  • apps/susimail/src/src/i2p/susi/webmail/WebMail.java

    r7da3de2 r8161f099  
    636636                                if( chosen != null ) {
    637637                                        showPart( out, chosen, level + 1, html );
     638                                        if (html) {
     639                                                // DEBUG
     640                                                for (MailPart subPart : mailPart.parts) {
     641                                                        if (chosen.equals(subPart))
     642                                                                continue;
     643                                                        out.println( "<!-- " );
     644                                                        out.println( "Debug: Not showing alternative Mail Part at level " + (level + 1) + " with hash code " + mailPart.hashCode());
     645                                                        out.println( "Debug: Mail Part headers follow");
     646                                                        for( int i = 0; i < subPart.headerLines.length; i++ ) {
     647                                                                out.println( subPart.headerLines[i].replace("--", "&#45;&#45;") );
     648                                                        }       
     649                                                        out.println( "-->" );
     650                                                }
     651                                        }
    638652                                        return;
    639653                                }
     
    15001514                 * process paging buttons
    15011515                 */
     1516/**** not on the folder view any more, handled in processConfigButtons()
    15021517                if (buttonPressed(request, SETPAGESIZE)) {
    15031518                        try {
     
    15111526                        }
    15121527                }
     1528****/
    15131529                if( buttonPressed( request, PREVPAGE ) ) {
    15141530                        String sp = request.getParameter(PREV_PAGE_NUM);
     
    16271643                                        sessionObject.error += _t("Host unchanged. Edit configation file {0} to change host.", cfg.getAbsolutePath()) + '\n';
    16281644                                }
    1629                                 Config.saveConfiguration(props);
    16301645                                String ps = props.getProperty(Folder.PAGESIZE);
    16311646                                if (sessionObject.folder != null && ps != null) {
     
    16371652                                        } catch( NumberFormatException nfe ) {}
    16381653                                }
     1654                                Config.saveConfiguration(props);
    16391655                                boolean release = !Boolean.parseBoolean(props.getProperty(CONFIG_DEBUG));
    16401656                                Debug.setLevel( release ? Debug.ERROR : Debug.DEBUG );
     
    16471663                        try {
    16481664                                int pageSize = Math.max(5, Integer.parseInt(request.getParameter(PAGESIZE)));
    1649                                 Properties props = Config.getProperties();
    1650                                 props.setProperty(Folder.PAGESIZE, String.valueOf(pageSize));
    1651                                 Config.saveConfiguration(props);
    16521665                                if (sessionObject.folder != null) {
    16531666                                        int oldPageSize = sessionObject.folder.getPageSize();
     
    16581671                                        state = State.AUTH;
    16591672                                }
     1673                                Properties props = Config.getProperties();
     1674                                props.setProperty(Folder.PAGESIZE, String.valueOf(pageSize));
     1675                                Config.saveConfiguration(props);
    16601676                        } catch (IOException ioe) {
    16611677                                sessionObject.error = ioe.toString();
     
    18981914                        }
    18991915
    1900                         //// End state determination, state will not change after here
    1901                         Debug.debug(Debug.DEBUG, "Final state is " + state);
    1902 
    19031916                        /*
    19041917                         * update folder content
     
    19061919                         */
    19071920                        Folder<String> folder = sessionObject.folder;
     1921                        // folder could be null after an error, we can't proceed if it is
     1922                        if (folder == null && (state == State.LIST || state == State.SHOW)) {
     1923                                sessionObject.error += "Internal error, no folder\n";
     1924                                state = State.AUTH;
     1925                        }
     1926
     1927                        //// End state determination, state will not change after here
     1928                        Debug.debug(Debug.DEBUG, "Final state is " + state);
     1929
    19081930                        if (state == State.LIST || state == State.SHOW) {
     1931
    19091932                                // sort buttons are GETs
    19101933                                String oldSort = folder.getCurrentSortBy();
  • apps/susimail/src/src/i2p/susi/webmail/encoding/HeaderLine.java

    r7da3de2 r8161f099  
    3030import i2p.susi.util.MemoryBuffer;
    3131
     32import java.io.EOFException;
    3233import java.io.IOException;
    3334import java.io.InputStream;
     
    210211                return out.toString();
    211212        }
     213
     214        // could be 75 for quoted-printable only
     215        private static final int DECODE_MAX = 256;
    212216
    213217        /**
     
    236240                        }
    237241                        if( c == '=' ) {
    238                                 // An encoded-word is 75 chars max including the delimiters, and must be on a single line
     242                                // An encoded-word should be 75 chars max including the delimiters, and must be on a single line
    239243                                // Store the full encoded word, including =? through ?=, in the buffer
     244                                // Sadly, base64 can be a lot longer
    240245                                if (encodedWord == null)
    241                                         encodedWord = new byte[75];
     246                                        encodedWord = new byte[DECODE_MAX];
    242247                                int offset = 0;
    243248                                int f1 = 0, f2 = 0, f3 = 0, f4 = 0;
     
    247252                                // We make a small attempt to pushback one char if it's not what we expect,
    248253                                // but for the most part it gets thrown out, as RFC 2047 allows
    249                                 for (; offset < 75; offset++) {
     254                                for (; offset < DECODE_MAX; offset++) {
    250255                                        c = in.read();
    251256                                        if (c == '?') {
     
    319324                                        Encoding e = EncodingFactory.getEncoding( enc );
    320325                                        if( e != null ) {
    321                                                 // System.err.println( "encoder found" );
    322326                                                try {
    323327                                                        // System.err.println( "decode(" + (f3 + 1) + "," + ( f4 - f3 - 1 ) + ")" );
    324328                                                        ReadBuffer tmpIn = new ReadBuffer(encodedWord, f3 + 1, f4 - f3 - 1);
    325                                                         MemoryBuffer tmp = new MemoryBuffer(75);
    326                                                         e.decode(tmpIn, tmp);
     329                                                        MemoryBuffer tmp = new MemoryBuffer(DECODE_MAX);
     330                                                        try {
     331                                                                e.decode(tmpIn, tmp);
     332                                                        } catch (EOFException eof) {
     333                                                                // probably Base64 exceeded DECODE_MAX
     334                                                                // Keep going and output what we got, if any
     335                                                                if (Debug.getLevel() >= Debug.DEBUG) {
     336                                                                        Debug.debug(Debug.DEBUG, "q-w " + enc, eof);
     337                                                                        Debug.debug(Debug.DEBUG, net.i2p.util.HexDump.dump(encodedWord));
     338                                                                }
     339                                                        }
    327340                                                        tmp.writeComplete(true);
    328341                                                        // get charset
     
    358371                                                        continue;
    359372                                                } catch (IOException e1) {
    360                                                         Debug.debug(Debug.DEBUG, "q-w", e1);
    361                                                         Debug.debug(Debug.DEBUG, "Decoder: " + enc + " Input:");
    362                                                         Debug.debug(Debug.DEBUG, net.i2p.util.HexDump.dump(encodedWord, f3 + 1, f4 - f3 - 1));
     373                                                        Debug.debug(Debug.ERROR, "q-w " + enc, e1);
     374                                                        if (Debug.getLevel() >= Debug.DEBUG) {
     375                                                                Debug.debug(Debug.DEBUG, net.i2p.util.HexDump.dump(encodedWord));
     376                                                        }
    363377                                                } catch (RuntimeException e1) {
    364                                                         Debug.debug(Debug.DEBUG, "q-w", e1);
    365                                                         Debug.debug(Debug.DEBUG, "Decoder: " + enc + " Input:");
    366                                                         Debug.debug(Debug.DEBUG, net.i2p.util.HexDump.dump(encodedWord, f3 + 1, f4 - f3 - 1));
     378                                                        Debug.debug(Debug.ERROR, "q-w " + enc, e1);
     379                                                        if (Debug.getLevel() >= Debug.DEBUG) {
     380                                                                Debug.debug(Debug.DEBUG, net.i2p.util.HexDump.dump(encodedWord));
     381                                                        }
    367382                                                }
    368383                                        } else {
  • history.txt

    r7da3de2 r8161f099  
     12018-02-08 zzz
     2 * SusiMail:
     3   - Error handling fixes
     4   - More tolerant parsing of Date headers
     5   - Set a date if we don't get a Date header
     6   - Fix parsing long Base64 encoded headers
     7   - Fix page count after changing page size
     8   - Make attribute name parsing case-insensitive
     9   - Import mail method for debugging
     10
    1112018-02-07 zzz
    212 * SusiMail: Use input streams for reading mail (ticket #2119)
  • router/java/src/net/i2p/router/RouterVersion.java

    r7da3de2 r8161f099  
    1919    public final static String ID = "Monotone";
    2020    public final static String VERSION = CoreVersion.VERSION;
    21     public final static long BUILD = 2;
     21    public final static long BUILD = 3;
    2222
    2323    /** for example "-test" */
Note: See TracChangeset for help on using the changeset viewer.