/* * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, * released on or about June 15, 1998. * * The Initial Developer of the Original Code is Netscape Communications * Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): ______________________________________. */ /* * Copyright (c) 1997 and 1998 Netscape Communications Corporation * (http://home.netscape.com/misc/trademarks.html) */ package netscape.messaging.mime; import java.io.*; import java.lang.*; import java.util.*; import netscape.messaging.mime.MIMEParser; /** * The MIMEMultiPart class implements the MIME multi-part Content Type. *

The multi-part content type represents messages with multiple * attachments of potentially different media. This content type * describes a message that is made up of one or more sub-body parts. * The Multipart type has several subtypes that describe how the * sub-parts relate to each other. These include mixed, alternative, * digest, and parallel. * @author Prasad Yendluri */ public class MIMEMultiPart extends MIMEBodyPart implements Cloneable { //=========================================================================== // // Internal members not visible at the API // //=========================================================================== private static final int UNINITIALIZED = -1; private static final String[] m_stringDisposition = { "Attachment", "Inline"}; private static final String[] m_stringEncoding = { "base64", "quoted-printable", "binary", "7bit", "8bit" }; private static final String MULTIPART = "MultiPart"; //private int m_contentType; //private int m_contentTransferEncoding; protected int m_parsedPart; private int m_bpCount; // count of bodyparts private boolean m_needPreamble; protected boolean m_endPart; // Vector to hold the bodyparts. private Vector m_bodyParts; private String m_boundary; private String m_preamble; private int m_contentTransferEncoding; //=========================================================================== // // CONSTRUCTORS // //=========================================================================== /** * Constructs a MIMEMultiPart object. * Default constructor for the MIMEMultiPart */ public MIMEMultiPart () { m_parsedPart = 0; m_contentDisposition = UNINITIALIZED; m_contentTransferEncoding = UNINITIALIZED; m_needPreamble = false; m_bodyParts = new Vector(); m_boundary = null; m_UserObject = null; } /** * Constructs a MIMEMultiPart object with the specified text and filename. * Both textIS and filename cannot be null or an exception is thrown. * (See MIMEMessage() if you need to do this.) * @param textIS InputStream to text that becomes (first) text part of the multi-part. * @param filename Name of the file that becomes second part of the multi-part. * @param encoding Encoding for the file attachment. To select a default encoding, use -1. * @see #MIMEMessage * @see MIMEBodyPart#BASE64 * @see MIMEBodyPart#QP * @see MIMEBodyPart#BINARY * @see MIMEBodyPart#E7BIT * @see MIMEBodyPart#E8BIT * @exception MIMEException If filename is null or textIS null. * @exception FileNotFoundException If filename file does not exist * @exception SecurityException If filename file can not be accessed * @exception IOExcepton On IO Errors on filename or textIS. */ public MIMEMultiPart (InputStream textIS, String filename, int encoding) throws MIMEException, FileNotFoundException, SecurityException, IOException { MIMEBasicPart l_txtPart; MIMEBasicPart l_filePart; FileInputStream l_fis; fileMIMEType l_fmt; m_parsedPart = 0; m_contentDisposition = UNINITIALIZED; m_contentTransferEncoding = UNINITIALIZED; m_needPreamble = false; m_bodyParts = new Vector(); m_boundary = null; if (textIS == null) throw new MIMEException ("Invalid null InputStream"); if (filename == null) throw new MIMEException ("Invalid null filename:" + filename); if ((encoding != -1) && (encoding < MIMEBodyPart.BASE64 || encoding > MIMEBodyPart.E8BIT)) throw new MIMEException ("Invalid MIME encoding:" + encoding); this.setContentSubType ("mixed"); l_fis = new FileInputStream (filename); // The text-part! l_txtPart = new MIMEBasicPart (MIMEBasicPart.TEXT); l_txtPart.setContentSubType ("plain"); l_txtPart.setContentTypeParams ("charset=us-ascii"); l_txtPart.setContentEncoding(MIMEBodyPart.E7BIT); //l_txtPart.setContentDisposition(MIMEBodyPart.INLINE); l_txtPart.setBodyData (textIS); // The file-part l_fmt = MIMEHelper.getFileMIMEType (filename); if (l_fmt == null) throw new MIMEException ("Can't determine MIME info for file: " + filename); l_filePart = new MIMEBasicPart (l_fmt.content_type); l_filePart.setContentSubType (l_fmt.content_subtype); if (l_fmt.content_params != null) l_filePart.setContentTypeParams (l_fmt.content_params); else l_filePart.setContentTypeParams (new String ("name=" + l_fmt.file_shortname)); if (encoding == -1) l_filePart.setContentEncoding(l_fmt.mime_encoding); else l_filePart.setContentEncoding (encoding); if (l_fmt.content_type == MIMEBasicPart.TEXT); //l_filePart.setContentDisposition (MIMEBodyPart.INLINE); else { //l_filePart.setContentDisposition (MIMEBodyPart.ATTACHMENT); l_filePart.setContentDispParams (new String ("filename=" + l_fmt.file_shortname)); l_filePart.setContentDescription (l_fmt.file_shortname); } // set body-data of this part l_filePart.setBodyData (l_fis); // Add this part to multi-part this.addBodyPart (l_txtPart, false); this.addBodyPart (l_filePart, false); } //=========================================================================== // // Methods overridden from MIMEBodyPart // //=========================================================================== /** * Returns Content-Type of this MIME Part. Always returns the String "MultiPart". */ public String getContentType () { return (MULTIPART); } //=========================================================================== // // Methods Specific to this Class alone. // //=========================================================================== /** * Returns the count of BodyParts in this MultiPart or * Zero (0) if no parts exist. */ public int getBodyPartCount () { if (m_bodyParts != null) return (m_bodyParts.size()); else return (0); } /** * Returns an Object of corresponding MIME BodyPart type at the * specified index of this MultiPart. * @param index Index for the BodyPart to return. Index ranges from 0 to (count_of_parts -1). * @param clone Whether to return a reference to the internal BodyPart object or a cloned copy; * if true: cloned copy; if false: reference to the BodyPart object. * @exception MIMEException If invalid index. * @see MIMEBasicPart * @see MIMEMessagePart * @see MIMEMultiPart * @see MIMEMultiPart#getBodyPartCount */ public Object getBodyPart (int index, boolean clone) throws MIMEException { if (index >= m_bodyParts.size() || index < 0) throw new MIMEException ("getBodyPart(). Index out of range: " + index); Object obj = m_bodyParts.elementAt (index); if (obj instanceof MIMEBasicPart) { MIMEBasicPart part = (MIMEBasicPart) obj; try { if (clone) return (part.clone()); else return (part); } catch (CloneNotSupportedException e) { throw new MIMEException (e.getMessage()); } } else if (obj instanceof MIMEMessagePart) { MIMEMessagePart part = (MIMEMessagePart) obj; try { if (clone) return (part.clone()); else return (part); } catch (CloneNotSupportedException e) { throw new MIMEException (e.getMessage()); } } else if (obj instanceof MIMEMultiPart) { MIMEMultiPart part = (MIMEMultiPart) obj; try { if (clone) return (part.clone()); else return (part); } catch (CloneNotSupportedException e) { throw new MIMEException (e.getMessage()); } } else throw new MIMEException ("Invalid bodyPart!"); } /** * The following is an internal routine. Not to be exposed at the API. * @exception MIMEException If the multipart is malformed. * @exception IOException If IO error occurs. */ protected void putByteStream (OutputStream os, boolean needPreamble) throws IOException, MIMEException { if (needPreamble == true) m_needPreamble = true; else m_needPreamble = false; // In case we did not reset last pass due exception putByteStream (os); m_needPreamble = false; // reset after done. } /** * Outputs a byte stream for this Message in MIME format. * Applies transfer encoding all bodyParts as applicable. * @param os OutputStream to be written to. * @exception IOException If an IO error occurs. * @exception MIMEException If detects an error during encoding. */ public void putByteStream (OutputStream os) throws IOException, MIMEException { int l_bpCount = m_bodyParts.size(); if ((m_bodyParts == null) || (l_bpCount < 1)) throw new MIMEException ("MIMEMultiPart.putByteStream(). No bodyParts!"); // Headers first StringBuffer l_hdrbuf = new StringBuffer (HDRBUFSZ); byte[] l_bytebuf; String l_boundary; boolean l_fQBounds=false; if (m_boundary != null && m_boundary.length() > 0) { l_boundary = m_boundary; l_fQBounds = true; } else l_boundary = generateBoundary(); // content-type l_hdrbuf.append ("Content-Type: multipart/"); if (m_contentSubType != null) l_hdrbuf.append (m_contentSubType); else throw new MIMEException ("putByteStream: No content-subtype." ); if (m_contentTypeParams != null && !m_contentTypeParams.startsWith ("boundary")) { l_hdrbuf.append ("; "); l_hdrbuf.append (m_contentTypeParams); l_hdrbuf.append ("; boundary="); if (l_fQBounds) l_hdrbuf.append ('"'); l_hdrbuf.append (l_boundary); if (l_fQBounds) l_hdrbuf.append ('"'); l_hdrbuf.append ("\r\n"); } else { l_hdrbuf.append ("; boundary="); if (l_fQBounds) l_hdrbuf.append ('"'); l_hdrbuf.append (l_boundary); if (l_fQBounds) l_hdrbuf.append ('"'); l_hdrbuf.append ("\r\n"); } // write the header to os try { //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); os.write (l_bytebuf); } catch (Exception e) { throw new MIMEException ("Content-Type: " + e.getMessage()); } // contentID if (m_contentID != null) { l_hdrbuf.setLength (0); l_hdrbuf.append ("Content-ID: " + m_contentID); l_hdrbuf.append ("\r\n"); try { // l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); os.write (l_bytebuf); } catch (Exception e) { throw new MIMEException ("Content-ID: " + e.getMessage()); } } // contentDisposition if (m_contentDisposition != UNINITIALIZED) { l_hdrbuf.setLength (0); l_hdrbuf.append ("Content-Disposition: " + m_stringDisposition [m_contentDisposition]); if (m_contentDispParams != null) { l_hdrbuf.append ("; "); l_hdrbuf.append (m_contentDispParams); } l_hdrbuf.append ("\r\n"); try { //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); os.write (l_bytebuf); } catch (Exception e) { throw new MIMEException ("Content-Disposition: " + e.getMessage()); } } // contentDescription if (m_contentDescription != null) { l_hdrbuf.setLength (0); l_hdrbuf.append ("Content-Description: " + m_contentDescription); l_hdrbuf.append ("\r\n"); try { //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); os.write (l_bytebuf); } catch (Exception e) { throw new MIMEException ("Content-Description: " + e.getMessage()); } } // This is the top-level multi-part. Add content-encoding if (m_needPreamble == true && m_contentTransferEncoding != UNINITIALIZED) { boolean fwriteit = false; switch (m_contentTransferEncoding) { case BINARY: case E7BIT: case E8BIT: l_hdrbuf.setLength (0); l_hdrbuf.append ("Content-Transfer-Encoding: " + m_stringEncoding [m_contentTransferEncoding]); l_hdrbuf.append ("\r\n"); fwriteit = true; break; } if (fwriteit) try { l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); os.write (l_bytebuf); } catch (Exception e) { throw new MIMEException ("Content-Encodding: " + e.getMessage()); } } // blank-line after headers //l_bytebuf = new String("\r\n").getBytes("iso-8859-1"); os.write (CRLF); if (m_preamble != null) { l_bytebuf = MIMEHelper.unicodeToASCII (m_preamble); os.write (l_bytebuf); } // Write the line "This is a multi-part message in MIME format". // This needs to be done only if it is the body of the message. Not when it is // embedded part of another part (like another multi-part or a Messagepart). // This is accomplished by passing a flag to putByteStream() by Message class. else if (m_needPreamble == true && m_parsedPart==0) { String l_preamble = new String ("\r\nThis is a multi-part message in MIME format\r\n"); // l_bytebuf = l_preamble.getBytes("iso-8859-1"); l_bytebuf = MIMEHelper.unicodeToASCII (l_preamble); os.write (l_bytebuf); m_needPreamble = false; } String l_trailBoundary = "\r\n" + "--" + l_boundary + "--" + "\r\n"; //l_boundary = "\r\n\r\n" + "--" + l_boundary + "\r\n"; l_boundary = "\r\n" + "--" + l_boundary + "\r\n"; //l_boundary = "--" + l_boundary + "\r\n"; for (int i = 0; i < l_bpCount; i++) { Object obj = m_bodyParts.elementAt (i); if (obj instanceof MIMEBasicPart) { //l_bytebuf = l_boundary.getBytes("iso-8859-1"); l_bytebuf = MIMEHelper.unicodeToASCII (l_boundary); os.write (l_bytebuf); MIMEBasicPart part = (MIMEBasicPart) obj; part.putByteStream (os); } else if (obj instanceof MIMEMessagePart) { //l_bytebuf = l_boundary.getBytes("iso-8859-1"); l_bytebuf = MIMEHelper.unicodeToASCII (l_boundary); os.write (l_bytebuf); MIMEMessagePart part = (MIMEMessagePart) obj; part.putByteStream (os); } else if (obj instanceof MIMEMultiPart) { //l_bytebuf = l_boundary.getBytes("iso-8859-1"); l_bytebuf = MIMEHelper.unicodeToASCII (l_boundary); os.write (l_bytebuf); MIMEMultiPart part = (MIMEMultiPart) obj; part.putByteStream (os); } else throw new MIMEException ("putByteStream(). Invalid bodyPart!"); } // end for l_bytebuf = MIMEHelper.unicodeToASCII (l_trailBoundary); os.write (l_bytebuf); } /** * Deletes bodyPart at the requested index from this Multipart. * Adjusts indices of any parts after the deleted part upwards * as needed. * @param index of the bodyPart to remove. * @exception MIMEException if invalid index. */ public void deleteBodyPart (int index) throws MIMEException { if (index > m_bodyParts.size()) throw new MIMEException ("deleteBodyPart(). Index out of range: " + index); m_bodyParts.removeElementAt (index); } /** * Adds the specified filename file as a (MIMEBasicPart) BodyPart to this MultiPart. * @param filename Name of file to add as bodyPart. * @param encoding Preferred MIME encoding for this part. To select a default, use -1. * @return The index at which this bodyPart is added. * @see MIMEBodyPart#BASE64 * @see MIMEBodyPart#QP * @see MIMEBodyPart#BINARY * @see MIMEBodyPart#E7BIT * @see MIMEBodyPart#E8BIT * @exception MIMEException If filename file is inaccessible or I/O errors. * @exception FileNotFoundException If filename file does not exist * @exception SecurityException If filename file cannot be accessed * @exception IOExcepton On IO Errors on filename */ public int addBodyPart (String filename, int encoding) throws MIMEException, FileNotFoundException, SecurityException, IOException { MIMEBasicPart l_filePart; FileInputStream l_fis; fileMIMEType l_fmt; if (filename == null) throw new MIMEException ("Invalid null filename:" + filename); if ((encoding != -1) && (encoding < MIMEBodyPart.BASE64 || encoding > MIMEBodyPart.E8BIT)) throw new MIMEException ("Invalid MIME encoding:" + encoding); l_fis = new FileInputStream (filename); l_fmt = MIMEHelper.getFileMIMEType (filename); if (l_fmt == null) throw new MIMEException ("Can't determine MIME info for file: " + filename); l_filePart = new MIMEBasicPart (l_fmt.content_type); l_filePart.setContentSubType (l_fmt.content_subtype); if (l_fmt.content_params != null) l_filePart.setContentTypeParams (l_fmt.content_params); else l_filePart.setContentTypeParams (new String ("name=" + filename)); if (encoding == -1) l_filePart.setContentEncoding (l_fmt.mime_encoding); else l_filePart.setContentEncoding (encoding); if (l_fmt.content_type == MIMEBasicPart.TEXT) l_filePart.setContentDisposition (MIMEBodyPart.INLINE); else { l_filePart.setContentDisposition (MIMEBodyPart.ATTACHMENT); l_filePart.setContentDispParams (new String ("filename=" + filename)); l_filePart.setContentDescription (filename); } // set body-data of this part l_filePart.setBodyData (l_fis); // Add this part to multi-part return addBodyPart (l_filePart, false); } /** * Adds a MIMEBodyPart to this MultiPart. This part must already be constructed * and fully set-up. As MIMEBodyPart is an abstract class, the object passed in the * part parameter must be one of three concrete classes: MIMEBasicPart, MIMEMultiPart, or * MIMEMessagePart. If not, a MIMEException is thrown. * @param part bodyPart to add. * @param clone Whether to store a reference to the passed object instead of cloning a copy; * if true: clones a copy; if false: stores reference to the passed part object. * @return The index at which to add this bodyPart. * @exception MIMEException If part is malformed or is not an object of one of three * classes: MIMEBasicPart, MIMEMultiPart, or MIMEMessagePart. * @see MIMEBasicPart * @see MIMEMultiPart * @see MIMEMessagePart */ public int addBodyPart (MIMEBodyPart part, boolean clone) throws MIMEException { if (part == null) throw new MIMEException ("addBodyPart(): null part passed!"); if (part instanceof MIMEBasicPart) return addBodyPart ((MIMEBasicPart) part, clone); else if (part instanceof MIMEMessagePart) return addBodyPart ((MIMEMessagePart) part, clone); else if (part instanceof MIMEMultiPart) return addBodyPart ((MIMEMultiPart) part, clone); else throw new MIMEException ("addBodyPart(): Invalid part "); } /** * Adds a MIMEBasicPart BodyPart to this MultiPart. This part should have been * constructed and fully set-up * @param part bodyPart to add. * @return The index at which this bodyPart is added. * @exception MIMEException If it is a malformed bodyPart (or) can not be added. * @see MIMEBasicPart */ protected int addBodyPart (MIMEBasicPart part ) throws MIMEException { return addBodyPart( part, true ); } /** * Adds a MIMEBasicPart BodyPart to this MultiPart. This part should have been * constructed and fully set-up. * @param part bodyPart to add. * @return The index at which this bodyPart is added. * @param clone if false stores reference to passed object instead of cloning a copy. * @exception MIMEException If it is a malformed bodyPart (or) can not be added. * @see MIMEBasicPart */ protected int addBodyPart (MIMEBasicPart part, boolean clone) throws MIMEException { MIMEBasicPart l_part; if (part == null) throw new MIMEException ("addBodyPart(): null part passed!"); if (clone) { try { l_part = (MIMEBasicPart) part.clone(); m_bodyParts.addElement (l_part); } catch (CloneNotSupportedException e) { throw new MIMEException (e.getMessage()); } } else m_bodyParts.addElement (part); return (m_bodyParts.size() -1); } /** * Adds a MIMEBasicPart BodyPart to this MultiPart. This part should have been * constructed and fully set-up * @param part bodyPart to add. * @return The index at which this bodyPart is added. * @exception MIMEException If it is a malformed bodyPart (or) can not be added. * @see MIMEBasicPart */ protected int addBodyPart (MIMEMultiPart part) throws MIMEException { return addBodyPart( part, true ); } /** * Adds a MIMEMultiPart BodyPart to this MultiPart. This part should have been * constructed and fully set-up. * @param part bodyPart to add. * @param clone if false stores reference to passed object instead of cloning a copy. * @return The index at which this bodyPart is added. * @exception MIMEException If it is a malformed bodyPart (or) can not be added. * clone made optional */ protected int addBodyPart (MIMEMultiPart part, boolean clone) throws MIMEException { MIMEMultiPart l_part; if (part == null) throw new MIMEException ("addBodyPart(): null part passed!"); if (clone) { try { l_part = (MIMEMultiPart) part.clone(); m_bodyParts.addElement (l_part ); } catch (CloneNotSupportedException e) { throw new MIMEException (e.getMessage()); } } else m_bodyParts.addElement (part); return (m_bodyParts.size() -1); } /** * Adds a MIMEBasicPart BodyPart to this MultiPart. This part should have been * constructed and fully set-up. * @param part bodyPart to add. * @return The index at which this bodyPart is added. * @exception MIMEException If it is a malformed bodyPart (or) can not be added. * @see MIMEBasicPart */ protected int addBodyPart (MIMEMessagePart part) throws MIMEException { return addBodyPart( part, true ); } /** * Adds a MIMEMessagePart BodyPart to this MultiPart. This part should have been * constructed and fully set-up. * @param part bodyPart to add. * @param clone if false stores reference to passed object instead of cloning a copy. * @return The index at which this bodyPart is added. * @exception MIMEException If it is a malformed bodyPart (or) can not be added. */ protected int addBodyPart (MIMEMessagePart part, boolean clone) throws MIMEException { MIMEMessagePart l_part; if (part == null) throw new MIMEException ("addBodyPart(): null part passed!"); if (clone) { try { l_part = (MIMEMessagePart) part.clone(); m_bodyParts.addElement (l_part); } catch (CloneNotSupportedException e) { throw new MIMEException (e.getMessage()); } } else m_bodyParts.addElement (part); return (m_bodyParts.size() -1); } protected void setContentEncoding (int encoding) //throws MIMEException { switch (encoding) { case BINARY: case E7BIT: case E8BIT: m_contentTransferEncoding = encoding; break; default: //throw new MIMEException ("Invalid Content Transfer Encoding : " + encoding); } } /** * Returns the preamble for this multi-part if present. * Returns null if preamble does not exist for this part. * @return Preamble for the multi-part if present, null otherwise. */ public String getPreamble () { return m_preamble; } /** * Sets the preamble for this multi-part. * @param preamble Preamble string. */ public void setPreamble (String preamble) { m_preamble = preamble; } protected void addPreamble (byte[] preamble, int len ) { if (m_preamble == null) m_preamble = new String (preamble, 0, len); else { m_preamble = new String (m_preamble + new String (preamble, 0, len)); } } protected void addPreamble (byte[] preamble) { if (m_preamble == null) m_preamble = new String (preamble); else { m_preamble = new String (m_preamble + new String (preamble)); } } /** * Generates and returns a boundary string that can be used in multi-parts etc. * @return The boundary string. */ private String generateBoundary () { Random l_rand = new Random (System.currentTimeMillis()); long l_numboundary = l_rand.nextLong(); String l_boundary = new String ("-----" + Long.toHexString(l_numboundary)); return (l_boundary); } /** * Clones an instance of this MIMEMultiPart object. * @exception CloneNotSupportedException If thrown by constituent components. */ public Object clone () throws CloneNotSupportedException { MIMEMultiPart l_theClone = (MIMEMultiPart) super.clone(); // Take care of all other "reference"s. if (m_bodyParts != null) l_theClone.m_bodyParts = (Vector) m_bodyParts.clone(); if (m_contentSubType != null) l_theClone.m_contentSubType = m_contentSubType; if (m_contentTypeParams != null) l_theClone.m_contentTypeParams = m_contentTypeParams; if (m_contentID != null) l_theClone.m_contentID = m_contentID; if (m_contentDispParams != null) l_theClone.m_contentDispParams = m_contentDispParams; if (m_contentDescription != null) l_theClone.m_contentDescription = m_contentDescription; m_needPreamble = false; l_theClone.m_UserObject = m_UserObject; return (l_theClone); } // end clone () // Should not be exposed at the API level. Internal only routines. protected void setBoundary( String boundary ) { if ( boundary != null ) m_boundary = boundary; } protected String getBoundary() { return m_boundary; } } // end class MIMEMultiPart