You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by rm...@apache.org on 2014/08/26 20:17:09 UTC

svn commit: r1620683 [9/17] - in /geronimo/specs/trunk: ./ geronimo-javamail_1.5_spec/ geronimo-javamail_1.5_spec/src/ geronimo-javamail_1.5_spec/src/main/ geronimo-javamail_1.5_spec/src/main/java/ geronimo-javamail_1.5_spec/src/main/java/javax/ geroni...

Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimeMultipart.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimeMultipart.java?rev=1620683&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimeMultipart.java (added)
+++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimeMultipart.java Tue Aug 26 18:17:06 2014
@@ -0,0 +1,778 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+import javax.activation.DataSource;
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.MultipartDataSource;
+
+import org.apache.geronimo.mail.util.SessionUtil;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class MimeMultipart extends Multipart {
+	private static final String MIME_IGNORE_MISSING_ENDBOUNDARY = "mail.mime.multipart.ignoremissingendboundary";
+	private static final String MIME_IGNORE_MISSING_BOUNDARY_PARAMETER = "mail.mime.multipart.ignoremissingboundaryparameter";
+	private static final String MIME_IGNORE_EXISTING_BOUNDARY_PARAMETER = "mail.mime.multipart.ignoreexistingboundaryparameter";
+	private static final String MIME_ALLOWEMPTY = "mail.mime.multipart.allowempty";
+	
+    /**
+     * DataSource that provides our InputStream.
+     */
+    protected DataSource ds;
+    
+    /**
+     * Indicates if the data has been parsed.
+     */
+    protected boolean parsed = true;
+
+    // the content type information
+    private transient ContentType type;
+
+    /** Have we seen the final bounary line?
+    *
+    * @since   JavaMail 1.5
+    */
+    protected boolean complete = true;
+
+    /**
+     * The MIME multipart preamble text, the text that
+     * occurs before the first boundary line.
+     *
+     * @since   JavaMail 1.5
+     */
+    protected String preamble = null;
+    
+    
+    /**
+     * Flag corresponding to the "mail.mime.multipart.ignoremissingendboundary"
+     * property, set in the {@link #initializeProperties} method called from
+     * constructors and the parse method.
+     *
+     * @since   JavaMail 1.5
+     */
+    protected boolean ignoreMissingEndBoundary = true;
+
+    /**
+     * Flag corresponding to the
+     * "mail.mime.multipart.ignoremissingboundaryparameter"
+     * property, set in the {@link #initializeProperties} method called from
+     * constructors and the parse method.
+     *
+     * @since   JavaMail 1.5
+     */
+    protected boolean ignoreMissingBoundaryParameter = true;
+
+    /**
+     * Flag corresponding to the
+     * "mail.mime.multipart.ignoreexistingboundaryparameter"
+     * property, set in the {@link #initializeProperties} method called from
+     * constructors and the parse method.
+     *
+     * @since   JavaMail 1.5
+     */
+    protected boolean ignoreExistingBoundaryParameter = false;
+
+    /**
+     * Flag corresponding to the "mail.mime.multipart.allowempty"
+     * property, set in the {@link #initializeProperties} method called from
+     * constructors and the parse method.
+     *
+     * @since   JavaMail 1.5
+     */
+    protected boolean allowEmpty = false;
+
+    /**
+     * Initialize flags that control parsing behavior,
+     * based on System properties described above in
+     * the class documentation.
+     *
+     * @since   JavaMail 1.5
+     */
+    protected void initializeProperties() {
+        
+        ignoreMissingEndBoundary = SessionUtil.getBooleanProperty(MIME_IGNORE_MISSING_ENDBOUNDARY, true);
+        ignoreMissingBoundaryParameter = SessionUtil.getBooleanProperty(MIME_IGNORE_MISSING_BOUNDARY_PARAMETER, true);
+        ignoreExistingBoundaryParameter = SessionUtil.getBooleanProperty(MIME_IGNORE_EXISTING_BOUNDARY_PARAMETER, false);
+        allowEmpty = SessionUtil.getBooleanProperty(MIME_ALLOWEMPTY, false);
+         
+    }
+
+    /**
+     * Create an empty MimeMultipart with content type "multipart/mixed"
+     */
+    public MimeMultipart() {
+        this("mixed");
+    }
+
+    /**
+     * Create an empty MimeMultipart with the subtype supplied.
+     *
+     * @param subtype the subtype
+     */
+    public MimeMultipart(final String subtype) {
+        type = new ContentType("multipart", subtype, null);
+        type.setParameter("boundary", getBoundary());
+        contentType = type.toString();
+        initializeProperties();
+    }
+
+    /**
+     * Create a MimeMultipart from the supplied DataSource.
+     *
+     * @param dataSource the DataSource to use
+     * @throws MessagingException
+     */
+    public MimeMultipart(final DataSource dataSource) throws MessagingException {
+        ds = dataSource;
+        if (dataSource instanceof MultipartDataSource) {
+            super.setMultipartDataSource((MultipartDataSource) dataSource);
+            parsed = true;
+        } else {
+            // We keep the original, provided content type string so that we
+            // don't end up changing quoting/formatting of the header unless
+            // changes are made to the content type.  James is somewhat dependent
+            // on that behavior.
+            contentType = ds.getContentType();
+            type = new ContentType(contentType);
+            parsed = false;
+        }
+    }
+    
+    
+    /**
+     * Construct a MimeMultipart object of the default "mixed" subtype,
+     * and with the given body parts.  More body parts may be added later.
+     *
+     * @since   JavaMail 1.5
+     */
+    public MimeMultipart(final BodyPart... parts) throws MessagingException {
+        this("mixed");
+        this.parts.addAll(Arrays.asList(parts));
+    }
+
+    /**
+     * Construct a MimeMultipart object of the given subtype
+     * and with the given body parts.  More body parts may be added later.
+     *
+     * @since   JavaMail 1.5
+     */
+    public MimeMultipart(final String subtype, final BodyPart... parts)
+                throws MessagingException {
+        this(subtype);
+        this.parts.addAll(Arrays.asList(parts));
+    }
+
+    public void setSubType(final String subtype) throws MessagingException {
+        type.setSubType(subtype);
+        contentType = type.toString();
+    }
+
+    @Override
+    public int getCount() throws MessagingException {
+        parse();
+        return super.getCount();
+    }
+
+    @Override
+    public synchronized BodyPart getBodyPart(final int part) throws MessagingException {
+        parse();
+        return super.getBodyPart(part);
+    }
+
+    public BodyPart getBodyPart(final String cid) throws MessagingException {
+        parse();
+        for (int i = 0; i < parts.size(); i++) {
+            final MimeBodyPart bodyPart = (MimeBodyPart) parts.get(i);
+            if (cid.equals(bodyPart.getContentID())) {
+                return bodyPart;
+            }
+        }
+        return null;
+    }
+
+    protected void updateHeaders() throws MessagingException {
+        parse();
+        for (int i = 0; i < parts.size(); i++) {
+            final MimeBodyPart bodyPart = (MimeBodyPart) parts.get(i);
+            bodyPart.updateHeaders();
+        }
+    }
+
+    private static byte[] dash = { '-', '-' };
+    private static byte[] crlf = { 13, 10 };
+
+    @Override
+    public void writeTo(final OutputStream out) throws IOException, MessagingException {
+        parse();
+        final String boundary = type.getParameter("boundary");
+        final byte[] bytes = boundary.getBytes("ISO8859-1");
+        
+        if(!allowEmpty && parts.size() == 0) {
+            throw new MessagingException("Multipart content with no body parts is not allowed");
+        }
+
+        if (preamble != null) {
+            final byte[] preambleBytes = preamble.getBytes("ISO8859-1");
+            // write this out, followed by a line break.
+            out.write(preambleBytes);
+            out.write(crlf);
+        }
+
+        for (int i = 0; i < parts.size(); i++) {
+            final BodyPart bodyPart = (BodyPart) parts.get(i);
+            out.write(dash);
+            out.write(bytes);
+            out.write(crlf);
+            bodyPart.writeTo(out);
+            out.write(crlf);
+        }
+        out.write(dash);
+        out.write(bytes);
+        out.write(dash);
+        out.write(crlf);
+        out.flush();
+    }
+
+    protected void parse() throws MessagingException {
+        if (parsed) {
+            return;
+        }
+                
+        initializeProperties();
+
+        try {
+            final ContentType cType = new ContentType(contentType);
+            final String boundaryString = cType.getParameter("boundary");
+            
+            if(!ignoreMissingBoundaryParameter && boundaryString  == null) {
+                throw new MessagingException("Missing boundary parameter in content-type");
+            }           
+                        
+            final InputStream is = new BufferedInputStream(ds.getInputStream());
+            BufferedInputStream pushbackInStream = null;
+            boolean boundaryFound = false;
+            
+            byte[] boundary = null;
+            if (boundaryString == null || ignoreExistingBoundaryParameter) {
+                pushbackInStream = new BufferedInputStream(is, 1200);
+                // read until we find something that looks like a boundary string
+                boundary = readTillFirstBoundary(pushbackInStream);
+                boundaryFound = boundary != null;
+            }
+            else {
+                boundary = ("--" + boundaryString).getBytes("ISO8859-1");
+                pushbackInStream = new BufferedInputStream(is, boundary.length + 1000);
+                boundaryFound = readTillFirstBoundary(pushbackInStream, boundary);
+            }
+            
+            
+            
+            if(allowEmpty && !boundaryFound) {
+            	parsed = true;
+                return;
+            }
+            
+            if(!allowEmpty && !boundaryFound) {
+                throw new MessagingException("Multipart content with no body parts is not allowed");
+            }
+            
+
+            while (true) {
+                MimeBodyPartInputStream partStream;
+                partStream = new MimeBodyPartInputStream(pushbackInStream, boundary);
+                addBodyPart(new MimeBodyPart(partStream));
+
+                // terminated by an EOF rather than a proper boundary?
+                if (!partStream.boundaryFound) {
+                	
+                    if (!ignoreMissingEndBoundary) {
+                        throw new MessagingException("Missing Multi-part end boundary");
+                    }
+                    complete = false;
+                    break;
+                }
+                // if we hit the final boundary, stop processing this
+                if (partStream.finalBoundaryFound) {
+                    break;
+                }
+            }
+            
+           
+            
+        } catch (final Exception e){
+            throw new MessagingException(e.toString(),e);
+        }
+        parsed = true;
+    }
+
+    /**
+     * Move the read pointer to the beginning of the first part
+     * read till the end of first boundary.  Any data read before this point are
+     * saved as the preamble.
+     *
+     * @param pushbackInStream
+     * @param boundary
+     * @throws MessagingException
+     */
+    private byte[] readTillFirstBoundary(final BufferedInputStream pushbackInStream) throws MessagingException {
+        final ByteArrayOutputStream preambleStream = new ByteArrayOutputStream();
+
+        try {
+            while (true) {
+                // read the next line
+                final byte[] line = readLine(pushbackInStream);
+                // hit an EOF?
+                if (line == null || line.length==0) {
+                    return 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.
+                    final byte[] preambleBytes = preambleStream.toByteArray();
+                    if (preambleBytes.length > 0) {
+                        preamble = new String(preambleBytes, "ISO8859-1");
+                    }
+                    return stripLinearWhiteSpace(line);
+                }
+                else {
+                    // this is part of the preamble.
+                    preambleStream.write(line);
+                    preambleStream.write('\r');
+                    preambleStream.write('\n');
+                }
+            }
+        } catch (final 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(final 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
+        final byte[] newLine = new byte[index + 1];
+        System.arraycopy(line, 0, newLine, 0, index + 1);
+        return newLine;
+    }
+
+    /**
+     * Move the read pointer to the beginning of the first part
+     * read till the end of first boundary.  Any data read before this point are
+     * saved as the preamble.
+     *
+     * @param pushbackInStream
+     * @param boundary
+     * @throws MessagingException
+     */
+    private boolean readTillFirstBoundary(final BufferedInputStream pushbackInStream, final byte[] boundary) throws MessagingException {
+        final ByteArrayOutputStream preambleStream = new ByteArrayOutputStream();
+
+        try {
+            while (true) {
+                // read the next line
+                final byte[] line = readLine(pushbackInStream);
+                // hit an EOF?
+                if (line == null || line.length==0) {
+                	return false;//throw new MessagingException("Unexpected End of Stream while searching for first Mime Boundary");
+                }
+
+                // apply the boundary comparison rules to this
+                if (compareBoundary(line, boundary)) {
+                    // save the preamble, if there is one.
+                    final byte[] preambleBytes = preambleStream.toByteArray();
+                    if (preambleBytes.length > 0) {
+                        preamble = new String(preambleBytes, "ISO8859-1");
+                    }
+                    return true;
+                }
+
+                // this is part of the preamble.
+                preambleStream.write(line);
+                preambleStream.write('\r');
+                preambleStream.write('\n');
+            }
+        } catch (final IOException ioe) {
+            throw new MessagingException(ioe.toString(), ioe);
+        }
+    }
+
+
+    /**
+     * Perform 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(final byte[] line, final 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.
+     *
+     * @param in     The source input stream.
+     *
+     * @return A byte array containing the line data.  Returns
+     *         null if there's nothing left in the stream.
+     * @exception MessagingException
+     */
+    private byte[] readLine(final BufferedInputStream in) throws IOException
+    {
+        final ByteArrayOutputStream line = new ByteArrayOutputStream();
+
+        while (in.available() > 0) {
+            int value = in.read();
+            if (value == -1) {
+                // if we have nothing in the accumulator, signal an EOF back
+                if (line.size() == 0) {
+                    return null;
+                }
+                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.reset();
+                }
+                break;
+            }
+            else if (value == '\n') {
+                // naked linefeed, allow that
+                break;
+            }
+            else {
+                // write this to the line
+                line.write((byte)value);
+            }
+        }
+        // return this as an array of bytes
+        return line.toByteArray();
+    }
+
+
+    protected InternetHeaders createInternetHeaders(final InputStream in) throws MessagingException {
+        return new InternetHeaders(in);
+    }
+
+    protected MimeBodyPart createMimeBodyPart(final InternetHeaders headers, final byte[] data) throws MessagingException {
+        return new MimeBodyPart(headers, data);
+    }
+
+    protected MimeBodyPart createMimeBodyPart(final InputStream in) throws MessagingException {
+        return new MimeBodyPart(in);
+    }
+
+    // static used to track boundary value allocations to help ensure uniqueness.
+    private static int part;
+
+    private synchronized static String getBoundary() {
+        int i;
+        synchronized(MimeMultipart.class) {
+            i = part++;
+        }
+        final StringBuffer buf = new StringBuffer(64);
+        buf.append("----=_Part_").append(i).append('_').append((new Object()).hashCode()).append('.').append(System.currentTimeMillis());
+        return buf.toString();
+    }
+
+    private class MimeBodyPartInputStream extends InputStream {
+        BufferedInputStream inStream;
+        public boolean boundaryFound = false;
+        byte[] boundary;
+        public boolean finalBoundaryFound = false;
+
+        public MimeBodyPartInputStream(final BufferedInputStream inStream, final 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
+         */
+        @Override
+        public int read() throws IOException {
+            if (boundaryFound) {
+                return -1;
+            }
+
+            // read the next value from stream
+            final int firstChar = inStream.read();
+            // premature end?  Handle it like a boundary located
+            if (firstChar == -1) {
+            	//DO NOT treat this a a boundary because if we do so we have no chance to detect missing end boundaries
+                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 (firstChar != '\r' && firstChar != '\n') {
+                // not a \r, just return the byte as is
+                return firstChar;
+            }
+            // 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, after first resetting
+                    inStream.reset();
+                    return '\r';
+                }
+            }
+
+            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]) {
+                // 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
+            // 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])) {
+                value = inStream.read();
+                boundaryIndex++;
+            }
+            // 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.
+                // 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
+            // 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 != '-') {
+                    // 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 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') {
+                    // 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
+                if (value == '\r') {
+                    // last check, this must be a line feed
+                    value = inStream.read();
+                    if (value != '\n') {
+                        // SO CLOSE!
+                        // 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 {
+                // 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') {
+                    // 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
+                if (value == '\r') {
+                    // last check, this must be a line feed
+                    value = inStream.read();
+                    if (value != '\n') {
+                        // SO CLOSE!
+                        // Boundary not found. Restoring bytes skipped.
+                        // just reset and return the first character as data
+                        inStream.reset();
+                        return firstChar;
+                    }
+                }
+            }
+            // 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.
+     *
+     * @return
+     * @exception MessagingException
+     */
+    public boolean isComplete() throws MessagingException {
+        // make sure we've parsed this
+        parse();
+        return complete;
+    }
+
+
+    /**
+     * Returns the preamble text that appears before the first bady
+     * part of a MIME multi part.  The preamble is optional, so this
+     * might be null.
+     *
+     * @return The preamble text string.
+     * @exception MessagingException
+     */
+    public String getPreamble() throws MessagingException {
+        parse();
+        return preamble;
+    }
+
+    /**
+     * Set the message preamble text.  This will be written before
+     * the first boundary of a multi-part message.
+     *
+     * @param preamble The new boundary text.  This is complete lines of text, including
+     *                 new lines.
+     *
+     * @exception MessagingException
+     */
+    public void setPreamble(final String preamble) throws MessagingException {
+        this.preamble = preamble;
+    }
+}

Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimePart.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimePart.java?rev=1620683&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimePart.java (added)
+++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimePart.java Tue Aug 26 18:17:06 2014
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.util.Enumeration;
+
+import javax.mail.MessagingException;
+import javax.mail.Part;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public interface MimePart extends Part {
+    public abstract void addHeaderLine(String line) throws MessagingException;
+
+    public abstract Enumeration getAllHeaderLines() throws MessagingException;
+
+    public abstract String getContentID() throws MessagingException;
+
+    public abstract String[] getContentLanguage() throws MessagingException;
+
+    public abstract String getContentMD5() throws MessagingException;
+
+    public abstract String getEncoding() throws MessagingException;
+
+    public abstract String getHeader(String header, String delimiter)
+            throws MessagingException;
+
+    public abstract Enumeration getMatchingHeaderLines(String[] names)
+            throws MessagingException;
+
+    public abstract Enumeration getNonMatchingHeaderLines(String[] names)
+            throws MessagingException;
+
+    public abstract void setContentLanguage(String[] languages)
+            throws MessagingException;
+
+    public abstract void setContentMD5(String content)
+            throws MessagingException;
+
+    public abstract void setText(String text) throws MessagingException;
+
+    public abstract void setText(String text, String charset)
+            throws MessagingException;
+
+    public abstract void setText(String text, String charset, String subType)
+            throws MessagingException;
+}

Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimePartDataSource.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimePartDataSource.java?rev=1620683&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimePartDataSource.java (added)
+++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimePartDataSource.java Tue Aug 26 18:17:06 2014
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.UnknownServiceException;
+
+import javax.activation.DataSource;
+import javax.mail.MessageAware;
+import javax.mail.MessageContext;
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class MimePartDataSource implements DataSource, MessageAware {
+    // the part that provides the data form this data source.
+    protected MimePart part;
+
+    public MimePartDataSource(final MimePart part) {
+        this.part = part;
+    }
+
+    public InputStream getInputStream() throws IOException {
+        try {
+            InputStream stream;
+            if (part instanceof MimeMessage) {
+                stream = ((MimeMessage) part).getContentStream();
+            } else if (part instanceof MimeBodyPart) {
+                stream = ((MimeBodyPart) part).getContentStream();
+            } else {
+                throw new MessagingException("Unknown part");
+            }
+            return checkPartEncoding(part, stream);
+        } catch (final 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(final MimePart part, final 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 
+        final String contentType = part.getContentType(); 
+        if (contentType != null) {
+            try {
+                final ContentType type = new ContentType(contentType); 
+                // no decoding done here 
+                if (type.match("multipart/*")) {
+                    return stream; 
+                }
+            } catch (final 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();
+    }
+
+    public String getContentType() {
+        try {
+            return part.getContentType();
+        } catch (final MessagingException e) {
+            return null;
+        }
+    }
+
+    public String getName() {
+        try {
+            if (part instanceof MimeBodyPart) {
+                return ((MimeBodyPart) part).getFileName();
+            }
+        } catch (final MessagingException mex) {
+            // ignore it
+        }
+        return "";
+    }
+
+    public synchronized MessageContext getMessageContext() {
+        return new MessageContext(part);
+    }
+}