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/26 18:18:00 UTC

svn commit: r689126 - in /geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet: MimeBodyPart.java MimeMultipart.java MimePartDataSource.java

Author: rickmcguire
Date: Tue Aug 26 09:17:53 2008
New Revision: 689126

URL: http://svn.apache.org/viewvc?rev=689126&view=rev
Log:
GERONIMO-4259 javax.mail.MessagingException: java.io.IOException: Base64 encoding error, data truncated

General cleanup of the part boundary search code. 


Modified:
    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/MimeMultipart.java
    geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimePartDataSource.java

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=689126&r1=689125&r2=689126&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 Tue Aug 26 09:17:53 2008
@@ -82,8 +82,9 @@
         byte[] buffer = new byte[1024];
         int count;
         try {
-            while((count = in.read(buffer, 0, 1024)) > 0)
+            while((count = in.read(buffer, 0, 1024)) > 0) {
                 baos.write(buffer, 0, count);
+            }
         } catch (IOException e) {
             throw new MessagingException(e.toString(),e);
         }

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=689126&r1=689125&r2=689126&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 Tue Aug 26 09:17:53 2008
@@ -169,6 +169,7 @@
         if (parsed) {
             return;
         }
+        
         try {
             ContentType cType = new ContentType(contentType);
             InputStream is = new BufferedInputStream(ds.getInputStream());
@@ -186,7 +187,7 @@
                 readTillFirstBoundary(pushbackInStream, boundary);
             }
             
-            while (pushbackInStream.available()>0){
+            while (pushbackInStream.available() > 0){
                 MimeBodyPartInputStream partStream;
                 partStream = new MimeBodyPartInputStream(pushbackInStream,
                         boundary);
@@ -360,67 +361,255 @@
         public boolean boundaryFound = false;
         byte[] boundary;
 
-        public MimeBodyPartInputStream(PushbackInputStream inStream,
-                                       byte[] boundary) {
+        public MimeBodyPartInputStream(PushbackInputStream inStream, byte[] boundary) {
             super();
             this.inStream = inStream;
             this.boundary = boundary;
         }
 
+        /**
+         * The base reading method for reading one character 
+         * at a time. 
+         * 
+         * @return The read character, or -1 if an EOF was encountered. 
+         * @exception IOException
+         */
         public int read() throws IOException {
             if (boundaryFound) {
                 return -1;
             }
+            
             // read the next value from stream
             int value = inStream.read();
-            // A problem occured because all the mime parts tends to have a /r/n at the end. Making it hard to transform them to correct DataSources.
-            // This logic introduced to handle it
-            //TODO look more in to this && for a better way to do this
-            if (value == 13) {
+            // premature end?  Handle it like a boundary located 
+            if (value == -1) {
+                boundaryFound = true; 
+                return -1; 
+            }
+            
+            // we first need to look for a line boundary.  If we find a boundary, it can be followed by the 
+            // boundary marker, so we need to remember what sort of thing we found, then read ahead looking 
+            // for the part boundary. 
+            
+            // 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') {
+                // not a \r, just return the byte as is 
+                return value;
+            }
+            
+            int lineendStyle = 2;    // this indicates the type of linend we need to push back. 
+            // 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 != 10) {
+                if (value != '\n') {
+                    // only a \r, so this can't be a boundary.  Return the 
+                    // \r as if it was data 
                     inStream.unread(value);
-                    return 13;
-                } else {
-                    value = inStream.read();
-                    if ((byte) value != boundary[0]) {
-                        inStream.unread(value);
-                        inStream.unread(10);
-                        return 13;
-                    }
+                    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';
                 }
-            } else if ((byte) value != boundary[0]) {
-                return value;
             }
+            
+            // we're here because we found a "\r\n-" sequence, which is a potential 
+            // boundary marker.  Read the individual characters of the next line until 
+            // we have a mismatch 
+            
             // read value is the first byte of the boundary. Start matching the
             // next characters to find a boundary
             int boundaryIndex = 0;
-            while ((boundaryIndex < boundary.length)
-                    && ((byte) value == boundary[boundaryIndex])) {
+            while ((boundaryIndex < boundary.length) && ((byte) value == boundary[boundaryIndex])) {
                 value = inStream.read();
                 boundaryIndex++;
             }
-            if (boundaryIndex == boundary.length) { // boundary found
-                boundaryFound = true;
-                // read the end of line character
-                if (inStream.read() == '-' && value == '-') {
-                    //Last mime boundary should have a succeeding "--"
-                    //as we are on it, read the terminating CRLF
-                    inStream.read();
-                    inStream.read();
+            // if we didn't match all the way, we need to push back what we've read and 
+            // 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';
                 }
-                return -1;
             }
-            // Boundary not found. Restoring bytes skipped.
-            // write first skipped byte, push back the rest
-            if (value != -1) { // Stream might have ended
-                inStream.unread(value);
+            
+            // The full boundary sequence should be \r\n--boundary string[--]\r\n
+            // if the last character we read was a '-', check for the end terminator 
+            if (value == '-') {
+                value = inStream.read();
+                // 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';
+                    }
+                }
+                // on the home stretch, but we need to verify the EOL sequence 
+                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') {
+                    // 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';
+                    }
+                }
+                
+                // if this is carriage return, check for a linefeed  
+                if (value == '\r') {
+                    // last check, this must be a line feed 
+                    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';
+                        }
+                    }
+                }
             }
-            inStream.unread(boundary, 1, boundaryIndex - 1);
-            return boundary[0];
+            else {
+                // now check for a linend sequence...either \r\n or \n is accepted. 
+                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';
+                    }
+                }
+                
+                // if this is carriage return, check for a linefeed  
+                if (value == '\r') {
+                    // last check, this must be a line feed 
+                    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';
+                        }
+                    }
+                }
+            }
+            // we have a boundary, so return this as an EOF condition 
+            boundaryFound = true;
+            return -1;
         }
     }
 
+    
     /**
      * Return true if the final boundary line for this multipart was
      * seen when parsing the data.

Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimePartDataSource.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimePartDataSource.java?rev=689126&r1=689125&r2=689126&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimePartDataSource.java (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimePartDataSource.java Tue Aug 26 09:17:53 2008
@@ -43,18 +43,61 @@
         try {
             InputStream stream;
             if (part instanceof MimeMessage) {
-                stream = ((MimeMessage) part).getContentStream();
+                // this never gets encoded, so we can skip other steps 
+                return ((MimeMessage) part).getContentStream();
             } else if (part instanceof MimeBodyPart) {
                 stream = ((MimeBodyPart) part).getContentStream();
             } else {
                 throw new MessagingException("Unknown part");
             }
-            String encoding = part.getEncoding();
-            return encoding == null ? stream : MimeUtility.decode(stream, encoding);
+            return checkPartEncoding(part, stream);
         } catch (MessagingException e) {
             throw (IOException) new IOException(e.getMessage()).initCause(e);
         }
     }
+    
+    
+    /**
+     * For a given part, decide it the data stream requires
+     * wrappering with a stream for decoding a particular 
+     * encoding. 
+     * 
+     * @param part   The part we're extracting.
+     * @param stream The raw input stream for the part.
+     * 
+     * @return An input stream configured for reading the 
+     *         source part and decoding it into raw bytes.
+     */
+    private InputStream checkPartEncoding(MimePart part, InputStream stream) throws MessagingException {
+        String encoding = part.getEncoding();
+        // if nothing is specified, there's nothing to do 
+        if (encoding == null) {
+            return stream; 
+        }
+        // now screen out the ones that never need decoding 
+        encoding = encoding.toLowerCase(); 
+        if (encoding.equals("7bit") || encoding.equals("8bit") || encoding.equals("binary")) {
+            return stream; 
+        }
+        // now we need to check the content type to prevent 
+        // MultiPart types from getting decoded, since the part is just an envelope around other 
+        // parts 
+        String contentType = part.getContentType(); 
+        if (contentType != null) {
+            try {
+                ContentType type = new ContentType(contentType); 
+                // no decoding done here 
+                if (type.match("multipart/*")) {
+                    return stream; 
+                }
+            } catch (ParseException e) {
+                // ignored....bad content type means we handle as a normal part 
+            }
+        }
+        // ok, wrap this is a decoding stream if required 
+        return MimeUtility.decode(stream, encoding);
+    }
+    
 
     public OutputStream getOutputStream() throws IOException {
         throw new UnknownServiceException();