Changeset ce49c00


Ignore:
Timestamp:
Jan 9, 2018 6:59:01 PM (2 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
823526a
Parents:
9f7ec39
Message:

SusiMail?: Rewrite/fix subject line encoding

Location:
apps/susimail/src/src/i2p/susi/webmail
Files:
2 edited

Legend:

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

    r9f7ec39 rce49c00  
    23352335                if( ok ) {
    23362336                        StringBuilder body = new StringBuilder(1024);
     2337                        // todo include real names, and headerline encode them
    23372338                        body.append( "From: " + from + "\r\n" );
    23382339                        Mail.appendRecipients( body, toList, "To: " );
    23392340                        Mail.appendRecipients( body, ccList, "Cc: " );
    2340                         body.append( "Subject: " );
    23412341                        try {
    2342                                 body.append( hl.encode( subject ) );
     2342                                body.append(hl.encode("Subject: " + subject.trim()));
    23432343                        } catch (EncodingException e) {
    23442344                                ok = false;
  • apps/susimail/src/src/i2p/susi/webmail/encoding/HeaderLine.java

    r9f7ec39 rce49c00  
    6464        }
    6565
     66        /** @since 0.9.33 */
     67        private static boolean isWhiteSpace(char c) {
     68                return c == ' ' || c == '\t';
     69        }
     70
     71        /** @since 0.9.33 */
     72/*
     73        private static boolean isControl(char c) {
     74                return (c < 32 && !isWhiteSpace(c)) ||
     75                       (c >= 127 && c < 160);
     76        }
     77*/
     78
     79        /** @since 0.9.33 */
     80        private static boolean isControlOrMultiByte(char c) {
     81                return (c < 32 && !isWhiteSpace(c)) || c >= 127;
     82        }
     83
     84        /** @since 0.9.33 */
     85/*
     86        private static boolean isSpecial(char c) {
     87                return c == '(' || c == ')' || c == '<' || c == '>' ||
     88                       c == '@' || c == ',' || c == ';' || c == ':' ||
     89                       c == '\\' || c == '"' || c == '.' || c == '[' ||
     90                       c == ']';
     91        }
     92*/
     93
     94        /** @since 0.9.33 */
     95        private static boolean isPSpecial(char c) {
     96                return c == '!' || c == '*' || c == '+' || c == '-' ||
     97                       c == '/' || c == '=' || c == '_';
     98        }
     99
     100        /** @since 0.9.33 */
     101        private static boolean isPSafe(char c) {
     102                return (c >= '0' && c <= '9') ||
     103                       (c >= 'a' && c <= 'z') ||
     104                       (c >= 'A' && c <= 'Z') ||
     105                       isPSpecial(c);
     106        }
     107
     108        /** @since 0.9.33 */
     109/*
     110        private static boolean isAtom(char c) {
     111                return ! (isWhiteSpace(c) || isControl(c) || isSpecial(c));
     112        }
     113*/
     114
     115        /**
     116         *  Encode a single header line ONLY. Do NOT include the \r\n.
     117         *  Returns a string of one or more lines including the trailing \r\n.
     118         *  Field-name will not be encoded, must be less than 62 chars.
     119         *
     120         *  The fieldBody is treated as "unstructured text",
     121         *  which is suitable only for the field names "Subject" and "Comments".
     122         *  We do NOT tokenize into structured fields.
     123         *
     124         *  To make things easy, we either encode the whole field body as RFC 2047,
     125         *  or don't encode at all. If it's too long for a single line, we
     126         *  encode it, even if we didn't otherwise have to.
     127         *  We don't do quoted-string.
     128         *
     129         *  This will not split multibyte chars, including supplementary chars,
     130         *  across lines.
     131         *
     132         *  TODO this will not work for quoting structured text
     133         *  such as recipient names on the "To" and "Cc" lines.
     134         *
     135         *  @param str must start with "field-name: "
     136         */
    66137        @Override
    67138        public String encode(String str) throws EncodingException {
     139                str = str.trim();
     140                int l = str.indexOf(": ");
     141                if (l <= 0 || l >= 64)
     142                        throw new EncodingException("bad 'field-name: '" + str);
     143                l += 2;
     144                boolean quote = false;
     145                if (str.length() > 76) {
     146                        quote = true;
     147                } else {
     148                        for (int i = l; i < str.length(); i++) {
     149                                char c = str.charAt(i);
     150                                if (isControlOrMultiByte(c) || isPSpecial(c)) {
     151                                        quote = true;
     152                                        break;
     153                                }
     154                        }
     155                }
     156                if (!quote)
     157                        return str + "\r\n";
     158                // Output encoded.
    68159                StringBuilder out = new StringBuilder();
    69                 int l = 0;
    70                 boolean quoting = false;
    71                 boolean quote = false;
    72                 boolean linebreak = false;
    73                 StringBuilder quotedSequence = null;
    74                 int index = 0;
    75                 while( true ) {
    76                         if (index >= str.length())
    77                                 break;
    78                         char c = str.charAt(index++);
    79                         quote = true;
    80                         if( c > 32 && c < 127 && c != 61 ) {
    81                                 quote = false;
    82                         }
    83                         else if( ( c == 32 || c == 9 ) ) {
    84                                 quote = false;
    85                                 if (index >= str.length())
    86                                         quote = true;
    87                                 else if (str.charAt(index) == '\r' || str.charAt(index) == '\n')
    88                                         quote = true;
    89                         }
    90                         else if (c == '\r' && index < str.length() && str.charAt(index) == '\n') {
    91                                 quote = false;
    92                                 linebreak = true;
    93                                 index++;
    94                         }
    95                         if( quote ) {
    96                                 // the encoded char
    97                                 StringBuilder qc = new StringBuilder(16);
    98                                 if (c <= 127) {
    99                                         // single byte char
     160                out.append(str.substring(0, l));
     161                int start = l;
     162                StringBuilder qc = new StringBuilder(16);
     163                for (int i = start; i < str.length(); i++) {
     164                        // use codePointAt(), not charAt(), so supplementary chars work
     165                        char c = str.charAt(i);
     166                        // put the encoded char in the temp buffer
     167                        qc.setLength(0);
     168                        if (c <= 127) {
     169                                // single byte char
     170                                if (c == ' ') {
     171                                        qc.append('_');
     172                                } else if (isPSafe(c) && c != '_' && c != '?') {
     173                                        qc.append(c);
     174                                } else {
    100175                                        qc.append(HexTable.table[c]);
     176                                }
     177                        } else {
     178                                // multi-byte char, possibly supplementary
     179                                byte[] utf;
     180                                if (Character.isHighSurrogate(c) && i < str.length() - 1) {
     181                                        // use substring() to get the whole thing if multi-char
     182                                        utf = DataHelper.getUTF8(str.substring(i, i + 2));
     183                                        // increment i below after start test
    101184                                } else {
    102                                         byte[] utf = DataHelper.getUTF8(String.valueOf(c));
    103                                         for (int j = 0; j < utf.length; j++) {
    104                                                 int b = utf[j] & 0xff;
    105                                                 qc.append(HexTable.table[b]);
    106                                         }
    107                                 }
    108                                 if (quoting) {
    109                                         // would it be too long?
    110                                         if (l + quotedSequence.length() + qc.length() + 2 >= 76) {
    111                                                 // close q-seq, wrap line, and start a new q-seq
    112                                                 out.append(quotedSequence);
    113                                                 out.append("?=\r\n\t");
    114                                                 l = 1;
    115                                                 quoting = false;
    116                                         }
    117                                 }
    118                                 if (!quoting) {
    119                                         // close q-seq, wrap line, and start a new q-seq
    120                                         quotedSequence = new StringBuilder(64);
    121                                         quotedSequence.append("=?utf-8?Q?");
    122                                         quoting = true;
    123                                 }
    124                                 quotedSequence.append(qc);
    125                         }
    126                         else {
    127                                 if( quoting ) {
    128                                         quotedSequence.append("?=");
    129                                         int sl = quotedSequence.length();
    130                                         if( l + sl >= 76 ) {
    131                                                 /*
    132                                                  * wrap line
    133                                                  */
    134                                                 out.append( "\r\n\t" );
    135                                                 l = 1;
    136                                         }
    137                                         out.append( quotedSequence );
    138                                         l += sl;
    139                                         quoting = false;
    140                                 }
    141                                 if( linebreak ) {
    142                                         out.append( "\r\n" );
    143                                         linebreak = false;
    144                                         l = 0;
    145                                 }
    146                                 else {
    147                                         if( l >= 76 ) {
    148                                                 out.append( "\r\n\t" );
    149                                                 l = 1;
    150                                         }
    151                                         out.append(c);
    152                                         l++;
    153                                 }
    154                         }
    155                 }
    156                 if( quoting ) {
    157                         quotedSequence.append("?=");
    158                         int sl = quotedSequence.length();
    159                         if( l + sl >= 76 ) {
    160                                 /*
    161                                  * wrap line
    162                                  */
    163                                 out.append( "\r\n\t" );
    164                                 l = 1;
    165                         }
    166                         out.append( quotedSequence );
    167                 }
     185                                        utf = DataHelper.getUTF8(String.valueOf(c));
     186                                }
     187                                for (int j = 0; j < utf.length; j++) {
     188                                        int b = utf[j] & 0xff;
     189                                        qc.append(HexTable.table[b]);
     190                                }
     191                        }
     192                        // now see if we have room
     193                        if (i == start) {
     194                                out.append("=?utf-8?Q?");
     195                                l += 10;
     196                        } else {
     197                                // subsequent chars
     198                                if (l + 2 + qc.length() > 76) {
     199                                        out.append("?=\r\n\t=?utf-8?Q?");
     200                                        l = 11;
     201                                }
     202                        }
     203                        if (Character.isHighSurrogate(c) && i < str.length() - 1)
     204                                i++;
     205                        out.append(qc);
     206                        l += qc.length();
     207                }
     208                out.append("?=\r\n");
    168209                return out.toString();
    169210        }
    170211
     212        /**
     213         *  Decode all the header lines, up through \r\n\r\n,
     214         *  and puts them in the ReadBuffer, including the \r\n\r\n
     215         */
    171216        public ReadBuffer decode( byte in[], int offset, int length ) throws DecodingException {
    172217                ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
     
    323368
    324369/*****
    325 TODO put UTF-8 back and move to a unit test
    326370        public static void main( String[] args ) throws EncodingException {
    327                 String text = "Subject: test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test \r\n" +
    328                 "From: UTF8 <smoerebroed@mail.i2p>\r\n" +
    329                 "To: UTF8 <lalala@mail.i2p>\r\n";
     371                test("Subject: not utf8");
     372                test("Subject: a=b c+d");
     373                test("Subject: está");
     374                test("Subject: 🚚 ORDER SHIPPED");
     375                test("12345678: 12345678901234567890123456789012345678901234567890123456789012345");
     376                test("12345678: 123456789012345678901234567890123456789012345678901234567890123456");
     377                test("12345678: 1234567890123456789012345678901234567890123456789012345678901234567");
     378                test("12345678: 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789");
     379                test("12345678: 12345678901234567890123456789012345678901234567890123456789012345@");
     380                test("12345678: 123456789012345678901234567890123456789012345678901234567890123456@");
     381                test("12345678: 123456789012345678901234567890123456789012345678901@234567890123456789");
     382                test("12345678: 1234567890123456789012345678901234567890123456789012@34567890123456789");
     383                test("12345678: 12345678901234567890123456789012345678901234567890123@4567890123456789");
     384                test("12345678: 123456789012345678901234567890123456789012345678901234@567890123456789");
     385        }
     386
     387        private static void test(String x) {
    330388                HeaderLine hl = new HeaderLine();
    331                 System.out.println( hl.encode( text ) );
    332                 System.out.println( hl.encode( "test UTF8" ) );
    333                 System.out.println( hl.encode( "UTF8" ) );
     389                String orig = x;
     390                System.out.println(x);
     391                try {
     392                        x = hl.encode(x);
     393                } catch (EncodingException e) {
     394                        e.printStackTrace();
     395                        return;
     396                }
     397                System.out.print(x);
     398                try {
     399                        ReadBuffer rb = hl.decode(x);
     400                        String rt = DataHelper.getUTF8(rb.content, rb.offset, rb.length);
     401                        if (rt.equals(orig + "\r\n")) {
     402                                System.out.println("Test passed\n");
     403                        } else {
     404                                System.out.print(rt);
     405                                System.out.println("*** Test failed ***\n");
     406                        }
     407                } catch (DecodingException e) {
     408                        e.printStackTrace();
     409                }
    334410        }
    335411****/
Note: See TracChangeset for help on using the changeset viewer.