MimeMultipartpublic class MimeMultipart extends Multipart 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. |
Fields Summary |
---|
private static boolean | ignoreMissingEndBoundary | private static boolean | ignoreMissingBoundaryParameter | private static boolean | bmparse | protected DataSource | dsThe DataSource supplying our InputStream. | protected boolean | parsedHave 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. | private boolean | completeHave we seen the final bounary line? | private String | preambleThe MIME multipart preamble text, the text that
occurs before the first boundary line. |
Constructors Summary |
---|
public MimeMultipart()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.
this("mixed");
| public MimeMultipart(String 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
contentType field.
MimeBodyParts may be added later.
super();
/*
* Compute a boundary string.
*/
String boundary = UniqueValue.getUniqueBoundaryValue();
ContentType cType = new ContentType("multipart", subtype, null);
cType.setParameter("boundary", boundary);
contentType = cType.toString();
| public MimeMultipart(DataSource ds)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.
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();
|
Methods Summary |
---|
public synchronized void | addBodyPart(javax.mail.BodyPart part)Adds a Part to the multipart. The BodyPart is appended to
the list of existing Parts.
parse();
super.addBodyPart(part);
| public synchronized void | addBodyPart(javax.mail.BodyPart part, int index)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.
parse();
super.addBodyPart(part, index);
| protected javax.mail.internet.InternetHeaders | createInternetHeaders(java.io.InputStream is)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.
return new InternetHeaders(is);
| protected javax.mail.internet.MimeBodyPart | createMimeBodyPart(javax.mail.internet.InternetHeaders headers, byte[] 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.
return new MimeBodyPart(headers, content);
| protected javax.mail.internet.MimeBodyPart | createMimeBodyPart(java.io.InputStream 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.
return new MimeBodyPart(is);
| public synchronized javax.mail.BodyPart | getBodyPart(int index)Get the specified BodyPart. BodyParts are numbered starting at 0.
parse();
return super.getBodyPart(index);
| public synchronized javax.mail.BodyPart | getBodyPart(java.lang.String CID)Get the MimeBodyPart referred to by the given ContentID (CID).
Returns null if the part is not found.
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;
| public synchronized int | getCount()Return the number of enclosed BodyPart objects.
parse();
return super.getCount();
| public synchronized java.lang.String | getPreamble()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.
parse();
return preamble;
| public synchronized boolean | isComplete()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.)
parse();
return complete;
| protected synchronized void | parse()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.
if (parsed)
return;
if (bmparse) {
parsebm();
return;
}
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;
String bp = cType.getParameter("boundary");
if (bp != null)
boundary = "--" + bp;
else if (!ignoreMissingBoundaryParameter)
throw new MessagingException("Missing boundary parameter");
try {
// Skip and save the preamble
LineInputStream lin = new LineInputStream(in);
StringBuffer preamblesb = null;
String line;
String lineSeparator = null;
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;
} 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.startsWith("--")) {
boundary = line;
break;
}
}
// save the preamble after skipping blank lines
if (line.length() > 0) {
// if we haven't figured out what the line seprator
// is, do it now
if (lineSeparator == null) {
try {
lineSeparator =
System.getProperty("line.separator", "\n");
} catch (SecurityException ex) {
lineSeparator = "\n";
}
}
// accumulate the preamble
if (preamblesb == null)
preamblesb = new StringBuffer(line.length() + 2);
preamblesb.append(line).append(lineSeparator);
}
}
if (line == null)
throw new MessagingException("Missing start boundary");
if (preamblesb != null)
preamble = preamblesb.toString();
// save individual boundary bytes for easy comparison later
byte[] bndbytes = ASCIIUtility.getBytes(boundary);
int bl = bndbytes.length;
/*
* 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 MessagingException(
"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;
boolean bol = true; // beginning of line flag
// the two possible end of line characters
int eol1 = -1, eol2 = -1;
/*
* Read and save the content bytes in buf.
*/
for (;;) {
if (bol) {
/*
* At the beginning of a line, check whether the
* next line is a boundary.
*/
int i;
in.mark(bl + 4 + 1000); // bnd + "--\r\n" + lots of LWSP
// read bytes, matching against the boundary
for (i = 0; i < bl; i++)
if (in.read() != (bndbytes[i] & 0xff))
break;
if (i == bl) {
// 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
}
}
// failed to match, reset and proceed normally
in.reset();
// if this is not the first line, write out the
// end of line characters from the previous line
if (buf != null && eol1 != -1) {
buf.write(eol1);
if (eol2 != -1)
buf.write(eol2);
eol1 = eol2 = -1;
}
}
// read the next byte
if ((b = in.read()) < 0) {
if (!ignoreMissingEndBoundary)
throw new MessagingException(
"missing multipart end boundary");
complete = false;
done = true;
break;
}
/*
* If we're at the end of the line, save the eol characters
* to be written out before the beginning of the next line.
*/
if (b == '\r" || b == '\n") {
bol = true;
if (sin != null)
end = sin.getPosition() - 1;
eol1 = b;
if (b == '\r") {
in.mark(1);
if ((b = in.read()) == '\n")
eol2 = b;
else
in.reset();
}
} else {
bol = false;
if (buf != null)
buf.write(b);
}
}
/*
* Create a MimeBody element to represent this body part.
*/
MimeBodyPart part;
if (sin != null)
part = createMimeBodyPart(sin.newStream(start, end));
else
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;
| private synchronized void | parsebm()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.
if (parsed)
return;
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;
String bp = cType.getParameter("boundary");
if (bp != null)
boundary = "--" + bp;
else if (!ignoreMissingBoundaryParameter)
throw new MessagingException("Missing boundary parameter");
try {
// Skip and save the preamble
LineInputStream lin = new LineInputStream(in);
StringBuffer preamblesb = null;
String line;
String lineSeparator = null;
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;
} 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.startsWith("--")) {
boundary = line;
break;
}
}
// save the preamble after skipping blank lines
if (line.length() > 0) {
// if we haven't figured out what the line seprator
// is, do it now
if (lineSeparator == null) {
try {
lineSeparator =
System.getProperty("line.separator", "\n");
} catch (SecurityException ex) {
lineSeparator = "\n";
}
}
// accumulate the preamble
if (preamblesb == null)
preamblesb = new StringBuffer(line.length() + 2);
preamblesb.append(line).append(lineSeparator);
}
}
if (line == null)
throw new MessagingException("Missing start boundary");
if (preamblesb != null)
preamble = preamblesb.toString();
// 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]] = 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 MessagingException(
"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 MessagingException(
"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 = createMimeBodyPart(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;
| private static int | readFully(java.io.InputStream in, byte[] buf, int off, int len)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.
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;
| public boolean | removeBodyPart(javax.mail.BodyPart part)Remove the specified part from the multipart message.
Shifts all the parts after the removed part down one.
parse();
return super.removeBodyPart(part);
| public void | removeBodyPart(int index)Remove the part at specified location (starting from 0).
Shifts all the parts after the removed part down one.
parse();
super.removeBodyPart(index);
| public synchronized void | setPreamble(java.lang.String 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.
this.preamble = preamble;
| public synchronized void | setSubType(java.lang.String subtype)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".
ContentType cType = new ContentType(contentType);
cType.setSubType(subtype);
contentType = cType.toString();
| private void | skipFully(java.io.InputStream in, long offset)Skip the specified number of bytes, repeatedly calling
the skip method as necessary.
while (offset > 0) {
long cur = in.skip(offset);
if (cur <= 0)
throw new EOFException("can't skip");
offset -= cur;
}
| protected void | updateHeaders()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.
for (int i = 0; i < parts.size(); i++)
((MimeBodyPart)parts.elementAt(i)).updateHeaders();
| public synchronized void | writeTo(java.io.OutputStream os)Iterates through all the parts and outputs each MIME part
separated by a boundary.
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
}
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 + "--");
|
|