/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2008 Sun Microsystems, Inc. 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://glassfish.dev.java.net/public/CDDL+GPL.html * or glassfish/bootstrap/legal/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 glassfish/bootstrap/legal/LICENSE.txt. * Sun designates this particular file as subject to the "Classpath" exception * as provided by Sun in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the License * Header, with the fields enclosed by brackets [] replaced by your own * identifying information: "Portions Copyrighted [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 java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.Enumeration; import java.util.Vector; import javax.activation.DataHandler; import javax.activation.FileDataSource; import javax.mail.BodyPart; import javax.mail.FolderClosedException; import javax.mail.IllegalWriteException; import javax.mail.Message; import javax.mail.MessageRemovedException; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.Part; import com.sun.mail.imap.IMAPInputStream; import com.sun.mail.util.ASCIIUtility; import com.sun.mail.util.FolderClosedIOException; import com.sun.mail.util.LineOutputStream; import com.sun.mail.util.MessageRemovedIOException; import com.sun.mail.util.PropUtil; /** * This class represents a MIME body part. It implements the * <code>BodyPart</code> abstract class and the <code>MimePart</code> interface. * MimeBodyParts are contained in <code>MimeMultipart</code> objects. * <p> * MimeBodyPart uses the <code>InternetHeaders</code> class to parse and store * the headers of that body part. * <p> * <hr> * <strong>A note on RFC 822 and MIME headers</strong> * <p> * RFC 822 header fields <strong>must</strong> contain only US-ASCII characters. * MIME allows non ASCII characters to be present in certain portions of certain * headers, by encoding those characters. RFC 2047 specifies the rules for doing * this. The MimeUtility class provided in this package can be used to to * achieve this. Callers of the <code>setHeader</code>, <code>addHeader</code>, * and <code>addHeaderLine</code> methods are responsible for enforcing the MIME * requirements for the specified headers. In addition, these header fields must * be folded (wrapped) before being sent if they exceed the line length * limitation for the transport (1000 bytes for SMTP). Received headers may have * been folded. The application is responsible for folding and unfolding headers * as appropriate. * <p> * * @author John Mani * @author Bill Shannon * @author Kanwar Oberoi * @see javax.mail.Part * @see javax.mail.internet.MimePart * @see javax.mail.internet.MimeUtility */ public class MimeBodyPart extends BodyPart implements MimePart { // Paranoia: // allow this last minute change to be disabled if it causes problems private static boolean setDefaultTextCharset = PropUtil .getBooleanSystemProperty( "mail.mime.setdefaulttextcharset", true); private static boolean setContentTypeFileName = PropUtil .getBooleanSystemProperty( "mail.mime.setcontenttypefilename", true); private static boolean encodeFileName = PropUtil .getBooleanSystemProperty( "mail.mime.encodefilename", false); private static boolean decodeFileName = PropUtil .getBooleanSystemProperty( "mail.mime.decodefilename", false); // Paranoia: // allow this last minute change to be disabled if it causes problems static boolean cacheMultipart = // accessed by // MimeMessage PropUtil .getBooleanSystemProperty( "mail.mime.cachemultipart", true); /** * The DataHandler object representing this Part's content. */ protected DataHandler dh; /** * Byte array that holds the bytes of the content of this Part. */ protected byte[] content; /** * If the data for this body part was supplied by an InputStream that * implements the SharedInputStream interface, <code>contentStream</code> is * another such stream representing the content of this body part. In this * case, <code>content</code> will be null. * * @since JavaMail 1.2 */ protected InputStream contentStream; /** * The InternetHeaders object that stores all the headers of this body part. */ protected InternetHeaders headers; /** * If our content is a Multipart of Message object, we save it the first * time it's created by parsing a stream so that changes to the contained * objects will not be lost. */ private Object cachedContent; /** * An empty MimeBodyPart object is created. This body part maybe filled in * by a client constructing a multipart message. */ public MimeBodyPart() { super(); headers = new InternetHeaders(); } /** * Constructs a MimeBodyPart by reading and parsing the data from the * specified input stream. The parser consumes data till the end of the * given input stream. The input stream must start at the beginning of a * valid MIME body part and must terminate at the end of that body part. * <p> * Note that the "boundary" string that delimits body parts must * <strong>not</strong> be included in the input stream. The intention is * that the MimeMultipart parser will extract each body part's bytes from a * multipart stream and feed them into this constructor, without the * delimiter strings. * * @param is * the body part Input Stream */ public MimeBodyPart(InputStream is) throws MessagingException { if (!(is instanceof ByteArrayInputStream) && !(is instanceof BufferedInputStream) && !(is instanceof SharedInputStream)) is = new BufferedInputStream( is); headers = new InternetHeaders(is); if (is instanceof SharedInputStream) { SharedInputStream sis = (SharedInputStream) is; contentStream = sis.newStream(sis.getPosition(), -1); } else { try { content = ASCIIUtility.getBytes(is); } catch (IOException ioex) { throw new MessagingException("Error reading input stream", ioex); } } } /** * Constructs a MimeBodyPart using the given header and content bytes. * <p> * Used by providers. * * @param headers * The header of this part * @param content * bytes representing the body of this part. */ public MimeBodyPart(InternetHeaders headers, byte[] content) throws MessagingException { super(); this.headers = headers; this.content = content; } /** * Return the size of the content of this body part in bytes. Return -1 if * the size cannot be determined. * <p> * Note that this number may not be an exact measure of the content size and * may or may not account for any transfer encoding of the content. * <p> * This implementation returns the size of the <code>content</code> array * (if not null), or, if <code>contentStream</code> is not null, and the * <code>available</code> method returns a positive number, it returns that * number as the size. Otherwise, it returns -1. * * @return size in bytes, or -1 if not known */ public int getSize() throws MessagingException { if (content != null) return content.length; if (contentStream != null) { try { int size = contentStream.available(); // only believe the size if it's greate than zero, since zero // is the default returned by the InputStream class itself if (size > 0) return size; } catch (IOException ex) { // ignore it } } return -1; } /** * Return the number of lines for the content of this Part. Return -1 if * this number cannot be determined. * <p> * Note that this number may not be an exact measure of the content length * and may or may not account for any transfer encoding of the content. * <p> * This implementation returns -1. * * @return number of lines, or -1 if not known */ public int getLineCount() throws MessagingException { return -1; } /** * Returns the value of the RFC 822 "Content-Type" header field. This * represents the content type of the content of this body part. This value * must not be null. If this field is unavailable, "text/plain" should be * returned. * <p> * This implementation uses <code>getHeader(name)</code> to obtain the * requisite header field. * * @return Content-Type of this body part */ public String getContentType() throws MessagingException { String s = getHeader("Content-Type", null); if (s == null) s = "text/plain"; return s; } /** * Is this Part of the specified MIME type? This method compares * <strong>only the <code>primaryType</code> and <code>subType</code> * </strong>. The parameters of the content types are ignored. * <p> * For example, this method will return <code>true</code> when comparing a * Part of content type <strong>"text/plain"</strong> with * <strong>"text/plain; charset=foobar"</strong>. * <p> * If the <code>subType</code> of <code>mimeType</code> is the special * character '*', then the subtype is ignored during the comparison. */ public boolean isMimeType(String mimeType) throws MessagingException { return isMimeType(this, mimeType); } /** * Returns the value of the "Content-Disposition" header field. This * represents the disposition of this part. The disposition describes how * the part should be presented to the user. * <p> * If the Content-Disposition field is unavailable, null is returned. * <p> * This implementation uses <code>getHeader(name)</code> to obtain the * requisite header field. * * @see #headers */ public String getDisposition() throws MessagingException { return getDisposition(this); } /** * Set the "Content-Disposition" header field of this body part. If the * disposition is null, any existing "Content-Disposition" header field is * removed. * * @exception IllegalWriteException * if the underlying implementation does not support * modification * @exception IllegalStateException * if this body part is obtained from a READ_ONLY folder. */ public void setDisposition(String disposition) throws MessagingException { setDisposition(this, disposition); } /** * Returns the content transfer encoding from the * "Content-Transfer-Encoding" header field. Returns <code>null</code> if * the header is unavailable or its value is absent. * <p> * This implementation uses <code>getHeader(name)</code> to obtain the * requisite header field. * * @see #headers */ public String getEncoding() throws MessagingException { return getEncoding(this); } /** * Returns the value of the "Content-ID" header field. Returns * <code>null</code> if the field is unavailable or its value is absent. * <p> * This implementation uses <code>getHeader(name)</code> to obtain the * requisite header field. */ public String getContentID() throws MessagingException { return getHeader("Content-Id", null); } /** * Set the "Content-ID" header field of this body part. If the * <code>cid</code> parameter is null, any existing "Content-ID" is removed. * * @exception IllegalWriteException * if the underlying implementation does not support * modification * @exception IllegalStateException * if this body part is obtained from a READ_ONLY folder. * @exception MessagingException * @since JavaMail 1.3 */ public void setContentID(String cid) throws MessagingException { if (cid == null) removeHeader("Content-ID"); else setHeader("Content-ID", cid); } /** * Return the value of the "Content-MD5" header field. Returns * <code>null</code> if this field is unavailable or its value is absent. * <p> * This implementation uses <code>getHeader(name)</code> to obtain the * requisite header field. */ public String getContentMD5() throws MessagingException { return getHeader("Content-MD5", null); } /** * Set the "Content-MD5" header field of this body part. * * @exception IllegalWriteException * if the underlying implementation does not support * modification * @exception IllegalStateException * if this body part is obtained from a READ_ONLY folder. */ public void setContentMD5(String md5) throws MessagingException { setHeader("Content-MD5", md5); } /** * Get the languages specified in the Content-Language header of this * MimePart. The Content-Language header is defined by RFC 1766. Returns * <code>null</code> if this header is not available or its value is absent. * <p> * This implementation uses <code>getHeader(name)</code> to obtain the * requisite header field. */ public String[] getContentLanguage() throws MessagingException { return getContentLanguage(this); } /** * Set the Content-Language header of this MimePart. The Content-Language * header is defined by RFC 1766. * * @param languages * array of language tags */ public void setContentLanguage(String[] languages) throws MessagingException { setContentLanguage(this, languages); } /** * Returns the "Content-Description" header field of this body part. This * typically associates some descriptive information with this part. Returns * null if this field is unavailable or its value is absent. * <p> * If the Content-Description field is encoded as per RFC 2047, it is * decoded and converted into Unicode. If the decoding or conversion fails, * the raw data is returned as is. * <p> * This implementation uses <code>getHeader(name)</code> to obtain the * requisite header field. * * @return content description */ public String getDescription() throws MessagingException { return getDescription(this); } /** * Set the "Content-Description" header field for this body part. If the * description parameter is <code>null</code>, then any existing * "Content-Description" fields are removed. * <p> * If the description contains non US-ASCII characters, it will be encoded * using the platform's default charset. If the description contains only * US-ASCII characters, no encoding is done and it is used as is. * <p> * Note that if the charset encoding process fails, a MessagingException is * thrown, and an UnsupportedEncodingException is included in the chain of * nested exceptions within the MessagingException. * * @param description * content description * @exception IllegalWriteException * if the underlying implementation does not support * modification * @exception IllegalStateException * if this body part is obtained from a READ_ONLY folder. * @exception MessagingException * otherwise; an UnsupportedEncodingException may be included * in the exception chain if the charset conversion fails. */ public void setDescription(String description) throws MessagingException { setDescription(description, null); } /** * Set the "Content-Description" header field for this body part. If the * description parameter is <code>null</code>, then any existing * "Content-Description" fields are removed. * <p> * If the description contains non US-ASCII characters, it will be encoded * using the specified charset. If the description contains only US-ASCII * characters, no encoding is done and it is used as is. * <p> * Note that if the charset encoding process fails, a MessagingException is * thrown, and an UnsupportedEncodingException is included in the chain of * nested exceptions within the MessagingException. * * @param description * Description * @param charset * Charset for encoding * @exception IllegalWriteException * if the underlying implementation does not support * modification * @exception IllegalStateException * if this body part is obtained from a READ_ONLY folder. * @exception MessagingException * otherwise; an UnsupportedEncodingException may be included * in the exception chain if the charset conversion fails. */ public void setDescription(String description, String charset) throws MessagingException { setDescription(this, description, charset); } /** * Get the filename associated with this body part. * <p> * Returns the value of the "filename" parameter from the * "Content-Disposition" header field of this body part. If its not * available, returns the value of the "name" parameter from the * "Content-Type" header field of this body part. Returns <code>null</code> * if both are absent. * <p> * If the <code>mail.mime.encodefilename</code> System property is set to * true, the {@link MimeUtility#decodeText MimeUtility.decodeText} method * will be used to decode the filename. While such encoding is not supported * by the MIME spec, many mailers use this technique to support non-ASCII * characters in filenames. The default value of this property is false. * * @return filename */ public String getFileName() throws MessagingException { return getFileName(this); } /** * Set the filename associated with this body part, if possible. * <p> * Sets the "filename" parameter of the "Content-Disposition" header field * of this body part. For compatibility with older mailers, the "name" * parameter of the "Content-Type" header is also set. * <p> * If the <code>mail.mime.encodefilename</code> System property is set to * true, the {@link MimeUtility#encodeText MimeUtility.encodeText} method * will be used to encode the filename. While such encoding is not supported * by the MIME spec, many mailers use this technique to support non-ASCII * characters in filenames. The default value of this property is false. * * @exception IllegalWriteException * if the underlying implementation does not support * modification * @exception IllegalStateException * if this body part is obtained from a READ_ONLY folder. */ public void setFileName(String filename) throws MessagingException { setFileName(this, filename); } /** * Return a decoded input stream for this body part's "content". * <p> * This implementation obtains the input stream from the DataHandler. That * is, it invokes getDataHandler().getInputStream(); * * @return an InputStream * @exception MessagingException * @exception IOException * this is typically thrown by the DataHandler. Refer to the * documentation for javax.activation.DataHandler for more * details. * @see #getContentStream * @see javax.activation.DataHandler#getInputStream */ public InputStream getInputStream() throws IOException, MessagingException { return getDataHandler().getInputStream(); } /** * Produce the raw bytes of the content. This method is used when creating a * DataHandler object for the content. Subclasses that can provide a * separate input stream for just the Part content might want to override * this method. * <p> * * @see #content * @see MimeMessage#getContentStream */ protected InputStream getContentStream() throws MessagingException { if (contentStream != null) return ((SharedInputStream) contentStream) .newStream(0, -1); if (content != null) return new ByteArrayInputStream(content); throw new MessagingException("No content"); } /** * Return an InputStream to the raw data with any Content-Transfer-Encoding * intact. This method is useful if the "Content-Transfer-Encoding" header * is incorrect or corrupt, which would prevent the * <code>getInputStream</code> method or <code>getContent</code> method from * returning the correct data. In such a case the application may use this * method and attempt to decode the raw data itself. * <p> * This implementation simply calls the <code>getContentStream</code> * method. * * @see #getInputStream * @see #getContentStream * @since JavaMail 1.2 */ public InputStream getRawInputStream() throws MessagingException { return getContentStream(); } /** * Return a DataHandler for this body part's content. * <p> * The implementation provided here works just like the the implementation * in MimeMessage. * * @see MimeMessage#getDataHandler */ public DataHandler getDataHandler() throws MessagingException { if (dh == null) dh = new DataHandler(new MimePartDataSource(this)); return dh; } /** * Return the content as a Java object. The type of the object returned is * of course dependent on the content itself. For example, the native format * of a text/plain content is usually a String object. The native format for * a "multipart" content is always a Multipart subclass. For content types * that are unknown to the DataHandler system, an input stream is returned * as the content. * <p> * This implementation obtains the content from the DataHandler. That is, it * invokes getDataHandler().getContent(); If the content is a Multipart or * Message object and was created by parsing a stream, the object is cached * and returned in subsequent calls so that modifications to the content * will not be lost. * * @return Object * @exception MessagingException * @exception IOException * this is typically thrown by the DataHandler. Refer to the * documentation for javax.activation.DataHandler for more * details. */ public Object getContent() throws IOException, MessagingException { if (cachedContent != null) return cachedContent; Object c; try { c = getDataHandler().getContent(); } catch (FolderClosedIOException fex) { throw new FolderClosedException(fex.getFolder(), fex.getMessage()); } catch (MessageRemovedIOException mex) { throw new MessageRemovedException(mex.getMessage()); } if (cacheMultipart && (c instanceof Multipart || c instanceof Message) && (content != null || contentStream != null)) { cachedContent = c; } return c; } /** * This method provides the mechanism to set this body part's content. The * given DataHandler object should wrap the actual content. * * @param dh * The DataHandler for the content * @exception IllegalWriteException * if the underlying implementation does not support * modification * @exception IllegalStateException * if this body part is obtained from a READ_ONLY folder. */ public void setDataHandler(DataHandler dh) throws MessagingException { this.dh = dh; cachedContent = null; MimeBodyPart.invalidateContentHeaders(this); } /** * A convenience method for setting this body part's content. * <p> * The content is wrapped in a DataHandler object. Note that a * DataContentHandler class for the specified type should be available to * the JavaMail implementation for this to work right. That is, to do * <code>setContent(foobar, "application/x-foobar")</code>, a * DataContentHandler for "application/x-foobar" should be installed. Refer * to the Java Activation Framework for more information. * * @param o * the content object * @param type * Mime type of the object * @exception IllegalWriteException * if the underlying implementation does not support * modification of existing values * @exception IllegalStateException * if this body part is obtained from a READ_ONLY folder. */ public void setContent(Object o, String type) throws MessagingException { if (o instanceof Multipart) { setContent((Multipart) o); } else { setDataHandler(new DataHandler(o, type)); } } /** * Convenience method that sets the given String as this part's content, * with a MIME type of "text/plain". If the string contains non US-ASCII * characters, it will be encoded using the platform's default charset. The * charset is also used to set the "charset" parameter. * <p> * Note that there may be a performance penalty if <code>text</code> is * large, since this method may have to scan all the characters to determine * what charset to use. * <p> * If the charset is already known, use the <code>setText</code> method that * takes the charset parameter. * * @param text * the text content to set * @exception MessagingException * if an error occurs * @see #setText(String text, String charset) */ public void setText(String text) throws MessagingException { setText(text, null); } /** * Convenience method that sets the given String as this part's content, * with a MIME type of "text/plain" and the specified charset. The given * Unicode string will be charset-encoded using the specified charset. The * charset is also used to set the "charset" parameter. * * @param text * the text content to set * @param charset * the charset to use for the text * @exception MessagingException * if an error occurs */ public void setText(String text, String charset) throws MessagingException { setText(this, text, charset, "plain"); } /** * Convenience method that sets the given String as this part's content, * with a primary MIME type of "text" and the specified MIME subtype. The * given Unicode string will be charset-encoded using the specified charset. * The charset is also used to set the "charset" parameter. * * @param text * the text content to set * @param charset * the charset to use for the text * @param subtype * the MIME subtype to use (e.g., "html") * @exception MessagingException * if an error occurs * @since JavaMail 1.4 */ public void setText(String text, String charset, String subtype) throws MessagingException { setText(this, text, charset, subtype); } /** * This method sets the body part's content to a Multipart object. * * @param mp * The multipart object that is the Message's content * @exception IllegalWriteException * if the underlying implementation does not support * modification of existing values. * @exception IllegalStateException * if this body part is obtained from a READ_ONLY folder. */ public void setContent(Multipart mp) throws MessagingException { setDataHandler(new DataHandler(mp, mp.getContentType())); mp.setParent(this); } /** * Use the specified file to provide the data for this part. The simple file * name is used as the file name for this part and the data in the file is * used as the data for this part. The encoding will be chosen appropriately * for the file data. * * @param file * the File object to attach * @exception IOException * errors related to accessing the file * @exception MessagingException * message related errors * @since JavaMail 1.4 */ public void attachFile(File file) throws IOException, MessagingException { FileDataSource fds = new FileDataSource(file); this.setDataHandler(new DataHandler(fds)); this.setFileName(fds.getName()); } /** * Use the specified file to provide the data for this part. The simple file * name is used as the file name for this part and the data in the file is * used as the data for this part. The encoding will be chosen appropriately * for the file data. * * @param file * the name of the file to attach * @exception IOException * errors related to accessing the file * @exception MessagingException * message related errors * @since JavaMail 1.4 */ public void attachFile(String file) throws IOException, MessagingException { File f = new File(file); attachFile(f); } /** * Save the contents of this part in the specified file. The content is * decoded and saved, without any of the MIME headers. * * @param file * the File object to write to * @exception IOException * errors related to accessing the file * @exception MessagingException * message related errors * @since JavaMail 1.4 */ public void saveFile(File file) throws IOException, MessagingException { OutputStream out = null; InputStream in = null; try { out = new BufferedOutputStream(new FileOutputStream(file)); in = this.getInputStream(); byte[] buf = new byte[8192]; int len; while ((len = in.read(buf)) > 0) out.write(buf, 0, len); } finally { // close streams, but don't mask original exception, if any try { if (in != null) in.close(); } catch (IOException ex) { } try { if (out != null) out.close(); } catch (IOException ex) { } } } /** * Save the contents of this part in the specified file. The content is * decoded and saved, without any of the MIME headers. * * @param file * the name of the file to write to * @exception IOException * errors related to accessing the file * @exception MessagingException * message related errors * @since JavaMail 1.4 */ public void saveFile(String file) throws IOException, MessagingException { File f = new File(file); saveFile(f); } /** * Output the body part as an RFC 822 format stream. * * @exception MessagingException * @exception IOException * if an error occurs writing to the stream or if an error is * generated by the javax.activation layer. * @see javax.activation.DataHandler#writeTo */ public void writeTo(OutputStream os) throws IOException, MessagingException { writeTo(this, os, null); } /** * Get all the headers for this header_name. Note that certain headers may * be encoded as per RFC 2047 if they contain non US-ASCII characters and * these should be decoded. * * @param name * name of header * @return array of headers * @see javax.mail.internet.MimeUtility */ public String[] getHeader(String name) throws MessagingException { return headers.getHeader(name); } /** * Get all the headers for this header name, returned as a single String, * with headers separated by the delimiter. If the delimiter is * <code>null</code>, only the first header is returned. * * @param name * the name of this header * @param delimiter * delimiter between fields in returned string * @return the value fields for all headers with this name * @exception MessagingException */ public String getHeader(String name, String delimiter) throws MessagingException { return headers.getHeader(name, delimiter); } /** * Set the value for this header_name. Replaces all existing header values * with this new value. Note that RFC 822 headers must contain only US-ASCII * characters, so a header that contains non US-ASCII characters must be * encoded as per the rules of RFC 2047. * * @param name * header name * @param value * header value * @see javax.mail.internet.MimeUtility */ public void setHeader(String name, String value) throws MessagingException { headers.setHeader(name, value); } /** * Add this value to the existing values for this header_name. Note that RFC * 822 headers must contain only US-ASCII characters, so a header that * contains non US-ASCII characters must be encoded as per the rules of RFC * 2047. * * @param name * header name * @param value * header value * @see javax.mail.internet.MimeUtility */ public void addHeader(String name, String value) throws MessagingException { headers.addHeader(name, value); } /** * Remove all headers with this name. */ public void removeHeader(String name) throws MessagingException { headers.removeHeader(name); } /** * Return all the headers from this Message as an Enumeration of Header * objects. */ public Enumeration getAllHeaders() throws MessagingException { return headers.getAllHeaders(); } /** * Return matching headers from this Message as an Enumeration of Header * objects. * <p> */ public Enumeration getMatchingHeaders(String[] names) throws MessagingException { return headers.getMatchingHeaders(names); } /** * Return non-matching headers from this Message as an Enumeration of Header * objects. */ public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException { return headers.getNonMatchingHeaders(names); } /** * Add a header line to this body part */ public void addHeaderLine(String line) throws MessagingException { headers.addHeaderLine(line); } /** * Get all header lines as an Enumeration of Strings. A Header line is a raw * RFC 822 header line, containing both the "name" and "value" field. */ public Enumeration getAllHeaderLines() throws MessagingException { return headers.getAllHeaderLines(); } /** * Get matching header lines as an Enumeration of Strings. A Header line is * a raw RFC 822 header line, containing both the "name" and "value" field. */ public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException { return headers.getMatchingHeaderLines(names); } /** * Get non-matching header lines as an Enumeration of Strings. A Header line * is a raw RFC 822 header line, containing both the "name" and "value" * field. */ public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException { return headers.getNonMatchingHeaderLines(names); } /** * Examine the content of this body part and update the appropriate MIME * headers. Typical headers that get set here are <code>Content-Type</code> * and <code>Content-Transfer-Encoding</code>. Headers might need to be * updated in two cases: <br> * - A message being crafted by a mail application will certainly need to * activate this method at some point to fill up its internal headers. <br> * - A message read in from a Store will have obtained all its headers from * the store, and so doesn't need this. However, if this message is editable * and if any edits have been made to either the content or message * structure, we might need to resync our headers. <br> * In both cases this method is typically called by the * <code>Message.saveChanges</code> method. */ protected void updateHeaders() throws MessagingException { updateHeaders(this); /* * If we've cached a Multipart or Message object then we're now * committed to using this instance of the object and we discard any * stream data used to create this object. */ if (cachedContent != null) { dh = new DataHandler(cachedContent, getContentType()); cachedContent = null; content = null; if (contentStream != null) { try { contentStream.close(); } catch (IOException ioex) { } // nothing to do } contentStream = null; } } // /////////////////////////////////////////////////////////// // Package private convenience methods to share code among // // MimeMessage and MimeBodyPart // // /////////////////////////////////////////////////////////// static boolean isMimeType(MimePart part, String mimeType) throws MessagingException { // XXX - lots of room for optimization here! try { ContentType ct = new ContentType(part.getContentType()); return ct.match(mimeType); } catch (ParseException ex) { return part.getContentType().equalsIgnoreCase(mimeType); } } static void setText(MimePart part, String text, String charset, String subtype) throws MessagingException { if (charset == null) { if (MimeUtility.checkAscii(text) != MimeUtility.ALL_ASCII) charset = MimeUtility .getDefaultMIMECharset(); else charset = "us-ascii"; } // XXX - should at least ensure that subtype is an atom part.setContent(text, "text/" + subtype + "; charset=" + MimeUtility.quote(charset, HeaderTokenizer.MIME)); } static String getDisposition(MimePart part) throws MessagingException { String s = part.getHeader("Content-Disposition", null); if (s == null) return null; ContentDisposition cd = new ContentDisposition(s); return cd.getDisposition(); } static void setDisposition(MimePart part, String disposition) throws MessagingException { if (disposition == null) part.removeHeader("Content-Disposition"); else { String s = part.getHeader("Content-Disposition", null); if (s != null) { /* * A Content-Disposition header already exists .. Override * disposition, but attempt to retain existing disposition * parameters */ ContentDisposition cd = new ContentDisposition(s); cd.setDisposition(disposition); disposition = cd.toString(); } part.setHeader("Content-Disposition", disposition); } } static String getDescription(MimePart part) throws MessagingException { String rawvalue = part.getHeader("Content-Description", null); if (rawvalue == null) return null; try { return MimeUtility.decodeText(MimeUtility.unfold(rawvalue)); } catch (UnsupportedEncodingException ex) { return rawvalue; } } static void setDescription(MimePart part, String description, String charset) throws MessagingException { if (description == null) { part.removeHeader("Content-Description"); return; } try { part.setHeader("Content-Description", MimeUtility.fold(21, MimeUtility.encodeText(description, charset, null))); } catch (UnsupportedEncodingException uex) { throw new MessagingException("Encoding error", uex); } } static String getFileName(MimePart part) throws MessagingException { String filename = null; String s = part.getHeader("Content-Disposition", null); if (s != null) { // Parse the header .. ContentDisposition cd = new ContentDisposition(s); filename = cd.getParameter("filename"); } if (filename == null) { // Still no filename ? Try the "name" ContentType parameter s = part.getHeader("Content-Type", null); if (s != null) { try { ContentType ct = new ContentType(s); filename = ct.getParameter("name"); } catch (ParseException pex) { } // ignore it } } if (decodeFileName && filename != null) { try { filename = MimeUtility.decodeText(filename); } catch (UnsupportedEncodingException ex) { throw new MessagingException("Can't decode filename", ex); } } return filename; } static void setFileName(MimePart part, String name) throws MessagingException { if (encodeFileName && name != null) { try { name = MimeUtility.encodeText(name); } catch (UnsupportedEncodingException ex) { throw new MessagingException("Can't encode filename", ex); } } // Set the Content-Disposition "filename" parameter String s = part.getHeader("Content-Disposition", null); ContentDisposition cd = new ContentDisposition( s == null ? Part.ATTACHMENT : s); cd.setParameter("filename", name); part.setHeader("Content-Disposition", cd.toString()); /* * Also attempt to set the Content-Type "name" parameter, to satisfy * ancient MUAs. XXX - This is not RFC compliant. */ if (setContentTypeFileName) { s = part.getHeader("Content-Type", null); if (s != null) { try { ContentType cType = new ContentType(s); cType.setParameter("name", name); part.setHeader("Content-Type", cType.toString()); } catch (ParseException pex) { } // ignore it } } } static String[] getContentLanguage(MimePart part) throws MessagingException { String s = part.getHeader("Content-Language", null); if (s == null) return null; // Tokenize the header to obtain the Language-tags (skip comments) HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME); Vector v = new Vector(); HeaderTokenizer.Token tk; int tkType; while (true) { tk = h.next(); // get a language-tag tkType = tk.getType(); if (tkType == HeaderTokenizer.Token.EOF) break; // done else if (tkType == HeaderTokenizer.Token.ATOM) v.addElement(tk .getValue()); else // invalid token, skip it. continue; } if (v.size() == 0) return null; String[] language = new String[v.size()]; v.copyInto(language); return language; } static void setContentLanguage(MimePart part, String[] languages) throws MessagingException { StringBuffer sb = new StringBuffer(languages[0]); for (int i = 1; i < languages.length; i++) sb.append(',').append(languages[i]); part.setHeader("Content-Language", sb.toString()); } static String getEncoding(MimePart part) throws MessagingException { String s = part.getHeader("Content-Transfer-Encoding", null); if (s == null) return null; s = s.trim(); // get rid of trailing spaces // quick check for known values to avoid unnecessary use // of tokenizer. if (s.equalsIgnoreCase("7bit") || s.equalsIgnoreCase("8bit") || s.equalsIgnoreCase("quoted-printable") || s.equalsIgnoreCase("binary") || s.equalsIgnoreCase("base64")) return s; // Tokenize the header to obtain the encoding (skip comments) HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME); HeaderTokenizer.Token tk; int tkType; for (;;) { tk = h.next(); // get a token tkType = tk.getType(); if (tkType == HeaderTokenizer.Token.EOF) break; // done else if (tkType == HeaderTokenizer.Token.ATOM) return tk.getValue(); else // invalid token, skip it. continue; } return s; } static void setEncoding(MimePart part, String encoding) throws MessagingException { part.setHeader("Content-Transfer-Encoding", encoding); } static void updateHeaders(MimePart part) throws MessagingException { DataHandler dh = part.getDataHandler(); if (dh == null) // Huh ? return; try { String type = dh.getContentType(); boolean composite = false; boolean needCTHeader = part.getHeader("Content-Type") == null; ContentType cType = new ContentType(type); if (cType.match("multipart/*")) { // If multipart, recurse composite = true; Object o; if (part instanceof MimeBodyPart) { MimeBodyPart mbp = (MimeBodyPart) part; o = mbp.cachedContent != null ? mbp.cachedContent : dh .getContent(); } else if (part instanceof MimeMessage) { MimeMessage msg = (MimeMessage) part; o = msg.cachedContent != null ? msg.cachedContent : dh .getContent(); } else o = dh.getContent(); if (o instanceof MimeMultipart) { ((MimeMultipart) o).updateHeaders(); } else if (o instanceof IMAPInputStream) { System.err.println("Ignoring inconsistent mail body on updateHeaders()"); } else { throw new MessagingException("MIME part of type \"" + type + "\" contains object of type " + o.getClass().getName() + " instead of MimeMultipart"); } } else if (cType.match("message/rfc822")) { composite = true; // XXX - call MimeMessage.updateHeaders()? } // Content-Transfer-Encoding, but only if we don't // already have one if (!composite) { // not allowed on composite parts if (part.getHeader("Content-Transfer-Encoding") == null) setEncoding( part, MimeUtility.getEncoding(dh)); if (needCTHeader && setDefaultTextCharset && cType.match("text/*") && cType.getParameter("charset") == null) { /* * Set a default charset for text parts. We really should * examine the data to determine whether or not it's all * ASCII, but that's too expensive so we make an assumption: * If we chose 7bit encoding for this data, it's probably * ASCII. (MimeUtility.getEncoding will choose 7bit only in * this case, but someone might've set the * Content-Transfer-Encoding header manually.) */ String charset; String enc = part.getEncoding(); if (enc != null && enc.equalsIgnoreCase("7bit")) charset = "us-ascii"; else charset = MimeUtility.getDefaultMIMECharset(); cType.setParameter("charset", charset); type = cType.toString(); } } // Now, let's update our own headers ... // Content-type, but only if we don't already have one if (needCTHeader) { /* * Pull out "filename" from Content-Disposition, and use that to * set the "name" parameter. This is to satisfy older MUAs * (DtMail, Roam and probably a bunch of others). */ String s = part.getHeader("Content-Disposition", null); if (s != null) { // Parse the header .. ContentDisposition cd = new ContentDisposition(s); String filename = cd.getParameter("filename"); if (filename != null) { cType.setParameter("name", filename); type = cType.toString(); } } part.setHeader("Content-Type", type); } } catch (IOException ex) { throw new MessagingException("IOException updating headers", ex); } } static void invalidateContentHeaders(MimePart part) throws MessagingException { part.removeHeader("Content-Type"); part.removeHeader("Content-Transfer-Encoding"); } static void writeTo(MimePart part, OutputStream os, String[] ignoreList) throws IOException, MessagingException { // see if we already have a LOS LineOutputStream los = null; if (os instanceof LineOutputStream) { los = (LineOutputStream) os; } else { los = new LineOutputStream(os); } // First, write out the header Enumeration hdrLines = part.getNonMatchingHeaderLines(ignoreList); while (hdrLines.hasMoreElements()) los.writeln((String) hdrLines.nextElement()); // The CRLF separator between header and content los.writeln(); // Finally, the content. Encode if required. // XXX: May need to account for ESMTP ? os = MimeUtility.encode(os, part.getEncoding()); part.getDataHandler().writeTo(os); os.flush(); // Needed to complete encoding } }