/*
* 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: - MessagingException – for failures
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: - MessagingException – for failures
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: - ParseException – for failures parsing the message
- MessagingException – for other failures
/**
* 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: - MessagingException – for failures
/**
* 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: - MessagingException – if no such BodyPart exists
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: - MessagingException – for failures
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: - MessagingException – if no such Part exists
- IllegalWriteException – if the underlying
implementation does not support modification
of existing values
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: - IndexOutOfBoundsException – if the given index
is out of range.
- IllegalWriteException – if the underlying
implementation does not support modification
of existing values
- MessagingException – for other failures
/**
* 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: - IllegalWriteException – if the underlying
implementation does not support modification
of existing values
- MessagingException – for other failures
/**
* 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: - IllegalWriteException – if the underlying
implementation does not support modification
of existing values
- MessagingException – for other failures
/**
* 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: - MessagingException – for failures
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: - MessagingException – for failures
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: - MessagingException – for failures
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: - ParseException – for failures parsing the message
- MessagingException – for other failures
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: - IOException – on I/O errors
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: - MessagingException – for failures
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: - MessagingException – for failures
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: - MessagingException – for failures
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
}
}
}
}