0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common Development
0008: * and Distribution License("CDDL") (collectively, the "License"). You
0009: * may not use this file except in compliance with the License. You can obtain
0010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
0011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
0012: * language governing permissions and limitations under the License.
0013: *
0014: * When distributing the software, include this License Header Notice in each
0015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
0016: * Sun designates this particular file as subject to the "Classpath" exception
0017: * as provided by Sun in the GPL Version 2 section of the License file that
0018: * accompanied this code. If applicable, add the following below the License
0019: * Header, with the fields enclosed by brackets [] replaced by your own
0020: * identifying information: "Portions Copyrighted [year]
0021: * [name of copyright owner]"
0022: *
0023: * Contributor(s):
0024: *
0025: * If you wish your version of this file to be governed by only the CDDL or
0026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
0027: * elects to include this software in this distribution under the [CDDL or GPL
0028: * Version 2] license." If you don't indicate a single choice of license, a
0029: * recipient has the option to distribute your version of this file under
0030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
0031: * its licensees as provided above. However, if you add GPL Version 2 code
0032: * and therefore, elected the GPL Version 2 license, then the option applies
0033: * only if the new code is made subject to such option by the copyright
0034: * holder.
0035: */
0036:
0037: /*
0038: * @(#)MimeMultipart.java 1.48 07/05/15
0039: */
0040:
0041: package javax.mail.internet;
0042:
0043: import javax.mail.*;
0044: import javax.activation.*;
0045: import java.util.*;
0046: import java.io.*;
0047: import com.sun.mail.util.LineOutputStream;
0048: import com.sun.mail.util.LineInputStream;
0049: import com.sun.mail.util.ASCIIUtility;
0050:
0051: /**
0052: * The MimeMultipart class is an implementation of the abstract Multipart
0053: * class that uses MIME conventions for the multipart data. <p>
0054: *
0055: * A MimeMultipart is obtained from a MimePart whose primary type
0056: * is "multipart" (by invoking the part's <code>getContent()</code> method)
0057: * or it can be created by a client as part of creating a new MimeMessage. <p>
0058: *
0059: * The default multipart subtype is "mixed". The other multipart
0060: * subtypes, such as "alternative", "related", and so on, can be
0061: * implemented as subclasses of MimeMultipart with additional methods
0062: * to implement the additional semantics of that type of multipart
0063: * content. The intent is that service providers, mail JavaBean writers
0064: * and mail clients will write many such subclasses and their Command
0065: * Beans, and will install them into the JavaBeans Activation
0066: * Framework, so that any JavaMail implementation and its clients can
0067: * transparently find and use these classes. Thus, a MIME multipart
0068: * handler is treated just like any other type handler, thereby
0069: * decoupling the process of providing multipart handlers from the
0070: * JavaMail API. Lacking these additional MimeMultipart subclasses,
0071: * all subtypes of MIME multipart data appear as MimeMultipart objects. <p>
0072: *
0073: * An application can directly construct a MIME multipart object of any
0074: * subtype by using the <code>MimeMultipart(String subtype)</code>
0075: * constructor. For example, to create a "multipart/alternative" object,
0076: * use <code>new MimeMultipart("alternative")</code>. <p>
0077: *
0078: * The <code>mail.mime.multipart.ignoremissingendboundary</code>
0079: * property may be set to <code>false</code> to cause a
0080: * <code>MessagingException</code> to be thrown if the multipart
0081: * data does not end with the required end boundary line. If this
0082: * property is set to <code>true</code> or not set, missing end
0083: * boundaries are not considered an error and the final body part
0084: * ends at the end of the data. <p>
0085: *
0086: * The <code>mail.mime.multipart.ignoremissingboundaryparameter</code>
0087: * System property may be set to <code>false</code> to cause a
0088: * <code>MessagingException</code> to be thrown if the Content-Type
0089: * of the MimeMultipart does not include a <code>boundary</code> parameter.
0090: * If this property is set to <code>true</code> or not set, the multipart
0091: * parsing code will look for a line that looks like a bounary line and
0092: * use that as the boundary separating the parts.
0093: *
0094: * @version 1.48, 07/05/15
0095: * @author John Mani
0096: * @author Bill Shannon
0097: * @author Max Spivak
0098: */
0099:
0100: public class MimeMultipart extends Multipart {
0101:
0102: private static boolean ignoreMissingEndBoundary = true;
0103: private static boolean ignoreMissingBoundaryParameter = true;
0104: private static boolean bmparse = true;
0105:
0106: static {
0107: try {
0108: String s = System
0109: .getProperty("mail.mime.multipart.ignoremissingendboundary");
0110: // default to true
0111: ignoreMissingEndBoundary = s == null
0112: || !s.equalsIgnoreCase("false");
0113: s = System
0114: .getProperty("mail.mime.multipart.ignoremissingboundaryparameter");
0115: // default to true
0116: ignoreMissingBoundaryParameter = s == null
0117: || !s.equalsIgnoreCase("false");
0118: s = System.getProperty("mail.mime.multipart.bmparse");
0119: // default to true
0120: bmparse = s == null || !s.equalsIgnoreCase("false");
0121: } catch (SecurityException sex) {
0122: // ignore it
0123: }
0124: }
0125:
0126: /**
0127: * The DataSource supplying our InputStream.
0128: */
0129: protected DataSource ds = null;
0130:
0131: /**
0132: * Have we parsed the data from our InputStream yet?
0133: * Defaults to true; set to false when our constructor is
0134: * given a DataSource with an InputStream that we need to
0135: * parse.
0136: */
0137: protected boolean parsed = true;
0138:
0139: /**
0140: * Have we seen the final bounary line?
0141: */
0142: private boolean complete = true;
0143:
0144: /**
0145: * The MIME multipart preamble text, the text that
0146: * occurs before the first boundary line.
0147: */
0148: private String preamble = null;
0149:
0150: /**
0151: * Default constructor. An empty MimeMultipart object
0152: * is created. Its content type is set to "multipart/mixed".
0153: * A unique boundary string is generated and this string is
0154: * setup as the "boundary" parameter for the
0155: * <code>contentType</code> field. <p>
0156: *
0157: * MimeBodyParts may be added later.
0158: */
0159: public MimeMultipart() {
0160: this ("mixed");
0161: }
0162:
0163: /**
0164: * Construct a MimeMultipart object of the given subtype.
0165: * A unique boundary string is generated and this string is
0166: * setup as the "boundary" parameter for the
0167: * <code>contentType</code> field. <p>
0168: *
0169: * MimeBodyParts may be added later.
0170: */
0171: public MimeMultipart(String subtype) {
0172: super ();
0173: /*
0174: * Compute a boundary string.
0175: */
0176: String boundary = UniqueValue.getUniqueBoundaryValue();
0177: ContentType cType = new ContentType("multipart", subtype, null);
0178: cType.setParameter("boundary", boundary);
0179: contentType = cType.toString();
0180: }
0181:
0182: /**
0183: * Constructs a MimeMultipart object and its bodyparts from the
0184: * given DataSource. <p>
0185: *
0186: * This constructor handles as a special case the situation where the
0187: * given DataSource is a MultipartDataSource object. In this case, this
0188: * method just invokes the superclass (i.e., Multipart) constructor
0189: * that takes a MultipartDataSource object. <p>
0190: *
0191: * Otherwise, the DataSource is assumed to provide a MIME multipart
0192: * byte stream. The <code>parsed</code> flag is set to false. When
0193: * the data for the body parts are needed, the parser extracts the
0194: * "boundary" parameter from the content type of this DataSource,
0195: * skips the 'preamble' and reads bytes till the terminating
0196: * boundary and creates MimeBodyParts for each part of the stream.
0197: *
0198: * @param ds DataSource, can be a MultipartDataSource
0199: */
0200: public MimeMultipart(DataSource ds) throws MessagingException {
0201: super ();
0202:
0203: if (ds instanceof MessageAware) {
0204: MessageContext mc = ((MessageAware) ds).getMessageContext();
0205: setParent(mc.getPart());
0206: }
0207:
0208: if (ds instanceof MultipartDataSource) {
0209: // ask super to do this for us.
0210: setMultipartDataSource((MultipartDataSource) ds);
0211: return;
0212: }
0213:
0214: // 'ds' was not a MultipartDataSource, we have
0215: // to parse this ourself.
0216: parsed = false;
0217: this .ds = ds;
0218: contentType = ds.getContentType();
0219: }
0220:
0221: /**
0222: * Set the subtype. This method should be invoked only on a new
0223: * MimeMultipart object created by the client. The default subtype
0224: * of such a multipart object is "mixed". <p>
0225: *
0226: * @param subtype Subtype
0227: */
0228: public synchronized void setSubType(String subtype)
0229: throws MessagingException {
0230: ContentType cType = new ContentType(contentType);
0231: cType.setSubType(subtype);
0232: contentType = cType.toString();
0233: }
0234:
0235: /**
0236: * Return the number of enclosed BodyPart objects.
0237: *
0238: * @return number of parts
0239: */
0240: public synchronized int getCount() throws MessagingException {
0241: parse();
0242: return super .getCount();
0243: }
0244:
0245: /**
0246: * Get the specified BodyPart. BodyParts are numbered starting at 0.
0247: *
0248: * @param index the index of the desired BodyPart
0249: * @return the Part
0250: * @exception MessagingException if no such BodyPart exists
0251: */
0252: public synchronized BodyPart getBodyPart(int index)
0253: throws MessagingException {
0254: parse();
0255: return super .getBodyPart(index);
0256: }
0257:
0258: /**
0259: * Get the MimeBodyPart referred to by the given ContentID (CID).
0260: * Returns null if the part is not found.
0261: *
0262: * @param CID the ContentID of the desired part
0263: * @return the Part
0264: */
0265: public synchronized BodyPart getBodyPart(String CID)
0266: throws MessagingException {
0267: parse();
0268:
0269: int count = getCount();
0270: for (int i = 0; i < count; i++) {
0271: MimeBodyPart part = (MimeBodyPart) getBodyPart(i);
0272: String s = part.getContentID();
0273: if (s != null && s.equals(CID))
0274: return part;
0275: }
0276: return null;
0277: }
0278:
0279: /**
0280: * Remove the specified part from the multipart message.
0281: * Shifts all the parts after the removed part down one.
0282: *
0283: * @param part The part to remove
0284: * @return true if part removed, false otherwise
0285: * @exception MessagingException if no such Part exists
0286: * @exception IllegalWriteException if the underlying
0287: * implementation does not support modification
0288: * of existing values
0289: */
0290: public boolean removeBodyPart(BodyPart part)
0291: throws MessagingException {
0292: parse();
0293: return super .removeBodyPart(part);
0294: }
0295:
0296: /**
0297: * Remove the part at specified location (starting from 0).
0298: * Shifts all the parts after the removed part down one.
0299: *
0300: * @param index Index of the part to remove
0301: * @exception MessagingException
0302: * @exception IndexOutOfBoundsException if the given index
0303: * is out of range.
0304: * @exception IllegalWriteException if the underlying
0305: * implementation does not support modification
0306: * of existing values
0307: */
0308: public void removeBodyPart(int index) throws MessagingException {
0309: parse();
0310: super .removeBodyPart(index);
0311: }
0312:
0313: /**
0314: * Adds a Part to the multipart. The BodyPart is appended to
0315: * the list of existing Parts.
0316: *
0317: * @param part The Part to be appended
0318: * @exception MessagingException
0319: * @exception IllegalWriteException if the underlying
0320: * implementation does not support modification
0321: * of existing values
0322: */
0323: public synchronized void addBodyPart(BodyPart part)
0324: throws MessagingException {
0325: parse();
0326: super .addBodyPart(part);
0327: }
0328:
0329: /**
0330: * Adds a BodyPart at position <code>index</code>.
0331: * If <code>index</code> is not the last one in the list,
0332: * the subsequent parts are shifted up. If <code>index</code>
0333: * is larger than the number of parts present, the
0334: * BodyPart is appended to the end.
0335: *
0336: * @param part The BodyPart to be inserted
0337: * @param index Location where to insert the part
0338: * @exception MessagingException
0339: * @exception IllegalWriteException if the underlying
0340: * implementation does not support modification
0341: * of existing values
0342: */
0343: public synchronized void addBodyPart(BodyPart part, int index)
0344: throws MessagingException {
0345: parse();
0346: super .addBodyPart(part, index);
0347: }
0348:
0349: /**
0350: * Return true if the final boundary line for this
0351: * multipart was seen. When parsing multipart content,
0352: * this class will (by default) terminate parsing with
0353: * no error if the end of input is reached before seeing
0354: * the final multipart boundary line. In such a case,
0355: * this method will return false. (If the System property
0356: * "mail.mime.multipart.ignoremissingendboundary" is set to
0357: * false, parsing such a message will instead throw a
0358: * MessagingException.)
0359: *
0360: * @return true if the final boundary line was seen
0361: * @since JavaMail 1.4
0362: */
0363: public synchronized boolean isComplete() throws MessagingException {
0364: parse();
0365: return complete;
0366: }
0367:
0368: /**
0369: * Get the preamble text, if any, that appears before the
0370: * first body part of this multipart. Some protocols,
0371: * such as IMAP, will not allow access to the preamble text.
0372: *
0373: * @return the preamble text, or null if no preamble
0374: * @since JavaMail 1.4
0375: */
0376: public synchronized String getPreamble() throws MessagingException {
0377: parse();
0378: return preamble;
0379: }
0380:
0381: /**
0382: * Set the preamble text to be included before the first
0383: * body part. Applications should generally not include
0384: * any preamble text. In some cases it may be helpful to
0385: * include preamble text with instructions for users of
0386: * pre-MIME software. The preamble text should be complete
0387: * lines, including newlines.
0388: *
0389: * @param preamble the preamble text
0390: * @since JavaMail 1.4
0391: */
0392: public synchronized void setPreamble(String preamble)
0393: throws MessagingException {
0394: this .preamble = preamble;
0395: }
0396:
0397: /**
0398: * Update headers. The default implementation here just
0399: * calls the <code>updateHeaders</code> method on each of its
0400: * children BodyParts. <p>
0401: *
0402: * Note that the boundary parameter is already set up when
0403: * a new and empty MimeMultipart object is created. <p>
0404: *
0405: * This method is called when the <code>saveChanges</code>
0406: * method is invoked on the Message object containing this
0407: * Multipart. This is typically done as part of the Message
0408: * send process, however note that a client is free to call
0409: * it any number of times. So if the header updating process is
0410: * expensive for a specific MimeMultipart subclass, then it
0411: * might itself want to track whether its internal state actually
0412: * did change, and do the header updating only if necessary.
0413: */
0414: protected void updateHeaders() throws MessagingException {
0415: for (int i = 0; i < parts.size(); i++)
0416: ((MimeBodyPart) parts.elementAt(i)).updateHeaders();
0417: }
0418:
0419: /**
0420: * Iterates through all the parts and outputs each MIME part
0421: * separated by a boundary.
0422: */
0423: public synchronized void writeTo(OutputStream os)
0424: throws IOException, MessagingException {
0425: parse();
0426:
0427: String boundary = "--"
0428: + (new ContentType(contentType))
0429: .getParameter("boundary");
0430: LineOutputStream los = new LineOutputStream(os);
0431:
0432: // if there's a preamble, write it out
0433: if (preamble != null) {
0434: byte[] pb = ASCIIUtility.getBytes(preamble);
0435: los.write(pb);
0436: // make sure it ends with a newline
0437: if (pb.length > 0
0438: && !(pb[pb.length - 1] == '\r' || pb[pb.length - 1] == '\n')) {
0439: los.writeln();
0440: }
0441: // XXX - could force a blank line before start boundary
0442: }
0443: for (int i = 0; i < parts.size(); i++) {
0444: los.writeln(boundary); // put out boundary
0445: ((MimeBodyPart) parts.elementAt(i)).writeTo(os);
0446: los.writeln(); // put out empty line
0447: }
0448:
0449: // put out last boundary
0450: los.writeln(boundary + "--");
0451: }
0452:
0453: /**
0454: * Parse the InputStream from our DataSource, constructing the
0455: * appropriate MimeBodyParts. The <code>parsed</code> flag is
0456: * set to true, and if true on entry nothing is done. This
0457: * method is called by all other methods that need data for
0458: * the body parts, to make sure the data has been parsed.
0459: *
0460: * @since JavaMail 1.2
0461: */
0462: protected synchronized void parse() throws MessagingException {
0463: if (parsed)
0464: return;
0465:
0466: if (bmparse) {
0467: parsebm();
0468: return;
0469: }
0470:
0471: InputStream in = null;
0472: SharedInputStream sin = null;
0473: long start = 0, end = 0;
0474:
0475: try {
0476: in = ds.getInputStream();
0477: if (!(in instanceof ByteArrayInputStream)
0478: && !(in instanceof BufferedInputStream)
0479: && !(in instanceof SharedInputStream))
0480: in = new BufferedInputStream(in);
0481: } catch (Exception ex) {
0482: throw new MessagingException(
0483: "No inputstream from datasource", ex);
0484: }
0485: if (in instanceof SharedInputStream)
0486: sin = (SharedInputStream) in;
0487:
0488: ContentType cType = new ContentType(contentType);
0489: String boundary = null;
0490: String bp = cType.getParameter("boundary");
0491: if (bp != null)
0492: boundary = "--" + bp;
0493: else if (!ignoreMissingBoundaryParameter)
0494: throw new MessagingException("Missing boundary parameter");
0495:
0496: try {
0497: // Skip and save the preamble
0498: LineInputStream lin = new LineInputStream(in);
0499: StringBuffer preamblesb = null;
0500: String line;
0501: String lineSeparator = null;
0502: while ((line = lin.readLine()) != null) {
0503: /*
0504: * Strip trailing whitespace. Can't use trim method
0505: * because it's too aggressive. Some bogus MIME
0506: * messages will include control characters in the
0507: * boundary string.
0508: */
0509: int i;
0510: for (i = line.length() - 1; i >= 0; i--) {
0511: char c = line.charAt(i);
0512: if (!(c == ' ' || c == '\t'))
0513: break;
0514: }
0515: line = line.substring(0, i + 1);
0516: if (boundary != null) {
0517: if (line.equals(boundary))
0518: break;
0519: } else {
0520: /*
0521: * Boundary hasn't been defined, does this line
0522: * look like a boundary? If so, assume it is
0523: * the boundary and save it.
0524: */
0525: if (line.startsWith("--")) {
0526: boundary = line;
0527: break;
0528: }
0529: }
0530:
0531: // save the preamble after skipping blank lines
0532: if (line.length() > 0) {
0533: // if we haven't figured out what the line seprator
0534: // is, do it now
0535: if (lineSeparator == null) {
0536: try {
0537: lineSeparator = System.getProperty(
0538: "line.separator", "\n");
0539: } catch (SecurityException ex) {
0540: lineSeparator = "\n";
0541: }
0542: }
0543: // accumulate the preamble
0544: if (preamblesb == null)
0545: preamblesb = new StringBuffer(line.length() + 2);
0546: preamblesb.append(line).append(lineSeparator);
0547: }
0548: }
0549: if (line == null)
0550: throw new MessagingException("Missing start boundary");
0551:
0552: if (preamblesb != null)
0553: preamble = preamblesb.toString();
0554:
0555: // save individual boundary bytes for easy comparison later
0556: byte[] bndbytes = ASCIIUtility.getBytes(boundary);
0557: int bl = bndbytes.length;
0558:
0559: /*
0560: * Read and process body parts until we see the
0561: * terminating boundary line (or EOF).
0562: */
0563: boolean done = false;
0564: getparts: while (!done) {
0565: InternetHeaders headers = null;
0566: if (sin != null) {
0567: start = sin.getPosition();
0568: // skip headers
0569: while ((line = lin.readLine()) != null
0570: && line.length() > 0)
0571: ;
0572: if (line == null) {
0573: if (!ignoreMissingEndBoundary)
0574: throw new MessagingException(
0575: "missing multipart end boundary");
0576: // assume there's just a missing end boundary
0577: complete = false;
0578: break getparts;
0579: }
0580: } else {
0581: // collect the headers for this body part
0582: headers = createInternetHeaders(in);
0583: }
0584:
0585: if (!in.markSupported())
0586: throw new MessagingException(
0587: "Stream doesn't support mark");
0588:
0589: ByteArrayOutputStream buf = null;
0590: // if we don't have a shared input stream, we copy the data
0591: if (sin == null)
0592: buf = new ByteArrayOutputStream();
0593: else
0594: end = sin.getPosition();
0595: int b;
0596: boolean bol = true; // beginning of line flag
0597: // the two possible end of line characters
0598: int eol1 = -1, eol2 = -1;
0599:
0600: /*
0601: * Read and save the content bytes in buf.
0602: */
0603: for (;;) {
0604: if (bol) {
0605: /*
0606: * At the beginning of a line, check whether the
0607: * next line is a boundary.
0608: */
0609: int i;
0610: in.mark(bl + 4 + 1000); // bnd + "--\r\n" + lots of LWSP
0611: // read bytes, matching against the boundary
0612: for (i = 0; i < bl; i++)
0613: if (in.read() != (bndbytes[i] & 0xff))
0614: break;
0615: if (i == bl) {
0616: // matched the boundary, check for last boundary
0617: int b2 = in.read();
0618: if (b2 == '-') {
0619: if (in.read() == '-') {
0620: complete = true;
0621: done = true;
0622: break; // ignore trailing text
0623: }
0624: }
0625: // skip linear whitespace
0626: while (b2 == ' ' || b2 == '\t')
0627: b2 = in.read();
0628: // check for end of line
0629: if (b2 == '\n')
0630: break; // got it! break out of the loop
0631: if (b2 == '\r') {
0632: in.mark(1);
0633: if (in.read() != '\n')
0634: in.reset();
0635: break; // got it! break out of the loop
0636: }
0637: }
0638: // failed to match, reset and proceed normally
0639: in.reset();
0640:
0641: // if this is not the first line, write out the
0642: // end of line characters from the previous line
0643: if (buf != null && eol1 != -1) {
0644: buf.write(eol1);
0645: if (eol2 != -1)
0646: buf.write(eol2);
0647: eol1 = eol2 = -1;
0648: }
0649: }
0650:
0651: // read the next byte
0652: if ((b = in.read()) < 0) {
0653: if (!ignoreMissingEndBoundary)
0654: throw new MessagingException(
0655: "missing multipart end boundary");
0656: complete = false;
0657: done = true;
0658: break;
0659: }
0660:
0661: /*
0662: * If we're at the end of the line, save the eol characters
0663: * to be written out before the beginning of the next line.
0664: */
0665: if (b == '\r' || b == '\n') {
0666: bol = true;
0667: if (sin != null)
0668: end = sin.getPosition() - 1;
0669: eol1 = b;
0670: if (b == '\r') {
0671: in.mark(1);
0672: if ((b = in.read()) == '\n')
0673: eol2 = b;
0674: else
0675: in.reset();
0676: }
0677: } else {
0678: bol = false;
0679: if (buf != null)
0680: buf.write(b);
0681: }
0682: }
0683:
0684: /*
0685: * Create a MimeBody element to represent this body part.
0686: */
0687: MimeBodyPart part;
0688: if (sin != null)
0689: part = createMimeBodyPart(sin.newStream(start, end));
0690: else
0691: part = createMimeBodyPart(headers, buf
0692: .toByteArray());
0693: super .addBodyPart(part);
0694: }
0695: } catch (IOException ioex) {
0696: throw new MessagingException("IO Error", ioex);
0697: } finally {
0698: try {
0699: in.close();
0700: } catch (IOException cex) {
0701: // ignore
0702: }
0703: }
0704:
0705: parsed = true;
0706: }
0707:
0708: /**
0709: * Parse the InputStream from our DataSource, constructing the
0710: * appropriate MimeBodyParts. The <code>parsed</code> flag is
0711: * set to true, and if true on entry nothing is done. This
0712: * method is called by all other methods that need data for
0713: * the body parts, to make sure the data has been parsed.
0714: *
0715: * @since JavaMail 1.2
0716: */
0717: /*
0718: * Boyer-Moore version of parser. Keep both versions around
0719: * until we're sure this new one works.
0720: */
0721: private synchronized void parsebm() throws MessagingException {
0722: if (parsed)
0723: return;
0724:
0725: InputStream in = null;
0726: SharedInputStream sin = null;
0727: long start = 0, end = 0;
0728:
0729: try {
0730: in = ds.getInputStream();
0731: if (!(in instanceof ByteArrayInputStream)
0732: && !(in instanceof BufferedInputStream)
0733: && !(in instanceof SharedInputStream))
0734: in = new BufferedInputStream(in);
0735: } catch (Exception ex) {
0736: throw new MessagingException(
0737: "No inputstream from datasource", ex);
0738: }
0739: if (in instanceof SharedInputStream)
0740: sin = (SharedInputStream) in;
0741:
0742: ContentType cType = new ContentType(contentType);
0743: String boundary = null;
0744: String bp = cType.getParameter("boundary");
0745: if (bp != null)
0746: boundary = "--" + bp;
0747: else if (!ignoreMissingBoundaryParameter)
0748: throw new MessagingException("Missing boundary parameter");
0749:
0750: try {
0751: // Skip and save the preamble
0752: LineInputStream lin = new LineInputStream(in);
0753: StringBuffer preamblesb = null;
0754: String line;
0755: String lineSeparator = null;
0756: while ((line = lin.readLine()) != null) {
0757: /*
0758: * Strip trailing whitespace. Can't use trim method
0759: * because it's too aggressive. Some bogus MIME
0760: * messages will include control characters in the
0761: * boundary string.
0762: */
0763: int i;
0764: for (i = line.length() - 1; i >= 0; i--) {
0765: char c = line.charAt(i);
0766: if (!(c == ' ' || c == '\t'))
0767: break;
0768: }
0769: line = line.substring(0, i + 1);
0770: if (boundary != null) {
0771: if (line.equals(boundary))
0772: break;
0773: } else {
0774: /*
0775: * Boundary hasn't been defined, does this line
0776: * look like a boundary? If so, assume it is
0777: * the boundary and save it.
0778: */
0779: if (line.startsWith("--")) {
0780: boundary = line;
0781: break;
0782: }
0783: }
0784:
0785: // save the preamble after skipping blank lines
0786: if (line.length() > 0) {
0787: // if we haven't figured out what the line seprator
0788: // is, do it now
0789: if (lineSeparator == null) {
0790: try {
0791: lineSeparator = System.getProperty(
0792: "line.separator", "\n");
0793: } catch (SecurityException ex) {
0794: lineSeparator = "\n";
0795: }
0796: }
0797: // accumulate the preamble
0798: if (preamblesb == null)
0799: preamblesb = new StringBuffer(line.length() + 2);
0800: preamblesb.append(line).append(lineSeparator);
0801: }
0802: }
0803: if (line == null)
0804: throw new MessagingException("Missing start boundary");
0805:
0806: if (preamblesb != null)
0807: preamble = preamblesb.toString();
0808:
0809: // save individual boundary bytes for comparison later
0810: byte[] bndbytes = ASCIIUtility.getBytes(boundary);
0811: int bl = bndbytes.length;
0812:
0813: /*
0814: * Compile Boyer-Moore parsing tables.
0815: */
0816:
0817: // initialize Bad Character Shift table
0818: int[] bcs = new int[256];
0819: for (int i = 0; i < bl; i++)
0820: bcs[bndbytes[i]] = i + 1;
0821:
0822: // initialize Good Suffix Shift table
0823: int[] gss = new int[bl];
0824: NEXT: for (int i = bl; i > 0; i--) {
0825: int j; // the beginning index of the suffix being considered
0826: for (j = bl - 1; j >= i; j--) {
0827: // Testing for good suffix
0828: if (bndbytes[j] == bndbytes[j - i]) {
0829: // bndbytes[j..len] is a good suffix
0830: gss[j - 1] = i;
0831: } else {
0832: // No match. The array has already been
0833: // filled up with correct values before.
0834: continue NEXT;
0835: }
0836: }
0837: while (j > 0)
0838: gss[--j] = i;
0839: }
0840: gss[bl - 1] = 1;
0841:
0842: /*
0843: * Read and process body parts until we see the
0844: * terminating boundary line (or EOF).
0845: */
0846: boolean done = false;
0847: getparts: while (!done) {
0848: InternetHeaders headers = null;
0849: if (sin != null) {
0850: start = sin.getPosition();
0851: // skip headers
0852: while ((line = lin.readLine()) != null
0853: && line.length() > 0)
0854: ;
0855: if (line == null) {
0856: if (!ignoreMissingEndBoundary)
0857: throw new MessagingException(
0858: "missing multipart end boundary");
0859: // assume there's just a missing end boundary
0860: complete = false;
0861: break getparts;
0862: }
0863: } else {
0864: // collect the headers for this body part
0865: headers = createInternetHeaders(in);
0866: }
0867:
0868: if (!in.markSupported())
0869: throw new MessagingException(
0870: "Stream doesn't support mark");
0871:
0872: ByteArrayOutputStream buf = null;
0873: // if we don't have a shared input stream, we copy the data
0874: if (sin == null)
0875: buf = new ByteArrayOutputStream();
0876: else
0877: end = sin.getPosition();
0878: int b;
0879:
0880: /*
0881: * These buffers contain the bytes we're checking
0882: * for a match. inbuf is the current buffer and
0883: * previnbuf is the previous buffer. We need the
0884: * previous buffer to check that we're preceeded
0885: * by an EOL.
0886: */
0887: // XXX - a smarter algorithm would use a sliding window
0888: // over a larger buffer
0889: byte[] inbuf = new byte[bl];
0890: byte[] previnbuf = new byte[bl];
0891: int inSize = 0; // number of valid bytes in inbuf
0892: int prevSize = 0; // number of valid bytes in previnbuf
0893: int eolLen;
0894: boolean first = true;
0895:
0896: /*
0897: * Read and save the content bytes in buf.
0898: */
0899: for (;;) {
0900: in.mark(bl + 4 + 1000); // bnd + "--\r\n" + lots of LWSP
0901: eolLen = 0;
0902: inSize = readFully(in, inbuf, 0, bl);
0903: if (inSize < bl) {
0904: // hit EOF
0905: if (!ignoreMissingEndBoundary)
0906: throw new MessagingException(
0907: "missing multipart end boundary");
0908: if (sin != null)
0909: end = sin.getPosition();
0910: complete = false;
0911: done = true;
0912: break;
0913: }
0914: // check whether inbuf contains a boundary string
0915: int i;
0916: for (i = bl - 1; i >= 0; i--) {
0917: if (inbuf[i] != bndbytes[i])
0918: break;
0919: }
0920: if (i < 0) { // matched all bytes
0921: eolLen = 0;
0922: if (!first) {
0923: // working backwards, find out if we were preceeded
0924: // by an EOL, and if so find its length
0925: b = previnbuf[prevSize - 1];
0926: if (b == '\r' || b == '\n') {
0927: eolLen = 1;
0928: if (b == '\n' && prevSize >= 2) {
0929: b = previnbuf[prevSize - 2];
0930: if (b == '\r')
0931: eolLen = 2;
0932: }
0933: }
0934: }
0935: if (first || eolLen > 0) { // yes, preceed by EOL
0936: if (sin != null) {
0937: // update "end", in case this really is
0938: // a valid boundary
0939: end = sin.getPosition() - bl - eolLen;
0940: }
0941: // matched the boundary, check for last boundary
0942: int b2 = in.read();
0943: if (b2 == '-') {
0944: if (in.read() == '-') {
0945: complete = true;
0946: done = true;
0947: break; // ignore trailing text
0948: }
0949: }
0950: // skip linear whitespace
0951: while (b2 == ' ' || b2 == '\t')
0952: b2 = in.read();
0953: // check for end of line
0954: if (b2 == '\n')
0955: break; // got it! break out of the loop
0956: if (b2 == '\r') {
0957: in.mark(1);
0958: if (in.read() != '\n')
0959: in.reset();
0960: break; // got it! break out of the loop
0961: }
0962: }
0963: i = 0;
0964: }
0965:
0966: /*
0967: * Get here if boundary didn't match,
0968: * wasn't preceeded by EOL, or wasn't
0969: * followed by whitespace or EOL.
0970: */
0971:
0972: // compute how many bytes we can skip
0973: int skip = Math.max(i + 1 - bcs[inbuf[i] & 0x7f],
0974: gss[i]);
0975: // want to keep at least two characters
0976: if (skip < 2) {
0977: // only skipping one byte, save one byte
0978: // from previous buffer as well
0979: // first, write out bytes we're done with
0980: if (sin == null && prevSize > 1)
0981: buf.write(previnbuf, 0, prevSize - 1);
0982: in.reset();
0983: skipFully(in, 1);
0984: if (prevSize >= 1) { // is there a byte to save?
0985: // yes, save one from previous and one from current
0986: previnbuf[0] = previnbuf[prevSize - 1];
0987: previnbuf[1] = inbuf[0];
0988: prevSize = 2;
0989: } else {
0990: // no previous bytes to save, can only save current
0991: previnbuf[0] = inbuf[0];
0992: prevSize = 1;
0993: }
0994: } else {
0995: // first, write out data from previous buffer before
0996: // we dump it
0997: if (prevSize > 0 && sin == null)
0998: buf.write(previnbuf, 0, prevSize);
0999: // all the bytes we're skipping are saved in previnbuf
1000: prevSize = skip;
1001: in.reset();
1002: skipFully(in, prevSize);
1003: // swap buffers
1004: byte[] tmp = inbuf;
1005: inbuf = previnbuf;
1006: previnbuf = tmp;
1007: }
1008: first = false;
1009: }
1010:
1011: /*
1012: * Create a MimeBody element to represent this body part.
1013: */
1014: MimeBodyPart part;
1015: if (sin != null) {
1016: part = createMimeBodyPart(sin.newStream(start, end));
1017: } else {
1018: // write out data from previous buffer, not including EOL
1019: if (prevSize - eolLen > 0)
1020: buf.write(previnbuf, 0, prevSize - eolLen);
1021: // if we didn't find a trailing boundary,
1022: // the current buffer has data we need too
1023: if (!complete && inSize > 0)
1024: buf.write(inbuf, 0, inSize);
1025: part = createMimeBodyPart(headers, buf
1026: .toByteArray());
1027: }
1028: super .addBodyPart(part);
1029: }
1030: } catch (IOException ioex) {
1031: throw new MessagingException("IO Error", ioex);
1032: } finally {
1033: try {
1034: in.close();
1035: } catch (IOException cex) {
1036: // ignore
1037: }
1038: }
1039:
1040: parsed = true;
1041: }
1042:
1043: /**
1044: * Read data from the input stream to fill the buffer starting
1045: * at the specified offset with the specified number of bytes.
1046: * If len is zero, return zero. If at EOF, return -1. Otherwise,
1047: * return the number of bytes read. Call the read method on the
1048: * input stream as many times as necessary to read len bytes.
1049: *
1050: * @param in InputStream to read from
1051: * @param buf buffer to read into
1052: * @param off offset in the buffer for first byte
1053: * @param len number of bytes to read
1054: * @return -1 on EOF, otherwise number of bytes read
1055: * @exception IOException on I/O errors
1056: */
1057: private static int readFully(InputStream in, byte[] buf, int off,
1058: int len) throws IOException {
1059: if (len == 0)
1060: return 0;
1061: int total = 0;
1062: while (len > 0) {
1063: int bsize = in.read(buf, off, len);
1064: if (bsize <= 0) // should never be zero
1065: break;
1066: off += bsize;
1067: total += bsize;
1068: len -= bsize;
1069: }
1070: return total > 0 ? total : -1;
1071: }
1072:
1073: /**
1074: * Skip the specified number of bytes, repeatedly calling
1075: * the skip method as necessary.
1076: */
1077: private void skipFully(InputStream in, long offset)
1078: throws IOException {
1079: while (offset > 0) {
1080: long cur = in.skip(offset);
1081: if (cur <= 0)
1082: throw new EOFException("can't skip");
1083: offset -= cur;
1084: }
1085: }
1086:
1087: /**
1088: * Create and return an InternetHeaders object that loads the
1089: * headers from the given InputStream. Subclasses can override
1090: * this method to return a subclass of InternetHeaders, if
1091: * necessary. This implementation simply constructs and returns
1092: * an InternetHeaders object.
1093: *
1094: * @param is the InputStream to read the headers from
1095: * @exception MessagingException
1096: * @since JavaMail 1.2
1097: */
1098: protected InternetHeaders createInternetHeaders(InputStream is)
1099: throws MessagingException {
1100: return new InternetHeaders(is);
1101: }
1102:
1103: /**
1104: * Create and return a MimeBodyPart object to represent a
1105: * body part parsed from the InputStream. Subclasses can override
1106: * this method to return a subclass of MimeBodyPart, if
1107: * necessary. This implementation simply constructs and returns
1108: * a MimeBodyPart object.
1109: *
1110: * @param headers the headers for the body part
1111: * @param content the content of the body part
1112: * @exception MessagingException
1113: * @since JavaMail 1.2
1114: */
1115: protected MimeBodyPart createMimeBodyPart(InternetHeaders headers,
1116: byte[] content) throws MessagingException {
1117: return new MimeBodyPart(headers, content);
1118: }
1119:
1120: /**
1121: * Create and return a MimeBodyPart object to represent a
1122: * body part parsed from the InputStream. Subclasses can override
1123: * this method to return a subclass of MimeBodyPart, if
1124: * necessary. This implementation simply constructs and returns
1125: * a MimeBodyPart object.
1126: *
1127: * @param is InputStream containing the body part
1128: * @exception MessagingException
1129: * @since JavaMail 1.2
1130: */
1131: protected MimeBodyPart createMimeBodyPart(InputStream is)
1132: throws MessagingException {
1133: return new MimeBodyPart(is);
1134: }
1135: }
|