| /* |
| * 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. <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. |
| */ |
| 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; |
| |
| /** |
| * 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 |
| */ |
| protected String preamble = null; |
| |
| /** |
| * 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 {@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 {@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 {@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 |
| * <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. |
| * 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. |
| * |
| * @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. |
| * |
| * @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. <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 |
| */ |
| 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". <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. |
| * |
| * @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. |
| * |
| * @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. |
| * |
| * @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. |
| * |
| * @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. |
| * |
| * @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. |
| * |
| * @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 <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.) |
| * |
| * @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. |
| * |
| * @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. |
| * |
| * @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 <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. |
| */ |
| @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 <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 ('-')? |
| */ |
| 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. |
| * |
| * @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. |
| */ |
| 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. |
| * |
| * @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. |
| * |
| * @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. |
| * |
| * @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 |
| } |
| } |
| } |
| } |