Changeset 65484510


Ignore:
Timestamp:
Feb 7, 2018 12:27:40 PM (3 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
96185d0
Parents:
b013173
Message:

SusiMail?: Use input streams for reading mail (ticket #2119)
Rewrite Base64, HeaderLine?, and QuotedPrintable? decoders
Rewrite and expansion of ReadBuffer? class and utilities for streams
Rewrite Mail and MailPart? to parse the headers only once
Rewrite MailPart? parser
MailPart? parser rewrite skips over the data without reading into memory or decoding
MailPart? decoder rewrite to decode stream-to-stream
ReadBuffer? becomes Buffer interface with multiple implementations
Logging and debugging tweaks

Files:
14 added
20 edited

Legend:

Unmodified
Added
Removed
  • apps/susimail/src/src/i2p/susi/debug/Debug.java

    rb013173 r65484510  
    4545        }
    4646
    47         public static void debug( int msgLevel, String msg )
     47        public static void debug( int msgLevel, String msg ) {
     48                debug(msgLevel, msg, null);
     49        }
     50
     51        /** @since 0.9.34 */
     52        public static void debug(int msgLevel, String msg, Throwable t)
    4853        {
    49                 if( msgLevel <= level )
     54                if( msgLevel <= level ) {
    5055                        System.err.println("SusiMail: " + msg);
     56                        if (t != null)
     57                                t.printStackTrace();
     58                }
    5159                if (msgLevel <= ERROR)
    52                         I2PAppContext.getGlobalContext().logManager().getLog(Debug.class).error(msg);
     60                        I2PAppContext.getGlobalContext().logManager().getLog(Debug.class).error(msg, t);
    5361        }
    5462}
  • apps/susimail/src/src/i2p/susi/util/ReadBuffer.java

    rb013173 r65484510  
    2424package i2p.susi.util;
    2525
     26import java.io.ByteArrayInputStream;
     27import java.io.InputStream;
     28import java.io.OutputStream;
     29
    2630import net.i2p.data.DataHelper;
    2731
    2832/**
     33 * Input only for constant data, initialized from a byte array.
     34 * See MemoryBuffer for read/write.
     35 *
    2936 * @author susi
    3037 */
    31 public class ReadBuffer {
     38public class ReadBuffer implements Buffer {
    3239
    3340        public final byte content[];
     
    4047        }
    4148
     49        /**
     50         * @return new ByteArrayInputStream over the content
     51         * @since 0.9.34
     52         */
     53        public InputStream getInputStream() {
     54                return new ByteArrayInputStream(content, offset, length);
     55        }
     56
     57        /**
     58         * @throws IllegalStateException always
     59         * @since 0.9.34
     60         */
     61        public OutputStream getOutputStream() {
     62                throw new IllegalStateException();
     63        }
     64
     65        /**
     66         * Does nothing
     67         * @since 0.9.34
     68         */
     69        public void readComplete(boolean success) {}
     70
     71        /**
     72         * Does nothing
     73         * @since 0.9.34
     74         */
     75        public void writeComplete(boolean success) {}
     76
     77        /**
     78         * Always valid
     79         */
     80        public int getLength() {
     81                return length;
     82        }
     83
     84        /**
     85         * Always valid
     86         */
     87        public int getOffset() {
     88                return offset;
     89        }
     90
     91        @Override
    4292        public String toString()
    4393        {
  • apps/susimail/src/src/i2p/susi/webmail/Mail.java

    rb013173 r65484510  
    2424package i2p.susi.webmail;
    2525
     26import i2p.susi.debug.Debug;
     27import i2p.susi.util.Buffer;
    2628import i2p.susi.util.Config;
    27 import i2p.susi.debug.Debug;
    28 import i2p.susi.util.ReadBuffer;
    29 import i2p.susi.webmail.encoding.DecodingException;
     29import i2p.susi.util.CountingInputStream;
     30import i2p.susi.util.EOFOnMatchInputStream;
     31import i2p.susi.util.MemoryBuffer;
    3032import i2p.susi.webmail.encoding.Encoding;
    3133import i2p.susi.webmail.encoding.EncodingFactory;
     
    3335import java.io.BufferedReader;
    3436import java.io.ByteArrayInputStream;
     37import java.io.IOException;
     38import java.io.InputStream;
    3539import java.io.InputStreamReader;
    3640import java.text.DateFormat;
     
    6064        private static final Pattern PATTERN1 = Pattern.compile(P1);
    6165        private static final Pattern PATTERN2 = Pattern.compile(P2);
     66        /**
     67         *  Also used by MailPart
     68         *  See MailPart for why we don't do \r\n\r\n
     69         */
     70        static final byte HEADER_MATCH[] = DataHelper.getASCII("\r\n\r");
    6271
    6372        private int size;
     
    7382        public final String uidl;
    7483        public Date date;
    75         private ReadBuffer header, body;
     84        private Buffer header, body;
    7685        private MailPart part;
    7786        String[] to, cc;        // addresses only, enclosed by <>
     
    101110         *  @return if null, nothing has been loaded yet for this UIDL
    102111         */
    103         public synchronized ReadBuffer getHeader() {
     112        public synchronized Buffer getHeader() {
    104113                return header;
    105114        }
    106115
    107         public synchronized void setHeader(ReadBuffer rb) {
     116        public synchronized void setHeader(Buffer rb) {
     117                try {
     118                        setHeader(rb, rb.getInputStream(), true);
     119                } catch (IOException ioe) {
     120                        // TODO...
     121                }
     122        }
     123
     124        /** @since 0.9.34 */
     125        private synchronized String[] setHeader(Buffer rb, InputStream in, boolean closeIn) {
    108126                if (rb == null)
    109                         return;
     127                        return null;
    110128                header = rb;
    111                 parseHeaders();
     129                String[] rv = parseHeaders(in);
     130                if (closeIn)
     131                        rb.readComplete(true);
     132                return rv;
    112133        }
    113134
     
    123144         *  @return may be null
    124145         */
    125         public synchronized ReadBuffer getBody() {
     146        public synchronized Buffer getBody() {
    126147                return body;
    127148        }
    128149
    129         public synchronized void setBody(ReadBuffer rb) {
     150        public synchronized void setBody(Buffer rb) {
    130151                if (rb == null)
    131152                        return;
    132                 if (header == null)
    133                         setHeader(rb);
     153                // In the common case where we have the body, we only parse the headers once.
     154                // we always re-set the header, even if it was non-null before,
     155                // as we have to parse them to find the start of the body
     156                // and who knows, the headers could have changed.
     157                //if (header == null)
     158                //      setHeader(rb);
    134159                body = rb;
    135                 size = rb.length;
     160                boolean success = false;
     161                CountingInputStream in = null;
    136162                try {
    137                         part = new MailPart(uidl, rb);
    138                 } catch (DecodingException de) {
    139                         Debug.debug(Debug.ERROR, "Decode error: " + de);
     163                        in = new CountingInputStream(rb.getInputStream());
     164                        String[] headerLines = setHeader(rb, in, false);
     165                        // TODO just fail?
     166                        if (headerLines == null)
     167                                headerLines = new String[0];
     168                        part = new MailPart(uidl, rb, in, in, headerLines);
     169                        rb.readComplete(true);
     170                        // may only be available after reading and calling readComplete()
     171                        size = rb.getLength();
     172                        success = true;
     173                } catch (IOException de) {
     174                        Debug.debug(Debug.ERROR, "Decode error", de);
    140175                } catch (RuntimeException e) {
    141                         Debug.debug(Debug.ERROR, "Parse error: " + e);
     176                        Debug.debug(Debug.ERROR, "Parse error", e);
     177                } finally {
     178                        try { in.close(); } catch (IOException ioe) {}
     179                        rb.readComplete(success);
    142180                }
    143181        }
     
    294332        }
    295333
    296         private void parseHeaders()
     334        /**
     335         * @return all headers, to pass to MailPart, or null on error
     336         */
     337        private String[] parseHeaders(InputStream in)
    297338        {
     339                String[] headerLines = null;
    298340                error = "";
    299341                if( header != null ) {
     
    318360                               
    319361                                try {
    320                                         ReadBuffer decoded = hl.decode( header );
    321                                         BufferedReader reader = new BufferedReader( new InputStreamReader( new ByteArrayInputStream( decoded.content, decoded.offset, decoded.length ), "UTF-8" ) );
    322                                         String line;
    323                                         while( ( line = reader.readLine() ) != null ) {
     362                                        EOFOnMatchInputStream eofin = new EOFOnMatchInputStream(in, HEADER_MATCH);
     363                                        MemoryBuffer decoded = new MemoryBuffer(4096);
     364                                        hl.decode(eofin, decoded);
     365                                        if (!eofin.wasFound())
     366                                                Debug.debug(Debug.DEBUG, "EOF hit before \\r\\n\\r\\n in Mail");
     367                                        // Fixme UTF-8 to bytes to UTF-8
     368                                        headerLines = DataHelper.split(new String(decoded.getContent(), decoded.getOffset(), decoded.getLength()), "\r\n");
     369                                        for (int j = 0; j < headerLines.length; j++) {
     370                                                String line = headerLines[j];
    324371                                                if( line.length() == 0 )
    325372                                                        break;
     
    419466                                catch( Exception e ) {
    420467                                        error += "Error parsing mail header: " + e.getClass().getName() + '\n';
     468                                        Debug.debug(Debug.ERROR, "Parse error", e);
    421469                                }               
    422470                        }
    423471                }
     472                return headerLines;
    424473        }
    425474}
  • apps/susimail/src/src/i2p/susi/webmail/MailCache.java

    rb013173 r65484510  
    2626import i2p.susi.debug.Debug;
    2727import i2p.susi.util.Config;
     28import i2p.susi.util.Buffer;
     29import i2p.susi.util.FileBuffer;
    2830import i2p.susi.util.ReadBuffer;
     31import i2p.susi.util.MemoryBuffer;
    2932import i2p.susi.webmail.pop3.POP3MailBox;
    3033import i2p.susi.webmail.pop3.POP3MailBox.FetchRequest;
    3134
     35import java.io.File;
    3236import java.io.IOException;
    3337import java.util.ArrayList;
     
    3943import java.util.Set;
    4044
     45import net.i2p.I2PAppContext;
     46
    4147/**
    4248 * @author user
     
    5157        private final Hashtable<String, Mail> mails;
    5258        private final PersistentMailCache disk;
     59        private final I2PAppContext _context;
    5360       
    5461        /** Includes header, headers are generally 1KB to 1.5 KB,
    5562         *  and bodies will compress well.
    5663         */
    57         private static final int FETCH_ALL_SIZE = 8192;
     64        private static final int FETCH_ALL_SIZE = 32*1024;
    5865
    5966        /**
    6067         * @param mailbox non-null
    6168         */
    62         MailCache(POP3MailBox mailbox,
     69        MailCache(I2PAppContext ctx, POP3MailBox mailbox,
    6370                  String host, int port, String user, String pass) {
    6471                this.mailbox = mailbox;
     
    7279                }
    7380                disk = pmc;
     81                _context = ctx;
    7482                if (disk != null)
    7583                        loadFromDisk();
     
    143151                } else if (mode == FetchMode.ALL) {
    144152                        if(!mail.hasBody()) {
    145                                 ReadBuffer rb = mailbox.getBody(uidl);
     153                                File file = new File(_context.getTempDir(), "susimail-new-" + _context.random().nextLong());
     154                                Buffer rb = mailbox.getBody(uidl, new FileBuffer(file));
    146155                                if (rb != null) {
    147156                                        mail.setBody(rb);
     
    217226                                                }
    218227                                        }
    219                                         POP3Request pr = new POP3Request(mail, true);
     228                                        POP3Request pr = new POP3Request(mail, true, new MemoryBuffer(1024));
    220229                                        fetches.add(pr);
    221230                                } else {
     
    239248                                                }
    240249                                        }
    241                                         POP3Request pr = new POP3Request(mail, false);
     250                                        File file = new File(_context.getTempDir(), "susimail-new-" + _context.random().nextLong());
     251                                        POP3Request pr = new POP3Request(mail, false, new FileBuffer(file));
    242252                                        fetches.add(pr);
    243253                                } else {
     
    259269                        //  Process results
    260270                        for (POP3Request pr : fetches) {
    261                                 ReadBuffer rb = pr.buf;
    262                                 if (rb != null) {
     271                                if (pr.getSuccess()) {
    263272                                        Mail mail = pr.mail;
    264273                                        if (!mail.hasHeader())
    265274                                                mail.setNew(true);
    266275                                        if (pr.getHeaderOnly()) {
    267                                                 mail.setHeader(rb);
     276                                                mail.setHeader(pr.getBuffer());
    268277                                        } else {
    269                                                 mail.setBody(rb);
     278                                                mail.setBody(pr.getBuffer());
    270279                                        }
    271280                                        rv = true;
     
    327336        private static class POP3Request implements FetchRequest {
    328337                public final Mail mail;
    329                 private final boolean headerOnly;
    330                 public ReadBuffer buf;
    331 
    332                 public POP3Request(Mail m, boolean hOnly) {
     338                private boolean headerOnly, success;
     339                public final Buffer buf;
     340
     341                public POP3Request(Mail m, boolean hOnly, Buffer buffer) {
    333342                        mail = m;
    334343                        headerOnly = hOnly;
     344                        buf = buffer;
    335345                }
    336346
     
    339349                }
    340350
    341                 public boolean getHeaderOnly() {
     351                /** @since 0.9.34 */
     352                public synchronized void setHeaderOnly(boolean headerOnly) {
     353                        this.headerOnly = headerOnly;
     354                }
     355
     356                public synchronized boolean getHeaderOnly() {
    342357                        return headerOnly;
    343358                }
    344359
    345                 public void setBuffer(ReadBuffer buffer) {
    346                         buf = buffer;
     360                /** @since 0.9.34 */
     361                public Buffer getBuffer() {
     362                        return buf;
     363                }
     364
     365                /** @since 0.9.34 */
     366                public synchronized void setSuccess(boolean success) {
     367                        this.success = success;
     368                }
     369
     370                /** @since 0.9.34 */
     371                public synchronized boolean getSuccess() {
     372                        return success;
    347373                }
    348374        }
  • apps/susimail/src/src/i2p/susi/webmail/MailPart.java

    rb013173 r65484510  
    2525
    2626import i2p.susi.debug.Debug;
     27import i2p.susi.util.Buffer;
     28import i2p.susi.util.CountingOutputStream;
     29import i2p.susi.util.DummyOutputStream;
     30import i2p.susi.util.EOFOnMatchInputStream;
     31import i2p.susi.util.LimitInputStream;
    2732import i2p.susi.util.ReadBuffer;
     33import i2p.susi.util.ReadCounter;
     34import i2p.susi.util.OutputStreamBuffer;
     35import i2p.susi.util.MemoryBuffer;
    2836import i2p.susi.webmail.encoding.DecodingException;
    2937import i2p.susi.webmail.encoding.Encoding;
    3038import i2p.susi.webmail.encoding.EncodingFactory;
    3139
     40import java.io.IOException;
     41import java.io.InputStream;
     42import java.io.OutputStream;
    3243import java.util.ArrayList;
    3344import java.util.List;
     
    4152class MailPart {
    4253
     54        private static final OutputStream DUMMY_OUTPUT = new DummyOutputStream();
    4355        public final String[] headerLines;
    4456        public final String type, encoding, name,
    4557                description, disposition, charset, version;
     58        /** begin, end, and beginBody are relative to readBuffer.getOffset().
     59         *  begin is before the headers
     60         *  beginBody is after the headers
     61         *  warning - end is exclusive
     62         */
    4663        private final int beginBody, begin, end;
    4764        /** fixme never set */
     
    4966        public final List<MailPart> parts;
    5067        public final boolean multipart, message;
    51         public final ReadBuffer buffer;
     68        public final Buffer buffer;
     69
     70        /**
     71         *  the decoded length if known, else -1
     72         *  @since 0.9.34
     73         */
     74        public int decodedLength = -1;
     75
    5276        /**
    5377         *  the UIDL of the mail, same for all parts
     
    5680        public final String uidl;
    5781       
    58 
    59         public MailPart(String uidl,  ReadBuffer readBuffer) throws DecodingException
    60         {
    61                 this(uidl, readBuffer, readBuffer.offset, readBuffer.length);
    62         }
    63 
    64         public MailPart(String uidl, ReadBuffer readBuffer, int offset, int length) throws DecodingException
     82        /**
     83         *  @param readBuffer has zero offset for top-level MailPart.
     84         *  @param in used for reading (NOT readBuffer.getInputStream())
     85         *  @param counter used for counting how much we have read.
     86         *                 Probably the same as InputStream but a different interface.
     87         *  @param hdrlines non-null for top-level MailPart, where they
     88         *         were already parsed in Mail. Null otherwise
     89         */
     90        public MailPart(String uidl, Buffer readBuffer, InputStream in, ReadCounter counter, String[] hdrlines) throws IOException
    6591        {
    6692                this.uidl = uidl;
    67                 begin = offset;
    68                 end = offset + length;
    6993                buffer = readBuffer;
    7094               
    7195                parts = new ArrayList<MailPart>();
    7296
    73                 /*
    74                  * parse header lines
    75                  */
    76                 int bb = end;
    77                 for( int i = begin; i < end - 4; i++ ) {
    78                         if( buffer.content[i] == '\r' &&
    79                                         buffer.content[i+1] == '\n' &&
    80                                         buffer.content[i+2] == '\r' &&
    81                                         buffer.content[i+3] == '\n' ) {
    82                                 bb = i + 2;
    83                                 break;
    84                         }
    85                 }
    86                 beginBody = bb;
    87                        
    88                 ReadBuffer decodedHeaders = EncodingFactory.getEncoding( "HEADERLINE" ).decode( buffer.content, begin, beginBody - begin );
    89                 headerLines = DataHelper.split(new String(decodedHeaders.content, decodedHeaders.offset, decodedHeaders.length), "\r\n");
     97                if (hdrlines != null) {
     98                        // from Mail headers
     99                        headerLines = hdrlines;
     100                        begin = 0;
     101                } else {
     102                        begin = (int) counter.getRead();
     103                        // parse header lines
     104                        // We don't do \r\n\r\n because then we can miss the \r\n--
     105                        // of the multipart boundary. So we do \r\n\r here,
     106                        // and \n-- below. If it's not multipart, we will swallow the
     107                        // \n below.
     108                        EOFOnMatchInputStream eofin = new EOFOnMatchInputStream(in, Mail.HEADER_MATCH);
     109                        MemoryBuffer decodedHeaders = new MemoryBuffer(4096);
     110                        EncodingFactory.getEncoding("HEADERLINE").decode(eofin, decodedHeaders);
     111                        if (!eofin.wasFound())
     112                                Debug.debug(Debug.DEBUG, "EOF hit before \\r\\n\\r\\n in MailPart");
     113                        // Fixme UTF-8 to bytes to UTF-8
     114                        headerLines = DataHelper.split(new String(decodedHeaders.getContent(), decodedHeaders.getOffset(), decodedHeaders.getLength()), "\r\n");
     115                }
    90116
    91117                String boundary = null;
     
    124150                                if (x_type.startsWith( "multipart" ) && boundary != null )
    125151                                        x_multipart = true;
    126                                 if (x_type.startsWith( "message" ) )
     152                                else if (x_type.startsWith( "message" ) )
    127153                                        x_message = true;
    128154                                str = getHeaderLineAttribute( headerLines[i], "name" );
     
    151177                version = x_version;
    152178
     179                // see above re: \n
     180                if (multipart) {
     181                        // EOFOnMatch will eat the \n
     182                        beginBody = (int) counter.getRead() + 1;
     183                } else {
     184                        // swallow the \n
     185                        int c = in.read();
     186                        if (c != '\n')
     187                                Debug.debug(Debug.DEBUG, "wasn't a \\n, it was " + c);
     188                        beginBody = (int) counter.getRead();
     189                }
     190
     191                int tmpEnd = 0;
    153192                /*
    154193                 * parse body
    155194                 */
    156                 int beginLastPart = -1;
    157195                if( multipart ) {
    158                         byte boundaryArray[] = DataHelper.getUTF8(boundary);
    159                         for( int i = beginBody; i < end - 4; i++ ) {
    160                                 if( buffer.content[i] == '\r' &&
    161                                                 buffer.content[i+1] == '\n' &&
    162                                                 buffer.content[i+2] == '-' &&
    163                                                 buffer.content[i+3] == '-' ) {
    164                                         /*
    165                                          * begin of possible boundary line
    166                                          */
    167                                         int j = 0;
    168                                         for( ; j < boundaryArray.length && i + 4 + j < end; j++ )
    169                                                 if( buffer.content[ i + 4 + j ] != boundaryArray[j] )
    170                                                         break;
    171                                         if( j == boundaryArray.length ) {
    172                                                 int k = i + 4 + j;
    173                                                 if( k < end - 2 &&
    174                                                                 buffer.content[k] == '-' &&
    175                                                                 buffer.content[k+1] == '-' )
    176                                                         k += 2;
    177                                                
    178                                                 if( k < end - 2 &&
    179                                                                 buffer.content[k] == '\r' &&
    180                                                                 buffer.content[k+1] == '\n' ) {
    181                                                        
    182                                                         k += 2;
    183                                                        
    184                                                         if( beginLastPart != -1 ) {
    185                                                                 int endLastPart = Math.min(i + 2, end);
    186                                                                 MailPart newPart = new MailPart(uidl, buffer, beginLastPart, endLastPart - beginLastPart);
    187                                                                 parts.add( newPart );
    188                                                         }
    189                                                         beginLastPart = k;
    190                                                 }
    191                                                 i = k;
     196                        // See above for why we don't include the \r
     197                        byte[] match = DataHelper.getASCII("\n--" + boundary);
     198                        for (int i = 0; ; i++) {
     199                                EOFOnMatchInputStream eofin = new EOFOnMatchInputStream(in, counter, match);
     200                                if (i == 0) {
     201                                        // Read through first boundary line, not including "\r\n" or "--\r\n"
     202                                        OutputStream dummy = new DummyOutputStream();
     203                                        DataHelper.copy(eofin, dummy); 
     204                                        if (!eofin.wasFound())
     205                                                Debug.debug(Debug.DEBUG, "EOF hit before first boundary " + boundary);
     206                                        if (readBoundaryTrailer(in)) {
     207                                                if (!eofin.wasFound())
     208                                                        Debug.debug(Debug.DEBUG, "EOF hit before first part body " + boundary);
     209                                                tmpEnd = (int) eofin.getRead();
     210                                                break;
    192211                                        }
    193                                 }                                       
     212                                        // From here on we do include the \r
     213                                        match = DataHelper.getASCII("\r\n--" + boundary);
     214                                        eofin = new EOFOnMatchInputStream(in, counter, match);
     215                                }
     216                                MailPart newPart = new MailPart(uidl, buffer, eofin, eofin, null);
     217                                parts.add( newPart );
     218                                tmpEnd = (int) eofin.getRead();
     219                                if (!eofin.wasFound()) {
     220                                        // if MailPart contains a MailPart, we may not have drained to the end
     221                                        DataHelper.copy(eofin, DUMMY_OUTPUT); 
     222                                        if (!eofin.wasFound())
     223                                                Debug.debug(Debug.DEBUG, "EOF hit before end of body " + i + " boundary: " + boundary);
     224                                }
     225                                if (readBoundaryTrailer(in))
     226                                        break;
    194227                        }
    195228                }
    196229                else if( message ) {
    197                         MailPart newPart = new MailPart(uidl, buffer, beginBody, end - beginBody);
     230                        MailPart newPart = new MailPart(uidl, buffer, in, counter, null);
     231                        // TODO newPart doesn't save message headers we might like to display,
     232                        // like From, To, and Subject
    198233                        parts.add( newPart );                   
    199                 }
     234                        tmpEnd = (int) counter.getRead();
     235                } else {
     236                        // read through to the end
     237                        DataHelper.copy(in, DUMMY_OUTPUT); 
     238                        tmpEnd = (int) counter.getRead();
     239                }
     240                end = tmpEnd;
     241                if (encoding == null || encoding.equals("7bit") || encoding.equals("8bit")) {
     242                        decodedLength = end - beginBody;
     243                }
     244                //if (Debug.getLevel() >= Debug.DEBUG)
     245                //      Debug.debug(Debug.DEBUG, "New " + this);
     246        }
     247
     248        /**
     249         *  Swallow "\r\n" or "--\r\n".
     250         *  We don't have any pushback if this goes wrong.
     251         *
     252         *  @return true if end of input
     253         */
     254        private static boolean readBoundaryTrailer(InputStream in) throws IOException {
     255                int c = in.read();
     256                if (c == '-') {
     257                        // end of parts with this boundary
     258                        c = in.read();
     259                        if (c != '-') {
     260                                Debug.debug(Debug.DEBUG, "Unexpected char after boundary-: " + c);
     261                                return true;
     262                        }
     263                        c = in.read();
     264                        if (c == -1) {
     265                                return true;
     266                        }
     267                        if (c != '\r') {
     268                                Debug.debug(Debug.DEBUG, "Unexpected char after boundary--: " + c);
     269                                return true;
     270                        }
     271                        c = in.read();
     272                        if (c != '\n')
     273                                Debug.debug(Debug.DEBUG, "Unexpected char after boundary--\\r: " + c);
     274                        return true;
     275                } else if (c == '\r') {
     276                        c = in.read();
     277                        if (c != '\n')
     278                                Debug.debug(Debug.DEBUG, "Unexpected char after boundary\\r: " + c);
     279                } else {
     280                        Debug.debug(Debug.DEBUG, "Unexpected char after boundary: " + c);
     281                }
     282                return c == -1;
    200283        }
    201284
     
    204287         *  @since 0.9.13
    205288         */
    206         public ReadBuffer decode(int offset) throws DecodingException {
     289        public void decode(int offset, Buffer out) throws IOException {
    207290                String encg = encoding;
    208291                if (encg == null) {
     
    214297                if(enc == null)
    215298                        throw new DecodingException(_t("No encoder found for encoding \\''{0}\\''.", WebMail.quoteHTML(encg)));
    216                 return enc.decode(buffer.content, beginBody + offset, end - beginBody - offset);
     299                InputStream in = null;
     300                LimitInputStream lin = null;
     301                CountingOutputStream cos = null;
     302                Buffer dout = null;
     303                try {
     304                        in = buffer.getInputStream();
     305                        DataHelper.skip(in, buffer.getOffset() + beginBody + offset);
     306                        lin = new LimitInputStream(in, end - beginBody - offset);
     307                        if (decodedLength < 0) {
     308                                cos = new CountingOutputStream(out.getOutputStream());
     309                                dout = new OutputStreamBuffer(cos);
     310                        } else {
     311                                dout = out;
     312                        }
     313                        enc.decode(lin, dout);
     314                        //dout.getOutputStream().flush();
     315                } catch (IOException ioe) {
     316                        if (lin != null)
     317                                Debug.debug(Debug.DEBUG, "Decode IOE at in position " + lin.getRead()
     318                                            + " offset " + offset, ioe);
     319                        else if (cos != null)
     320                                Debug.debug(Debug.DEBUG, "Decode IOE at out position " + cos.getWritten()
     321                                            + " offset " + offset, ioe);
     322                        else
     323                                Debug.debug(Debug.DEBUG, "Decode IOE", ioe);
     324                        throw ioe;
     325                } finally {
     326                        if (in != null) try { in.close(); } catch (IOException ioe) {};
     327                        if (lin != null) try { lin.close(); } catch (IOException ioe) {};
     328                        buffer.readComplete(true);
     329                        // let the servlet do this
     330                        //if (cos != null) try { cos.close(); } catch (IOException ioe) {};
     331                        //if (dout != null)
     332                        //      dout.writeComplete(true);
     333                        //out.writeComplete(true);
     334                }
     335                if (cos != null)
     336                        decodedLength = (int) cos.getWritten();
    217337        }
    218338
     
    306426                return Messages.getString(s, o);
    307427        }
     428
     429        @Override
     430        public String toString() {
     431                StringBuilder buf = new StringBuilder(1024);
     432                buf.append(
     433                        "MailPart:" +
     434                        "\n\tuidl:\t" + uidl +
     435                        "\n\tbuffer:\t" + buffer +
     436                        "\n\tbuffer offset:\t" + buffer.getOffset() +
     437                        "\n\tbegin:\t" + begin +
     438                        "\n\theader lines:\t" + headerLines.length +
     439                        "\n"
     440                );
     441                for (int i = 0; i < headerLines.length; i++) {
     442                        buf.append("\t\t\"").append(headerLines[i]).append("\"\n");
     443                }
     444                buf.append(
     445                        "\tmultipart?\t" + multipart +
     446                        "\n\tmessage?\t" + message +
     447                        "\n\ttype:\t" + type +
     448                        "\n\tencoding:\t" + encoding +
     449                        "\n\tname:\t" + name +
     450                        "\n\tdescription:\t" + description +
     451                        "\n\tdisposition:\t" + disposition +
     452                        "\n\tcharset:\t" + charset +
     453                        "\n\tversion:\t" + version +
     454                        "\n\tsubparts:\t" + parts.size() +
     455                        "\n\tbeginbody:\t" + beginBody +
     456                        "\n\tbody len:\t" + (end - beginBody) +
     457                        "\n\tdecoded len:\t" + decodedLength +
     458                        "\n\tend:\t" + (end - 1) +
     459                        "\n\ttotal len:\t" + (end - begin) +
     460                        "\n\tbuffer len:\t" + buffer.getLength()
     461                );
     462                return  buf.toString();
     463        }
    308464}
  • apps/susimail/src/src/i2p/susi/webmail/PersistentMailCache.java

    rb013173 r65484510  
    33import i2p.susi.debug.Debug;
    44import i2p.susi.webmail.Messages;
     5import i2p.susi.util.Buffer;
     6import i2p.susi.util.FileBuffer;
     7import i2p.susi.util.GzipFileBuffer;
    58import i2p.susi.util.ReadBuffer;
    69
     
    128131                File f = getFullFile(mail.uidl);
    129132                if (f.exists()) {
    130                         ReadBuffer rb = read(f);
     133                        Buffer rb = read(f);
    131134                        if (rb != null) {
    132135                                mail.setBody(rb);
     
    136139                f = getHeaderFile(mail.uidl);
    137140                if (f.exists()) {
    138                         ReadBuffer rb = read(f);
     141                        Buffer rb = read(f);
    139142                        if (rb != null) {
    140143                                mail.setHeader(rb);
     
    157160
    158161        private boolean locked_saveMail(Mail mail) {
    159                 ReadBuffer rb = mail.getBody();
     162                Buffer rb = mail.getBody();
    160163                if (rb != null) {
    161164                        File f = getFullFile(mail.uidl);
     
    250253         * @return success
    251254         */
    252         private static boolean write(ReadBuffer rb, File f) {
     255        private static boolean write(Buffer rb, File f) {
     256                InputStream in = null;
    253257                OutputStream out = null;
    254258                try {
    255                         out = new BufferedOutputStream(new GZIPOutputStream(new SecureFileOutputStream(f)));
    256                         out.write(rb.content, rb.offset, rb.length);
     259                        in = rb.getInputStream();
     260                        GzipFileBuffer gb = new GzipFileBuffer(f);
     261                        out = gb.getOutputStream();
     262                        DataHelper.copy(in, out);
    257263                        return true;
    258264                } catch (IOException ioe) {
     
    260266                        return false;
    261267                } finally {
     268                        if (in != null)
     269                                try { in.close(); } catch (IOException ioe) {}
    262270                        if (out != null)
    263271                                try { out.close(); } catch (IOException ioe) {}
     
    268276         *  @return null on failure
    269277         */
    270         private static ReadBuffer read(File f) {
    271                 InputStream in = null;
    272                 try {
    273                         long len = f.length();
    274                         if (len > 16 * 1024 * 1024) {
    275                                 throw new IOException("too big");
    276                         }
    277                         in = new GZIPInputStream(new BufferedInputStream(new FileInputStream(f)));
    278                         ByteArrayOutputStream out = new ByteArrayOutputStream((int) len);
    279                         DataHelper.copy(in, out);
    280                         ReadBuffer rb = new ReadBuffer(out.toByteArray(), 0, out.size());
    281                         return rb;
    282                 } catch (IOException ioe) {
    283                         Debug.debug(Debug.ERROR, "Error reading: " + f + ": " + ioe);
    284                         return null;
    285                 } catch (OutOfMemoryError oom) {
    286                         Debug.debug(Debug.ERROR, "Error reading: " + f + ": " + oom);
    287                         return null;
    288                 } finally {
    289                         if (in != null)
    290                                 try { in.close(); } catch (IOException ioe) {}
    291                 }
     278        private static Buffer read(File f) {
     279                return new GzipFileBuffer(f);
    292280        }
    293281
     
    310298                if (uidl == null)
    311299                        return null;
    312                 ReadBuffer rb = read(f);
     300                Buffer rb = read(f);
    313301                if (rb == null)
    314302                        return null;
  • apps/susimail/src/src/i2p/susi/webmail/WebMail.java

    rb013173 r65484510  
    2525
    2626import i2p.susi.debug.Debug;
     27import i2p.susi.util.Buffer;
    2728import i2p.susi.util.Config;
     29import i2p.susi.util.DecodingOutputStream;
     30import i2p.susi.util.EscapeHTMLOutputStream;
     31import i2p.susi.util.EscapeHTMLWriter;
    2832import i2p.susi.util.Folder;
    2933import i2p.susi.util.Folder.SortOrder;
    30 import i2p.susi.util.ReadBuffer;
     34import i2p.susi.util.Buffer;
     35import i2p.susi.util.OutputStreamBuffer;
    3136import i2p.susi.webmail.Messages;
    32 import i2p.susi.webmail.encoding.DecodingException;
    3337import i2p.susi.webmail.encoding.Encoding;
    3438import i2p.susi.webmail.encoding.EncodingException;
     
    4953import java.io.StringWriter;
    5054import java.io.UnsupportedEncodingException;
     55import java.io.Writer;
    5156import java.text.Collator;
    5257import java.util.ArrayList;
     
    7782import net.i2p.servlet.RequestWrapper;
    7883import net.i2p.servlet.util.ServletUtil;
     84import net.i2p.servlet.util.WriterOutputStream;
    7985import net.i2p.util.SecureFileOutputStream;
    8086import net.i2p.util.Translate;
     
    132138        private static final String NEXT_PAGE_NUM = "nextpagenum";
    133139        private static final String CURRENT_SORT = "currentsort";
     140        private static final String CURRENT_FOLDER = "currentfolder";
    134141        private static final String DEBUG_STATE = "currentstate";
    135142
     
    608615        {
    609616                String br = html ? "<br>\r\n" : "\r\n";
    610                
     617
    611618                if( html ) {
    612619                        out.println( "<!-- " );
    613                         out.println( "Debug: Showing Mail Part with hash code " + mailPart.hashCode());
     620                        out.println( "Debug: Showing Mail Part at level " + level + " with hash code " + mailPart.hashCode());
    614621                        out.println( "Debug: Mail Part headers follow");
    615622                        for( int i = 0; i < mailPart.headerLines.length; i++ ) {
    616623                                // fix Content-Type: multipart/alternative; boundary="----------8CDE39ECAF2633"
    617                                 out.println( mailPart.headerLines[i].replace("--", "&mdash;") );
     624                                out.println( mailPart.headerLines[i].replace("--", "&#45;&#45;") );
    618625                        }       
    619626                        out.println( "-->" );
     
    645652                        boolean prepareAttachment = false;
    646653                        String reason = "";
    647                         StringBuilder body = null;
    648654                       
    649655                        String ident = quoteHTML(
     
    659665                                showBody = true;
    660666                        }
    661                         if( showBody == false && mailPart.type != null ) {
     667                        if (!showBody && mailPart.type != null) {
    662668                                if( mailPart.type.equals("text/plain")) {
    663669                                        showBody = true;
     
    666672                                        prepareAttachment = true;
    667673                        }
    668                         if( showBody ) {                       
    669                                         String charset = mailPart.charset;
    670                                         if( charset == null ) {
    671                                                 charset = "ISO-8859-1";
    672                                                 // don't show this in text mode which is used to include the mail in the reply or forward
    673                                                 if (html)
    674                                                         reason += _t("Warning: no charset found, fallback to US-ASCII.") + br;
    675                                         }
    676                                         try {
    677                                                 ReadBuffer decoded = mailPart.decode(0);
    678                                                 BufferedReader reader = new BufferedReader( new InputStreamReader( new ByteArrayInputStream( decoded.content, decoded.offset, decoded.length ), charset ) );
    679                                                 body = new StringBuilder();
    680                                                 String line;
    681                                                 while( ( line = reader.readLine() ) != null ) {
    682                                                         body.append( quoteHTML( line ) );
    683                                                         body.append( br );
    684                                                 }
    685                                         }
    686                                         catch( UnsupportedEncodingException uee ) {
    687                                                 showBody = false;
    688                                                 reason = _t("Charset \\''{0}\\'' not supported.", quoteHTML( mailPart.charset )) + br;
    689                                         }
    690                                         catch (IOException e1) {
    691                                                 showBody = false;
    692                                                 reason += _t("Part ({0}) not shown, because of {1}", ident, e1.toString()) + br;
    693                                         }
    694                         }
    695                         if( html )
    696                                 out.println( "<tr class=\"mailbody\"><td colspan=\"2\" align=\"center\">" );
    697674                        if( reason != null && reason.length() > 0 ) {
    698675                                if( html )
     
    701678                                if( html )
    702679                                        out.println( "</p>" );
    703                         }
    704                         if( showBody ) {
     680                                reason = "";
     681                        }
     682                        if( html )
     683                                out.println( "<tr class=\"mailbody\"><td colspan=\"2\" align=\"center\">" );
     684                        if( showBody ) {                       
    705685                                if( html )
    706                                         out.println( "<p class=\"mailbody\">" );
    707                                 out.println( body.toString() );
     686                                        out.println( "<p class=\"mailbody\"><br>" );
     687                                String charset = mailPart.charset;
     688                                if( charset == null ) {
     689                                        charset = "ISO-8859-1";
     690                                        // don't show this in text mode which is used to include the mail in the reply or forward
     691                                        if (html)
     692                                                reason = _t("Warning: no charset found, fallback to US-ASCII.") + br;
     693                                }
     694                                try {
     695                                        Writer escaper;
     696                                        if (html)
     697                                                escaper = new EscapeHTMLWriter(out);
     698                                        else
     699                                                escaper = out;
     700                                        Buffer ob = new OutputStreamBuffer(new DecodingOutputStream(escaper, charset));
     701                                        mailPart.decode(0, ob);
     702                                        // todo Finally
     703                                        ob.writeComplete(true);
     704                                }
     705                                catch( UnsupportedEncodingException uee ) {
     706                                        showBody = false;
     707                                        reason = _t("Charset \\''{0}\\'' not supported.", quoteHTML( mailPart.charset )) + br;
     708                                }
     709                                catch (IOException e1) {
     710                                        showBody = false;
     711                                        reason += _t("Part ({0}) not shown, because of {1}", ident, e1.toString()) + br;
     712                                }
     713                                if( html )
     714                                        out.println( "<br></p>" );
     715                        }
     716                        if( reason != null && reason.length() > 0 ) {
     717                                // FIXME css has -32 margin
     718                                if( html )
     719                                        out.println( "<p class=\"info\">");
     720                                out.println( reason );
    708721                                if( html )
    709722                                        out.println( "</p>" );
     
    759772                if( html ) {
    760773                        out.println( "<!-- " );
    761                         out.println( "Debug: End of Mail Part with hash code " + mailPart.hashCode());
     774                        out.println( "Debug: End of Mail Part at level " + level + " with hash code " + mailPart.hashCode());
    762775                        out.println( "-->" );
    763776                }
     
    869882                                                sessionObject.smtpPort = smtpPortNo;
    870883                                                state = State.LIST;
    871                                                 MailCache mc = new MailCache(mailbox, host, pop3PortNo, user, pass);
     884                                                I2PAppContext ctx = I2PAppContext.getGlobalContext();
     885                                                MailCache mc = new MailCache(ctx, mailbox, host, pop3PortNo, user, pass);
    872886                                                sessionObject.mailCache = mc;
    873887                                                sessionObject.folder = new Folder<String>();
     
    14601474               
    14611475                if( part.hashCode() == hashCode )
     1476{
    14621477                        return part;
     1478}
    14631479               
    14641480                if( part.multipart || part.message ) {
     
    20132029                                        String fullSort = curOrder == SortOrder.UP ? '-' + curSort : curSort;
    20142030                                        out.println("<input type=\"hidden\" name=\"" + CURRENT_SORT + "\" value=\"" + fullSort + "\">");
     2031                                        out.println("<input type=\"hidden\" name=\"" + CURRENT_FOLDER + "\" value=\"" + PersistentMailCache.DIR_FOLDER + "\">");
    20152032                                }
    20162033                                if( sessionObject.error != null && sessionObject.error.length() > 0 ) {
     
    20822099                boolean shown = false;
    20832100                if(part != null) {
    2084                         ReadBuffer content = part.buffer;
    2085                        
    2086                         // we always decode, even if part.encoding is null, will default to 7bit
    2087                         try {
    2088                                 // +2 probably for \r\n
    2089                                 content = part.decode(2);
    2090                         } catch (DecodingException e) {
    2091                                 sessionObject.error += _t("Error decoding content: {0}", e.getMessage()) + '\n';
    2092                                 content = null;
    2093                         }
    2094                         if(content == null)
    2095                                 return false;
    20962101                        String name = part.filename;
    20972102                        if (name == null) {
     
    21112116                                        if (part.type != null)
    21122117                                                response.setContentType(part.type);
    2113                                         response.setContentLength(content.length);
     2118                                        if (part.decodedLength >= 0)
     2119                                                response.setContentLength(part.decodedLength);
     2120                                        Debug.debug(Debug.DEBUG, "Sending raw attachment " + name + " length " + part.decodedLength);
    21142121                                        // cache-control?
    2115                                         response.getOutputStream().write(content.content, content.offset, content.length);
     2122                                        // was 2
     2123                                        part.decode(0, new OutputStreamBuffer(response.getOutputStream()));
    21162124                                        shown = true;
    21172125                                } catch (IOException e) {
    2118                                         e.printStackTrace();
     2126                                        Debug.debug(Debug.ERROR, "Error sending raw attachment " + name + " length " + part.decodedLength, e);
    21192127                                }
    21202128                        } else {
     
    21272135                                        ZipEntry entry = new ZipEntry( name );
    21282136                                        zip.putNextEntry( entry );
    2129                                         zip.write( content.content, content.offset, content.length );
     2137                                        // was 2
     2138                                        part.decode(0, new OutputStreamBuffer(zip));
    21302139                                        zip.closeEntry();
    21312140                                        zip.finish();
    21322141                                        shown = true;
    21332142                                } catch (IOException e) {
    2134                                         // TODO Auto-generated catch block
    2135                                         e.printStackTrace();
     2143                                        Debug.debug(Debug.ERROR, "Error sending zip attachment " + name + " length " + part.decodedLength, e);
    21362144                                } finally {
    21372145                                        if ( zip != null)
     
    21542162                                                 HttpServletResponse response)
    21552163        {
    2156                 ReadBuffer content = mail.getBody();
     2164                Buffer content = mail.getBody();
    21572165
    21582166                if(content == null)
     
    21652173                String name2 = sanitizeFilename(name);
    21662174                String name3 = encodeFilenameRFC5987(name);
     2175                InputStream in = null;
    21672176                try {
    21682177                        response.setContentType("message/rfc822");
    2169                         response.setContentLength(content.length);
     2178                        response.setContentLength(content.getLength());
    21702179                        // cache-control?
    21712180                        response.addHeader("Content-Disposition", "attachment; filename=\"" + name2 + "\"; " +
    21722181                                           "filename*=" + name3);
    2173                         response.getOutputStream().write(content.content, content.offset, content.length);
     2182                        in = content.getInputStream();
     2183                        DataHelper.copy(in, response.getOutputStream());
    21742184                        return true;
    21752185                } catch (IOException e) {
    21762186                        e.printStackTrace();
    21772187                        return false;
     2188                } finally {
     2189                        if (in != null) try { in.close(); } catch (IOException ioe) {}
    21782190                }
    21792191        }
     
    23772389                                if( relay.sendMail( sessionObject.host, sessionObject.smtpPort,
    23782390                                                sessionObject.user, sessionObject.pass,
    2379                                                 sender, recipients.toArray(), sessionObject.sentMail,
     2391                                                sender, recipients.toArray(new String[recipients.size()]), sessionObject.sentMail,
    23802392                                                sessionObject.attachments, boundary)) {
    23812393                                        sessionObject.info += _t("Mail sent.");
     
    27152727                        out.println( "<!--" );
    27162728                        out.println( "Debug: Mail header and body follow");
    2717                         ReadBuffer body = mail.getBody();
    2718                         out.println(quoteHTML(new String(body.content, body.offset, body.length)).replace("--", "&mdash;"));
     2729                        Buffer body = mail.getBody();
     2730                        InputStream in = null;
     2731                        OutputStream sout = null;
     2732                        try {
     2733                                in = body.getInputStream();
     2734                                sout = new EscapeHTMLOutputStream(new WriterOutputStream(out));
     2735                                DataHelper.copy(in, sout);
     2736                        } catch (IOException ioe) {
     2737                        } finally {
     2738                                if (in != null) try { in.close(); } catch (IOException ioe) {}
     2739                                if (sout != null) try { sout.close(); } catch (IOException ioe) {}
     2740                                body.readComplete(true);
     2741                        }
    27192742                        out.println( "-->" );
    27202743                }
  • apps/susimail/src/src/i2p/susi/webmail/encoding/Base64.java

    rb013173 r65484510  
    2424package i2p.susi.webmail.encoding;
    2525
    26 import i2p.susi.util.ReadBuffer;
     26import i2p.susi.util.Buffer;
     27import i2p.susi.util.MemoryBuffer;
    2728
    2829import java.io.ByteArrayInputStream;
     30import java.io.EOFException;
    2931import java.io.IOException;
    3032import java.io.InputStream;
     33import java.io.OutputStream;
    3134import java.io.StringWriter;
    3235import java.io.Writer;
     
    143146        }
    144147
    145         private static byte decodeByte( byte b ) throws DecodingException {
     148        private static byte decodeByte( int c ) throws IOException {
     149                if (c < 0)
     150                        throw new EOFException();
     151                byte b = (byte) (c & 0xff);
    146152                if( b >= 'A' && b <= 'Z' )
    147153                        b -= 'A';
     
    157163                        b = 0;
    158164                else
    159                         throw new DecodingException( "Decoding base64 failed (invalid data)." );
     165                        throw new DecodingException("Decoding base64 failed, invalid data: " + c);
    160166                // System.err.println( "decoded " + (char)a + " to " + b );
    161167                return b;
    162168        }
    163169
    164         public ReadBuffer decode(byte[] in, int offset, int length) throws DecodingException {
    165                 byte out[] = new byte[length * 3 / 4 + 1 ];
    166                 int written = 0;
    167                 while( length > 0 ) {
    168                         if( in[offset] == '\r' || in[offset] == '\n' ||
    169                                         in[offset] == ' ' || in[offset] == '\t' ) {
    170                                 offset++;
    171                                 length--;
    172                                 continue;
    173                         }
    174                         if( length >= 4 ) {
    175                                 // System.out.println( "decode: " + (char)in[offset] + (char)in[offset+1]+ (char)in[offset+2]+ (char)in[offset+3] );
    176                                 byte b1 = decodeByte( in[offset++] );
    177                                 byte b2 = decodeByte( in[offset++] );
    178                                 out[written++] = (byte) (( b1 << 2 ) | ( ( b2 >> 4 ) & 3 ) );
    179                                 byte b3 = in[offset++];
    180                                 if( b3 != '=' ) {
    181                                         b3 = decodeByte( b3 );
    182                                         out[written++] = (byte)( ( ( b2 & 15 ) << 4 ) | ( ( b3 >> 2 ) & 15 ) );
    183                                 }
    184                                 byte b4 = in[offset++];
    185                                 if( b4 != '=' ) {
    186                                         b4 = decodeByte( b4 );
    187                                         out[written++] = (byte)( ( ( b3 & 3 ) << 6 ) | b4 & 63 );
    188                                 }
    189                                 length -= 4;
    190                         }
    191                         else {
    192                                 //System.err.println( "" );
    193                                 throw new DecodingException( "Decoding base64 failed (trailing garbage)." );
    194                         }
     170        private static int readIn(InputStream in) throws IOException {
     171                int c;
     172                do {
     173                        c = in.read();
     174                } while (c == '\r' || c == '\n' || c == ' ' || c == '\t');
     175                return c;
     176        }
     177
     178        /**
     179         * @since 0.9.34
     180         */
     181        public void decode(InputStream in, Buffer bout) throws IOException {
     182                OutputStream out = bout.getOutputStream();
     183                while (true) {
     184                        int c = readIn(in);
     185                        if (c < 0)
     186                                break;
     187
     188                        // System.out.println( "decode: " + (char)in[offset] + (char)in[offset+1]+ (char)in[offset+2]+ (char)in[offset+3] );
     189                        byte b1 = decodeByte(c);
     190                        c = readIn(in);
     191                        byte b2 = decodeByte(c);
     192                        out.write(((b1 << 2) | ((b2 >> 4) & 3)) & 0xff);
     193
     194                        c = readIn(in);
     195                        if (c < 0)
     196                                break;
     197                        byte b3 = 0;
     198                        if (c != '=') {
     199                                b3 = decodeByte(c);
     200                                out.write((((b2 & 15) << 4) | ((b3 >> 2) & 15)) & 0xff);
     201                        }
     202                        c = readIn(in);
     203                        if (c < 0)
     204                                break;
     205                        if (c == '=') break; // done
     206                        byte b4 = decodeByte(c);
     207                        out.write((((b3 & 3) << 6) | (b4 & 63)) & 0xff);
    195208                }
    196                 return new ReadBuffer(out, 0, written);
    197209        }
    198210}
  • apps/susimail/src/src/i2p/susi/webmail/encoding/DecodingException.java

    rb013173 r65484510  
    3636        }
    3737
     38        /**
     39         * @since 0.9.34
     40         */
     41        public DecodingException(String msg, Exception cause)
     42        {
     43                super(msg, cause);
     44        }
    3845}
  • apps/susimail/src/src/i2p/susi/webmail/encoding/EightBit.java

    rb013173 r65484510  
    2424package i2p.susi.webmail.encoding;
    2525
     26import java.io.IOException;
     27import java.io.InputStream;
     28
     29import i2p.susi.util.Buffer;
    2630import i2p.susi.util.ReadBuffer;
     31
     32import net.i2p.data.DataHelper;
    2733
    2834/**
     
    5258        }
    5359
    54         public ReadBuffer decode(byte[] in, int offset, int length)
    55                 throws DecodingException {
     60        @Override
     61        public Buffer decode(byte[] in, int offset, int length) {
    5662                return new ReadBuffer(in, offset, length);
    5763        }
     
    6167         */
    6268        @Override
    63         public ReadBuffer decode(ReadBuffer in) throws DecodingException {
     69        public Buffer decode(Buffer in) {
    6470                return in;
    6571        }
     72
     73        /**
     74         * Copy in to out, unchanged
     75         * @since 0.9.34
     76         */
     77        public void decode(InputStream in, Buffer out) throws IOException {
     78                DataHelper.copy(in, out.getOutputStream());
     79                // read complete, write complete
     80        }
    6681}
  • apps/susimail/src/src/i2p/susi/webmail/encoding/Encoding.java

    rb013173 r65484510  
    2929import java.io.Writer;
    3030
     31import i2p.susi.util.Buffer;
    3132import i2p.susi.util.ReadBuffer;
     33import i2p.susi.util.MemoryBuffer;
    3234
    3335import net.i2p.data.DataHelper;
     
    107109         * @since 0.9.33 implementation moved from subclasses
    108110         */
    109         public ReadBuffer decode(byte in[]) throws DecodingException {
     111        public Buffer decode(byte in[]) throws DecodingException {
    110112                return decode(in, 0, in.length);
    111113        }
     
    119121         * @throws DecodingException
    120122         */
    121         public abstract ReadBuffer decode( byte in[], int offset, int length ) throws DecodingException;
     123        public Buffer decode(byte in[], int offset, int length) throws DecodingException {
     124                try {
     125                        ReadBuffer rb = new ReadBuffer(in, offset, length);
     126                        return decode(rb);
     127                } catch (IOException ioe) {
     128                        throw new DecodingException("decode error", ioe);
     129                }
     130        }
    122131
    123132        /**
     
    132141         * @since 0.9.33 implementation moved from subclasses
    133142         */
    134         public ReadBuffer decode(String str) throws DecodingException {
     143        public Buffer decode(String str) throws DecodingException {
    135144                return str != null ? decode(DataHelper.getUTF8(str)) : null;
    136145        }
     
    145154         * @since 0.9.33 implementation moved from subclasses
    146155         */
    147         public ReadBuffer decode(ReadBuffer in) throws DecodingException {
    148                 return decode(in.content, in.offset, in.length);
     156        public Buffer decode(Buffer in) throws IOException {
     157                MemoryBuffer rv = new MemoryBuffer(4096);
     158                decode(in, rv);
     159                return rv;
    149160        }
     161
     162        /**
     163         * @param in
     164         * @see Encoding#decode(byte[], int, int)
     165         * @throws DecodingException
     166         * @since 0.9.34
     167         */
     168        public void decode(Buffer in, Buffer out) throws IOException {
     169                decode(in.getInputStream(), out);
     170        }
     171
     172        /**
     173         * @param in
     174         * @see Encoding#decode(byte[], int, int)
     175         * @throws DecodingException
     176         * @since 0.9.34
     177         */
     178        public abstract void decode(InputStream in, Buffer out) throws IOException;
    150179}
  • apps/susimail/src/src/i2p/susi/webmail/encoding/EncodingFactory.java

    rb013173 r65484510  
    2626import i2p.susi.debug.Debug;
    2727import i2p.susi.util.Config;
    28 import i2p.susi.util.ReadBuffer;
     28import i2p.susi.util.Buffer;
    2929
    3030import java.io.IOException;
     
    6666                        }
    6767                }
    68                 // TEST
    69                 //main(null);
    7068        }
    7169
     
    105103                                        continue;
    106104                                }
    107                                 ReadBuffer rb = e.decode(enc);
     105                                Buffer rb = e.decode(enc);
    108106                                if (rb == null) {
    109107                                        System.out.println(s + "\tFAIL - null decode result");
  • apps/susimail/src/src/i2p/susi/webmail/encoding/HTML.java

    rb013173 r65484510  
    2424package i2p.susi.webmail.encoding;
    2525
    26 import i2p.susi.util.ReadBuffer;
     26import java.io.InputStream;
     27
     28import i2p.susi.util.Buffer;
    2729
    2830/**
     
    4850        }
    4951
    50         public ReadBuffer decode(byte[] in, int offset, int length)
    51                 throws DecodingException {
     52        public void decode(InputStream in, Buffer out) throws DecodingException {
    5253                throw new DecodingException("unsupported");
    5354        }
  • apps/susimail/src/src/i2p/susi/webmail/encoding/HeaderLine.java

    rb013173 r65484510  
    2626import i2p.susi.debug.Debug;
    2727import i2p.susi.util.HexTable;
     28import i2p.susi.util.Buffer;
    2829import i2p.susi.util.ReadBuffer;
    29 
    30 import java.io.ByteArrayInputStream;
    31 import java.io.ByteArrayOutputStream;
     30import i2p.susi.util.MemoryBuffer;
     31
    3232import java.io.IOException;
    3333import java.io.InputStream;
     34import java.io.OutputStream;
    3435import java.util.Locale;
    3536
     
    214215         *  and puts them in the ReadBuffer, including the \r\n\r\n
    215216         */
    216         public ReadBuffer decode( byte in[], int offset, int length ) throws DecodingException {
    217                 ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
    218                 int written = 0;
    219                 int end = offset + length;
    220                 if( end > in.length )
    221                         throw new DecodingException( "Index out of bound." );
     217        public void decode(InputStream in, Buffer bout) throws IOException {
     218                OutputStream out = bout.getOutputStream();
    222219                boolean linebreak = false;
    223220                boolean lastCharWasQuoted = false;
    224                 int lastSkip = 0;
    225                 while( length-- > 0 ) {
    226                         byte c = in[offset++];
     221                byte[] encodedWord = null;
     222                // we support one char of pushback,
     223                // to catch some simple malformed input
     224                int pushbackChar = 0;
     225                boolean hasPushback = false;
     226                while (true) {
     227                        int c;
     228                        if (hasPushback) {
     229                                c = pushbackChar;
     230                                hasPushback = false;
     231                                //Debug.debug(Debug.DEBUG, "Loop " + count + " Using pbchar(dec) " + c);
     232                        } else {
     233                                c = in.read();
     234                                if (c < 0)
     235                                        break;
     236                        }
    227237                        if( c == '=' ) {
    228                                 if( length > 0 ) {
    229                                         if( in[offset] == '?' ) {
    230                                                 // System.err.println( "=? found at " + ( offset -1 ) );
    231                                                 // save charset position here f1+1 to f2-1
    232                                                 int f1 = offset;
    233                                                 int f2 = f1 + 1;
    234                                                 for( ; f2 < end && in[f2] != '?'; f2++ );
    235                                                 if( f2 < end ) {
    236                                                         /*
    237                                                          * 2nd question mark found
    238                                                          */
    239                                                         // System.err.println( "2nd ? found at " + f2 );
    240                                                         int f3 = f2 + 1;
    241                                                         for( ; f3 < end && in[f3] != '?'; f3++ );
    242                                                         if( f3 < end ) {
    243                                                                 /*
    244                                                                  * 3rd question mark found
    245                                                                  */
    246                                                                 // System.err.println( "3rd ? found at " + f3 );
    247                                                                 int f4 = f3 + 1;
    248                                                                 for( ; f4 < end && in[f4] != '?'; f4++ );
    249                                                                 if( f4 < end - 1 && in[f4+1] == '=' ) {
    250                                                                         /*
    251                                                                          * 4th question mark found, we are complete, so lets start
    252                                                                          */
    253                                                                         String enc = ( in[f2+1] == 'Q' || in[f2+1] == 'q' ) ? "quoted-printable" : ( ( in[f2+1] == 'B' || in[f2+1] == 'b' ) ? "base64" : null );
    254                                                                         // System.err.println( "4th ? found at " + f4 + ", encoding=" + enc );
    255                                                                         if( enc != null ) {
    256                                                                                 Encoding e = EncodingFactory.getEncoding( enc );
    257                                                                                 if( e != null ) {
    258                                                                                         // System.err.println( "encoder found" );
    259                                                                                         ReadBuffer tmp = null;
    260                                                                                         try {
    261                                                                                                 // System.err.println( "decode(" + (f3 + 1) + "," + ( f4 - f3 - 1 ) + ")" );
    262                                                                                                 tmp = e.decode( in, f3 + 1, f4 - f3 - 1 );
    263                                                                                                 // get charset
    264                                                                                                 String charset = new String(in, f1 + 1, f2 - f1 - 1, "ISO-8859-1");
    265                                                                                                 String clc = charset.toLowerCase(Locale.US);
    266                                                                                                 if (clc.equals("utf-8") || clc.equals("utf8")) {
    267                                                                                                         if (enc.equals("quoted-printable")) {
    268                                                                                                                 for( int j = 0; j < tmp.length; j++ ) {
    269                                                                                                                         byte d = tmp.content[ tmp.offset + j ];
    270                                                                                                                         out.write( d == '_' ? 32 : d );
    271                                                                                                                 }
    272                                                                                                         } else {
    273                                                                                                                 out.write(tmp.content, tmp.offset, tmp.length);
    274                                                                                                         }
    275                                                                                                 } else {
    276                                                                                                         // decode string
    277                                                                                                         String decoded = new String(tmp.content, tmp.offset, tmp.length, charset);
    278                                                                                                         // encode string
    279                                                                                                         byte[] utf8 = DataHelper.getUTF8(decoded);
    280                                                                                                         if (enc.equals("quoted-printable")) {
    281                                                                                                                 for( int j = 0; j < utf8.length; j++ ) {
    282                                                                                                                         byte d = utf8[j];
    283                                                                                                                         out.write( d == '_' ? 32 : d );
    284                                                                                                                 }
    285                                                                                                         } else {
    286                                                                                                                 out.write(utf8);
    287                                                                                                         }
    288                                                                                                 }
    289                                                                                                 int distance = f4 + 2 - offset;
    290                                                                                                 offset += distance;
    291                                                                                                 length -= distance;
    292                                                                                                 lastCharWasQuoted = true;
    293                                                                                                 continue;
    294                                                                                         } catch (IOException e1) {
    295                                                                                                 Debug.debug(Debug.ERROR, e1.toString());
    296                                                                                         } catch (RuntimeException e1) {
    297                                                                                                 Debug.debug(Debug.ERROR, e1.toString());
    298                                                                                         }
    299                                                                                 }
     238                                // An encoded-word is 75 chars max including the delimiters, and must be on a single line
     239                                // Store the full encoded word, including =? through ?=, in the buffer
     240                                if (encodedWord == null)
     241                                        encodedWord = new byte[75];
     242                                int offset = 0;
     243                                int f1 = 0, f2 = 0, f3 = 0, f4 = 0;
     244                                encodedWord[offset++] = (byte) c;
     245                                // Read until we have 4 '?', stored in encodedWord positions f1, f2, f3, f4,
     246                                // plus one char after the 4th '?', which should be '='
     247                                // We make a small attempt to pushback one char if it's not what we expect,
     248                                // but for the most part it gets thrown out, as RFC 2047 allows
     249                                for (; offset < 75; offset++) {
     250                                        c = in.read();
     251                                        if (c == '?') {
     252                                                if (f1 == 0)
     253                                                        f1 = offset;
     254                                                else if (f2 == 0)
     255                                                        f2 = offset;
     256                                                else if (f3 == 0)
     257                                                        f3 = offset;
     258                                                else if (f4 == 0)
     259                                                        f4 = offset;
     260                                        } else if (c == -1) {
     261                                                break;
     262                                        } else if (c == '\r' || c == '\n') {
     263                                                pushbackChar = c;
     264                                                hasPushback = true;
     265                                                break;
     266                                        } else if (offset == 1) {
     267                                                // no '?' after '='
     268                                                out.write('=');
     269                                                pushbackChar = c;
     270                                                hasPushback = true;
     271                                                break;
     272                                        }
     273                                        encodedWord[offset] = (byte) c;
     274                                        // store one past the 4th '?', presumably the '='
     275                                        if (f4 > 0 && offset >= f4 + 1) {
     276                                                if (c == '=') {
     277                                                        offset++;
     278                                                } else {
     279                                                        pushbackChar = c;
     280                                                        hasPushback = true;
     281                                                }
     282                                                break;
     283                                        }
     284                                }
     285                                //if (f1 > 0)
     286                                //      Debug.debug(Debug.DEBUG, "End of encoded word, f1 " + f1 + " f2 " + f2 + " f3 " + f3 + " f4 " + f4 +
     287                                //      " offset " + offset + " pushback? " + hasPushback + " pbchar(dec) " + c + '\n' +
     288                                //      net.i2p.util.HexDump.dump(encodedWord, 0, offset));
     289                                if (f4 == 0) {
     290                                        // at most 1 byte is pushed back, the rest is discarded
     291                                        if (f1 == 0) {
     292                                                // This is normal
     293                                                continue;
     294                                        } else if (f2 == 0) {
     295                                                Debug.debug(Debug.DEBUG, "2nd '?' not found");
     296                                                continue;
     297                                        } else if (f3 == 0) {
     298                                                Debug.debug(Debug.DEBUG, "3rd '?' not found");
     299                                                continue;
     300                                        } else {
     301                                                // probably just too long, but could be end of line without the "?=".
     302                                                // synthesize a 4th '?' in an attempt to output
     303                                                // something, probably with some trailing garbage
     304                                                Debug.debug(Debug.DEBUG, "4th '?' not found");
     305                                                f4 = offset + 1;
     306                                                // keep going and output what we have
     307                                        }
     308                                }
     309                                /*
     310                                 * 4th question mark found, we are complete, so lets start
     311                                 */
     312                                String enc = (encodedWord[f2+1] == 'Q' || encodedWord[f2+1] == 'q') ?
     313                                             "quoted-printable" :
     314                                             ((encodedWord[f2+1] == 'B' || encodedWord[f2+1] == 'b') ?
     315                                              "base64" :
     316                                              null);
     317                                // System.err.println( "4th ? found at " + f4 + ", encoding=" + enc );
     318                                if (enc != null) {
     319                                        Encoding e = EncodingFactory.getEncoding( enc );
     320                                        if( e != null ) {
     321                                                // System.err.println( "encoder found" );
     322                                                try {
     323                                                        // System.err.println( "decode(" + (f3 + 1) + "," + ( f4 - f3 - 1 ) + ")" );
     324                                                        ReadBuffer tmpIn = new ReadBuffer(encodedWord, f3 + 1, f4 - f3 - 1);
     325                                                        MemoryBuffer tmp = new MemoryBuffer(75);
     326                                                        e.decode(tmpIn, tmp);
     327                                                        tmp.writeComplete(true);
     328                                                        // get charset
     329                                                        String charset = new String(encodedWord, f1 + 1, f2 - f1 - 1, "ISO-8859-1");
     330                                                        String clc = charset.toLowerCase(Locale.US);
     331                                                        if (clc.equals("utf-8") || clc.equals("utf8")) {
     332                                                                // FIXME could be more efficient?
     333                                                                InputStream tis = tmp.getInputStream();
     334                                                                if (enc.equals("quoted-printable")) {
     335                                                                        int d;
     336                                                                        while ((d = tis.read()) != -1) {
     337                                                                                out.write(d == '_' ? 32 : d);
    300338                                                                        }
     339                                                                } else {
     340                                                                        DataHelper.copy(tis, out);
     341                                                                }
     342                                                        } else {
     343                                                                // FIXME could be more efficient?
     344                                                                // decode string
     345                                                                String decoded = new String(tmp.getContent(), tmp.getOffset(), tmp.getLength(), charset);
     346                                                                // encode string
     347                                                                byte[] utf8 = DataHelper.getUTF8(decoded);
     348                                                                if (enc.equals("quoted-printable")) {
     349                                                                        for (int j = 0; j < utf8.length; j++) {
     350                                                                                byte d = utf8[j];
     351                                                                                out.write(d == '_' ? 32 : d);
     352                                                                        }
     353                                                                } else {
     354                                                                        out.write(utf8);
    301355                                                                }
    302356                                                        }
     357                                                        lastCharWasQuoted = true;
     358                                                        continue;
     359                                                } 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));
     363                                                } 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));
    303367                                                }
    304                                         }
    305                                 }
    306                         }
     368                                        } else {
     369                                                // can't happen
     370                                                Debug.debug(Debug.DEBUG, "No decoder for " + enc);
     371                                        }  // e != null
     372                                } else {
     373                                        Debug.debug(Debug.DEBUG, "Invalid encoding '" + (char) encodedWord[f2+1] + '\'');
     374                                }  // enc != null
     375                        }  // c == '='
    307376                        else if( c == '\r' ) {
    308                                 if( length > 0 && in[offset] == '\n' ) {
     377                                if ((c = in.read()) == '\n' ) {
    309378                                        /*
    310379                                         * delay linebreak in case of long line
    311380                                         */
    312381                                        linebreak = true;
    313                                         // The ReadBuffer can contain the body too.
    314                                         // If we just had a linebreak, we are done...
    315                                         // don't keep parsing!
    316                                         if( length > 2 && in[offset+1] == '\r' && in[offset+2] == '\n')
    317                                                 break;
    318                                         length--;
    319                                         offset++;
    320                                         continue;
    321                                 }
    322                         }
     382                                } else {
     383                                        // pushback?
     384                                        Debug.debug(Debug.DEBUG, "No \\n after \\r");
     385                                }
     386                        }
     387                        // swallow whitespace here if lastCharWasQuoted
    323388                        if( linebreak ) {
    324389                                linebreak = false;
    325                                 if( c != ' ' && c != '\t' ) {
    326                                         /*
    327                                          * new line does not start with whitespace, so its not a new part of a
    328                                          * long line
    329                                          */
    330                                         out.write('\r');
    331                                         out.write('\n');
    332                                         lastSkip = 0;
    333                                 }
    334                                 else {
    335                                         if( !lastCharWasQuoted )
    336                                                 out.write(' ');
     390                                for (int i = 0; ; i++) {
     391                                        c = in.read();
     392                                        if (c == -1)
     393                                                break;
     394                                        if (c != ' ' && c != '\t') {
     395                                                if (i == 0) {
     396                                                        /*
     397                                                         * new line does not start with whitespace, so its not a new part of a
     398                                                         * long line
     399                                                         */
     400                                                        out.write('\r');
     401                                                        out.write('\n');
     402                                                        if (c == '\r') {
     403                                                                linebreak = true;
     404                                                                in.read();    //  \n
     405                                                                break;
     406                                                        }
     407                                                } else {
     408                                                        // treat all preceding whitespace as a single one
     409                                                        if (!lastCharWasQuoted)
     410                                                                out.write(' ');
     411                                                }
     412                                                pushbackChar = c;
     413                                                hasPushback = true;
     414                                                break;
     415                                        }
    337416                                        /*
    338417                                         * skip whitespace
    339418                                         */
    340                                         int skipped = 1;
    341                                         while( length > 0 && ( in[offset] == ' ' || in[offset] == '\t' ) ) {
    342                                                 if( lastSkip > 0 && skipped >= lastSkip ) {
    343                                                         break;
    344                                                 }
    345                                                 offset++;
    346                                                 length--;
    347                                                 skipped++;
    348                                         }
    349                                         if( lastSkip == 0 && skipped > 0 ) {
    350                                                 lastSkip = skipped;
    351                                         }
    352                                         continue;
    353                                 }
    354                         }
    355                         /*
    356                          * print out everything else literally
    357                          */
    358                         out.write(c);
    359                         lastCharWasQuoted = false;
    360                 }
     419                                }
     420                                // if \r\n\r\n, we are done
     421                                if (linebreak)
     422                                        break;
     423                        } else {
     424                                /*
     425                                 * print out everything else literally
     426                                 */
     427                                out.write(c);
     428                                lastCharWasQuoted = false;
     429                        }
     430                }  // while true
    361431                if( linebreak ) {
    362432                        out.write('\r');
    363433                        out.write('\n');
    364434                }
    365                        
    366                 return new ReadBuffer(out.toByteArray(), 0, out.size());
     435                bout.writeComplete(true);
    367436        }
    368437
  • apps/susimail/src/src/i2p/susi/webmail/encoding/QuotedPrintable.java

    rb013173 r65484510  
    2525
    2626import i2p.susi.util.HexTable;
    27 import i2p.susi.util.ReadBuffer;
     27import i2p.susi.util.Buffer;
     28import i2p.susi.util.MemoryBuffer;
    2829
    2930import java.io.ByteArrayInputStream;
    3031import java.io.IOException;
    3132import java.io.InputStream;
     33import java.io.OutputStream;
    3234import java.io.StringWriter;
    3335import java.io.Writer;
     
    137139        }
    138140
    139         public ReadBuffer decode(byte[] in, int offset, int length) {
    140                 byte[] out = new byte[length];
    141                 int written = 0;
    142                 while( length-- > 0 ) {
    143                         byte c = in[offset++];
     141        /**
     142         * @since 0.9.34
     143         */
     144        public void decode(InputStream in, Buffer bout) throws IOException {
     145                OutputStream out = bout.getOutputStream();
     146                while (true) {
     147                        int c = in.read();
     148                        if (c < 0)
     149                                break;
    144150                        if( c == '=' ) {
    145                                 if( length >= 2 ) {
    146                                         byte a = in[offset];
    147                                         byte b = in[offset + 1];
     151                                        int a = in.read();
     152                                        if (a < 0) {
     153                                                out.write(c);
     154                                                break;
     155                                        }
     156                                        int b = in.read();
     157                                        if (b < 0) {
     158                                                out.write(c);
     159                                                out.write(a);
     160                                                break;
     161                                        }
    148162                                        if( ( ( a >= '0' && a <= '9' ) || ( a >= 'A' && a <= 'F' ) ) &&
    149163                                                        ( ( b >= '0' && b <= '9' ) || ( b >= 'A' && b <= 'F' ) ) ) {
     
    152166                                                 */
    153167                                                // System.err.println( "decoding 0x" + (char)a + "" + (char)b );
    154                                                 length -= 2 ;
    155                                                 offset += 2;
    156                                                
    157168                                                if( a >= '0' && a <= '9' )
    158169                                                        a -= '0';
     
    165176                                                        b = (byte) (b - 'A' + 10);
    166177                                               
    167                                                 out[written++]=(byte) (a*16 + b);
     178                                                out.write(a*16 + b);
    168179                                                continue;
    169180                                        }
     
    172183                                                 * softbreak, simply ignore it
    173184                                                 */
    174                                                 length -= 2;
    175                                                 offset += 2;
    176185                                                continue;
     186                                        } else {
     187                                                throw new DecodingException("Bad q-p data after '='");
    177188                                        }
    178                                         /*
    179                                          * no correct encoded sequence found, ignore it and print it literally
    180                                          */
    181                                 }
    182189                        }
    183190                        /*
    184191                         * print out everything else literally
    185192                         */
    186                         out[written++] = c;
     193                        out.write(c);
    187194                }
    188                
    189                 return new ReadBuffer(out, 0, written);
     195                bout.writeComplete(true);
    190196        }
    191197}
  • apps/susimail/src/src/i2p/susi/webmail/encoding/SevenBit.java

    rb013173 r65484510  
    2424package i2p.susi.webmail.encoding;
    2525
     26import java.io.IOException;
     27import java.io.InputStream;
     28
     29import i2p.susi.util.Buffer;
    2630import i2p.susi.util.ReadBuffer;
     31
     32import net.i2p.data.DataHelper;
    2733
    2834/**
     
    3642        }
    3743
    38         /**
     44        /**
    3945         * @throws EncodingException always
    4046         */
     
    4349        }
    4450
    45         /**
     51        /**
    4652         * @throws DecodingException on illegal characters
    4753         */
    48         public ReadBuffer decode(byte[] in, int offset, int length)
     54        @Override
     55        public Buffer decode(byte[] in, int offset, int length)
    4956                        throws DecodingException {
    5057                int backupLength = length;
     
    6269                return new ReadBuffer(in, backupOffset, backupLength);
    6370        }
     71
     72        /**
     73         * We don't do any 8-bit checks like we do for decode(byte[])
     74         * @return in, unchanged
     75         */
     76        @Override
     77        public Buffer decode(Buffer in) {
     78                return in;
     79        }
     80
     81        /**
     82         * Copy in to out, unchanged
     83         * We don't do any 8-bit checks like we do for decode(byte[])
     84         * @since 0.9.34
     85         */
     86        public void decode(InputStream in, Buffer out) throws IOException {
     87                DataHelper.copy(in, out.getOutputStream());
     88                // read complete, write complete
     89        }
    6490}
  • apps/susimail/src/src/i2p/susi/webmail/pop3/POP3MailBox.java

    rb013173 r65484510  
    2929import i2p.susi.webmail.WebMail;
    3030import i2p.susi.util.Config;
     31import i2p.susi.util.Buffer;
    3132import i2p.susi.util.ReadBuffer;
    32 
    33 import java.io.ByteArrayOutputStream;
     33import i2p.susi.util.MemoryBuffer;
     34
     35import java.io.OutputStream;
    3436import java.io.IOException;
    3537import java.io.InputStream;
     
    112114         * @return Byte buffer containing header data or null
    113115         */
    114         public ReadBuffer getHeader( String uidl ) {
     116        public Buffer getHeader( String uidl ) {
    115117                synchronized( synchronizer ) {
    116118                        try {
     
    135137         * @return Byte buffer containing header data or null
    136138         */
    137         private ReadBuffer getHeader( int id ) {
     139        private Buffer getHeader( int id ) {
    138140                        Debug.debug(Debug.DEBUG, "getHeader(" + id + ")");
    139                         ReadBuffer header = null;
     141                        Buffer header = null;
    140142                        if (id >= 1 && id <= mails) {
    141143                                /*
    142144                                 * try 'TOP n 0' command
    143145                                 */
    144                                 header = sendCmdN("TOP " + id + " 0" );
     146                                header = sendCmdN("TOP " + id + " 0", new MemoryBuffer(1024));
    145147                                if( header == null) {
    146148                                        /*
    147149                                         * try 'RETR n' command
    148150                                         */
    149                                         header = sendCmdN("RETR " + id );
     151                                        header = sendCmdN("RETR " + id, new MemoryBuffer(2048));
    150152                                        if (header == null)
    151153                                                Debug.debug( Debug.DEBUG, "RETR returned null" );
     
    161163         *
    162164         * @param uidl
    163          * @return Byte buffer containing body data or null
    164          */
    165         public ReadBuffer getBody( String uidl ) {
     165         * @return the buffer containing body data or null
     166         */
     167        public Buffer getBody(String uidl, Buffer buffer) {
    166168                synchronized( synchronizer ) {
    167169                        try {
     
    175177                        if (id < 0)
    176178                                return null;
    177                         return getBody(id);
     179                        return getBody(id, buffer);
    178180                }
    179181        }
     
    201203                                        continue;
    202204                                SendRecv sr;
    203                                 if (fr.getHeaderOnly() && supportsTOP)
    204                                         sr = new SendRecv("TOP " + id + " 0", Mode.RB);
    205                                 else
    206                                         sr = new SendRecv("RETR " + id, Mode.RB);
     205                                if (fr.getHeaderOnly() && supportsTOP) {
     206                                        sr = new SendRecv("TOP " + id + " 0", fr.getBuffer());
     207                                } else {
     208                                        fr.setHeaderOnly(false);
     209                                        sr = new SendRecv("RETR " + id, fr.getBuffer());
     210                                }
    207211                                sr.savedObject = fr;
    208212                                srs.add(sr);
     
    223227                }
    224228                for (SendRecv sr : srs) {
    225                         if (sr.result) {
    226                                 FetchRequest fr = (FetchRequest) sr.savedObject;
    227                                 fr.setBuffer(sr.rb);
    228                         }
     229                        FetchRequest fr = (FetchRequest) sr.savedObject;
     230                        fr.setSuccess(sr.result);
    229231                }
    230232        }
     
    235237         *
    236238         * @param id message id
    237          * @return Byte buffer containing body data or null
    238          */
    239         private ReadBuffer getBody(int id) {
     239         * @return the buffer containing body data or null
     240         */
     241        private Buffer getBody(int id, Buffer buffer) {
    240242                        Debug.debug(Debug.DEBUG, "getBody(" + id + ")");
    241                         ReadBuffer body = null;
     243                        Buffer body = null;
    242244                        if (id >= 1 && id <= mails) {
    243245                                try {
    244                                         body = sendCmdN( "RETR " + id );
     246                                        body = sendCmdN("RETR " + id, buffer);
    245247                                        if (body == null)
    246248                                                Debug.debug( Debug.DEBUG, "RETR returned null" );
     
    829831                                    case RB:
    830832                                        try {
    831                                                 sr.rb = getResultNa();
     833                                                getResultNa(sr.rb);
    832834                                                sr.result = true;
    833835                                        } catch (IOException ioe) {
     
    890892         * Caller must sync.
    891893         *
    892          * @return buffer or null
    893          */
    894         private ReadBuffer sendCmdN(String cmd )
     894         * @return the buffer or null
     895         */
     896        private Buffer sendCmdN(String cmd, Buffer buffer)
    895897        {
    896898                synchronized (synchronizer) {
    897899                        try {
    898                                 return sendCmdNa(cmd);
     900                                return sendCmdNa(cmd, buffer);
    899901                        } catch (IOException e) {
    900902                                lastError = e.toString();
     
    909911                        if (connected) {
    910912                                try {
    911                                         return sendCmdNa(cmd);
     913                                        return sendCmdNa(cmd, buffer);
    912914                                } catch (IOException e2) {
    913915                                        lastError = e2.toString();
     
    930932         * Caller must sync.
    931933         *
    932          * @return buffer or null
     934         * @return the buffer or null
    933935         * @throws IOException
    934936         */
    935         private ReadBuffer sendCmdNa(String cmd) throws IOException
     937        private Buffer sendCmdNa(String cmd, Buffer buffer) throws IOException
    936938        {
    937939                if (sendCmd1a(cmd)) {
    938                         return getResultNa();
     940                        getResultNa(buffer);
     941                        return buffer;
    939942                } else {
    940943                        Debug.debug( Debug.DEBUG, "sendCmd1a returned false" );
     
    967970         * Caller must sync.
    968971         *
    969          * @return buffer non-null
     972         * @param buffer non-null
    970973         * @throws IOException
    971974         */
    972         private ReadBuffer getResultNa() throws IOException
     975        private void getResultNa(Buffer buffer) throws IOException
    973976        {
    974977                InputStream input = socket.getInputStream();
    975                 StringBuilder buf = new StringBuilder(512);
    976                 ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
    977                 while (DataHelper.readLine(input, buf)) {
    978                         updateActivity();
    979                         int len = buf.length();
    980                         if (len == 0)
    981                                 break; // huh? no \r?
    982                         if (len == 2 && buf.charAt(0) == '.' && buf.charAt(1) == '\r')
    983                                 break;
    984                         String line;
    985                         // RFC 1939 sec. 3 de-byte-stuffing
    986                         if (buf.charAt(0) == '.')
    987                                 line = buf.substring(1);
    988                         else
    989                                 line = buf.toString();
    990                         baos.write(DataHelper.getASCII(line));
    991                         if (buf.charAt(len - 1) != '\r')
    992                                 baos.write((byte) '\n');
    993                         baos.write((byte) '\n');
    994                         buf.setLength(0);
    995                 }
    996                 return new ReadBuffer(baos.toByteArray(), 0, baos.size());
     978                OutputStream out = null;
     979                boolean success = false;
     980                try {
     981                        out = buffer.getOutputStream();
     982                        StringBuilder buf = new StringBuilder(512);
     983                        while (DataHelper.readLine(input, buf)) {
     984                                updateActivity();
     985                                int len = buf.length();
     986                                if (len == 0)
     987                                        break; // huh? no \r?
     988                                if (len == 2 && buf.charAt(0) == '.' && buf.charAt(1) == '\r')
     989                                        break;
     990                                String line;
     991                                // RFC 1939 sec. 3 de-byte-stuffing
     992                                if (buf.charAt(0) == '.')
     993                                        line = buf.substring(1);
     994                                else
     995                                        line = buf.toString();
     996                                out.write(DataHelper.getASCII(line));
     997                                if (buf.charAt(len - 1) != '\r')
     998                                        out.write('\n');
     999                                out.write('\n');
     1000                                buf.setLength(0);
     1001                        }
     1002                        success = true;
     1003                } finally {
     1004                        if (out != null) try { out.close(); } catch (IOException ioe) {}
     1005                        buffer.writeComplete(success);
     1006                }
    9971007        }
    9981008
     
    12791289                public final Mode mode;
    12801290                public String response;
     1291                /** true for success */
    12811292                public boolean result;
    1282                 public ReadBuffer rb;
     1293                /** non-null for RB mode only */
     1294                public final Buffer rb;
    12831295                public List<String> ls;
    12841296                // to remember things
     
    12891301                        send = s;
    12901302                        mode = m;
     1303                        rb = null;
     1304                }
     1305
     1306                /**
     1307                 *  RB mode only
     1308                 *  @param s may be null
     1309                 *  @since 0.9.34
     1310                 */
     1311                public SendRecv(String s, Buffer buffer) {
     1312                        send = s;
     1313                        mode = Mode.RB;
     1314                        rb = buffer;
    12911315                }
    12921316        }
     
    12951319                public String getUIDL();
    12961320                public boolean getHeaderOnly();
    1297                 public void setBuffer(ReadBuffer buffer);
     1321                /** @since 0.9.34 */
     1322                public Buffer getBuffer();
     1323                /** @since 0.9.34 */
     1324                public void setSuccess(boolean success);
     1325                /** @since 0.9.34 */
     1326                public void setHeaderOnly(boolean headerOnly);
    12981327        }
    12991328
  • apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java

    rb013173 r65484510  
    227227         */
    228228        public boolean sendMail(String host, int port, String user, String pass, String sender,
    229                                 Object[] recipients, StringBuilder body,
     229                                String[] recipients, StringBuilder body,
    230230                                List<Attachment> attachments, String boundary)
    231231        {
  • history.txt

    rb013173 r65484510  
     12018-02-07 zzz
     2 * SusiMail: Use input streams for reading mail (ticket #2119)
     3   - Rewrite Base64, HeaderLine, and QuotedPrintable decoders
     4   - Rewrite ReadBuffer class and utilities for streams
     5   - ReadBuffer becomes Buffer interface with multiple implementations
     6   - Rewrite Mail and MailPart to parse the headers only once
     7   - Rewrite MailPart parser to use streams
     8   - MailPart decoder rewrite to decode stream-to-stream
     9
    1102018-02-01 zzz
    211 * Console: Fix number formatting (tickets #1912, #1913, #2126)
  • router/java/src/net/i2p/router/RouterVersion.java

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