/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2016 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://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/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 packager/legal/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.io.*;
import java.util.*;
import com.sun.mail.util.*;
/**
* This class represents a MIME body part. It implements the
* BodyPart
abstract class and the MimePart
* interface. MimeBodyParts are contained in MimeMultipart
* objects.
*
* MimeBodyPart uses the InternetHeaders
class to parse
* and store the headers of that body part.
*
*
*
* RFC 822 header fields must 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 setHeader
, addHeader
, and
* addHeaderLine
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.
*
* @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 final boolean setDefaultTextCharset =
PropUtil.getBooleanSystemProperty(
"mail.mime.setdefaulttextcharset", true);
private static final boolean setContentTypeFileName =
PropUtil.getBooleanSystemProperty(
"mail.mime.setcontenttypefilename", true);
private static final boolean encodeFileName =
PropUtil.getBooleanSystemProperty("mail.mime.encodefilename", false);
private static final boolean decodeFileName =
PropUtil.getBooleanSystemProperty("mail.mime.decodefilename", false);
private static final boolean ignoreMultipartEncoding =
PropUtil.getBooleanSystemProperty(
"mail.mime.ignoremultipartencoding", true);
private static final boolean allowutf8 =
PropUtil.getBooleanSystemProperty("mail.mime.allowutf8", true);
// Paranoia:
// allow this last minute change to be disabled if it causes problems
static final 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,
* contentStream
is another such stream representing
* the content of this body part. In this case, content
* 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.
*
* If this field is not null, it's return by the {@link #getContent}
* method. The {@link #getContent} method sets this field if it
* would return a Multipart or MimeMessage object. This field is
* is cleared by the {@link #setDataHandler} method.
*
* @since JavaMail 1.5
*/
protected 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.
* * Note that the "boundary" string that delimits body parts must * not 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 * @exception MessagingException for failures */ 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.
* * Used by providers. * * @param headers The header of this part * @param content bytes representing the body of this part. * @exception MessagingException for failures */ 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.
* * 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.
*
* This implementation returns the size of the content
* array (if not null), or, if contentStream
is not
* null, and the available
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
*/
@Override
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.
* * 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.
* * This implementation returns -1. * * @return number of lines, or -1 if not known */ @Override 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.
*
* This implementation uses getHeader(name)
* to obtain the requisite header field.
*
* @return Content-Type of this body part
*/
@Override
public String getContentType() throws MessagingException {
String s = getHeader("Content-Type", null);
s = MimeUtil.cleanContentType(this, s);
if (s == null)
s = "text/plain";
return s;
}
/**
* Is this Part of the specified MIME type? This method
* compares only the primaryType
and
* subType
.
* The parameters of the content types are ignored.
*
* For example, this method will return true
when
* comparing a Part of content type "text/plain"
* with "text/plain; charset=foobar".
*
* If the subType
of mimeType
is the
* special character '*', then the subtype is ignored during the
* comparison.
*
* @exception MessagingException for failures
*/
@Override
public boolean isMimeType(String mimeType) throws MessagingException {
return isMimeType(this, mimeType);
}
/**
* Returns the disposition from the "Content-Disposition" header field.
* This represents the disposition of this part. The disposition
* describes how the part should be presented to the user.
* * If the Content-Disposition field is unavailable, * null is returned.
*
* This implementation uses getHeader(name)
* to obtain the requisite header field.
*
* @exception MessagingException for failures
* @see #headers
*/
@Override
public String getDisposition() throws MessagingException {
return getDisposition(this);
}
/**
* Set the disposition in 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.
* @exception MessagingException for other failures
*/
@Override
public void setDisposition(String disposition) throws MessagingException {
setDisposition(this, disposition);
}
/**
* Returns the content transfer encoding from the
* "Content-Transfer-Encoding" header
* field. Returns null
if the header is unavailable
* or its value is absent.
*
* This implementation uses getHeader(name)
* to obtain the requisite header field.
*
* @see #headers
*/
@Override
public String getEncoding() throws MessagingException {
return getEncoding(this);
}
/**
* Returns the value of the "Content-ID" header field. Returns
* null
if the field is unavailable or its value is
* absent.
*
* This implementation uses getHeader(name)
* to obtain the requisite header field.
*/
@Override
public String getContentID() throws MessagingException {
return getHeader("Content-Id", null);
}
/**
* Set the "Content-ID" header field of this body part.
* If the cid
parameter is null, any existing
* "Content-ID" is removed.
*
* @param cid the Content-ID
* @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 for other failures
* @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
* null
if this field is unavailable or its value
* is absent.
*
* This implementation uses getHeader(name)
* to obtain the requisite header field.
*/
@Override
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.
*/
@Override
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 null
if this header is not
* available or its value is absent.
*
* This implementation uses getHeader(name)
* to obtain the requisite header field.
*/
@Override
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
*/
@Override
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.
* * 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.
*
* This implementation uses getHeader(name)
* to obtain the requisite header field.
*
* @return content description
*/
@Override
public String getDescription() throws MessagingException {
return getDescription(this);
}
/**
* Set the "Content-Description" header field for this body part.
* If the description parameter is null
, then any
* existing "Content-Description" fields are removed.
* * 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.
*
* 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.
*/
@Override
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 null
, then any
* existing "Content-Description" fields are removed.
* * 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.
* * 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.
*
* 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 null
if both are absent.
*
* If the mail.mime.decodefilename
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
*/
@Override
public String getFileName() throws MessagingException {
return getFileName(this);
}
/**
* Set the filename associated with this body part, if possible.
* * 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.
*
* If the mail.mime.encodefilename
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.
*
* @param filename the file name
* @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 for other failures
*/
@Override
public void setFileName(String filename) throws MessagingException {
setFileName(this, filename);
}
/**
* Return a decoded input stream for this body part's "content".
* * This implementation obtains the input stream from the DataHandler. * That is, it invokes getDataHandler().getInputStream(); * * @return an InputStream * @exception IOException this is typically thrown by the * DataHandler. Refer to the documentation for * javax.activation.DataHandler for more details. * @exception MessagingException for other failures * * @see #getContentStream * @see javax.activation.DataHandler#getInputStream */ @Override 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.
*
* @return an InputStream containing the raw bytes
* @exception MessagingException for failures
* @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 MimeBodyPart 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
* getInputStream
method or getContent
method
* from returning the correct data. In such a case the application may
* use this method and attempt to decode the raw data itself.
*
* This implementation simply calls the getContentStream
* method.
*
* @return an InputStream containing the raw bytes
* @exception MessagingException for failures
* @see #getInputStream
* @see #getContentStream
* @since JavaMail 1.2
*/
public InputStream getRawInputStream() throws MessagingException {
return getContentStream();
}
/**
* Return a DataHandler for this body part's content.
* * The implementation provided here works just like the * the implementation in MimeMessage. * @see MimeMessage#getDataHandler */ @Override public DataHandler getDataHandler() throws MessagingException { if (dh == null) dh = new MimePartDataHandler(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.
* * 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 IOException this is typically thrown by the * DataHandler. Refer to the documentation for * javax.activation.DataHandler for more details. * @exception MessagingException for other failures */ @Override 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; /* * We may abandon the input stream so make sure * the MimeMultipart has consumed the stream. */ if (c instanceof MimeMultipart) ((MimeMultipart)c).parse(); } 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. */ @Override 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.
*
* 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 setContent(foobar, "application/x-foobar")
,
* 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.
*/
@Override
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.
*
* Note that there may be a performance penalty if
* text
is large, since this method may have
* to scan all the characters to determine what charset to
* use.
*
* If the charset is already known, use the
*
*
* If the {@link #cachedContent} field is not null (that is,
* it references a Multipart or Message object), then
* that object is used to set a new DataHandler, any
* stream data used to create this object is discarded,
* and the {@link #cachedContent} field is cleared.
*
* @exception MessagingException for failures
*/
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!
String type = part.getContentType();
try {
return new ContentType(type).match(mimeType);
} catch (ParseException ex) {
// we only need the type and subtype so throw away the rest
try {
int i = type.indexOf(';');
if (i > 0)
return new ContentType(type.substring(0, i)).match(mimeType);
} catch (ParseException pex2) {
}
return type.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);
s = MimeUtil.cleanContentType(part, s);
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);
// ensure that the filename is encoded if necessary
String charset = MimeUtility.getDefaultMIMECharset();
ParameterList p = cd.getParameterList();
if (p == null) {
p = new ParameterList();
cd.setParameterList(p);
}
if (encodeFileName)
p.setLiteral("filename", name);
else
p.set("filename", name, charset);
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);
s = MimeUtil.cleanContentType(part, s);
if (s != null) {
try {
ContentType cType = new ContentType(s);
// ensure that the filename is encoded if necessary
p = cType.getParameterList();
if (p == null) {
p = new ParameterList();
cType.setParameterList(p);
}
if (encodeFileName)
p.setLiteral("name", name);
else
p.set("name", name, charset);
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);
ListsetText
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)
*/
@Override
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
*/
@Override
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
*/
@Override
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.
*/
@Override
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. The disposition of this part is set to
* {@link Part#ATTACHMENT Part.ATTACHMENT}.
*
* @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());
this.setDisposition(ATTACHMENT);
}
/**
* 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);
}
/**
* Use the specified file with the specified Content-Type and
* Content-Transfer-Encoding to provide the data for this part.
* If contentType or encoding are null, appropriate values will
* be chosen.
* 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 disposition of this part is set to
* {@link Part#ATTACHMENT Part.ATTACHMENT}.
*
* @param file the File object to attach
* @param contentType the Content-Type, or null
* @param encoding the Content-Transfer-Encoding, or null
* @exception IOException errors related to accessing the file
* @exception MessagingException message related errors
* @since JavaMail 1.5
*/
public void attachFile(File file, String contentType, String encoding)
throws IOException, MessagingException {
DataSource fds = new EncodedFileDataSource(file, contentType, encoding);
this.setDataHandler(new DataHandler(fds));
this.setFileName(fds.getName());
this.setDisposition(ATTACHMENT);
}
/**
* Use the specified file with the specified Content-Type and
* Content-Transfer-Encoding to provide the data for this part.
* If contentType or encoding are null, appropriate values will
* be chosen.
* 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 disposition of this part is set to
* {@link Part#ATTACHMENT Part.ATTACHMENT}.
*
* @param file the name of the file
* @param contentType the Content-Type, or null
* @param encoding the Content-Transfer-Encoding, or null
* @exception IOException errors related to accessing the file
* @exception MessagingException message related errors
* @since JavaMail 1.5
*/
public void attachFile(String file, String contentType, String encoding)
throws IOException, MessagingException {
attachFile(new File(file), contentType, encoding);
}
/**
* A FileDataSource class that allows us to specify the
* Content-Type and Content-Transfer-Encoding.
*/
private static class EncodedFileDataSource extends FileDataSource
implements EncodingAware {
private String contentType;
private String encoding;
public EncodedFileDataSource(File file, String contentType,
String encoding) {
super(file);
this.contentType = contentType;
this.encoding = encoding;
}
// overrides DataSource.getContentType()
@Override
public String getContentType() {
return contentType != null ? contentType : super.getContentType();
}
// implements EncodingAware.getEncoding()
@Override
public String getEncoding() {
return encoding;
}
}
/**
* 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 IOException if an error occurs writing to the
* stream or if an error is generated
* by the javax.activation layer.
* @exception MessagingException for other failures
* @see javax.activation.DataHandler#writeTo
*/
@Override
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
*/
@Override
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 null
, 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 for failures
*/
@Override
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
*/
@Override
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
*/
@Override
public void addHeader(String name, String value)
throws MessagingException {
headers.addHeader(name, value);
}
/**
* Remove all headers with this name.
*/
@Override
public void removeHeader(String name) throws MessagingException {
headers.removeHeader(name);
}
/**
* Return all the headers from this Message as an Enumeration of
* Header objects.
*/
@Override
public EnumerationContent-Type
and Content-Transfer-Encoding
.
* Headers might need to be updated in two cases:
*
*
* - A message being crafted by a mail application will certainly
* need to activate this method at some point to fill up its internal
* headers.
*
*
* - 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.
*
*
* In both cases this method is typically called by the
* Message.saveChanges
method.