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/08/27 16:11:03 UTC

svn commit: r689486 - /geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeMultipart.java

Author: rickmcguire
Date: Wed Aug 27 07:11:03 2008
New Revision: 689486

URL: http://svn.apache.org/viewvc?rev=689486&view=rev
Log:
GERONIMO-4270 javamail MimeMultipart not accepting linear white space at end of boundary strings.


Modified:
    geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeMultipart.java

Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeMultipart.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeMultipart.java?rev=689486&r1=689485&r2=689486&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeMultipart.java (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeMultipart.java Wed Aug 27 07:11:03 2008
@@ -25,7 +25,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.io.PushbackInputStream;
 
 import java.util.Arrays; 
 
@@ -173,24 +172,23 @@
         try {
             ContentType cType = new ContentType(contentType);
             InputStream is = new BufferedInputStream(ds.getInputStream());
-            PushbackInputStream pushbackInStream = null;
+            BufferedInputStream pushbackInStream = null;
             String boundaryString = cType.getParameter("boundary"); 
             byte[] boundary = null; 
             if (boundaryString == null) {
-                pushbackInStream = new PushbackInputStream(is, 128);  
+                pushbackInStream = new BufferedInputStream(is, 1200);  
                 // read until we find something that looks like a boundary string 
                 boundary = readTillFirstBoundary(pushbackInStream); 
             }
             else {
                 boundary = ("--" + boundaryString).getBytes();
-                pushbackInStream = new PushbackInputStream(is, (boundary.length + 2));
+                pushbackInStream = new BufferedInputStream(is, boundary.length + 1000);
                 readTillFirstBoundary(pushbackInStream, boundary);
             }
             
-            while (pushbackInStream.available() > 0){
+            while (true) {
                 MimeBodyPartInputStream partStream;
-                partStream = new MimeBodyPartInputStream(pushbackInStream,
-                        boundary);
+                partStream = new MimeBodyPartInputStream(pushbackInStream, boundary);
                 addBodyPart(new MimeBodyPart(partStream));
 
                 // terminated by an EOF rather than a proper boundary?
@@ -220,13 +218,17 @@
      * @param boundary
      * @throws MessagingException
      */
-    private byte[] readTillFirstBoundary(PushbackInputStream pushbackInStream) throws MessagingException {
+    private byte[] readTillFirstBoundary(BufferedInputStream pushbackInStream) throws MessagingException {
         ByteArrayOutputStream preambleStream = new ByteArrayOutputStream();
 
         try {
-            while (pushbackInStream.available() > 0) {
+            while (true) {
                 // read the next line 
                 byte[] line = readLine(pushbackInStream); 
+                // hit an EOF?
+                if (line == null) {
+                    throw new MessagingException("Unexpected End of Stream while searching for first Mime Boundary");
+                }
                 // if this looks like a boundary, then make it so 
                 if (line.length > 2 && line[0] == '-' && line[1] == '-') {
                     // save the preamble, if there is one.
@@ -234,7 +236,7 @@
                     if (preambleBytes.length > 0) {
                         preamble = new String(preambleBytes);
                     }
-                    return line;        
+                    return stripLinearWhiteSpace(line);        
                 }
                 else {
                     // this is part of the preamble.
@@ -243,11 +245,40 @@
                     preambleStream.write('\n'); 
                 }
             }
-            throw new MessagingException("Unexpected End of Stream while searching for first Mime Boundary");
         } catch (IOException ioe) {
             throw new MessagingException(ioe.toString(), ioe);
         }
     }
+    
+    
+    /**
+     * Scan a line buffer stripping off linear whitespace 
+     * characters, returning a new array without the 
+     * characters, if possible. 
+     * 
+     * @param line   The source line buffer.
+     * 
+     * @return A byte array with white space characters removed, 
+     *         if necessary.
+     */
+    private byte[] stripLinearWhiteSpace(byte[] line) {
+        int index = line.length - 1; 
+        // if the last character is not a space or tab, we 
+        // can use this unchanged 
+        if (line[index] != ' ' && line[index] != '\t') {
+            return line; 
+        }
+        // scan backwards for the first non-white space 
+        for (; index > 0; index--) {
+            if (line[index] != ' ' && line[index] != '\t') {
+                break;       
+            }
+        }
+        // make a shorter copy of this 
+        byte[] newLine = new byte[index + 1]; 
+        System.arraycopy(line, 0, newLine, 0, index + 1); 
+        return newLine; 
+    }
 
     /**
      * Move the read pointer to the begining of the first part
@@ -258,16 +289,20 @@
      * @param boundary
      * @throws MessagingException
      */
-    private void readTillFirstBoundary(PushbackInputStream pushbackInStream, byte[] boundary) throws MessagingException {
+    private void readTillFirstBoundary(BufferedInputStream pushbackInStream, byte[] boundary) throws MessagingException {
         ByteArrayOutputStream preambleStream = new ByteArrayOutputStream();
 
         try {
-            while (pushbackInStream.available() > 0) {
+            while (true) {
                 // read the next line 
                 byte[] line = readLine(pushbackInStream); 
+                // hit an EOF?
+                if (line == null) {
+                    throw new MessagingException("Unexpected End of Stream while searching for first Mime Boundary");
+                }
                 
-                // if this is the same length as our target boundary, then compare the two. 
-                if (Arrays.equals(line, boundary)) {
+                // apply the boundary comparison rules to this 
+                if (compareBoundary(line, boundary)) {
                     // save the preamble, if there is one.
                     byte[] preambleBytes = preambleStream.toByteArray();
                     if (preambleBytes.length > 0) {
@@ -275,19 +310,56 @@
                     }
                     return;        
                 }
-                else {
-                    // this is part of the preamble.
-                    preambleStream.write(line);
-                    preambleStream.write('\r'); 
-                    preambleStream.write('\n'); 
-                }
+                
+                // this is part of the preamble.
+                preambleStream.write(line);
+                preambleStream.write('\r'); 
+                preambleStream.write('\n'); 
             }
-            throw new MessagingException("Unexpected End of Stream while searching for first Mime Boundary");
         } catch (IOException ioe) {
             throw new MessagingException(ioe.toString(), ioe);
         }
     }
     
+    
+    /**
+     * Peform a boundary comparison, taking into account 
+     * potential linear white space 
+     * 
+     * @param line     The line to compare.
+     * @param boundary The boundary we're searching for
+     * 
+     * @return true if this is a valid boundary line, false for 
+     *         any mismatches.
+     */
+    private boolean compareBoundary(byte[] line, byte[] boundary) {
+        // if the line is too short, this is an easy failure 
+        if (line.length < boundary.length) {
+            return false;
+        }
+        
+        // this is the most common situation
+        if (line.length == boundary.length) {
+            return Arrays.equals(line, boundary); 
+        }
+        // the line might have linear white space after the boundary portions
+        for (int i = 0; i < boundary.length; i++) {
+            // fail on any mismatch 
+            if (line[i] != boundary[i]) {
+                return false; 
+            }
+        }
+        // everything after the boundary portion must be linear whitespace 
+        for (int i = boundary.length; i < line.length; i++) {
+            // fail on any mismatch 
+            if (line[i] != ' ' && line[i] != '\t') { 
+                return false; 
+            }
+        }
+        // these are equivalent 
+        return true; 
+    }
+    
     /**
      * Read a single line of data from the input stream, 
      * returning it as an array of bytes. 
@@ -298,7 +370,7 @@
      *         null if there's nothing left in the stream.
      * @exception MessagingException
      */
-    private byte[] readLine(PushbackInputStream in) throws IOException 
+    private byte[] readLine(BufferedInputStream in) throws IOException 
     {
         ByteArrayOutputStream line = new ByteArrayOutputStream();
         
@@ -312,11 +384,12 @@
                 break; 
             }
             else if (value == '\r') {
+                in.mark(10); 
                 value = in.read(); 
                 // we expect to find a linefeed after the carriage return, but 
                 // some things play loose with the rules. 
                 if (value != '\n') {
-                    in.unread(value); 
+                    in.reset(); 
                 }
                 break; 
             }
@@ -333,7 +406,6 @@
         return line.toByteArray(); 
     }
     
-    
 
     protected InternetHeaders createInternetHeaders(InputStream in) throws MessagingException {
         return new InternetHeaders(in);
@@ -361,12 +433,12 @@
     }
 
     private class MimeBodyPartInputStream extends InputStream {
-        PushbackInputStream inStream;
+        BufferedInputStream inStream;
         public boolean boundaryFound = false;
         byte[] boundary;
         public boolean finalBoundaryFound = false; 
 
-        public MimeBodyPartInputStream(PushbackInputStream inStream, byte[] boundary) {
+        public MimeBodyPartInputStream(BufferedInputStream inStream, byte[] boundary) {
             super();
             this.inStream = inStream;
             this.boundary = boundary;
@@ -385,10 +457,12 @@
             }
             
             // read the next value from stream
-            int value = inStream.read();
+            int firstChar = inStream.read();
             // premature end?  Handle it like a boundary located 
-            if (value == -1) {
+            if (firstChar == -1) {
                 boundaryFound = true; 
+                // also mark this as the end 
+                finalBoundaryFound = true; 
                 return -1; 
             }
             
@@ -399,41 +473,35 @@
             // NB:, we only handle [\r]\n--boundary marker[--]
             // we need to at least accept what most mail servers would consider an 
             // invalid format using just '\n'
-            if (value != '\r' && value != '\n') {
+            if (firstChar != '\r' && firstChar != '\n') {
                 // not a \r, just return the byte as is 
-                return value;
+                return firstChar;
             }
-            
-            int lineendStyle = 2;    // this indicates the type of linend we need to push back. 
+            // we might need to rewind to this point.  The padding is to allow for 
+            // line terminators and linear whitespace on the boundary lines 
+            inStream.mark(boundary.length + 1000); 
+            // we need to keep track of the first read character in case we need to 
+            // rewind back to the mark point 
+            int value = firstChar; 
             // if this is a '\r', then we require the '\n'
             if (value == '\r') {
                 // now scan ahead for the second character 
                 value = inStream.read();
                 if (value != '\n') {
                     // only a \r, so this can't be a boundary.  Return the 
-                    // \r as if it was data 
-                    inStream.unread(value);
+                    // \r as if it was data, after first resetting  
+                    inStream.reset(); 
                     return '\r';
                 } 
-            } else {
-                lineendStyle = 1;    // single character linend 
-            }
+            } 
+            
             value = inStream.read();
             // if the next character is not a boundary start, we 
             // need to handle this as a normal line end 
             if ((byte) value != boundary[0]) {
-                inStream.unread(value);
-                // just a naked line feed...return that without pushing anything back 
-                if (lineendStyle == 1) {
-                    return '\n'; 
-                }
-                else 
-                {
-                    inStream.unread('\n');
-                    // the next character read will by the 0x0a, which will 
-                    // be handled as 
-                    return '\r';
-                }
+                // just reset and return the first character as data 
+                inStream.reset(); 
+                return firstChar; 
             }
             
             // we're here because we found a "\r\n-" sequence, which is a potential 
@@ -451,25 +519,9 @@
             // return the EOL character 
             if (boundaryIndex != boundary.length) { 
                 // Boundary not found. Restoring bytes skipped.
-                // write first skipped byte, push back the rest
-                
-                // Stream might have ended 
-                if (value != -1) { 
-                    inStream.unread(value);
-                }
-                // restore the portion of the boundary string that we matched 
-                inStream.unread(boundary, 0, boundaryIndex);
-                // just a naked line feed...return that without pushing anything back 
-                if (lineendStyle == 1) {
-                    return '\n'; 
-                }
-                else 
-                {
-                    inStream.unread('\n');
-                    // the next character read will by the 0x0a, which will 
-                    // be handled as 
-                    return '\r';
-                }
+                // just reset and return the first character as data 
+                inStream.reset(); 
+                return firstChar; 
             }
             
             // The full boundary sequence should be \r\n--boundary string[--]\r\n
@@ -479,50 +531,37 @@
                 // crud, we have a bad boundary terminator.  We need to unwind this all the way 
                 // back to the lineend and pretend none of this ever happened
                 if (value != '-') {
-                    // push back the end markers 
-                    // Stream might have ended 
-                    if (value != -1) { 
-                        inStream.unread(value);
-                    }
-                    inStream.unread('-'); 
-                    // the entire boundary string 
-                    inStream.unread(boundary);
-                    // just a naked line feed...return that without pushing anything back 
-                    if (lineendStyle == 1) {
-                        return '\n'; 
-                    }
-                    else 
-                    {
-                        inStream.unread('\n');
-                        // the next character read will by the 0x0a, which will 
-                        // be handled as 
-                        return '\r';
-                    }
+                    // Boundary not found. Restoring bytes skipped.
+                    // just reset and return the first character as data 
+                    inStream.reset(); 
+                    return firstChar; 
                 }
-                // on the home stretch, but we need to verify the EOL sequence 
+                // on the home stretch, but we need to verify the LWSP/EOL sequence 
                 value = inStream.read();
+                // first skip over the linear whitespace 
+                while (value == ' ' || value == '\t') {
+                    value = inStream.read();
+                }
+                
+                // We've matched the final boundary, skipped any whitespace, but 
+                // we've hit the end of the stream.  This is highly likely when 
+                // we have nested multiparts, since the linend terminator for the 
+                // final boundary marker is eated up as the start of the outer 
+                // boundary marker.  No CRLF sequence here is ok. 
+                if (value == -1) {
+                    // we've hit the end of times...
+                    finalBoundaryFound = true; 
+                    // we have a boundary, so return this as an EOF condition 
+                    boundaryFound = true;
+                    return -1;
+                }
+                
                 // this must be a CR or a LF...which leaves us even more to push back and forget 
                 if (value != '\r' && value != '\n') {
-                    // Stream might have ended 
-                    if (value != -1) { 
-                        inStream.unread(value);
-                    }
-                    inStream.unread(value); 
-                    inStream.unread('-'); 
-                    inStream.unread('-'); 
-                    // the entire boundary string 
-                    inStream.unread(boundary);
-                    // just a naked line feed...return that without pushing anything back 
-                    if (lineendStyle == 1) {
-                        return '\n'; 
-                    }
-                    else 
-                    {
-                        inStream.unread('\n');
-                        // the next character read will by the 0x0a, which will 
-                        // be handled as 
-                        return '\r';
-                    }
+                    // Boundary not found. Restoring bytes skipped.
+                    // just reset and return the first character as data 
+                    inStream.reset(); 
+                    return firstChar; 
                 }
                 
                 // if this is carriage return, check for a linefeed  
@@ -531,53 +570,27 @@
                     value = inStream.read();
                     if (value != '\n') {
                         // SO CLOSE!
-                        // push back the end markers 
-                        // Stream might have ended 
-                        if (value != -1) { 
-                            inStream.unread(value);
-                        }
-                        inStream.unread('\r'); 
-                        inStream.unread('-'); 
-                        inStream.unread('-'); 
-                        // the entire boundary string 
-                        inStream.unread(boundary);
-                        // just a naked line feed...return that without pushing anything back 
-                        if (lineendStyle == 1) {
-                            return '\n'; 
-                        }
-                        else 
-                        {
-                            inStream.unread('\n');
-                            // the next character read will by the 0x0a, which will 
-                            // be handled as 
-                            return '\r';
-                        }
+                        // Boundary not found. Restoring bytes skipped.
+                        // just reset and return the first character as data 
+                        inStream.reset(); 
+                        return firstChar; 
                     }
                 }
+                
                 // we've hit the end of times...
                 finalBoundaryFound = true; 
             }
             else {
-                // now check for a linend sequence...either \r\n or \n is accepted. 
+                // first skip over the linear whitespace 
+                while (value == ' ' || value == '\t') {
+                    value = inStream.read();
+                }
+                // this must be a CR or a LF...which leaves us even more to push back and forget 
                 if (value != '\r' && value != '\n') {
-                    // push back the end markers 
-                    // Stream might have ended 
-                    if (value != -1) { 
-                        inStream.unread(value);
-                    }
-                    // the entire boundary string 
-                    inStream.unread(boundary);
-                    // just a naked line feed...return that without pushing anything back 
-                    if (lineendStyle == 1) {
-                        return '\n'; 
-                    }
-                    else 
-                    {
-                        inStream.unread('\n');
-                        // the next character read will by the 0x0a, which will 
-                        // be handled as 
-                        return '\r';
-                    }
+                    // Boundary not found. Restoring bytes skipped.
+                    // just reset and return the first character as data 
+                    inStream.reset(); 
+                    return firstChar; 
                 }
                 
                 // if this is carriage return, check for a linefeed  
@@ -586,27 +599,10 @@
                     value = inStream.read();
                     if (value != '\n') {
                         // SO CLOSE!
-                        // push back the end markers 
-                        // Stream might have ended 
-                        if (value != -1) { 
-                            inStream.unread(value);
-                        }
-                        inStream.unread('\r'); 
-                        inStream.unread('-'); 
-                        inStream.unread('-'); 
-                        // the entire boundary string 
-                        inStream.unread(boundary);
-                        // just a naked line feed...return that without pushing anything back 
-                        if (lineendStyle == 1) {
-                            return '\n'; 
-                        }
-                        else 
-                        {
-                            inStream.unread('\n');
-                            // the next character read will by the 0x0a, which will 
-                            // be handled as 
-                            return '\r';
-                        }
+                        // Boundary not found. Restoring bytes skipped.
+                        // just reset and return the first character as data 
+                        inStream.reset(); 
+                        return firstChar; 
                     }
                 }
             }