jakarta.mail.internet.MimeMessage Maven / Gradle / Ivy
/*
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.internet;
import jakarta.mail.*;
import jakarta.activation.*;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.text.ParseException;
import com.sun.mail.util.PropUtil;
import com.sun.mail.util.ASCIIUtility;
import com.sun.mail.util.MimeUtil;
import com.sun.mail.util.MessageRemovedIOException;
import com.sun.mail.util.FolderClosedIOException;
import com.sun.mail.util.LineOutputStream;
import jakarta.mail.util.SharedByteArrayInputStream;
/**
* This class represents a MIME style email message. It implements
* the Message
abstract class and the MimePart
* interface.
*
* Clients wanting to create new MIME style messages will instantiate
* an empty MimeMessage object and then fill it with appropriate
* attributes and content.
*
* Service providers that implement MIME compliant backend stores may
* want to subclass MimeMessage and override certain methods to provide
* specific implementations. The simplest case is probably a provider
* that generates a MIME style input stream and leaves the parsing of
* the stream to this class.
*
* MimeMessage uses the InternetHeaders
class to parse and
* store the top level RFC 822 headers of a message.
*
* The mail.mime.address.strict
session property controls
* the parsing of address headers. By default, strict parsing of address
* headers is done. If this property is set to "false"
,
* strict parsing is not done and many illegal addresses that sometimes
* occur in real messages are allowed. See the InternetAddress
* class for details.
*
*
A note on RFC 822 and MIME headers
*
* 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 Max Spivak
* @author Kanwar Oberoi
* @see jakarta.mail.internet.MimeUtility
* @see jakarta.mail.Part
* @see jakarta.mail.Message
* @see jakarta.mail.internet.MimePart
* @see jakarta.mail.internet.InternetAddress
*/
public class MimeMessage extends Message implements MimePart {
/**
* The DataHandler object representing this Message's content.
*/
protected DataHandler dh;
/**
* Byte array that holds the bytes of this Message's content.
*/
protected byte[] content;
/**
* If the data for this message was supplied by an
* InputStream that implements the SharedInputStream interface,
* contentStream
is another such stream representing
* the content of this message. In this case, content
* will be null.
*
* @since JavaMail 1.2
*/
protected InputStream contentStream;
/**
* The InternetHeaders object that stores the header
* of this message.
*/
protected InternetHeaders headers;
/**
* The Flags for this message.
*/
protected Flags flags;
/**
* A flag indicating whether the message has been modified.
* If the message has not been modified, any data in the
* content
array is assumed to be valid and is used
* directly in the writeTo
method. This flag is
* set to true when an empty message is created or when the
* saveChanges
method is called.
*
* @since JavaMail 1.2
*/
protected boolean modified = false;
/**
* Does the saveChanges
method need to be called on
* this message? This flag is set to false by the public constructor
* and set to true by the saveChanges
method. The
* writeTo
method checks this flag and calls the
* saveChanges
method as necessary. This avoids the
* common mistake of forgetting to call the saveChanges
* method on a newly constructed message.
*
* @since JavaMail 1.2
*/
protected boolean saved = false;
/**
* If our content is a Multipart or 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;
// Used to parse dates
private static final MailDateFormat mailDateFormat = new MailDateFormat();
// Should addresses in headers be parsed in "strict" mode?
private boolean strict = true;
// Is UTF-8 allowed in headers?
private boolean allowutf8 = false;
/**
* Default constructor. An empty message object is created.
* The headers
field is set to an empty InternetHeaders
* object. The flags
field is set to an empty Flags
* object. The modified
flag is set to true.
*
* @param session the Sesssion
*/
public MimeMessage(Session session) {
super(session);
modified = true;
headers = new InternetHeaders();
flags = new Flags(); // empty flags object
initStrict();
}
/**
* Constructs a MimeMessage by reading and parsing the data from the
* specified MIME InputStream. The InputStream will be left positioned
* at the end of the data for the message. Note that the input stream
* parse is done within this constructor itself.
*
* The input stream contains an entire MIME formatted message with
* headers and data.
*
* @param session Session object for this message
* @param is the message input stream
* @exception MessagingException for failures
*/
public MimeMessage(Session session, InputStream is)
throws MessagingException {
super(session);
flags = new Flags(); // empty Flags object
initStrict();
parse(is);
saved = true;
}
/**
* Constructs a new MimeMessage with content initialized from the
* source
MimeMessage. The new message is independent
* of the original.
*
* Note: The current implementation is rather inefficient, copying
* the data more times than strictly necessary.
*
* @param source the message to copy content from
* @exception MessagingException for failures
* @since JavaMail 1.2
*/
public MimeMessage(MimeMessage source) throws MessagingException {
super(source.session);
flags = source.getFlags();
if (flags == null) // make sure flags is always set
flags = new Flags();
ByteArrayOutputStream bos;
int size = source.getSize();
if (size > 0)
bos = new ByteArrayOutputStream(size);
else
bos = new ByteArrayOutputStream();
try {
strict = source.strict;
source.writeTo(bos);
bos.close();
SharedByteArrayInputStream bis =
new SharedByteArrayInputStream(bos.toByteArray());
parse(bis);
bis.close();
saved = true;
} catch (IOException ex) {
// should never happen, but just in case...
throw new MessagingException("IOException while copying message",
ex);
}
}
/**
* Constructs an empty MimeMessage object with the given Folder
* and message number.
*
* This method is for providers subclassing MimeMessage
.
*
* @param folder the Folder this message is from
* @param msgnum the number of this message
*/
protected MimeMessage(Folder folder, int msgnum) {
super(folder, msgnum);
flags = new Flags(); // empty Flags object
saved = true;
initStrict();
}
/**
* Constructs a MimeMessage by reading and parsing the data from the
* specified MIME InputStream. The InputStream will be left positioned
* at the end of the data for the message. Note that the input stream
* parse is done within this constructor itself.
*
* This method is for providers subclassing MimeMessage
.
*
* @param folder The containing folder.
* @param is the message input stream
* @param msgnum Message number of this message within its folder
* @exception MessagingException for failures
*/
protected MimeMessage(Folder folder, InputStream is, int msgnum)
throws MessagingException {
this(folder, msgnum);
initStrict();
parse(is);
}
/**
* Constructs a MimeMessage from the given InternetHeaders object
* and content.
*
* This method is for providers subclassing MimeMessage
.
*
* @param folder The containing folder.
* @param headers The headers
* @param content The message content
* @param msgnum Message number of this message within its folder
* @exception MessagingException for failures
*/
protected MimeMessage(Folder folder, InternetHeaders headers,
byte[] content, int msgnum) throws MessagingException {
this(folder, msgnum);
this.headers = headers;
this.content = content;
initStrict();
}
/**
* Set the strict flag based on property.
*/
private void initStrict() {
if (session != null) {
Properties props = session.getProperties();
strict = PropUtil.getBooleanProperty(props,
"mail.mime.address.strict", true);
allowutf8 = PropUtil.getBooleanProperty(props,
"mail.mime.allowutf8", false);
}
}
/**
* Parse the InputStream setting the headers
and
* content
fields appropriately. Also resets the
* modified
flag.
*
* This method is intended for use by subclasses that need to
* control when the InputStream is parsed.
*
* @param is The message input stream
* @exception MessagingException for failures
*/
protected void parse(InputStream is) throws MessagingException {
if (!(is instanceof ByteArrayInputStream) &&
!(is instanceof BufferedInputStream) &&
!(is instanceof SharedInputStream))
is = new BufferedInputStream(is);
headers = createInternetHeaders(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("IOException", ioex);
}
}
modified = false;
}
/**
* Returns the value of the RFC 822 "From" header fields. If this
* header field is absent, the "Sender" header field is used.
* If the "Sender" header field is also absent, null
* is returned.
*
* This implementation uses the getHeader
method
* to obtain the requisite header field.
*
* @return Address object
* @exception MessagingException for failures
* @see #headers
*/
@Override
public Address[] getFrom() throws MessagingException {
Address[] a = getAddressHeader("From");
if (a == null)
a = getAddressHeader("Sender");
return a;
}
/**
* Set the RFC 822 "From" header field. Any existing values are
* replaced with the given address. If address is null
,
* this header is removed.
*
* @param address the sender of this message
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void setFrom(Address address) throws MessagingException {
setAddressHeader("From", new Address[] { address });
}
/**
* Set the RFC 822 "From" header field. Any existing values are
* replaced with the given addresses. If address is null
,
* this header is removed.
*
* @param address the sender(s) of this message
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
* @since JvaMail 1.5
*/
public void setFrom(String address) throws MessagingException {
setAddressHeader("From", InternetAddress.parse(address));
}
/**
* Set the RFC 822 "From" header field using the value of the
* InternetAddress.getLocalAddress
method.
*
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void setFrom() throws MessagingException {
InternetAddress me = null;
try {
me = InternetAddress._getLocalAddress(session);
} catch (Exception ex) {
// if anything goes wrong (SecurityException, UnknownHostException),
// chain the exception
throw new MessagingException("No From address", ex);
}
if (me != null)
setFrom(me);
else
throw new MessagingException("No From address");
}
/**
* Add the specified addresses to the existing "From" field. If
* the "From" field does not already exist, it is created.
*
* @param addresses the senders of this message
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void addFrom(Address[] addresses) throws MessagingException {
addAddressHeader("From", addresses);
}
/**
* Returns the value of the RFC 822 "Sender" header field.
* If the "Sender" header field is absent, null
* is returned.
*
* This implementation uses the getHeader
method
* to obtain the requisite header field.
*
* @return Address object
* @exception MessagingException for failures
* @see #headers
* @since JavaMail 1.3
*/
public Address getSender() throws MessagingException {
Address[] a = getAddressHeader("Sender");
if (a == null || a.length == 0)
return null;
return a[0]; // there can be only one
}
/**
* Set the RFC 822 "Sender" header field. Any existing values are
* replaced with the given address. If address is null
,
* this header is removed.
*
* @param address the sender of this message
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
* @since JavaMail 1.3
*/
public void setSender(Address address) throws MessagingException {
setAddressHeader("Sender", new Address[] { address });
}
/**
* This inner class extends the jakarta.mail.Message.RecipientType
* class to add additional RecipientTypes. The one additional
* RecipientType currently defined here is NEWSGROUPS.
*
* @see jakarta.mail.Message.RecipientType
*/
public static class RecipientType extends Message.RecipientType {
private static final long serialVersionUID = -5468290701714395543L;
/**
* The "Newsgroup" (Usenet news) recipients.
*/
public static final RecipientType NEWSGROUPS =
new RecipientType("Newsgroups");
protected RecipientType(String type) {
super(type);
}
@Override
protected Object readResolve() throws ObjectStreamException {
if (type.equals("Newsgroups"))
return NEWSGROUPS;
else
return super.readResolve();
}
}
/**
* Returns the recepients specified by the type. The mapping
* between the type and the corresponding RFC 822 header is
* as follows:
*
* Message.RecipientType.TO "To"
* Message.RecipientType.CC "Cc"
* Message.RecipientType.BCC "Bcc"
* MimeMessage.RecipientType.NEWSGROUPS "Newsgroups"
*
*
* Returns null if the header specified by the type is not found
* or if its value is empty.
*
* This implementation uses the getHeader
method
* to obtain the requisite header field.
*
* @param type Type of recepient
* @return array of Address objects
* @exception MessagingException if header could not
* be retrieved
* @exception AddressException if the header is misformatted
* @see #headers
* @see jakarta.mail.Message.RecipientType#TO
* @see jakarta.mail.Message.RecipientType#CC
* @see jakarta.mail.Message.RecipientType#BCC
* @see jakarta.mail.internet.MimeMessage.RecipientType#NEWSGROUPS
*/
@Override
public Address[] getRecipients(Message.RecipientType type)
throws MessagingException {
if (type == RecipientType.NEWSGROUPS) {
String s = getHeader("Newsgroups", ",");
return (s == null) ? null : NewsAddress.parse(s);
} else
return getAddressHeader(getHeaderName(type));
}
/**
* Get all the recipient addresses for the message.
* Extracts the TO, CC, BCC, and NEWSGROUPS recipients.
*
* @return array of Address objects
* @exception MessagingException for failures
* @see jakarta.mail.Message.RecipientType#TO
* @see jakarta.mail.Message.RecipientType#CC
* @see jakarta.mail.Message.RecipientType#BCC
* @see jakarta.mail.internet.MimeMessage.RecipientType#NEWSGROUPS
*/
@Override
public Address[] getAllRecipients() throws MessagingException {
Address[] all = super.getAllRecipients();
Address[] ng = getRecipients(RecipientType.NEWSGROUPS);
if (ng == null)
return all; // the common case
if (all == null)
return ng; // a rare case
Address[] addresses = new Address[all.length + ng.length];
System.arraycopy(all, 0, addresses, 0, all.length);
System.arraycopy(ng, 0, addresses, all.length, ng.length);
return addresses;
}
/**
* Set the specified recipient type to the given addresses.
* If the address parameter is null
, the corresponding
* recipient field is removed.
*
* @param type Recipient type
* @param addresses Addresses
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
* @see #getRecipients
*/
@Override
public void setRecipients(Message.RecipientType type, Address[] addresses)
throws MessagingException {
if (type == RecipientType.NEWSGROUPS) {
if (addresses == null || addresses.length == 0)
removeHeader("Newsgroups");
else
setHeader("Newsgroups", NewsAddress.toString(addresses));
} else
setAddressHeader(getHeaderName(type), addresses);
}
/**
* Set the specified recipient type to the given addresses.
* If the address parameter is null
, the corresponding
* recipient field is removed.
*
* @param type Recipient type
* @param addresses Addresses
* @exception AddressException if the attempt to parse the
* addresses String fails
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
* @see #getRecipients
* @since JavaMail 1.2
*/
public void setRecipients(Message.RecipientType type, String addresses)
throws MessagingException {
if (type == RecipientType.NEWSGROUPS) {
if (addresses == null || addresses.length() == 0)
removeHeader("Newsgroups");
else
setHeader("Newsgroups", addresses);
} else
setAddressHeader(getHeaderName(type),
addresses == null ? null : InternetAddress.parse(addresses));
}
/**
* Add the given addresses to the specified recipient type.
*
* @param type Recipient type
* @param addresses Addresses
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void addRecipients(Message.RecipientType type, Address[] addresses)
throws MessagingException {
if (type == RecipientType.NEWSGROUPS) {
String s = NewsAddress.toString(addresses);
if (s != null)
addHeader("Newsgroups", s);
} else
addAddressHeader(getHeaderName(type), addresses);
}
/**
* Add the given addresses to the specified recipient type.
*
* @param type Recipient type
* @param addresses Addresses
* @exception AddressException if the attempt to parse the
* addresses String fails
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
* @since JavaMail 1.2
*/
public void addRecipients(Message.RecipientType type, String addresses)
throws MessagingException {
if (type == RecipientType.NEWSGROUPS) {
if (addresses != null && addresses.length() != 0)
addHeader("Newsgroups", addresses);
} else
addAddressHeader(getHeaderName(type),
InternetAddress.parse(addresses));
}
/**
* Return the value of the RFC 822 "Reply-To" header field. If
* this header is unavailable or its value is absent, then
* the getFrom
method is called and its value is returned.
*
* This implementation uses the getHeader
method
* to obtain the requisite header field.
*
* @exception MessagingException for failures
* @see #headers
*/
@Override
public Address[] getReplyTo() throws MessagingException {
Address[] a = getAddressHeader("Reply-To");
if (a == null || a.length == 0)
a = getFrom();
return a;
}
/**
* Set the RFC 822 "Reply-To" header field. If the address
* parameter is null
, this header is removed.
*
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void setReplyTo(Address[] addresses) throws MessagingException {
setAddressHeader("Reply-To", addresses);
}
// Convenience method to get addresses
private Address[] getAddressHeader(String name)
throws MessagingException {
String s = getHeader(name, ",");
return (s == null) ? null : InternetAddress.parseHeader(s, strict);
}
// Convenience method to set addresses
private void setAddressHeader(String name, Address[] addresses)
throws MessagingException {
String s;
if (allowutf8)
s = InternetAddress.toUnicodeString(addresses, name.length() + 2);
else
s = InternetAddress.toString(addresses, name.length() + 2);
if (s == null)
removeHeader(name);
else
setHeader(name, s);
}
private void addAddressHeader(String name, Address[] addresses)
throws MessagingException {
if (addresses == null || addresses.length == 0)
return;
Address[] a = getAddressHeader(name);
Address[] anew;
if (a == null || a.length == 0)
anew = addresses;
else {
anew = new Address[a.length + addresses.length];
System.arraycopy(a, 0, anew, 0, a.length);
System.arraycopy(addresses, 0, anew, a.length, addresses.length);
}
String s;
if (allowutf8)
s = InternetAddress.toUnicodeString(anew, name.length() + 2);
else
s = InternetAddress.toString(anew, name.length() + 2);
if (s == null)
return;
setHeader(name, s);
}
/**
* Returns the value of the "Subject" header field. Returns null
* if the subject field is unavailable or its value is absent.
*
* If the subject 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 the getHeader
method
* to obtain the requisite header field.
*
* @return Subject
* @exception MessagingException for failures
* @see #headers
*/
@Override
public String getSubject() throws MessagingException {
String rawvalue = getHeader("Subject", null);
if (rawvalue == null)
return null;
try {
return MimeUtility.decodeText(MimeUtility.unfold(rawvalue));
} catch (UnsupportedEncodingException ex) {
return rawvalue;
}
}
/**
* Set the "Subject" header field. If the subject contains
* non US-ASCII characters, it will be encoded using the
* platform's default charset. If the subject contains only
* US-ASCII characters, no encoding is done and it is used
* as-is. If the subject is null, the existing "Subject" field
* is removed.
*
* The application must ensure that the subject does not contain
* any line breaks.
*
* 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 subject The subject
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void setSubject(String subject) throws MessagingException {
setSubject(subject, null);
}
/**
* Set the "Subject" header field. If the subject contains non
* US-ASCII characters, it will be encoded using the specified
* charset. If the subject contains only US-ASCII characters, no
* encoding is done and it is used as-is. If the subject is null,
* the existing "Subject" header field is removed.
*
* The application must ensure that the subject does not contain
* any line breaks.
*
* 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 subject The subject
* @param charset The charset
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
public void setSubject(String subject, String charset)
throws MessagingException {
if (subject == null) {
removeHeader("Subject");
} else {
try {
setHeader("Subject", MimeUtility.fold(9,
MimeUtility.encodeText(subject, charset, null)));
} catch (UnsupportedEncodingException uex) {
throw new MessagingException("Encoding error", uex);
}
}
}
/**
* Returns the value of the RFC 822 "Date" field. This is the date
* on which this message was sent. Returns null if this field is
* unavailable or its value is absent.
*
* This implementation uses the getHeader
method
* to obtain the requisite header field.
*
* @return The sent Date
* @exception MessagingException for failures
*/
@Override
public Date getSentDate() throws MessagingException {
String s = getHeader("Date", null);
if (s != null) {
try {
synchronized (mailDateFormat) {
return mailDateFormat.parse(s);
}
} catch (ParseException pex) {
return null;
}
}
return null;
}
/**
* Set the RFC 822 "Date" header field. This is the date on which the
* creator of the message indicates that the message is complete
* and ready for delivery. If the date parameter is
* null
, the existing "Date" field is removed.
*
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void setSentDate(Date d) throws MessagingException {
if (d == null)
removeHeader("Date");
else {
synchronized (mailDateFormat) {
setHeader("Date", mailDateFormat.format(d));
}
}
}
/**
* Returns the Date on this message was received. Returns
* null
if this date cannot be obtained.
*
* Note that RFC 822 does not define a field for the received
* date. Hence only implementations that can provide this date
* need return a valid value.
*
* This implementation returns null
.
*
* @return the date this message was received
* @exception MessagingException for failures
*/
@Override
public Date getReceivedDate() throws MessagingException {
return null;
}
/**
* Return the size of the content of this message 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 of content in bytes
* @exception MessagingException for failures
*/
@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 greater 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 message.
* 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 in the content.
* @exception MessagingException for failures
*/
@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
* message. This value must not be null. If this field is
* unavailable, "text/plain" should be returned.
*
* This implementation uses the getHeader
method
* to obtain the requisite header field.
*
* @return The ContentType of this part
* @exception MessagingException for failures
* @see jakarta.activation.DataHandler
*/
@Override
public String getContentType() throws MessagingException {
String s = getHeader("Content-Type", null);
s = MimeUtil.cleanContentType(this, s);
if (s == null)
return "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.
*
* @param mimeType the MIME type to check
* @return true if it matches the MIME type
* @exception MessagingException for failures
*/
@Override
public boolean isMimeType(String mimeType) throws MessagingException {
return MimeBodyPart.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 the getHeader
method
* to obtain the requisite header field.
*
* @return disposition of this part, or null if unknown
* @exception MessagingException for failures
*/
@Override
public String getDisposition() throws MessagingException {
return MimeBodyPart.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 message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void setDisposition(String disposition) throws MessagingException {
MimeBodyPart.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 the getHeader
method
* to obtain the requisite header field.
*
* @return content-transfer-encoding
* @exception MessagingException for failures
*/
@Override
public String getEncoding() throws MessagingException {
return MimeBodyPart.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 the getHeader
method
* to obtain the requisite header field.
*
* @return content-ID
* @exception MessagingException for failures
*/
@Override
public String getContentID() throws MessagingException {
return getHeader("Content-Id", null);
}
/**
* Set the "Content-ID" header field of this Message.
* 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 message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
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 the getHeader
method
* to obtain the requisite header field.
*
* @return content-MD5
* @exception MessagingException for failures
*/
@Override
public String getContentMD5() throws MessagingException {
return getHeader("Content-MD5", null);
}
/**
* Set the "Content-MD5" header field of this Message.
*
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void setContentMD5(String md5) throws MessagingException {
setHeader("Content-MD5", md5);
}
/**
* Returns the "Content-Description" header field of this Message.
* 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 the getHeader
method
* to obtain the requisite header field.
*
* @return content-description
* @exception MessagingException for failures
*/
@Override
public String getDescription() throws MessagingException {
return MimeBodyPart.getDescription(this);
}
/**
* Set the "Content-Description" header field for this Message.
* 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 message is
* obtained from a READ_ONLY folder.
* @exception MessagingException 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 Message.
* 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 message is
* obtained from a READ_ONLY folder.
* @exception MessagingException An
* UnsupportedEncodingException may be included
* in the exception chain if the charset
* conversion fails.
*/
public void setDescription(String description, String charset)
throws MessagingException {
MimeBodyPart.setDescription(this, description, charset);
}
/**
* Get the languages specified in the "Content-Language" header
* field of this message. The Content-Language header is defined by
* RFC 1766. Returns null
if this field is unavailable
* or its value is absent.
*
* This implementation uses the getHeader
method
* to obtain the requisite header field.
*
* @return value of content-language header.
* @exception MessagingException for failures
*/
@Override
public String[] getContentLanguage() throws MessagingException {
return MimeBodyPart.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
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void setContentLanguage(String[] languages)
throws MessagingException {
MimeBodyPart.setContentLanguage(this, languages);
}
/**
* Returns the value of the "Message-ID" header field. Returns
* null if this field is unavailable or its value is absent.
*
* The default implementation provided here uses the
* getHeader
method to return the value of the
* "Message-ID" field.
*
* @return Message-ID
* @exception MessagingException if the retrieval of this field
* causes any exception.
* @see jakarta.mail.search.MessageIDTerm
* @since JavaMail 1.1
*/
public String getMessageID() throws MessagingException {
return getHeader("Message-ID", null);
}
/**
* Get the filename associated with this Message.
*
* Returns the value of the "filename" parameter from the
* "Content-Disposition" header field of this message. If it's
* not available, returns the value of the "name" parameter from
* the "Content-Type" header field of this BodyPart.
* Returns null
if both are absent.
*
* If the mail.mime.encodefilename
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
* @exception MessagingException for failures
*/
@Override
public String getFileName() throws MessagingException {
return MimeBodyPart.getFileName(this);
}
/**
* Set the filename associated with this part, if possible.
*
* Sets the "filename" parameter of the "Content-Disposition"
* header field of this message.
*
* 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.
*
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void setFileName(String filename) throws MessagingException {
MimeBodyPart.setFileName(this, filename);
}
private String getHeaderName(Message.RecipientType type)
throws MessagingException {
String headerName;
if (type == Message.RecipientType.TO)
headerName = "To";
else if (type == Message.RecipientType.CC)
headerName = "Cc";
else if (type == Message.RecipientType.BCC)
headerName = "Bcc";
else if (type == MimeMessage.RecipientType.NEWSGROUPS)
headerName = "Newsgroups";
else
throw new MessagingException("Invalid Recipient Type");
return headerName;
}
/**
* Return a decoded input stream for this Message'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
* jakarta.activation.DataHandler for more details.
* @exception MessagingException for other failures
*
* @see #getContentStream
* @see jakarta.activation.DataHandler#getInputStream
*/
@Override
public InputStream getInputStream()
throws IOException, MessagingException {
return getDataHandler().getInputStream();
}
/**
* Produce the raw bytes of the content. This method is used during
* parsing, to create a DataHandler object for the content. Subclasses
* that can provide a separate input stream for just the message
* content might want to override this method.
*
* This implementation returns a SharedInputStream, if
* contentStream
is not null. Otherwise, it
* returns a ByteArrayInputStream constructed
* out of the content
byte array.
*
* @return an InputStream containing the raw bytes
* @exception MessagingException for failures
* @see #content
*/
protected InputStream getContentStream() throws MessagingException {
if (contentStream != null)
return ((SharedInputStream)contentStream).newStream(0, -1);
if (content != null)
return new SharedByteArrayInputStream(content);
throw new MessagingException("No MimeMessage 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 Message's content.
*
* The implementation provided here works approximately as follows.
* Note the use of the getContentStream
method to
* generate the byte stream for the content. Also note that
* any transfer-decoding is done automatically within this method.
*
*
* getDataHandler() {
* if (dh == null) {
* dh = new DataHandler(new MimePartDataSource(this));
* }
* return dh;
* }
*
* class MimePartDataSource implements DataSource {
* public getInputStream() {
* return MimeUtility.decode(
* getContentStream(), getEncoding());
* }
*
* .... <other DataSource methods>
* }
*
*
* @exception MessagingException for failures
*/
@Override
public synchronized DataHandler getDataHandler()
throws MessagingException {
if (dh == null)
dh = new MimeBodyPart.MimePartDataHandler(this);
return dh;
}
/**
* Return the content as a Java object. The type of this
* object is 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"
* message 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
* @see jakarta.mail.Part
* @see jakarta.activation.DataHandler#getContent
* @exception IOException this is typically thrown by the
* DataHandler. Refer to the documentation for
* jakarta.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 (MimeBodyPart.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 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 message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public synchronized void setDataHandler(DataHandler dh)
throws MessagingException {
this.dh = dh;
cachedContent = null;
MimeBodyPart.invalidateContentHeaders(this);
}
/**
* A convenience method for setting this Message'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.
* i.e., 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 message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@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
* setText
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 {
MimeBodyPart.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 {
MimeBodyPart.setText(this, text, charset, subtype);
}
/**
* This method sets the Message'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 message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void setContent(Multipart mp) throws MessagingException {
setDataHandler(new DataHandler(mp, mp.getContentType()));
mp.setParent(this);
}
/**
* Get a new Message suitable for a reply to this message.
* The new Message will have its attributes and headers
* set up appropriately. Note that this new message object
* will be empty, i.e., it will not have a "content".
* These will have to be suitably filled in by the client.
*
* If replyToAll
is set, the new Message will be addressed
* to all recipients of this message. Otherwise, the reply will be
* addressed to only the sender of this message (using the value
* of the getReplyTo
method).
*
* The "Subject" field is filled in with the original subject
* prefixed with "Re:" (unless it already starts with "Re:").
* The "In-Reply-To" header is set in the new message if this
* message has a "Message-Id" header. The ANSWERED
* flag is set in this message.
*
* The current implementation also sets the "References" header
* in the new message to include the contents of the "References"
* header (or, if missing, the "In-Reply-To" header) in this message,
* plus the contents of the "Message-Id" header of this message,
* as described in RFC 2822.
*
* @param replyToAll reply should be sent to all recipients
* of this message
* @return the reply Message
* @exception MessagingException for failures
*/
@Override
public Message reply(boolean replyToAll) throws MessagingException {
return reply(replyToAll, true);
}
/**
* Get a new Message suitable for a reply to this message.
* The new Message will have its attributes and headers
* set up appropriately. Note that this new message object
* will be empty, i.e., it will not have a "content".
* These will have to be suitably filled in by the client.
*
* If replyToAll
is set, the new Message will be addressed
* to all recipients of this message. Otherwise, the reply will be
* addressed to only the sender of this message (using the value
* of the getReplyTo
method).
*
* If setAnswered
is set, the
* {@link jakarta.mail.Flags.Flag#ANSWERED ANSWERED} flag is set
* in this message.
*
* The "Subject" field is filled in with the original subject
* prefixed with "Re:" (unless it already starts with "Re:").
* The "In-Reply-To" header is set in the new message if this
* message has a "Message-Id" header.
*
* The current implementation also sets the "References" header
* in the new message to include the contents of the "References"
* header (or, if missing, the "In-Reply-To" header) in this message,
* plus the contents of the "Message-Id" header of this message,
* as described in RFC 2822.
*
* @param replyToAll reply should be sent to all recipients
* of this message
* @param setAnswered set the ANSWERED flag in this message?
* @return the reply Message
* @exception MessagingException for failures
* @since JavaMail 1.5
*/
public Message reply(boolean replyToAll, boolean setAnswered)
throws MessagingException {
MimeMessage reply = createMimeMessage(session);
/*
* Have to manipulate the raw Subject header so that we don't lose
* any encoding information. This is safe because "Re:" isn't
* internationalized and (generally) isn't encoded. If the entire
* Subject header is encoded, prefixing it with "Re: " still leaves
* a valid and correct encoded header.
*/
String subject = getHeader("Subject", null);
if (subject != null) {
if (!subject.regionMatches(true, 0, "Re: ", 0, 4))
subject = "Re: " + subject;
reply.setHeader("Subject", subject);
}
Address a[] = getReplyTo();
reply.setRecipients(Message.RecipientType.TO, a);
if (replyToAll) {
List
v = new ArrayList<>();
// add my own address to list
InternetAddress me = InternetAddress.getLocalAddress(session);
if (me != null)
v.add(me);
// add any alternate names I'm known by
String alternates = null;
if (session != null)
alternates = session.getProperty("mail.alternates");
if (alternates != null)
eliminateDuplicates(v,
InternetAddress.parse(alternates, false));
// should we Cc all other original recipients?
boolean replyallcc = false;
if (session != null)
replyallcc = PropUtil.getBooleanProperty(
session.getProperties(),
"mail.replyallcc", false);
// add the recipients from the To field so far
eliminateDuplicates(v, a);
a = getRecipients(Message.RecipientType.TO);
a = eliminateDuplicates(v, a);
if (a != null && a.length > 0) {
if (replyallcc)
reply.addRecipients(Message.RecipientType.CC, a);
else
reply.addRecipients(Message.RecipientType.TO, a);
}
a = getRecipients(Message.RecipientType.CC);
a = eliminateDuplicates(v, a);
if (a != null && a.length > 0)
reply.addRecipients(Message.RecipientType.CC, a);
// don't eliminate duplicate newsgroups
a = getRecipients(RecipientType.NEWSGROUPS);
if (a != null && a.length > 0)
reply.setRecipients(RecipientType.NEWSGROUPS, a);
}
String msgId = getHeader("Message-Id", null);
if (msgId != null)
reply.setHeader("In-Reply-To", msgId);
/*
* Set the References header as described in RFC 2822:
*
* The "References:" field will contain the contents of the parent's
* "References:" field (if any) followed by the contents of the parent's
* "Message-ID:" field (if any). If the parent message does not contain
* a "References:" field but does have an "In-Reply-To:" field
* containing a single message identifier, then the "References:" field
* will contain the contents of the parent's "In-Reply-To:" field
* followed by the contents of the parent's "Message-ID:" field (if
* any). If the parent has none of the "References:", "In-Reply-To:",
* or "Message-ID:" fields, then the new message will have no
* "References:" field.
*/
String refs = getHeader("References", " ");
if (refs == null) {
// XXX - should only use if it contains a single message identifier
refs = getHeader("In-Reply-To", " ");
}
if (msgId != null) {
if (refs != null)
refs = MimeUtility.unfold(refs) + " " + msgId;
else
refs = msgId;
}
if (refs != null)
reply.setHeader("References", MimeUtility.fold(12, refs));
if (setAnswered) {
try {
setFlags(answeredFlag, true);
} catch (MessagingException mex) {
// ignore it
}
}
return reply;
}
// used above in reply()
private static final Flags answeredFlag = new Flags(Flags.Flag.ANSWERED);
/**
* Check addrs for any duplicates that may already be in v.
* Return a new array without the duplicates. Add any new
* addresses to v. Note that the input array may be modified.
*/
private Address[] eliminateDuplicates(List v, Address[] addrs) {
if (addrs == null)
return null;
int gone = 0;
for (int i = 0; i < addrs.length; i++) {
boolean found = false;
// search the list for this address
for (int j = 0; j < v.size(); j++) {
if (((InternetAddress)v.get(j)).equals(addrs[i])) {
// found it; count it and remove it from the input array
found = true;
gone++;
addrs[i] = null;
break;
}
}
if (!found)
v.add(addrs[i]); // add new address to list
}
// if we found any duplicates, squish the array
if (gone != 0) {
Address[] a;
// new array should be same type as original array
// XXX - there must be a better way, perhaps reflection?
if (addrs instanceof InternetAddress[])
a = new InternetAddress[addrs.length - gone];
else
a = new Address[addrs.length - gone];
for (int i = 0, j = 0; i < addrs.length; i++)
if (addrs[i] != null)
a[j++] = addrs[i];
addrs = a;
}
return addrs;
}
/**
* Output the message as an RFC 822 format stream.
*
* Note that, depending on how the messag was constructed, it may
* use a variety of line termination conventions. Generally the
* output should be sent through an appropriate FilterOutputStream
* that converts the line terminators to the desired form, either
* CRLF for MIME compatibility and for use in Internet protocols,
* or the local platform's line terminator for storage in a local
* text file.
*
* This implementation calls the writeTo(OutputStream,
* String[])
method with a null ignore list.
*
* @exception IOException if an error occurs writing to the stream
* or if an error is generated by the
* jakarta.activation layer.
* @exception MessagingException for other failures
* @see jakarta.activation.DataHandler#writeTo
*/
@Override
public void writeTo(OutputStream os)
throws IOException, MessagingException {
writeTo(os, null);
}
/**
* Output the message as an RFC 822 format stream, without
* specified headers. If the saved
flag is not set,
* the saveChanges
method is called.
* If the modified
flag is not
* set and the content
array is not null, the
* content
array is written directly, after
* writing the appropriate message headers.
*
* @param os the stream to write to
* @param ignoreList the headers to not include in the output
* @exception IOException if an error occurs writing to the stream
* or if an error is generated by the
* jakarta.activation layer.
* @exception jakarta.mail.MessagingException for other failures
* @see jakarta.activation.DataHandler#writeTo
*/
public void writeTo(OutputStream os, String[] ignoreList)
throws IOException, MessagingException {
if (!saved)
saveChanges();
if (modified) {
MimeBodyPart.writeTo(this, os, ignoreList);
return;
}
// Else, the content is untouched, so we can just output it
// First, write out the header
Enumeration hdrLines = getNonMatchingHeaderLines(ignoreList);
LineOutputStream los = new LineOutputStream(os, allowutf8);
while (hdrLines.hasMoreElements())
los.writeln(hdrLines.nextElement());
// The CRLF separator between header and content
los.writeln();
// Finally, the content.
if (content == null) {
// call getContentStream to give subclass a chance to
// provide the data on demand
InputStream is = null;
byte[] buf = new byte[8192];
try {
is = getContentStream();
// now copy the data to the output stream
int len;
while ((len = is.read(buf)) > 0)
os.write(buf, 0, len);
} finally {
if (is != null)
is.close();
buf = null;
}
} else {
os.write(content);
}
os.flush();
}
/**
* 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.
*
* This implementation obtains the headers from the
* headers
InternetHeaders object.
*
* @param name name of header
* @return array of headers
* @exception MessagingException for failures
* @see jakarta.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 separator between values
* @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 have been encoded by the
* caller as per the rules of RFC 2047.
*
* @param name header name
* @param value header value
* @see jakarta.mail.internet.MimeUtility
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@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 have been encoded as per the rules of RFC 2047.
*
* @param name header name
* @param value header value
* @see jakarta.mail.internet.MimeUtility
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void addHeader(String name, String value)
throws MessagingException {
headers.addHeader(name, value);
}
/**
* Remove all headers with this name.
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void removeHeader(String name)
throws MessagingException {
headers.removeHeader(name);
}
/**
* Return all the headers from this Message as an enumeration
* of Header objects.
*
* Note that certain headers may be encoded as per RFC 2047
* if they contain non US-ASCII characters and these should
* be decoded.
*
* This implementation obtains the headers from the
* headers
InternetHeaders object.
*
* @return array of header objects
* @exception MessagingException for failures
* @see jakarta.mail.internet.MimeUtility
*/
@Override
public Enumeration getAllHeaders() throws MessagingException {
return headers.getAllHeaders();
}
/**
* Return matching headers from this Message as an Enumeration of
* Header objects. This implementation obtains the headers from
* the headers
InternetHeaders object.
*
* @exception MessagingException for failures
*/
@Override
public Enumeration getMatchingHeaders(String[] names)
throws MessagingException {
return headers.getMatchingHeaders(names);
}
/**
* Return non-matching headers from this Message as an
* Enumeration of Header objects. This implementation
* obtains the header from the headers
InternetHeaders object.
*
* @exception MessagingException for failures
*/
@Override
public Enumeration getNonMatchingHeaders(String[] names)
throws MessagingException {
return headers.getNonMatchingHeaders(names);
}
/**
* Add a raw RFC 822 header-line.
*
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
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.
*
* @exception MessagingException for failures
*/
@Override
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.
*
* @exception MessagingException for failures
*/
@Override
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.
*
* @exception MessagingException for failures
*/
@Override
public Enumeration getNonMatchingHeaderLines(String[] names)
throws MessagingException {
return headers.getNonMatchingHeaderLines(names);
}
/**
* Return a Flags
object containing the flags for
* this message.
*
* Note that a clone of the internal Flags object is returned, so
* modifying the returned Flags object will not affect the flags
* of this message.
*
* @return Flags object containing the flags for this message
* @exception MessagingException for failures
* @see jakarta.mail.Flags
*/
@Override
public synchronized Flags getFlags() throws MessagingException {
return (Flags)flags.clone();
}
/**
* Check whether the flag specified in the flag
* argument is set in this message.
*
* This implementation checks this message's internal
* flags
object.
*
* @param flag the flag
* @return value of the specified flag for this message
* @exception MessagingException for failures
* @see jakarta.mail.Flags.Flag
* @see jakarta.mail.Flags.Flag#ANSWERED
* @see jakarta.mail.Flags.Flag#DELETED
* @see jakarta.mail.Flags.Flag#DRAFT
* @see jakarta.mail.Flags.Flag#FLAGGED
* @see jakarta.mail.Flags.Flag#RECENT
* @see jakarta.mail.Flags.Flag#SEEN
*/
@Override
public synchronized boolean isSet(Flags.Flag flag)
throws MessagingException {
return (flags.contains(flag));
}
/**
* Set the flags for this message.
*
* This implementation modifies the flags
field.
*
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public synchronized void setFlags(Flags flag, boolean set)
throws MessagingException {
if (set)
flags.add(flag);
else
flags.remove(flag);
}
/**
* Updates the appropriate header fields of this message to be
* consistent with the message's contents. If this message is
* contained in a Folder, any changes made to this message are
* committed to the containing folder.
*
* If any part of a message's headers or contents are changed,
* saveChanges
must be called to ensure that those
* changes are permanent. Otherwise, any such modifications may or
* may not be saved, depending on the folder implementation.
*
* Messages obtained from folders opened READ_ONLY should not be
* modified and saveChanges should not be called on such messages.
*
* This method sets the modified
flag to true, the
* save
flag to true, and then calls the
* updateHeaders
method.
*
* @exception IllegalWriteException if the underlying
* implementation does not support modification
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
@Override
public void saveChanges() throws MessagingException {
modified = true;
saved = true;
updateHeaders();
}
/**
* Update the Message-ID header. This method is called
* by the updateHeaders
and allows a subclass
* to override only the algorithm for choosing a Message-ID.
*
* @exception MessagingException for failures
* @since JavaMail 1.4
*/
protected void updateMessageID() throws MessagingException {
setHeader("Message-ID",
"<" + UniqueValue.getUniqueMessageIDValue(session) + ">");
}
/**
* Called by the saveChanges
method to actually
* update the MIME headers. The implementation here sets the
* Content-Transfer-Encoding
header (if needed
* and not already set), the Date
header (if
* not already set), the MIME-Version
header
* and the Message-ID
header. Also, if the content
* of this message is a MimeMultipart
, its
* updateHeaders
method is called.
*
* 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 IllegalWriteException if the underlying
* implementation does not support modification
* @exception IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @exception MessagingException for other failures
*/
protected synchronized void updateHeaders() throws MessagingException {
MimeBodyPart.updateHeaders(this);
setHeader("MIME-Version", "1.0");
if (getHeader("Date") == null)
setSentDate(new Date());
updateMessageID();
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;
}
}
/**
* Create and return an InternetHeaders object that loads the
* headers from the given InputStream. Subclasses can override
* this method to return a subclass of InternetHeaders, if
* necessary. This implementation simply constructs and returns
* an InternetHeaders object.
*
* @return an InternetHeaders object
* @param is the InputStream to read the headers from
* @exception MessagingException for failures
* @since JavaMail 1.2
*/
protected InternetHeaders createInternetHeaders(InputStream is)
throws MessagingException {
return new InternetHeaders(is, allowutf8);
}
/**
* Create and return a MimeMessage object. The reply method
* uses this method to create the MimeMessage object that it
* will return. Subclasses can override this method to return
* a subclass of MimeMessage. This implementation simply constructs
* and returns a MimeMessage object using the supplied Session.
*
* @param session the Session to use for the new message
* @return the new MimeMessage object
* @exception MessagingException for failures
* @since JavaMail 1.4
*/
protected MimeMessage createMimeMessage(Session session)
throws MessagingException {
return new MimeMessage(session);
}
}