You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by ri...@apache.org on 2008/02/13 19:27:26 UTC

svn commit: r627556 - in /geronimo/specs/trunk/geronimo-javamail_1.4_spec: ./ src/main/java/javax/mail/internet/ src/main/java/org/apache/geronimo/mail/util/ src/test/java/javax/mail/internet/

Author: rickmcguire
Date: Wed Feb 13 10:27:22 2008
New Revision: 627556

URL: http://svn.apache.org/viewvc?rev=627556&view=rev
Log:
GERONIMO-3842 Incorrect Subject header encoding for long non-ascii line


Modified:
    geronimo/specs/trunk/geronimo-javamail_1.4_spec/pom.xml
    geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeBodyPart.java
    geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeUtility.java
    geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/Base64Encoder.java
    geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoder.java
    geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeMessageTest.java
    geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeTest.java

Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/pom.xml
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/pom.xml?rev=627556&r1=627555&r2=627556&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/pom.xml (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/pom.xml Wed Feb 13 10:27:22 2008
@@ -34,7 +34,7 @@
     <artifactId>geronimo-javamail_1.4_spec</artifactId>
     <packaging>bundle</packaging>
     <name>JavaMail 1.4</name>
-    <version>1.2</version>
+    <version>1.3-SNAPSHOT</version>
 
     <properties>
         <geronimo.osgi.export.pkg>javax.mail*</geronimo.osgi.export.pkg>

Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeBodyPart.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeBodyPart.java?rev=627556&r1=627555&r2=627556&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeBodyPart.java (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeBodyPart.java Wed Feb 13 10:27:22 2008
@@ -327,7 +327,6 @@
 
 
     public void setFileName(String name) throws MessagingException {
-        System.out.println("Setting file name to " + name);
         // there's an optional session property that requests file name encoding...we need to process this before
         // setting the value.
         if (name != null && SessionUtil.getBooleanProperty(MIME_ENCODEFILENAME, false)) {

Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeUtility.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeUtility.java?rev=627556&r1=627555&r2=627556&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeUtility.java (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeUtility.java Wed Feb 13 10:27:22 2008
@@ -567,23 +567,132 @@
         }
 
         try {
-            // get the string bytes in the correct source charset
-            InputStream in = new ByteArrayInputStream(word.getBytes( javaCharset(charset)));
-            ByteArrayOutputStream out = new ByteArrayOutputStream();
-
+            
+            // we'll format this directly into the string buffer 
+            StringBuffer result = new StringBuffer(); 
+            
+            // this is the maximum size of a segment of encoded data, which is based off 
+            // of a 75 character size limit and all of the encoding overhead elements.
+            int sizeLimit = 75 - 7 - charset.length();
+            
+            // now do the appropriate encoding work 
             if (encoder.equals("base64")) {
                 Base64Encoder dataEncoder = new Base64Encoder();
-                dataEncoder.encodeWord(in, charset, out, SessionUtil.getBooleanProperty(MIME_FOLDENCODEDWORDS, false));
+                // this may recurse on the encoding if the string is too long.  The left-most will not 
+                // get a segment delimiter 
+                encodeBase64(word, result, sizeLimit, charset, dataEncoder, true, SessionUtil.getBooleanProperty(MIME_FOLDENCODEDWORDS, false)); 
             }
             else {
                 QuotedPrintableEncoder dataEncoder = new QuotedPrintableEncoder();
-                dataEncoder.encodeWord(in, charset, encodingWord ? QP_WORD_SPECIALS : QP_TEXT_SPECIALS, out, SessionUtil.getBooleanProperty(MIME_FOLDENCODEDWORDS, false));
+                encodeQuotedPrintable(word, result, sizeLimit, charset, dataEncoder, true, 
+                    SessionUtil.getBooleanProperty(MIME_FOLDENCODEDWORDS, false), encodingWord ? QP_WORD_SPECIALS : QP_TEXT_SPECIALS); 
             }
-
-            byte[] bytes = out.toByteArray();
-            return new String(bytes);
+            return result.toString();    
         } catch (IOException e) {
             throw new UnsupportedEncodingException("Invalid encoding");
+        }
+    }
+    
+    
+    /**
+     * Encode a string into base64 encoding, taking into 
+     * account the maximum segment length. 
+     * 
+     * @param data      The string data to encode.
+     * @param out       The output buffer used for the result.
+     * @param sizeLimit The maximum amount of encoded data we're allowed
+     *                  to have in a single encoded segment.
+     * @param charset   The character set marker that needs to be added to the
+     *                  encoding header.
+     * @param encoder   The encoder instance we're using.
+     * @param firstSegment
+     *                  If true, this is the first (left-most) segment in the
+     *                  data.  Used to determine if segment delimiters need to
+     *                  be added between sections.
+     * @param foldSegments
+     *                  Indicates the type of delimiter to use (blank or newline sequence).
+     */
+    static private void encodeBase64(String data, StringBuffer out, int sizeLimit, String charset, Base64Encoder encoder, boolean firstSegment, boolean foldSegments) throws IOException
+    {
+        // this needs to be converted into the appropriate transfer encoding. 
+        byte [] bytes = data.getBytes(javaCharset(charset)); 
+        
+        int estimatedSize = encoder.estimateEncodedLength(bytes); 
+        
+        // if the estimated encoding size is over our segment limit, split the string in half and 
+        // recurse.  Eventually we'll reach a point where things are small enough.  
+        if (estimatedSize > sizeLimit) {
+            // the first segment indicator travels with the left half. 
+            encodeBase64(data.substring(0, data.length() / 2), out, sizeLimit, charset, encoder, firstSegment, foldSegments);
+            // the second half can never be the first segment 
+            encodeBase64(data.substring(data.length() / 2), out, sizeLimit, charset, encoder, false, foldSegments);
+        }
+        else 
+        {
+            // if this is not the first sement of the encoding, we need to add either a blank or 
+            // a newline sequence to the data 
+            if (!firstSegment) {
+                if (foldSegments) {
+                    out.append("\r\n"); 
+                }
+                else {
+                    out.append(' '); 
+                }
+            }
+            // do the encoding of the segment.
+            encoder.encodeWord(bytes, out, charset);
+        }
+    }
+    
+    
+    /**
+     * Encode a string into quoted printable encoding, taking into 
+     * account the maximum segment length. 
+     * 
+     * @param data      The string data to encode.
+     * @param out       The output buffer used for the result.
+     * @param sizeLimit The maximum amount of encoded data we're allowed
+     *                  to have in a single encoded segment.
+     * @param charset   The character set marker that needs to be added to the
+     *                  encoding header.
+     * @param encoder   The encoder instance we're using.
+     * @param firstSegment
+     *                  If true, this is the first (left-most) segment in the
+     *                  data.  Used to determine if segment delimiters need to
+     *                  be added between sections.
+     * @param foldSegments
+     *                  Indicates the type of delimiter to use (blank or newline sequence).
+     */
+    static private void encodeQuotedPrintable(String data, StringBuffer out, int sizeLimit, String charset, QuotedPrintableEncoder encoder, 
+        boolean firstSegment, boolean foldSegments, String specials)  throws IOException 
+    {
+        // this needs to be converted into the appropriate transfer encoding. 
+        byte [] bytes = data.getBytes(javaCharset(charset)); 
+        
+        int estimatedSize = encoder.estimateEncodedLength(bytes, specials); 
+        
+        // if the estimated encoding size is over our segment limit, split the string in half and 
+        // recurse.  Eventually we'll reach a point where things are small enough.  
+        if (estimatedSize > sizeLimit) {
+            // the first segment indicator travels with the left half. 
+            encodeQuotedPrintable(data.substring(0, data.length() / 2), out, sizeLimit, charset, encoder, firstSegment, foldSegments, specials);
+            // the second half can never be the first segment 
+            encodeQuotedPrintable(data.substring(data.length() / 2), out, sizeLimit, charset, encoder, false, foldSegments, specials);
+        }
+        else 
+        {
+            // if this is not the first sement of the encoding, we need to add either a blank or 
+            // a newline sequence to the data 
+            if (!firstSegment) {
+                if (foldSegments) {
+                    out.append("\r\n"); 
+                }
+                else {
+                    out.append(' '); 
+                }
+            }
+            // do the encoding of the segment.
+            encoder.encodeWord(bytes, out, charset, specials);
         }
     }
 

Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/Base64Encoder.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/Base64Encoder.java?rev=627556&r1=627555&r2=627556&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/Base64Encoder.java (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/Base64Encoder.java Wed Feb 13 10:27:22 2008
@@ -464,7 +464,7 @@
         PrintStream writer = new PrintStream(out);
 
         // encoded words are restricted to 76 bytes, including the control adornments.
-        int limit = 76 - 7 - charset.length();
+        int limit = 75 - 7 - charset.length();
         boolean firstLine = true;
         StringBuffer encodedString = new StringBuffer(76);
 
@@ -499,9 +499,95 @@
 
             // reset our string buffer for the next segment.
             encodedString.setLength(0);
+            // we need a delimiter after this 
+            firstLine = false; 
         }
     }
 
+
+    /**
+     * Perform RFC-2047 word encoding using Base64 data encoding.
+     *
+     * @param in      The source for the encoded data.
+     * @param charset The charset tag to be added to each encoded data section.
+     * @param out     The output stream where the encoded data is to be written.
+     * @param fold    Controls whether separate sections of encoded data are separated by
+     *                linebreaks or whitespace.
+     *
+     * @exception IOException
+     */
+    public void encodeWord(byte[] data, StringBuffer out, String charset) throws IOException
+    {
+        // append the word header 
+        out.append("=?");
+        out.append(charset);
+        out.append("?B?"); 
+        // add on the encodeded data       
+        encodeWordData(data, out); 
+        // the end of the encoding marker 
+        out.append("?="); 
+    }
+    
+    /**
+     * encode the input data producing a base 64 output stream.
+     *
+     * @return the number of bytes produced.
+     */
+    public void encodeWordData(byte[] data, StringBuffer out) 
+    {
+        int modulus = data.length % 3;
+        int dataLength = (data.length - modulus);
+        int a1, a2, a3;
+
+        for (int i = 0; i < dataLength; i += 3)
+        {
+            a1 = data[i] & 0xff;
+            a2 = data[i + 1] & 0xff;
+            a3 = data[i + 2] & 0xff;
+            
+            out.append((char)encodingTable[(a1 >>> 2) & 0x3f]);
+            out.append((char)encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
+            out.append((char)encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
+            out.append((char)encodingTable[a3 & 0x3f]);
+        }
+
+        /*
+         * process the tail end.
+         */
+        int    b1, b2, b3;
+        int    d1, d2;
+
+        switch (modulus)
+        {
+        case 0:        /* nothing left to do */
+            break;
+        case 1:
+            d1 = data[dataLength] & 0xff;
+            b1 = (d1 >>> 2) & 0x3f;
+            b2 = (d1 << 4) & 0x3f;
+
+            out.append((char)encodingTable[b1]);
+            out.append((char)encodingTable[b2]);
+            out.append((char)padding);
+            out.append((char)padding);
+            break;
+        case 2:
+            d1 = data[dataLength] & 0xff;
+            d2 = data[dataLength + 1] & 0xff;
+
+            b1 = (d1 >>> 2) & 0x3f;
+            b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
+            b3 = (d2 << 2) & 0x3f;
+
+            out.append((char)encodingTable[b1]);
+            out.append((char)encodingTable[b2]);
+            out.append((char)encodingTable[b3]);
+            out.append((char)padding);
+            break;
+        }
+    }
+    
+
     /**
      * encode the input data producing a base 64 output stream.
      *
@@ -520,7 +606,7 @@
                 int  a1 = inBuffer[0] & 0xff;
                 int  a2 = inBuffer[1] & 0xff;
                 int  a3 = inBuffer[2] & 0xff;
-
+                
                 out.append((char)encodingTable[(a1 >>> 2) & 0x3f]);
                 out.append((char)encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
                 out.append((char)encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
@@ -550,5 +636,22 @@
                 return;
             }
         }
+    }
+    
+    
+    /**
+     * Estimate the final encoded size of a segment of data. 
+     * This is used to ensure that the encoded blocks do 
+     * not get split across a unicode character boundary and 
+     * that the encoding will fit within the bounds of 
+     * a mail header line. 
+     * 
+     * @param data   The data we're anticipating encoding.
+     * 
+     * @return The size of the byte data in encoded form. 
+     */
+    public int estimateEncodedLength(byte[] data) 
+    {
+        return ((data.length + 2) / 3) * 4; 
     }
 }

Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoder.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoder.java?rev=627556&r1=627555&r2=627556&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoder.java (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoder.java Wed Feb 13 10:27:22 2008
@@ -636,8 +636,8 @@
         PushbackInputStream inStream = new PushbackInputStream(in);
         PrintStream writer = new PrintStream(out);
 
-        // segments of encoded data are limited to 76 byes, including the control sections.
-        int limit = 76 - 7 - charset.length();
+        // segments of encoded data are limited to 75 byes, including the control sections.
+        int limit = 75 - 7 - charset.length();
         boolean firstLine = true;
         StringBuffer encodedString = new StringBuffer(76);
 
@@ -672,7 +672,102 @@
 
             // we reset the string buffer and reuse it.
             encodedString.setLength(0);
+            // we need a delimiter between sections from this point on. 
+            firstLine = false;
         }
+    }
+
+
+    /**
+     * Perform RFC-2047 word encoding using Base64 data encoding.
+     *
+     * @param in      The source for the encoded data.
+     * @param charset The charset tag to be added to each encoded data section.
+     * @param out     The output stream where the encoded data is to be written.
+     * @param fold    Controls whether separate sections of encoded data are separated by
+     *                linebreaks or whitespace.
+     *
+     * @exception IOException
+     */
+    public void encodeWord(byte[] data, StringBuffer out, String charset, String specials) throws IOException
+    {
+        // append the word header 
+        out.append("=?");
+        out.append(charset);
+        out.append("?Q?"); 
+        // add on the encodeded data       
+        encodeWordData(data, out, specials); 
+        // the end of the encoding marker 
+        out.append("?="); 
+    }
+
+
+    /**
+     * Perform RFC-2047 word encoding using Q-P data encoding.
+     *
+     * @param in       The source for the encoded data.
+     * @param charset  The charset tag to be added to each encoded data section.
+     * @param specials The set of special characters that we require to encoded.
+     * @param out      The output stream where the encoded data is to be written.
+     * @param fold     Controls whether separate sections of encoded data are separated by
+     *                 linebreaks or whitespace.
+     *
+     * @exception IOException
+     */
+    public void encodeWordData(byte[] data, StringBuffer out, String specials) throws IOException {
+        for (int i = 0; i < data.length; i++) {
+            int ch = data[i] & 0xff; ; 
+
+            // spaces require special handling.  If the next character is a line terminator, then
+            // the space needs to be encoded.
+            if (ch == ' ') {
+                // blanks get translated into underscores, because the encoded tokens can't have embedded blanks.
+                out.append('_');
+            }
+            // non-ascii chars and the designated specials all get encoded.
+            else if (ch < 32 || ch >= 127 || specials.indexOf(ch) != -1) {
+                out.append('=');
+                out.append((char)encodingTable[ch >> 4]);
+                out.append((char)encodingTable[ch & 0x0F]);
+            }
+            else {
+                // good character, just use unchanged.
+                out.append((char)ch);
+            }
+        }
+    }
+    
+    
+    /**
+     * Estimate the final encoded size of a segment of data. 
+     * This is used to ensure that the encoded blocks do 
+     * not get split across a unicode character boundary and 
+     * that the encoding will fit within the bounds of 
+     * a mail header line. 
+     * 
+     * @param data   The data we're anticipating encoding.
+     * 
+     * @return The size of the byte data in encoded form. 
+     */
+    public int estimateEncodedLength(byte[] data, String specials) 
+    {
+        int count = 0; 
+        
+        for (int i = 0; i < data.length; i++) {
+            // make sure this is just a single byte value.
+            int  ch = data[i] & 0xff;
+
+            // non-ascii chars and the designated specials all get encoded.
+            if (ch < 32 || ch >= 127 || specials.indexOf(ch) != -1) {
+                // Q encoding translates a single char into 3 characters 
+                count += 3; 
+            }
+            else {
+                // non-encoded character 
+                count++;
+            }
+        }
+        return count; 
     }
 }
 

Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeMessageTest.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeMessageTest.java?rev=627556&r1=627555&r2=627556&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeMessageTest.java (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeMessageTest.java Wed Feb 13 10:27:22 2008
@@ -67,7 +67,6 @@
         assertEquals(((InternetAddress)newMessage.getSender()).getAddress(), "foo");
 
         String[] headers = newMessage.getHeader("foo");
-        System.out.println("Get header returned " + headers);
         assertTrue(headers.length == 1);
         assertEquals(headers[0], "bar");
 
@@ -188,7 +187,6 @@
 
         recipients = msg.getAllRecipients();
 
-        System.out.println("Get all recipients returns " + recipients.length);
         assertTrue(recipients.length == 3);
         assertEquals(recipients[0], dev);
         assertEquals(recipients[1], user);
@@ -266,7 +264,6 @@
         msg.setRecipients(type, "geronimo-dev");
         recipients = msg.getRecipients(type);
         assertTrue(recipients.length == 1);
-        System.out.println("Received address " + recipients[0] + " of type " + recipients[0].getType());
         assertEquals(recipients[0], dev);
 
         msg.addRecipients(type, "geronimo-user");

Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeTest.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeTest.java?rev=627556&r1=627555&r2=627556&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeTest.java (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeTest.java Wed Feb 13 10:27:22 2008
@@ -52,8 +52,6 @@
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         mime.writeTo(baos);
 
-        System.out.println(baos);
-
         MimeMessage mime2 = new MimeMessage(session, new ByteArrayInputStream(baos.toByteArray()));
         assertTrue(mime2.getContent() instanceof MimeMultipart);
         MimeMultipart parts2 = (MimeMultipart) mime2.getContent();