- /*
- * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
-
- 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;
-
- /**
- * 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>.
- *
- * @version 1.26, 00/09/18
- * @author John Mani
- * @author Bill Shannon
- * @author Max Spivak
- */
-
- public class MimeMultipart extends Multipart {
-
- /**
- * 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.
- */
- protected boolean parsed = true;
-
- /**
- * 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
- * <code>contentType</code> field. <p>
- *
- * MimeBodyParts may be added later.
- */
- 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();
- }
-
- /**
- * 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
- */
- 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();
- }
-
- /**
- * 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
- */
- 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.
- *
- * @return number of parts
- */
- public synchronized int getCount() throws MessagingException {
- parse();
- return super.getCount();
- }
-
- /**
- * 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
- */
- 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.
- *
- * @param CID the ContentID of the desired part
- * @return the Part
- */
- 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;
- }
-
- /**
- * 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.
- */
- protected void updateHeaders() throws MessagingException {
- 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.
- */
- public void writeTo(OutputStream os)
- throws IOException, MessagingException {
- parse();
-
- String boundary = "--" +
- (new ContentType(contentType)).getParameter("boundary");
- LineOutputStream los = new LineOutputStream(os);
-
- 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 <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.
- *
- * @since JavaMail 1.2
- */
- protected synchronized void parse() throws MessagingException {
- 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 = new BufferedInputStream(in);
- } catch (Exception ex) {
- throw new MessagingException("No inputstream from datasource");
- }
- if (in instanceof SharedInputStream)
- sin = (SharedInputStream)in;
-
- ContentType cType = new ContentType(contentType);
- String boundary = "--" + cType.getParameter("boundary");
- int bl = boundary.length();
- byte[] bndbytes = new byte[bl];
- boundary.getBytes(0, bl, bndbytes, 0);
-
- try {
- // Skip the preamble
- LineInputStream lin = new LineInputStream(in);
- String line;
- while ((line = lin.readLine()) != null) {
- if (line.trim().equals(boundary))
- break;
- }
- if (line == null)
- throw new MessagingException("Missing start boundary");
-
- /*
- * Read and process body parts until we see the
- * terminating boundary line (or EOF).
- */
- boolean done = false;
- while (!done) {
- InternetHeaders headers = null;
- if (sin != null) {
- start = sin.getPosition();
- // skip headers
- while ((line = lin.readLine()) != null && line.length() > 0)
- ;
- if (line == null)
- throw new MessagingException("EOF skipping headers");
- } 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();
- 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])
- break;
- if (i == bl) {
- // matched the boundary, check for last boundary
- int b2 = in.read();
- if (b2 == '-') {
- if (in.read() == '-') {
- 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) {
- 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());
- addBodyPart(part);
- }
- } catch (IOException ioex) {
- throw new MessagingException("IO Error", ioex);
- }
-
- parsed = true;
- }
-
- /**
- * 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
- * @exception MessagingException
- * @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.
- *
- * @param headers the headers for the body part
- * @param content the content of the body part
- * @exception MessagingException
- * @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.
- *
- * @param is InputStream containing the body part
- * @exception MessagingException
- * @since JavaMail 1.2
- */
- protected MimeBodyPart createMimeBodyPart(InputStream is)
- throws MessagingException {
- return new MimeBodyPart(is);
- }
- }