/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2018 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
 * or LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package javax.mail.internet;

import javax.mail.*;
import javax.activation.*;
import java.util.*;
import java.io.*;
import com.sun.mail.util.LineOutputStream;
import com.sun.mail.util.LineInputStream;
import com.sun.mail.util.ASCIIUtility;
import com.sun.mail.util.PropUtil;

The MimeMultipart class is an implementation of the abstract Multipart class that uses MIME conventions for the multipart data.

A MimeMultipart is obtained from a MimePart whose primary type is "multipart" (by invoking the part's getContent() method) or it can be created by a client as part of creating a new MimeMessage.

The default multipart subtype is "mixed". The other multipart subtypes, such as "alternative", "related", and so on, can be implemented as subclasses of MimeMultipart with additional methods to implement the additional semantics of that type of multipart content. The intent is that service providers, mail JavaBean writers and mail clients will write many such subclasses and their Command Beans, and will install them into the JavaBeans Activation Framework, so that any JavaMail implementation and its clients can transparently find and use these classes. Thus, a MIME multipart handler is treated just like any other type handler, thereby decoupling the process of providing multipart handlers from the JavaMail API. Lacking these additional MimeMultipart subclasses, all subtypes of MIME multipart data appear as MimeMultipart objects.

An application can directly construct a MIME multipart object of any subtype by using the MimeMultipart(String subtype) constructor. For example, to create a "multipart/alternative" object, use new MimeMultipart("alternative").

The mail.mime.multipart.ignoremissingendboundary property may be set to false to cause a MessagingException to be thrown if the multipart data does not end with the required end boundary line. If this property is set to true or not set, missing end boundaries are not considered an error and the final body part ends at the end of the data.

The mail.mime.multipart.ignoremissingboundaryparameter System property may be set to false to cause a MessagingException to be thrown if the Content-Type of the MimeMultipart does not include a boundary parameter. If this property is set to true or not set, the multipart parsing code will look for a line that looks like a bounary line and use that as the boundary separating the parts.

The mail.mime.multipart.ignoreexistingboundaryparameter System property may be set to true to cause any boundary to be ignored and instead search for a boundary line in the message as with mail.mime.multipart.ignoremissingboundaryparameter.

Normally, when writing out a MimeMultipart that contains no body parts, or when trying to parse a multipart message with no body parts, a MessagingException is thrown. The MIME spec does not allow multipart content with no body parts. The mail.mime.multipart.allowempty System property may be set to true to override this behavior. When writing out such a MimeMultipart, a single empty part will be included. When reading such a multipart, a MimeMultipart will be created with no body parts.

Author: John Mani, Bill Shannon, Max Spivak
/** * The MimeMultipart class is an implementation of the abstract Multipart * class that uses MIME conventions for the multipart data. <p> * * A MimeMultipart is obtained from a MimePart whose primary type * is "multipart" (by invoking the part's <code>getContent()</code> method) * or it can be created by a client as part of creating a new MimeMessage. <p> * * The default multipart subtype is "mixed". The other multipart * subtypes, such as "alternative", "related", and so on, can be * implemented as subclasses of MimeMultipart with additional methods * to implement the additional semantics of that type of multipart * content. The intent is that service providers, mail JavaBean writers * and mail clients will write many such subclasses and their Command * Beans, and will install them into the JavaBeans Activation * Framework, so that any JavaMail implementation and its clients can * transparently find and use these classes. Thus, a MIME multipart * handler is treated just like any other type handler, thereby * decoupling the process of providing multipart handlers from the * JavaMail API. Lacking these additional MimeMultipart subclasses, * all subtypes of MIME multipart data appear as MimeMultipart objects. <p> * * An application can directly construct a MIME multipart object of any * subtype by using the <code>MimeMultipart(String subtype)</code> * constructor. For example, to create a "multipart/alternative" object, * use <code>new MimeMultipart("alternative")</code>. <p> * * The <code>mail.mime.multipart.ignoremissingendboundary</code> * property may be set to <code>false</code> to cause a * <code>MessagingException</code> to be thrown if the multipart * data does not end with the required end boundary line. If this * property is set to <code>true</code> or not set, missing end * boundaries are not considered an error and the final body part * ends at the end of the data. <p> * * The <code>mail.mime.multipart.ignoremissingboundaryparameter</code> * System property may be set to <code>false</code> to cause a * <code>MessagingException</code> to be thrown if the Content-Type * of the MimeMultipart does not include a <code>boundary</code> parameter. * If this property is set to <code>true</code> or not set, the multipart * parsing code will look for a line that looks like a bounary line and * use that as the boundary separating the parts. <p> * * The <code>mail.mime.multipart.ignoreexistingboundaryparameter</code> * System property may be set to <code>true</code> to cause any boundary * to be ignored and instead search for a boundary line in the message * as with <code>mail.mime.multipart.ignoremissingboundaryparameter</code>. <p> * * Normally, when writing out a MimeMultipart that contains no body * parts, or when trying to parse a multipart message with no body parts, * a <code>MessagingException</code> is thrown. The MIME spec does not allow * multipart content with no body parts. The * <code>mail.mime.multipart.allowempty</code> System property may be set to * <code>true</code> to override this behavior. * When writing out such a MimeMultipart, a single empty part will be * included. When reading such a multipart, a MimeMultipart will be created * with no body parts. * * @author John Mani * @author Bill Shannon * @author Max Spivak */
public class MimeMultipart extends Multipart {
The DataSource supplying our InputStream.
/** * The DataSource supplying our InputStream. */
protected DataSource ds = null;
Have we parsed the data from our InputStream yet? Defaults to true; set to false when our constructor is given a DataSource with an InputStream that we need to parse.
/** * Have we parsed the data from our InputStream yet? * Defaults to true; set to false when our constructor is * given a DataSource with an InputStream that we need to * parse. */
protected boolean parsed = true;
Have we seen the final bounary line?
Since: JavaMail 1.5
/** * 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
/** * 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 initializeProperties method called from constructors and the parse method.
Since: JavaMail 1.5
/** * 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 initializeProperties method called from constructors and the parse method.
Since: JavaMail 1.5
/** * 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 initializeProperties method called from constructors and the parse method.
Since: JavaMail 1.5
/** * 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 initializeProperties method called from constructors and the parse method.
Since: JavaMail 1.5
/** * 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;
Default constructor. An empty MimeMultipart object is created. Its content type is set to "multipart/mixed". A unique boundary string is generated and this string is setup as the "boundary" parameter for the contentType field.

MimeBodyParts may be added later.

/** * Default constructor. An empty MimeMultipart object * is created. Its content type is set to "multipart/mixed". * A unique boundary string is generated and this string is * setup as the "boundary" parameter for the * <code>contentType</code> field. <p> * * MimeBodyParts may be added later. */
public MimeMultipart() { this("mixed"); }
Construct a MimeMultipart object of the given subtype. A unique boundary string is generated and this string is setup as the "boundary" parameter for the contentType field. Calls the initializeProperties method.

MimeBodyParts may be added later.

Params:
  • subtype – the MIME content subtype
/** * Construct a MimeMultipart object of the given subtype. * A unique boundary string is generated and this string is * setup as the "boundary" parameter for the * <code>contentType</code> field. * Calls the {@link #initializeProperties} method.<p> * * MimeBodyParts may be added later. * * @param subtype the MIME content subtype */
public MimeMultipart(String subtype) { super(); /* * Compute a boundary string. */ String boundary = UniqueValue.getUniqueBoundaryValue(); ContentType cType = new ContentType("multipart", subtype, null); cType.setParameter("boundary", boundary); contentType = cType.toString(); initializeProperties(); }
Construct a MimeMultipart object of the default "mixed" subtype, and with the given body parts. More body parts may be added later.
Params:
  • parts – the body parts
Throws:
Since: JavaMail 1.5
/** * Construct a MimeMultipart object of the default "mixed" subtype, * and with the given body parts. More body parts may be added later. * * @param parts the body parts * @exception MessagingException for failures * @since JavaMail 1.5 */
public MimeMultipart(BodyPart... parts) throws MessagingException { this(); for (BodyPart bp : parts) super.addBodyPart(bp); }
Construct a MimeMultipart object of the given subtype and with the given body parts. More body parts may be added later.
Params:
  • subtype – the MIME content subtype
  • parts – the body parts
Throws:
Since: JavaMail 1.5
/** * Construct a MimeMultipart object of the given subtype * and with the given body parts. More body parts may be added later. * * @param subtype the MIME content subtype * @param parts the body parts * @exception MessagingException for failures * @since JavaMail 1.5 */
public MimeMultipart(String subtype, BodyPart... parts) throws MessagingException { this(subtype); for (BodyPart bp : parts) super.addBodyPart(bp); }
Constructs a MimeMultipart object and its bodyparts from the given DataSource.

This constructor handles as a special case the situation where the given DataSource is a MultipartDataSource object. In this case, this method just invokes the superclass (i.e., Multipart) constructor that takes a MultipartDataSource object.

Otherwise, the DataSource is assumed to provide a MIME multipart byte stream. The parsed flag is set to false. When the data for the body parts are needed, the parser extracts the "boundary" parameter from the content type of this DataSource, skips the 'preamble' and reads bytes till the terminating boundary and creates MimeBodyParts for each part of the stream.

Params:
  • ds – DataSource, can be a MultipartDataSource
Throws:
/** * Constructs a MimeMultipart object and its bodyparts from the * given DataSource. <p> * * This constructor handles as a special case the situation where the * given DataSource is a MultipartDataSource object. In this case, this * method just invokes the superclass (i.e., Multipart) constructor * that takes a MultipartDataSource object. <p> * * Otherwise, the DataSource is assumed to provide a MIME multipart * byte stream. The <code>parsed</code> flag is set to false. When * the data for the body parts are needed, the parser extracts the * "boundary" parameter from the content type of this DataSource, * skips the 'preamble' and reads bytes till the terminating * boundary and creates MimeBodyParts for each part of the stream. * * @param ds DataSource, can be a MultipartDataSource * @exception ParseException for failures parsing the message * @exception MessagingException for other failures */
public MimeMultipart(DataSource ds) throws MessagingException { super(); if (ds instanceof MessageAware) { MessageContext mc = ((MessageAware)ds).getMessageContext(); setParent(mc.getPart()); } if (ds instanceof MultipartDataSource) { // ask super to do this for us. setMultipartDataSource((MultipartDataSource)ds); return; } // 'ds' was not a MultipartDataSource, we have // to parse this ourself. parsed = false; this.ds = ds; contentType = ds.getContentType(); }
Initialize flags that control parsing behavior, based on System properties described above in the class documentation.
Since: JavaMail 1.5
/** * Initialize flags that control parsing behavior, * based on System properties described above in * the class documentation. * * @since JavaMail 1.5 */
protected void initializeProperties() { // read properties that control parsing // default to true ignoreMissingEndBoundary = PropUtil.getBooleanSystemProperty( "mail.mime.multipart.ignoremissingendboundary", true); // default to true ignoreMissingBoundaryParameter = PropUtil.getBooleanSystemProperty( "mail.mime.multipart.ignoremissingboundaryparameter", true); // default to false ignoreExistingBoundaryParameter = PropUtil.getBooleanSystemProperty( "mail.mime.multipart.ignoreexistingboundaryparameter", false); // default to false allowEmpty = PropUtil.getBooleanSystemProperty( "mail.mime.multipart.allowempty", false); }
Set the subtype. This method should be invoked only on a new MimeMultipart object created by the client. The default subtype of such a multipart object is "mixed".

Params:
  • subtype – Subtype
Throws:
/** * Set the subtype. This method should be invoked only on a new * MimeMultipart object created by the client. The default subtype * of such a multipart object is "mixed". <p> * * @param subtype Subtype * @exception MessagingException for failures */
public synchronized void setSubType(String subtype) throws MessagingException { ContentType cType = new ContentType(contentType); cType.setSubType(subtype); contentType = cType.toString(); }
Return the number of enclosed BodyPart objects.
Returns: number of parts
/** * Return the number of enclosed BodyPart objects. * * @return number of parts */
@Override public synchronized int getCount() throws MessagingException { parse(); return super.getCount(); }
Get the specified BodyPart. BodyParts are numbered starting at 0.
Params:
  • index – the index of the desired BodyPart
Throws:
Returns: the Part
/** * Get the specified BodyPart. BodyParts are numbered starting at 0. * * @param index the index of the desired BodyPart * @return the Part * @exception MessagingException if no such BodyPart exists */
@Override public synchronized BodyPart getBodyPart(int index) throws MessagingException { parse(); return super.getBodyPart(index); }
Get the MimeBodyPart referred to by the given ContentID (CID). Returns null if the part is not found.
Params:
  • CID – the ContentID of the desired part
Throws:
Returns: the Part
/** * Get the MimeBodyPart referred to by the given ContentID (CID). * Returns null if the part is not found. * * @param CID the ContentID of the desired part * @return the Part * @exception MessagingException for failures */
public synchronized BodyPart getBodyPart(String CID) throws MessagingException { parse(); int count = getCount(); for (int i = 0; i < count; i++) { MimeBodyPart part = (MimeBodyPart)getBodyPart(i); String s = part.getContentID(); if (s != null && s.equals(CID)) return part; } return null; }
Remove the specified part from the multipart message. Shifts all the parts after the removed part down one.
Params:
  • part – The part to remove
Throws:
Returns: true if part removed, false otherwise
/** * Remove the specified part from the multipart message. * Shifts all the parts after the removed part down one. * * @param part The part to remove * @return true if part removed, false otherwise * @exception MessagingException if no such Part exists * @exception IllegalWriteException if the underlying * implementation does not support modification * of existing values */
@Override public boolean removeBodyPart(BodyPart part) throws MessagingException { parse(); return super.removeBodyPart(part); }
Remove the part at specified location (starting from 0). Shifts all the parts after the removed part down one.
Params:
  • index – Index of the part to remove
Throws:
/** * Remove the part at specified location (starting from 0). * Shifts all the parts after the removed part down one. * * @param index Index of the part to remove * @exception IndexOutOfBoundsException if the given index * is out of range. * @exception IllegalWriteException if the underlying * implementation does not support modification * of existing values * @exception MessagingException for other failures */
@Override public void removeBodyPart(int index) throws MessagingException { parse(); super.removeBodyPart(index); }
Adds a Part to the multipart. The BodyPart is appended to the list of existing Parts.
Params:
  • part – The Part to be appended
Throws:
/** * Adds a Part to the multipart. The BodyPart is appended to * the list of existing Parts. * * @param part The Part to be appended * @exception IllegalWriteException if the underlying * implementation does not support modification * of existing values * @exception MessagingException for other failures */
@Override public synchronized void addBodyPart(BodyPart part) throws MessagingException { parse(); super.addBodyPart(part); }
Adds a BodyPart at position index. If index is not the last one in the list, the subsequent parts are shifted up. If index is larger than the number of parts present, the BodyPart is appended to the end.
Params:
  • part – The BodyPart to be inserted
  • index – Location where to insert the part
Throws:
/** * Adds a BodyPart at position <code>index</code>. * If <code>index</code> is not the last one in the list, * the subsequent parts are shifted up. If <code>index</code> * is larger than the number of parts present, the * BodyPart is appended to the end. * * @param part The BodyPart to be inserted * @param index Location where to insert the part * @exception IllegalWriteException if the underlying * implementation does not support modification * of existing values * @exception MessagingException for other failures */
@Override public synchronized void addBodyPart(BodyPart part, int index) throws MessagingException { parse(); super.addBodyPart(part, index); }
Return true if the final boundary line for this multipart was seen. When parsing multipart content, this class will (by default) terminate parsing with no error if the end of input is reached before seeing the final multipart boundary line. In such a case, this method will return false. (If the System property "mail.mime.multipart.ignoremissingendboundary" is set to false, parsing such a message will instead throw a MessagingException.)
Throws:
Returns: true if the final boundary line was seen
Since: JavaMail 1.4
/** * Return true if the final boundary line for this * multipart was seen. When parsing multipart content, * this class will (by default) terminate parsing with * no error if the end of input is reached before seeing * the final multipart boundary line. In such a case, * this method will return false. (If the System property * "mail.mime.multipart.ignoremissingendboundary" is set to * false, parsing such a message will instead throw a * MessagingException.) * * @return true if the final boundary line was seen * @exception MessagingException for failures * @since JavaMail 1.4 */
public synchronized boolean isComplete() throws MessagingException { parse(); return complete; }
Get the preamble text, if any, that appears before the first body part of this multipart. Some protocols, such as IMAP, will not allow access to the preamble text.
Throws:
Returns: the preamble text, or null if no preamble
Since: JavaMail 1.4
/** * Get the preamble text, if any, that appears before the * first body part of this multipart. Some protocols, * such as IMAP, will not allow access to the preamble text. * * @return the preamble text, or null if no preamble * @exception MessagingException for failures * @since JavaMail 1.4 */
public synchronized String getPreamble() throws MessagingException { parse(); return preamble; }
Set the preamble text to be included before the first body part. Applications should generally not include any preamble text. In some cases it may be helpful to include preamble text with instructions for users of pre-MIME software. The preamble text should be complete lines, including newlines.
Params:
  • preamble – the preamble text
Throws:
Since: JavaMail 1.4
/** * Set the preamble text to be included before the first * body part. Applications should generally not include * any preamble text. In some cases it may be helpful to * include preamble text with instructions for users of * pre-MIME software. The preamble text should be complete * lines, including newlines. * * @param preamble the preamble text * @exception MessagingException for failures * @since JavaMail 1.4 */
public synchronized void setPreamble(String preamble) throws MessagingException { this.preamble = preamble; }
Update headers. The default implementation here just calls the updateHeaders method on each of its children BodyParts.

Note that the boundary parameter is already set up when a new and empty MimeMultipart object is created.

This method is called when the saveChanges method is invoked on the Message object containing this Multipart. This is typically done as part of the Message send process, however note that a client is free to call it any number of times. So if the header updating process is expensive for a specific MimeMultipart subclass, then it might itself want to track whether its internal state actually did change, and do the header updating only if necessary.

Throws:
  • MessagingException – for failures
/** * Update headers. The default implementation here just * calls the <code>updateHeaders</code> method on each of its * children BodyParts. <p> * * Note that the boundary parameter is already set up when * a new and empty MimeMultipart object is created. <p> * * This method is called when the <code>saveChanges</code> * method is invoked on the Message object containing this * Multipart. This is typically done as part of the Message * send process, however note that a client is free to call * it any number of times. So if the header updating process is * expensive for a specific MimeMultipart subclass, then it * might itself want to track whether its internal state actually * did change, and do the header updating only if necessary. * * @exception MessagingException for failures */
protected synchronized void updateHeaders() throws MessagingException { parse(); for (int i = 0; i < parts.size(); i++) ((MimeBodyPart)parts.elementAt(i)).updateHeaders(); }
Iterates through all the parts and outputs each MIME part separated by a boundary.
/** * Iterates through all the parts and outputs each MIME part * separated by a boundary. */
@Override public synchronized void writeTo(OutputStream os) throws IOException, MessagingException { parse(); String boundary = "--" + (new ContentType(contentType)).getParameter("boundary"); LineOutputStream los = new LineOutputStream(os); // if there's a preamble, write it out if (preamble != null) { byte[] pb = ASCIIUtility.getBytes(preamble); los.write(pb); // make sure it ends with a newline if (pb.length > 0 && !(pb[pb.length-1] == '\r' || pb[pb.length-1] == '\n')) { los.writeln(); } // XXX - could force a blank line before start boundary } if (parts.size() == 0) { if (allowEmpty) { // write out a single empty body part los.writeln(boundary); // put out boundary los.writeln(); // put out empty line } else { throw new MessagingException("Empty multipart: " + contentType); } } else { for (int i = 0; i < parts.size(); i++) { los.writeln(boundary); // put out boundary ((MimeBodyPart)parts.elementAt(i)).writeTo(os); los.writeln(); // put out empty line } } // put out last boundary los.writeln(boundary + "--"); }
Parse the InputStream from our DataSource, constructing the appropriate MimeBodyParts. The parsed flag is set to true, and if true on entry nothing is done. This method is called by all other methods that need data for the body parts, to make sure the data has been parsed. The initializeProperties method is called before parsing the data.
Throws:
Since: JavaMail 1.2
/** * Parse the InputStream from our DataSource, constructing the * appropriate MimeBodyParts. The <code>parsed</code> flag is * set to true, and if true on entry nothing is done. This * method is called by all other methods that need data for * the body parts, to make sure the data has been parsed. * The {@link #initializeProperties} method is called before * parsing the data. * * @exception ParseException for failures parsing the message * @exception MessagingException for other failures * @since JavaMail 1.2 */
protected synchronized void parse() throws MessagingException { if (parsed) return; initializeProperties(); InputStream in = null; SharedInputStream sin = null; long start = 0, end = 0; try { in = ds.getInputStream(); if (!(in instanceof ByteArrayInputStream) && !(in instanceof BufferedInputStream) && !(in instanceof SharedInputStream)) in = new BufferedInputStream(in); } catch (Exception ex) { throw new MessagingException("No inputstream from datasource", ex); } if (in instanceof SharedInputStream) sin = (SharedInputStream)in; ContentType cType = new ContentType(contentType); String boundary = null; if (!ignoreExistingBoundaryParameter) { String bp = cType.getParameter("boundary"); if (bp != null) boundary = "--" + bp; } if (boundary == null && !ignoreMissingBoundaryParameter && !ignoreExistingBoundaryParameter) throw new ParseException("Missing boundary parameter"); try { // Skip and save the preamble LineInputStream lin = new LineInputStream(in); StringBuilder preamblesb = null; String line; while ((line = lin.readLine()) != null) { /* * Strip trailing whitespace. Can't use trim method * because it's too aggressive. Some bogus MIME * messages will include control characters in the * boundary string. */ int i; for (i = line.length() - 1; i >= 0; i--) { char c = line.charAt(i); if (!(c == ' ' || c == '\t')) break; } line = line.substring(0, i + 1); if (boundary != null) { if (line.equals(boundary)) break; if (line.length() == boundary.length() + 2 && line.startsWith(boundary) && line.endsWith("--")) { line = null; // signal end of multipart break; } } else { /* * Boundary hasn't been defined, does this line * look like a boundary? If so, assume it is * the boundary and save it. */ if (line.length() > 2 && line.startsWith("--")) { if (line.length() > 4 && allDashes(line)) { /* * The first boundary-like line we find is * probably *not* the end-of-multipart boundary * line. More likely it's a line full of dashes * in the preamble text. Just keep reading. */ } else { boundary = line; break; } } } // save the preamble after skipping blank lines if (line.length() > 0) { // accumulate the preamble if (preamblesb == null) preamblesb = new StringBuilder(line.length() + 2); preamblesb.append(line).append(System.lineSeparator()); } } if (preamblesb != null) preamble = preamblesb.toString(); if (line == null) { if (allowEmpty) return; else throw new ParseException("Missing start boundary"); } // save individual boundary bytes for comparison later byte[] bndbytes = ASCIIUtility.getBytes(boundary); int bl = bndbytes.length; /* * Compile Boyer-Moore parsing tables. */ // initialize Bad Character Shift table int[] bcs = new int[256]; for (int i = 0; i < bl; i++) bcs[bndbytes[i] & 0xff] = i + 1; // initialize Good Suffix Shift table int[] gss = new int[bl]; NEXT: for (int i = bl; i > 0; i--) { int j; // the beginning index of the suffix being considered for (j = bl - 1; j >= i; j--) { // Testing for good suffix if (bndbytes[j] == bndbytes[j - i]) { // bndbytes[j..len] is a good suffix gss[j - 1] = i; } else { // No match. The array has already been // filled up with correct values before. continue NEXT; } } while (j > 0) gss[--j] = i; } gss[bl - 1] = 1; /* * Read and process body parts until we see the * terminating boundary line (or EOF). */ boolean done = false; getparts: while (!done) { InternetHeaders headers = null; if (sin != null) { start = sin.getPosition(); // skip headers while ((line = lin.readLine()) != null && line.length() > 0) ; if (line == null) { if (!ignoreMissingEndBoundary) throw new ParseException( "missing multipart end boundary"); // assume there's just a missing end boundary complete = false; break getparts; } } else { // collect the headers for this body part headers = createInternetHeaders(in); } if (!in.markSupported()) throw new MessagingException("Stream doesn't support mark"); ByteArrayOutputStream buf = null; // if we don't have a shared input stream, we copy the data if (sin == null) buf = new ByteArrayOutputStream(); else end = sin.getPosition(); int b; /* * These buffers contain the bytes we're checking * for a match. inbuf is the current buffer and * previnbuf is the previous buffer. We need the * previous buffer to check that we're preceeded * by an EOL. */ // XXX - a smarter algorithm would use a sliding window // over a larger buffer byte[] inbuf = new byte[bl]; byte[] previnbuf = new byte[bl]; int inSize = 0; // number of valid bytes in inbuf int prevSize = 0; // number of valid bytes in previnbuf int eolLen; boolean first = true; /* * Read and save the content bytes in buf. */ for (;;) { in.mark(bl + 4 + 1000); // bnd + "--\r\n" + lots of LWSP eolLen = 0; inSize = readFully(in, inbuf, 0, bl); if (inSize < bl) { // hit EOF if (!ignoreMissingEndBoundary) throw new ParseException( "missing multipart end boundary"); if (sin != null) end = sin.getPosition(); complete = false; done = true; break; } // check whether inbuf contains a boundary string int i; for (i = bl - 1; i >= 0; i--) { if (inbuf[i] != bndbytes[i]) break; } if (i < 0) { // matched all bytes eolLen = 0; if (!first) { // working backwards, find out if we were preceeded // by an EOL, and if so find its length b = previnbuf[prevSize - 1]; if (b == '\r' || b == '\n') { eolLen = 1; if (b == '\n' && prevSize >= 2) { b = previnbuf[prevSize - 2]; if (b == '\r') eolLen = 2; } } } if (first || eolLen > 0) { // yes, preceed by EOL if (sin != null) { // update "end", in case this really is // a valid boundary end = sin.getPosition() - bl - eolLen; } // matched the boundary, check for last boundary int b2 = in.read(); if (b2 == '-') { if (in.read() == '-') { complete = true; done = true; break; // ignore trailing text } } // skip linear whitespace while (b2 == ' ' || b2 == '\t') b2 = in.read(); // check for end of line if (b2 == '\n') break; // got it! break out of the loop if (b2 == '\r') { in.mark(1); if (in.read() != '\n') in.reset(); break; // got it! break out of the loop } } i = 0; } /* * Get here if boundary didn't match, * wasn't preceeded by EOL, or wasn't * followed by whitespace or EOL. */ // compute how many bytes we can skip int skip = Math.max(i + 1 - bcs[inbuf[i] & 0x7f], gss[i]); // want to keep at least two characters if (skip < 2) { // only skipping one byte, save one byte // from previous buffer as well // first, write out bytes we're done with if (sin == null && prevSize > 1) buf.write(previnbuf, 0, prevSize - 1); in.reset(); skipFully(in, 1); if (prevSize >= 1) { // is there a byte to save? // yes, save one from previous and one from current previnbuf[0] = previnbuf[prevSize - 1]; previnbuf[1] = inbuf[0]; prevSize = 2; } else { // no previous bytes to save, can only save current previnbuf[0] = inbuf[0]; prevSize = 1; } } else { // first, write out data from previous buffer before // we dump it if (prevSize > 0 && sin == null) buf.write(previnbuf, 0, prevSize); // all the bytes we're skipping are saved in previnbuf prevSize = skip; in.reset(); skipFully(in, prevSize); // swap buffers byte[] tmp = inbuf; inbuf = previnbuf; previnbuf = tmp; } first = false; } /* * Create a MimeBody element to represent this body part. */ MimeBodyPart part; if (sin != null) { part = createMimeBodyPartIs(sin.newStream(start, end)); } else { // write out data from previous buffer, not including EOL if (prevSize - eolLen > 0) buf.write(previnbuf, 0, prevSize - eolLen); // if we didn't find a trailing boundary, // the current buffer has data we need too if (!complete && inSize > 0) buf.write(inbuf, 0, inSize); part = createMimeBodyPart(headers, buf.toByteArray()); } super.addBodyPart(part); } } catch (IOException ioex) { throw new MessagingException("IO Error", ioex); } finally { try { in.close(); } catch (IOException cex) { // ignore } } parsed = true; }
Is the string all dashes ('-')?
/** * Is the string all dashes ('-')? */
private static boolean allDashes(String s) { for (int i = 0; i < s.length(); i++) { if (s.charAt(i) != '-') return false; } return true; }
Read data from the input stream to fill the buffer starting at the specified offset with the specified number of bytes. If len is zero, return zero. If at EOF, return -1. Otherwise, return the number of bytes read. Call the read method on the input stream as many times as necessary to read len bytes.
Params:
  • in – InputStream to read from
  • buf – buffer to read into
  • off – offset in the buffer for first byte
  • len – number of bytes to read
Throws:
Returns: -1 on EOF, otherwise number of bytes read
/** * Read data from the input stream to fill the buffer starting * at the specified offset with the specified number of bytes. * If len is zero, return zero. If at EOF, return -1. Otherwise, * return the number of bytes read. Call the read method on the * input stream as many times as necessary to read len bytes. * * @param in InputStream to read from * @param buf buffer to read into * @param off offset in the buffer for first byte * @param len number of bytes to read * @return -1 on EOF, otherwise number of bytes read * @exception IOException on I/O errors */
private static int readFully(InputStream in, byte[] buf, int off, int len) throws IOException { if (len == 0) return 0; int total = 0; while (len > 0) { int bsize = in.read(buf, off, len); if (bsize <= 0) // should never be zero break; off += bsize; total += bsize; len -= bsize; } return total > 0 ? total : -1; }
Skip the specified number of bytes, repeatedly calling the skip method as necessary.
/** * Skip the specified number of bytes, repeatedly calling * the skip method as necessary. */
private void skipFully(InputStream in, long offset) throws IOException { while (offset > 0) { long cur = in.skip(offset); if (cur <= 0) throw new EOFException("can't skip"); offset -= cur; } }
Create and return an InternetHeaders object that loads the headers from the given InputStream. Subclasses can override this method to return a subclass of InternetHeaders, if necessary. This implementation simply constructs and returns an InternetHeaders object.
Params:
  • is – the InputStream to read the headers from
Throws:
Returns: an InternetHeaders object
Since: JavaMail 1.2
/** * Create and return an InternetHeaders object that loads the * headers from the given InputStream. Subclasses can override * this method to return a subclass of InternetHeaders, if * necessary. This implementation simply constructs and returns * an InternetHeaders object. * * @param is the InputStream to read the headers from * @return an InternetHeaders object * @exception MessagingException for failures * @since JavaMail 1.2 */
protected InternetHeaders createInternetHeaders(InputStream is) throws MessagingException { return new InternetHeaders(is); }
Create and return a MimeBodyPart object to represent a body part parsed from the InputStream. Subclasses can override this method to return a subclass of MimeBodyPart, if necessary. This implementation simply constructs and returns a MimeBodyPart object.
Params:
  • headers – the headers for the body part
  • content – the content of the body part
Throws:
Returns: a MimeBodyPart
Since: JavaMail 1.2
/** * Create and return a MimeBodyPart object to represent a * body part parsed from the InputStream. Subclasses can override * this method to return a subclass of MimeBodyPart, if * necessary. This implementation simply constructs and returns * a MimeBodyPart object. * * @param headers the headers for the body part * @param content the content of the body part * @return a MimeBodyPart * @exception MessagingException for failures * @since JavaMail 1.2 */
protected MimeBodyPart createMimeBodyPart(InternetHeaders headers, byte[] content) throws MessagingException { return new MimeBodyPart(headers, content); }
Create and return a MimeBodyPart object to represent a body part parsed from the InputStream. Subclasses can override this method to return a subclass of MimeBodyPart, if necessary. This implementation simply constructs and returns a MimeBodyPart object.
Params:
  • is – InputStream containing the body part
Throws:
Returns: a MimeBodyPart
Since: JavaMail 1.2
/** * Create and return a MimeBodyPart object to represent a * body part parsed from the InputStream. Subclasses can override * this method to return a subclass of MimeBodyPart, if * necessary. This implementation simply constructs and returns * a MimeBodyPart object. * * @param is InputStream containing the body part * @return a MimeBodyPart * @exception MessagingException for failures * @since JavaMail 1.2 */
protected MimeBodyPart createMimeBodyPart(InputStream is) throws MessagingException { return new MimeBodyPart(is); } private MimeBodyPart createMimeBodyPartIs(InputStream is) throws MessagingException { try { return createMimeBodyPart(is); } finally { try { is.close(); } catch (IOException ex) { // ignore it } } } }