/*
 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @(#)MimeMultipart.java     1.31 03/01/29
 */


package com.sun.xml.internal.messaging.saaj.packaging.mime.internet;

import com.sun.xml.internal.messaging.saaj.packaging.mime.MessagingException;
import com.sun.xml.internal.messaging.saaj.packaging.mime.util.ASCIIUtility;
import com.sun.xml.internal.messaging.saaj.packaging.mime.util.OutputUtil;
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;

import javax.activation.DataSource;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.BitSet;

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 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>. */
//TODO: cleanup the SharedInputStream handling public class BMMimeMultipart extends MimeMultipart { /* * When true it indicates parsing hasnt been done at all */ private boolean begining = true; int[] bcs = new int[256]; int[] gss = null; private static final int BUFFER_SIZE = 4096; private byte[] buffer = new byte[BUFFER_SIZE]; private byte[] prevBuffer = new byte[BUFFER_SIZE]; private BitSet lastPartFound = new BitSet(1); // cached inputstream which is possibly partially consumed private InputStream in = null; private String boundary = null; // current stream position, set to -1 on EOF int b = 0; // property to indicate if lazyAttachments is ON private boolean lazyAttachments = false;
Default constructor. An empty MimeMultipart object is created. Its content type is set to "multipart/mixed". A unique boundary string is generated and this string is setup as the "boundary" parameter for the contentType field.

MimeBodyParts may be added later.

/** * Default constructor. An empty MimeMultipart object * is created. Its content type is set to "multipart/mixed". * A unique boundary string is generated and this string is * setup as the "boundary" parameter for the * <code>contentType</code> field. <p> * * MimeBodyParts may be added later. */
public BMMimeMultipart() { super(); //this("mixed"); }
Construct a MimeMultipart object of the given subtype. A unique boundary string is generated and this string is setup as the "boundary" parameter for the contentType field.

MimeBodyParts may be added later.

Params:
  • subtype – subtype.
/** * Construct a MimeMultipart object of the given subtype. * A unique boundary string is generated and this string is * setup as the "boundary" parameter for the * <code>contentType</code> field. <p> * * MimeBodyParts may be added later. * * @param subtype subtype. */
public BMMimeMultipart(String subtype) { super(subtype); /* * Compute a boundary string. String boundary = UniqueValue.getUniqueBoundaryValue(); ContentType cType = new ContentType("multipart", subtype, null); contentType.setParameter("boundary", boundary); */ }
Constructs a MimeMultipart object and its bodyparts from the given DataSource.

This constructor handles as a special case the situation where the given DataSource is a MultipartDataSource object. In this case, this method just invokes the superclass (i.e., Multipart) constructor that takes a MultipartDataSource object.

Otherwise, the DataSource is assumed to provide a MIME multipart byte stream. The parsed flag is set to false. When the data for the body parts are needed, the parser extracts the "boundary" parameter from the content type of this DataSource, skips the 'preamble' and reads bytes till the terminating boundary and creates MimeBodyParts for each part of the stream.

Params:
  • ct – content type.
  • ds – DataSource, can be a MultipartDataSource.
Throws:
/** * 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 ct content type. * @param ds DataSource, can be a MultipartDataSource. * @throws MessagingException in case of error. */
public BMMimeMultipart(DataSource ds, ContentType ct) throws MessagingException { super(ds, ct); boundary = ct.getParameter("boundary"); /* 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; if (ct==null) contentType = new ContentType(ds.getContentType()); else contentType = ct; */ } public InputStream initStream() throws MessagingException { if (in == null) { 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"); } if (!in.markSupported()) { throw new MessagingException( "InputStream does not support Marking"); } } return in; }
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.
Since:JavaMail 1.2
/** * Parse the InputStream from our DataSource, constructing the * appropriate MimeBodyParts. The <code>parsed</code> flag is * set to true, and if true on entry nothing is done. This * method is called by all other methods that need data for * the body parts, to make sure the data has been parsed. * * @since JavaMail 1.2 */
@Override protected void parse() throws MessagingException { if (parsed) return; initStream(); SharedInputStream sin = null; if (in instanceof SharedInputStream) { sin = (SharedInputStream) in; } String bnd = "--" + boundary; byte[] bndbytes = ASCIIUtility.getBytes(bnd); try { parse(in, bndbytes, sin); } catch (IOException ioex) { throw new MessagingException("IO Error", ioex); } catch (Exception ex) { throw new MessagingException("Error", ex); } parsed = true; } public boolean lastBodyPartFound() { return lastPartFound.get(0); } public MimeBodyPart getNextPart( InputStream stream, byte[] pattern, SharedInputStream sin) throws Exception { if (!stream.markSupported()) { throw new Exception("InputStream does not support Marking"); } if (begining) { compile(pattern); if (!skipPreamble(stream, pattern, sin)) { throw new Exception( "Missing Start Boundary, or boundary does not start on a new line"); } begining = false; } if (lastBodyPartFound()) { throw new Exception("No parts found in Multipart InputStream"); } if (sin != null) { long start = sin.getPosition(); b = readHeaders(stream); if (b == -1) { throw new Exception( "End of Stream encountered while reading part headers"); } long[] v = new long[1]; v[0] = -1; // just to ensure the code later sets it correctly b = readBody(stream, pattern, v, null, sin); // looks like this check has to be disabled // it is allowed to have Mime Package without closing boundary if (!ignoreMissingEndBoundary) { if ((b == -1) && !lastBodyPartFound()) { throw new MessagingException("Missing End Boundary for Mime Package : EOF while skipping headers"); } } long end = v[0]; MimeBodyPart mbp = createMimeBodyPart(sin.newStream(start, end)); addBodyPart(mbp); return mbp; } else { InternetHeaders headers = createInternetHeaders(stream); ByteOutputStream baos = new ByteOutputStream(); b = readBody(stream, pattern, null, baos, null); // looks like this check has to be disabled // in the old impl it is allowed to have Mime Package // without closing boundary if (!ignoreMissingEndBoundary) { if ((b == -1) && !lastBodyPartFound()) { throw new MessagingException("Missing End Boundary for Mime Package : EOF while skipping headers"); } } MimeBodyPart mbp = createMimeBodyPart( headers, baos.getBytes(), baos.getCount()); addBodyPart(mbp); return mbp; } } public boolean parse( InputStream stream, byte[] pattern, SharedInputStream sin) throws Exception { while (!lastPartFound.get(0) && (b != -1)) { getNextPart(stream, pattern, sin); } return true; } private int readHeaders(InputStream is) throws Exception { // if the headers are to end properly then there has to be CRLF // actually we just need to mark the start and end positions int b = is.read(); while (b != -1) { // when it is a shared input stream no need to copy if (b == '\r') { b = is.read(); if (b == '\n') { b = is.read(); if (b == '\r') { b = is.read(); if (b == '\n') { return b; } else { continue; } } else { continue; } } else { continue; } } b = is.read(); } if (b == -1) { throw new Exception( "End of inputstream while reading Mime-Part Headers"); } return b; } private int readBody( InputStream is, byte[] pattern, long[] posVector, ByteOutputStream baos, SharedInputStream sin) throws Exception { if (!find(is, pattern, posVector, baos, sin)) { throw new Exception( "Missing boundary delimitier while reading Body Part"); } return b; } private boolean skipPreamble( InputStream is, byte[] pattern, SharedInputStream sin) throws Exception { if (!find(is, pattern, sin)) { return false; } if (lastPartFound.get(0)) { throw new Exception( "Found closing boundary delimiter while trying to skip preamble"); } return true; } public int readNext(InputStream is, byte[] buff, int patternLength, BitSet eof, long[] posVector, SharedInputStream sin) throws Exception { int bufferLength = is.read(buffer, 0, patternLength); if (bufferLength == -1) { eof.flip(0); } else if (bufferLength < patternLength) { //repeatedly read patternLength - bufferLength int temp = 0; long pos = 0; int i = bufferLength; for (; i < patternLength; i++) { if (sin != null) { pos = sin.getPosition(); } temp = is.read(); if (temp == -1) { eof.flip(0); if (sin != null) { posVector[0] = pos; } break; } buffer[i] = (byte) temp; } bufferLength = i; } return bufferLength; } public boolean find(InputStream is, byte[] pattern, SharedInputStream sin) throws Exception { int i; int l = pattern.length; int lx = l - 1; BitSet eof = new BitSet(1); long[] posVector = new long[1]; while (true) { is.mark(l); readNext(is, buffer, l, eof, posVector, sin); if (eof.get(0)) { // End of stream return false; } /* if (bufferLength < l) { //is.reset(); return false; }*/ for (i = lx; i >= 0; i--) { if (buffer[i] != pattern[i]) { break; } } if (i < 0) { // found the boundary, skip *LWSP-char and CRLF if (!skipLWSPAndCRLF(is)) { throw new Exception("Boundary does not terminate with CRLF"); } return true; } int s = Math.max(i + 1 - bcs[buffer[i] & 0x7f], gss[i]); is.reset(); is.skip(s); } } public boolean find( InputStream is, byte[] pattern, long[] posVector, ByteOutputStream out, SharedInputStream sin) throws Exception { int i; int l = pattern.length; int lx = l - 1; int bufferLength = 0; int s = 0; long endPos = -1; byte[] tmp = null; boolean first = true; BitSet eof = new BitSet(1); while (true) { is.mark(l); if (!first) { tmp = prevBuffer; prevBuffer = buffer; buffer = tmp; } if (sin != null) { endPos = sin.getPosition(); } bufferLength = readNext(is, buffer, l, eof, posVector, sin); if (bufferLength == -1) { // End of stream // looks like it is allowed to not have a closing boundary //return false; //if (sin != null) { // posVector[0] = endPos; //} b = -1; if ((s == l) && (sin == null)) { out.write(prevBuffer, 0, s); } return true; } if (bufferLength < l) { if (sin != null) { //endPos = sin.getPosition(); //posVector[0] = endPos; } else { // looks like it is allowed to not have a closing boundary // in the old implementation out.write(buffer, 0, bufferLength); } // looks like it is allowed to not have a closing boundary // in the old implementation //return false; b = -1; return true; } for (i = lx; i >= 0; i--) { if (buffer[i] != pattern[i]) { break; } } if (i < 0) { if (s > 0) { //looks like the earlier impl allowed just an LF // so if s == 1 : it must be an LF // if s == 2 : it must be a CR LF if (s <= 2) { //it could be "some-char\n" so write some-char if (s == 2) { if (prevBuffer[1] == '\n') { if (prevBuffer[0] != '\r' && prevBuffer[0] != '\n') { out.write(prevBuffer, 0, 1); } if (sin != null) { posVector[0] = endPos; } } else { throw new Exception( "Boundary characters encountered in part Body " + "without a preceeding CRLF"); } } else if (s == 1) { if (prevBuffer[0] != '\n') { throw new Exception( "Boundary characters encountered in part Body " + "without a preceeding CRLF"); } else { if (sin != null) { posVector[0] = endPos; } } } } else if (s > 2) { if ((prevBuffer[s - 2] == '\r') && (prevBuffer[s - 1] == '\n')) { if (sin != null) { posVector[0] = endPos - 2; } else { out.write(prevBuffer, 0, s - 2); } } else if (prevBuffer[s - 1] == '\n') { //old impl allowed just a \n if (sin != null) { posVector[0] = endPos - 1; } else { out.write(prevBuffer, 0, s - 1); } } else { throw new Exception( "Boundary characters encountered in part Body " + "without a preceeding CRLF"); } } } // found the boundary, skip *LWSP-char and CRLF if (!skipLWSPAndCRLF(is)) { //throw new Exception( // "Boundary does not terminate with CRLF"); } return true; } if ((s > 0) && (sin == null)) { if (prevBuffer[s - 1] == (byte) 13) { // if buffer[0] == (byte)10 if (buffer[0] == (byte) 10) { int j; for (j = lx - 1; j > 0; j--) { if (buffer[j + 1] != pattern[j]) { break; } } if (j == 0) { // matched the pattern excluding the last char of the pattern // so dont write the CR into stream out.write(prevBuffer, 0, s - 1); } else { out.write(prevBuffer, 0, s); } } else { out.write(prevBuffer, 0, s); } } else { out.write(prevBuffer, 0, s); } } s = Math.max(i + 1 - bcs[buffer[i] & 0x7f], gss[i]); is.reset(); is.skip(s); if (first) { first = false; } } } private boolean skipLWSPAndCRLF(InputStream is) throws Exception { b = is.read(); //looks like old impl allowed just a \n as well if (b == '\n') { return true; } if (b == '\r') { b = is.read(); //skip any multiple '\r' "\r\n" --> "\r\r\n" on Win2k if (b == '\r') { b = is.read(); } if (b == '\n') { return true; } else { throw new Exception( "transport padding after a Mime Boundary should end in a CRLF, found CR only"); } } if (b == '-') { b = is.read(); if (b != '-') { throw new Exception( "Unexpected singular '-' character after Mime Boundary"); } else { //System.out.println("Last Part Found"); lastPartFound.flip(0); // read the next char b = is.read(); } } while ((b != -1) && ((b == ' ') || (b == '\t'))) { b = is.read(); if (b == '\n') { return true; } if (b == '\r') { b = is.read(); //skip any multiple '\r': "\r\n" --> "\r\r\n" on Win2k if (b == '\r') { b = is.read(); } if (b == '\n') { return true; } } } if (b == -1) { // the last boundary need not have CRLF if (!lastPartFound.get(0)) { throw new Exception( "End of Multipart Stream before encountering closing boundary delimiter"); } return true; } return false; } private void compile(byte[] pattern) { int l = pattern.length; int i; int j; // Copied from J2SE 1.4 regex code // java.util.regex.Pattern.java // Initialise Bad Character Shift table for (i = 0; i < l; i++) { bcs[pattern[i]] = i + 1; } // Initialise Good Suffix Shift table gss = new int[l]; NEXT: for (i = l; i > 0; i--) { // j is the beginning index of suffix being considered for (j = l - 1; j >= i; j--) { // Testing for good suffix if (pattern[j] == pattern[j - i]) { // pattern[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[l - 1] = 1; }
Iterates through all the parts and outputs each Mime part separated by a boundary.
/** * Iterates through all the parts and outputs each Mime part * separated by a boundary. */
@Override public void writeTo(OutputStream os) throws IOException, MessagingException { // inputStream was not null if (in != null) { contentType.setParameter("boundary", this.boundary); } String bnd = "--" + contentType.getParameter("boundary"); for (int i = 0; i < parts.size(); i++) { OutputUtil.writeln(bnd, os); // put out boundary parts.get(i).writeTo(os); OutputUtil.writeln(os); // put out empty line } if (in != null) { OutputUtil.writeln(bnd, os); // put out boundary if ((os instanceof ByteOutputStream) && lazyAttachments) { ((ByteOutputStream) os).write(in); } else { ByteOutputStream baos = null; try { baos = new ByteOutputStream(in.available()); baos.write(in); baos.writeTo(os); // reset the inputstream so that we can support a // getAttachment later in = baos.newInputStream(); } finally { if (baos != null) baos.close(); } } // this will endup writing the end boundary } else { // put out last boundary OutputUtil.writeAsAscii(bnd, os); OutputUtil.writeAsAscii("--", os); } } public void setInputStream(InputStream is) { this.in = is; } public InputStream getInputStream() { return this.in; } public void setBoundary(String bnd) { this.boundary = bnd; if (this.contentType != null) { this.contentType.setParameter("boundary", bnd); } } public String getBoundary() { return this.boundary; } public boolean isEndOfStream() { return (b == -1); } public void setLazyAttachments(boolean flag) { lazyAttachments = flag; } }