You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by ri...@apache.org on 2006/07/14 12:02:29 UTC
svn commit: r421852 [7/15] - in /geronimo/specs/trunk: ./
geronimo-spec-j2ee/ geronimo-spec-javamail-1.3.1/
geronimo-spec-javamail-1.3.1/src/ geronimo-spec-javamail-1.4/
geronimo-spec-javamail-1.4/src/ geronimo-spec-javamail-1.4/src/main/
geronimo-spec...
Added: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimeMessage.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimeMessage.java?rev=421852&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimeMessage.java (added)
+++ geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimeMessage.java Fri Jul 14 03:02:19 2006
@@ -0,0 +1,1584 @@
+/**
+ *
+ * Copyright 2003-2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectStreamException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.activation.DataHandler;
+import javax.mail.Address;
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Part;
+import javax.mail.Session;
+import javax.mail.internet.HeaderTokenizer.Token;
+
+import org.apache.geronimo.mail.util.ASCIIUtil;
+import org.apache.geronimo.mail.util.SessionUtil;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class MimeMessage extends Message implements MimePart {
+ private static final String MIME_ADDRESS_STRICT = "mail.mime.address.strict";
+ private static final String MIME_DECODEFILENAME = "mail.mime.decodefilename";
+ private static final String MIME_ENCODEFILENAME = "mail.mime.encodefilename";
+
+ private static final String MAIL_ALTERNATES = "mail.alternates";
+ private static final String MAIL_REPLYALLCC = "mail.replyallcc";
+
+ // static used to ensure message ID uniqueness
+ private static int messageID = 0;
+
+
+ /**
+ * Extends {@link javax.mail.Message.RecipientType} to support addition recipient types.
+ */
+ public static class RecipientType extends Message.RecipientType {
+ /**
+ * Recipient type for Usenet news.
+ */
+ public static final RecipientType NEWSGROUPS = new RecipientType("Newsgroups");
+
+ protected RecipientType(String type) {
+ super(type);
+ }
+
+ /**
+ * Ensure the singleton is returned.
+ *
+ * @return resolved object
+ */
+ protected Object readResolve() throws ObjectStreamException {
+ if (this.type.equals("Newsgroups")) {
+ return NEWSGROUPS;
+ } else {
+ return super.readResolve();
+ }
+ }
+ }
+
+ /**
+ * The {@link DataHandler} for this Message's content.
+ */
+ protected DataHandler dh;
+ /**
+ * This message's content (unless sourced from a SharedInputStream).
+ */
+ protected byte[] content;
+ /**
+ * If the data for this message was supplied by a {@link SharedInputStream}
+ * then this is another such stream representing the content of this message;
+ * if this field is non-null, then {@link #content} will be null.
+ */
+ protected InputStream contentStream;
+ /**
+ * This message's headers.
+ */
+ protected InternetHeaders headers;
+ /**
+ * This message's flags.
+ */
+ protected Flags flags;
+ /**
+ * Flag indicating that the message has been modified; set to true when
+ * an empty message is created or when {@link #saveChanges()} is called.
+ */
+ protected boolean modified;
+ /**
+ * Flag indicating that the message has been saved.
+ */
+ protected boolean saved;
+
+ private final MailDateFormat dateFormat = new MailDateFormat();
+
+ /**
+ * Create a new MimeMessage.
+ * An empty message is created, with empty {@link #headers} and empty {@link #flags}.
+ * The {@link #modified} flag is set.
+ *
+ * @param session the session for this message
+ */
+ public MimeMessage(Session session) {
+ super(session);
+ headers = new InternetHeaders();
+ flags = new Flags();
+ // empty messages are modified, because the content is not there, and require saving before use.
+ modified = true;
+ saved = false;
+ }
+
+ /**
+ * Create a MimeMessage by reading an parsing the data from the supplied stream.
+ *
+ * @param session the session for this message
+ * @param in the stream to load from
+ * @throws MessagingException if there is a problem reading or parsing the stream
+ */
+ public MimeMessage(Session session, InputStream in) throws MessagingException {
+ this(session);
+ parse(in);
+ // this message is complete, so marked as unmodified.
+ modified = false;
+ // and no saving required
+ saved = true;
+ }
+
+ /**
+ * Copy a MimeMessage.
+ *
+ * @param message the message to copy
+ * @throws MessagingException is there was a problem copying the message
+ */
+ public MimeMessage(MimeMessage message) throws MessagingException {
+ super(message.session);
+ // this is somewhat difficult to do. There's a lot of data in both the superclass and this
+ // class that needs to undergo a "deep cloning" operation. These operations don't really exist
+ // on the objects in question, so the only solution I can come up with is to serialize the
+ // message data of the source object using the write() method, then reparse the data in this
+ // object. I've not found a lot of uses for this particular constructor, so perhaps that's not
+ // really all that bad of a solution.
+
+ // serialized this out to an in-memory stream.
+ ByteArrayOutputStream copy = new ByteArrayOutputStream();
+
+ try {
+ // write this out the stream.
+ message.writeTo(copy);
+ copy.close();
+ // I think this ends up creating a new array for the data, but I'm not aware of any more
+ // efficient options.
+ ByteArrayInputStream inData = new ByteArrayInputStream(copy.toByteArray());
+ // now reparse this message into this object.
+ inData.close();
+ parse (inData);
+ // writing out the source data requires saving it, so we should consider this one saved also.
+ saved = true;
+ // this message is complete, so marked as unmodified.
+ modified = false;
+ } catch (IOException e) {
+ // I'm not sure ByteArrayInput/OutputStream actually throws IOExceptions or not, but the method
+ // signatures declare it, so we need to deal with it. Turning it into a messaging exception
+ // should fit the bill.
+ throw new MessagingException("Error copying MimeMessage data", e);
+ }
+ }
+
+ /**
+ * Create an new MimeMessage in the supplied {@link Folder} and message number.
+ *
+ * @param folder the Folder that contains the new message
+ * @param number the message number of the new message
+ */
+ protected MimeMessage(Folder folder, int number) {
+ super(folder, number);
+ headers = new InternetHeaders();
+ flags = new Flags();
+ // saving primarly involves updates to the message header. Since we're taking the header info
+ // from a message store in this context, we mark the message as saved.
+ saved = true;
+ // we've not filled in the content yet, so this needs to be marked as modified
+ modified = true;
+ }
+
+ /**
+ * Create a MimeMessage by reading an parsing the data from the supplied stream.
+ *
+ * @param folder the folder for this message
+ * @param in the stream to load from
+ * @param number the message number of the new message
+ * @throws MessagingException if there is a problem reading or parsing the stream
+ */
+ protected MimeMessage(Folder folder, InputStream in, int number) throws MessagingException {
+ this(folder, number);
+ parse(in);
+ // this message is complete, so marked as unmodified.
+ modified = false;
+ // and no saving required
+ saved = true;
+ }
+
+
+ /**
+ * Create a MimeMessage with the supplied headers and content.
+ *
+ * @param folder the folder for this message
+ * @param headers the headers for the new message
+ * @param content the content of the new message
+ * @param number the message number of the new message
+ * @throws MessagingException if there is a problem reading or parsing the stream
+ */
+ protected MimeMessage(Folder folder, InternetHeaders headers, byte[] content, int number) throws MessagingException {
+ this(folder, number);
+ this.headers = headers;
+ this.content = content;
+ // this message is complete, so marked as unmodified.
+ modified = false;
+ }
+
+ /**
+ * Parse the supplied stream and initialize {@link #headers} and {@link #content} appropriately.
+ *
+ * @param in the stream to read
+ * @throws MessagingException if there was a problem parsing the stream
+ */
+ protected void parse(InputStream in) throws MessagingException {
+ in = new BufferedInputStream(in);
+ // create the headers first from the stream
+ headers = new InternetHeaders(in);
+
+ // now we need to get the rest of the content as a byte array...this means reading from the current
+ // position in the stream until the end and writing it to an accumulator ByteArrayOutputStream.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ byte buffer[] = new byte[1024];
+ int count;
+ while ((count = in.read(buffer, 0, 1024)) != -1) {
+ baos.write(buffer, 0, count);
+ }
+ } catch (Exception e) {
+ throw new MessagingException(e.toString(), e);
+ }
+ // and finally extract the content as a byte array.
+ content = baos.toByteArray();
+ }
+
+ /**
+ * Get the message "From" addresses. This looks first at the
+ * "From" headers, and no "From" header is found, the "Sender"
+ * header is checked. Returns null if not found.
+ *
+ * @return An array of addresses identifying the message from target. Returns
+ * null if this is not resolveable from the headers.
+ * @exception MessagingException
+ */
+ public Address[] getFrom() throws MessagingException {
+ // strict addressing controls this.
+ boolean strict = isStrictAddressing();
+ Address[] result = getHeaderAsInternetAddresses("From", strict);
+ if (result == null) {
+ result = getHeaderAsInternetAddresses("Sender", strict);
+ }
+ return result;
+ }
+
+ /**
+ * Set the current message "From" recipient. This replaces any
+ * existing "From" header. If the address is null, the header is
+ * removed.
+ *
+ * @param address The new "From" target.
+ *
+ * @exception MessagingException
+ */
+ public void setFrom(Address address) throws MessagingException {
+ setHeader("From", address);
+ }
+
+ /**
+ * Set the "From" header using the value returned by {@link InternetAddress#getLocalAddress(javax.mail.Session)}.
+ *
+ * @throws MessagingException if there was a problem setting the header
+ */
+ public void setFrom() throws MessagingException {
+ InternetAddress address = InternetAddress.getLocalAddress(session);
+ // no local address resolvable? This is an error.
+ if (address == null) {
+ throw new MessagingException("No local address defined");
+ }
+ setFrom(address);
+ }
+
+ /**
+ * Add a set of addresses to the existing From header.
+ *
+ * @param addresses The list to add.
+ *
+ * @exception MessagingException
+ */
+ public void addFrom(Address[] addresses) throws MessagingException {
+ addHeader("From", addresses);
+ }
+
+ /**
+ * Return the "Sender" header as an address.
+ *
+ * @return the "Sender" header as an address, or null if not present
+ * @throws MessagingException if there was a problem parsing the header
+ */
+ public Address getSender() throws MessagingException {
+ Address[] addrs = getHeaderAsInternetAddresses("Sender", isStrictAddressing());
+ return addrs != null && addrs.length > 0 ? addrs[0] : null;
+ }
+
+ /**
+ * Set the "Sender" header. If the address is null, this
+ * will remove the current sender header.
+ *
+ * @param address the new Sender address
+ *
+ * @throws MessagingException
+ * if there was a problem setting the header
+ */
+ public void setSender(Address address) throws MessagingException {
+ setHeader("Sender", address);
+ }
+
+ /**
+ * Gets the recipients by type. Returns null if there are no
+ * headers of the specified type. Acceptable RecipientTypes are:
+ *
+ * javax.mail.Message.RecipientType.TO
+ * javax.mail.Message.RecipientType.CC
+ * javax.mail.Message.RecipientType.BCC
+ * javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS
+ *
+ * @param type The message RecipientType identifier.
+ *
+ * @return The array of addresses for the specified recipient types.
+ * @exception MessagingException
+ */
+ public Address[] getRecipients(Message.RecipientType type) throws MessagingException {
+ // is this a NEWSGROUP request? We need to handle this as a special case here, because
+ // this needs to return NewsAddress instances instead of InternetAddress items.
+ if (type == RecipientType.NEWSGROUPS) {
+ return getHeaderAsNewsAddresses(getHeaderForRecipientType(type));
+ }
+ // the other types are all internet addresses.
+ return getHeaderAsInternetAddresses(getHeaderForRecipientType(type), isStrictAddressing());
+ }
+
+ /**
+ * Retrieve all of the recipients defined for this message. This
+ * returns a merged array of all possible message recipients
+ * extracted from the headers. The relevant header types are:
+ *
+ *
+ * javax.mail.Message.RecipientType.TO
+ * javax.mail.Message.RecipientType.CC
+ * javax.mail.Message.RecipientType.BCC
+ * javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS
+ *
+ * @return An array of all target message recipients.
+ * @exception MessagingException
+ */
+ public Address[] getAllRecipients() throws MessagingException {
+ List recipients = new ArrayList();
+ addRecipientsToList(recipients, RecipientType.TO);
+ addRecipientsToList(recipients, RecipientType.CC);
+ addRecipientsToList(recipients, RecipientType.BCC);
+ addRecipientsToList(recipients, RecipientType.NEWSGROUPS);
+
+ // this is supposed to return null if nothing is there.
+ if (recipients.isEmpty()) {
+ return null;
+ }
+ return (Address[]) recipients.toArray(new Address[recipients.size()]);
+ }
+
+ /**
+ * Utility routine to merge different recipient types into a
+ * single list.
+ *
+ * @param list The accumulator list.
+ * @param type The recipient type to extract.
+ *
+ * @exception MessagingException
+ */
+ private void addRecipientsToList(List list, Message.RecipientType type) throws MessagingException {
+
+ Address[] recipients;
+ if (type == RecipientType.NEWSGROUPS) {
+ recipients = getHeaderAsNewsAddresses(getHeaderForRecipientType(type));
+ }
+ else {
+ recipients = getHeaderAsInternetAddresses(getHeaderForRecipientType(type), isStrictAddressing());
+ }
+ if (recipients != null) {
+ list.addAll(Arrays.asList(recipients));
+ }
+ }
+
+ /**
+ * Set a recipients list for a particular recipient type. If the
+ * list is null, the corresponding header is removed.
+ *
+ * @param type The type of recipient to set.
+ * @param addresses The list of addresses.
+ *
+ * @exception MessagingException
+ */
+ public void setRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException {
+ setHeader(getHeaderForRecipientType(type), addresses);
+ }
+
+ /**
+ * Set a recipient field to a string address (which may be a
+ * list or group type).
+ *
+ * If the address is null, the field is removed.
+ *
+ * @param type The type of recipient to set.
+ * @param address The address string.
+ *
+ * @exception MessagingException
+ */
+ public void setRecipients(Message.RecipientType type, String address) throws MessagingException {
+ setOrRemoveHeader(getHeaderForRecipientType(type), address);
+ }
+
+
+ /**
+ * Add a list of addresses to a target recipient list.
+ *
+ * @param type The target recipient type.
+ * @param address An array of addresses to add.
+ *
+ * @exception MessagingException
+ */
+ public void addRecipients(Message.RecipientType type, Address[] address) throws MessagingException {
+ addHeader(getHeaderForRecipientType(type), address);
+ }
+
+ /**
+ * Add an address to a target recipient list by string name.
+ *
+ * @param type The target header type.
+ * @param address The address to add.
+ *
+ * @exception MessagingException
+ */
+ public void addRecipients(Message.RecipientType type, String address) throws MessagingException {
+ addHeader(getHeaderForRecipientType(type), address);
+ }
+
+ /**
+ * Get the ReplyTo address information. The headers are parsed
+ * using the "mail.mime.address.strict" setting. If the "Reply-To" header does
+ * not have any addresses, then the value of the "From" field is used.
+ *
+ * @return An array of addresses obtained from parsing the header.
+ * @exception MessagingException
+ */
+ public Address[] getReplyTo() throws MessagingException {
+ Address[] addresses = getHeaderAsInternetAddresses("Reply-To", isStrictAddressing());
+ if (addresses == null) {
+ addresses = getFrom();
+ }
+ return addresses;
+ }
+
+ /**
+ * Set the Reply-To field to the provided list of addresses. If
+ * the address list is null, the header is removed.
+ *
+ * @param address The new field value.
+ *
+ * @exception MessagingException
+ */
+ public void setReplyTo(Address[] address) throws MessagingException {
+ setHeader("Reply-To", address);
+ }
+
+ /**
+ * Returns the value of the "Subject" header. If the subject
+ * is encoded as an RFC 2047 value, the value is decoded before
+ * return. If decoding fails, the raw string value is
+ * returned.
+ *
+ * @return The String value of the subject field.
+ * @exception MessagingException
+ */
+ public String getSubject() throws MessagingException {
+ String subject = getSingleHeader("Subject");
+ if (subject == null) {
+ return null;
+ } else {
+ try {
+ // this needs to be unfolded before decodeing.
+ return MimeUtility.decodeText(MimeUtility.unfold(subject));
+ } catch (UnsupportedEncodingException e) {
+ // ignored.
+ }
+ }
+
+ return subject;
+ }
+
+ /**
+ * Set the value for the "Subject" header. If the subject
+ * contains non US-ASCII characters, it is encoded in RFC 2047
+ * fashion.
+ *
+ * If the subject value is null, the Subject field is removed.
+ *
+ * @param subject The new subject value.
+ *
+ * @exception MessagingException
+ */
+ public void setSubject(String subject) throws MessagingException {
+ // just set this using the default character set.
+ setSubject(subject, null);
+ }
+
+ public void setSubject(String subject, String charset) throws MessagingException {
+ // standard null removal (yada, yada, yada....)
+ if (subject == null) {
+ removeHeader("Subject");
+ }
+ else {
+ try {
+ String s = MimeUtility.fold(9, MimeUtility.encodeText(subject, charset, null));
+ // encode this, and then fold to fit the line lengths.
+ setHeader("Subject", MimeUtility.fold(9, MimeUtility.encodeText(subject, charset, null)));
+ } catch (UnsupportedEncodingException e) {
+ throw new MessagingException("Encoding error", e);
+ }
+ }
+ }
+
+ /**
+ * Get the value of the "Date" header field. Returns null if
+ * if the field is absent or the date is not in a parseable format.
+ *
+ * @return A Date object parsed according to RFC 822.
+ * @exception MessagingException
+ */
+ public Date getSentDate() throws MessagingException {
+ String value = getSingleHeader("Date");
+ if (value == null) {
+ return null;
+ }
+ try {
+ return dateFormat.parse(value);
+ } catch (java.text.ParseException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Set the message sent date. This updates the "Date" header.
+ * If the provided date is null, the header is removed.
+ *
+ * @param sent The new sent date value.
+ *
+ * @exception MessagingException
+ */
+ public void setSentDate(Date sent) throws MessagingException {
+ setOrRemoveHeader("Date", dateFormat.format(sent));
+ }
+
+ /**
+ * Get the message received date. The Sun implementation is
+ * documented as always returning null, so this one does too.
+ *
+ * @return Always returns null.
+ * @exception MessagingException
+ */
+ public Date getReceivedDate() throws MessagingException {
+ return null;
+ }
+
+ /**
+ * Return the content size of this message. This is obtained
+ * either from the size of the content field (if available) or
+ * from the contentStream, IFF the contentStream returns a positive
+ * size. Returns -1 if the size is not available.
+ *
+ * @return Size of the content in bytes.
+ * @exception MessagingException
+ */
+ public int getSize() throws MessagingException {
+ if (content != null) {
+ return content.length;
+ }
+ if (contentStream != null) {
+ try {
+ int size = contentStream.available();
+ if (size > 0) {
+ return size;
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Retrieve the line count for the current message. Returns
+ * -1 if the count cannot be determined.
+ *
+ * The Sun implementation always returns -1, so this version
+ * does too.
+ *
+ * @return The content line count (always -1 in this implementation).
+ * @exception MessagingException
+ */
+ public int getLineCount() throws MessagingException {
+ return -1;
+ }
+
+ /**
+ * Returns the current content type (defined in the "Content-Type"
+ * header. If not available, "text/plain" is the default.
+ *
+ * @return The String name of the message content type.
+ * @exception MessagingException
+ */
+ public String getContentType() throws MessagingException {
+ String value = getSingleHeader("Content-Type");
+ if (value == null) {
+ value = "text/plain";
+ }
+ return value;
+ }
+
+
+ /**
+ * Tests to see if this message has a mime-type match with the
+ * given type name.
+ *
+ * @param type The tested type name.
+ *
+ * @return If this is a type match on the primary and secondare portion of the types.
+ * @exception MessagingException
+ */
+ public boolean isMimeType(String type) throws MessagingException {
+ return new ContentType(getContentType()).match(type);
+ }
+
+ /**
+ * Retrieve the message "Content-Disposition" header field.
+ * This value represents how the part should be represented to
+ * the user.
+ *
+ * @return The string value of the Content-Disposition field.
+ * @exception MessagingException
+ */
+ public String getDisposition() throws MessagingException {
+ String disp = getSingleHeader("Content-Disposition");
+ if (disp != null) {
+ return new ContentDisposition(disp).getDisposition();
+ }
+ return null;
+ }
+
+
+ /**
+ * Set a new dispostion value for the "Content-Disposition" field.
+ * If the new value is null, the header is removed.
+ *
+ * @param disposition
+ * The new disposition value.
+ *
+ * @exception MessagingException
+ */
+ public void setDisposition(String disposition) throws MessagingException {
+ if (disposition == null) {
+ removeHeader("Content-Disposition");
+ }
+ else {
+ // the disposition has parameters, which we'll attempt to preserve in any existing header.
+ String currentHeader = getSingleHeader("Content-Disposition");
+ if (currentHeader != null) {
+ ContentDisposition content = new ContentDisposition(currentHeader);
+ content.setDisposition(disposition);
+ setHeader("Content-Disposition", content.toString());
+ }
+ else {
+ // set using the raw string.
+ setHeader("Content-Disposition", disposition);
+ }
+ }
+ }
+
+ /**
+ * Decode the Content-Transfer-Encoding header to determine
+ * the transfer encoding type.
+ *
+ * @return The string name of the required encoding.
+ * @exception MessagingException
+ */
+ public String getEncoding() throws MessagingException {
+ // this might require some parsing to sort out.
+ String encoding = getSingleHeader("Content-Transfer-Encoding");
+ if (encoding != null) {
+ // we need to parse this into ATOMs and other constituent parts. We want the first
+ // ATOM token on the string.
+ HeaderTokenizer tokenizer = new HeaderTokenizer(encoding, HeaderTokenizer.MIME);
+
+ Token token = tokenizer.next();
+ while (token.getType() != Token.EOF) {
+ // if this is an ATOM type, return it.
+ if (token.getType() == Token.ATOM) {
+ return token.getValue();
+ }
+ }
+ // not ATOMs found, just return the entire header value....somebody might be able to make sense of
+ // this.
+ return encoding;
+ }
+ // no header, nothing to return.
+ return null;
+ }
+
+ /**
+ * Retrieve the value of the "Content-ID" header. Returns null
+ * if the header does not exist.
+ *
+ * @return The current header value or null.
+ * @exception MessagingException
+ */
+ public String getContentID() throws MessagingException {
+ return getSingleHeader("Content-ID");
+ }
+
+ public void setContentID(String cid) throws MessagingException {
+ setOrRemoveHeader("Content-ID", cid);
+ }
+
+ public String getContentMD5() throws MessagingException {
+ return getSingleHeader("Content-MD5");
+ }
+
+ public void setContentMD5(String md5) throws MessagingException {
+ setOrRemoveHeader("Content-MD5", md5);
+ }
+
+ public String getDescription() throws MessagingException {
+ String description = getSingleHeader("Content-Description");
+ if (description != null) {
+ try {
+ // this could be both folded and encoded. Return this to usable form.
+ return MimeUtility.decodeText(MimeUtility.unfold(description));
+ } catch (UnsupportedEncodingException e) {
+ // ignore
+ }
+ }
+ // return the raw version for any errors.
+ return description;
+ }
+
+ public void setDescription(String description) throws MessagingException {
+ setDescription(description, null);
+ }
+
+ public void setDescription(String description, String charset) throws MessagingException {
+ if (description == null) {
+ removeHeader("Content-Description");
+ }
+ else {
+ try {
+ setHeader("Content-Description", MimeUtility.fold(21, MimeUtility.encodeText(description, charset, null)));
+ } catch (UnsupportedEncodingException e) {
+ throw new MessagingException(e.getMessage(), e);
+ }
+ }
+
+ }
+
+ public String[] getContentLanguage() throws MessagingException {
+ return getHeader("Content-Language");
+ }
+
+ public void setContentLanguage(String[] languages) throws MessagingException {
+ if (languages == null) {
+ removeHeader("Content-Language");
+ } else if (languages.length == 1) {
+ setHeader("Content-Language", languages[0]);
+ } else {
+ StringBuffer buf = new StringBuffer(languages.length * 20);
+ buf.append(languages[0]);
+ for (int i = 1; i < languages.length; i++) {
+ buf.append(',').append(languages[i]);
+ }
+ setHeader("Content-Language", buf.toString());
+ }
+ }
+
+ public String getMessageID() throws MessagingException {
+ return getSingleHeader("Message-ID");
+ }
+
+ public String getFileName() throws MessagingException {
+ // see if there is a disposition. If there is, parse off the filename parameter.
+ String disposition = getDisposition();
+ String filename = null;
+
+ if (disposition != null) {
+ filename = new ContentDisposition(disposition).getParameter("filename");
+ }
+
+ // if there's no filename on the disposition, there might be a name parameter on a
+ // Content-Type header.
+ if (filename == null) {
+ String type = getContentType();
+ if (type != null) {
+ try {
+ filename = new ContentType(type).getParameter("name");
+ } catch (ParseException e) {
+ }
+ }
+ }
+ // if we have a name, we might need to decode this if an additional property is set.
+ if (filename != null && SessionUtil.getBooleanProperty(session, MIME_DECODEFILENAME, false)) {
+ try {
+ filename = MimeUtility.decodeText(filename);
+ } catch (UnsupportedEncodingException e) {
+ throw new MessagingException("Unable to decode filename", e);
+ }
+ }
+
+ return filename;
+ }
+
+
+ public void setFileName(String name) throws MessagingException {
+ // there's an optional session property that requests file name encoding...we need to process this before
+ // setting the value.
+ if (name != null && SessionUtil.getBooleanProperty(session, MIME_ENCODEFILENAME, false)) {
+ try {
+ name = MimeUtility.encodeText(name);
+ } catch (UnsupportedEncodingException e) {
+ throw new MessagingException("Unable to encode filename", e);
+ }
+ }
+
+ // get the disposition string.
+ String disposition = getDisposition();
+ // if not there, then this is an attachment.
+ if (disposition == null) {
+ disposition = Part.ATTACHMENT;
+ }
+ // now create a disposition object and set the parameter.
+ ContentDisposition contentDisposition = new ContentDisposition(disposition);
+ contentDisposition.setParameter("filename", name);
+
+ // serialize this back out and reset.
+ setDisposition(contentDisposition.toString());
+ }
+
+ public InputStream getInputStream() throws MessagingException, IOException {
+ return getDataHandler().getInputStream();
+ }
+
+ protected InputStream getContentStream() throws MessagingException {
+ if (contentStream != null) {
+ return contentStream;
+ }
+
+ if (content != null) {
+ return new ByteArrayInputStream(content);
+ } else {
+ throw new MessagingException("No content");
+ }
+ }
+
+ public InputStream getRawInputStream() throws MessagingException {
+ return getContentStream();
+ }
+
+ public synchronized DataHandler getDataHandler() throws MessagingException {
+ if (dh == null) {
+ dh = new DataHandler(new MimePartDataSource(this));
+ }
+ return dh;
+ }
+
+ public Object getContent() throws MessagingException, IOException {
+ return getDataHandler().getContent();
+ }
+
+ public void setDataHandler(DataHandler handler) throws MessagingException {
+ dh = handler;
+ // if we have a handler override, then we need to invalidate any content
+ // headers that define the types. This information will be derived from the
+ // data heander unless subsequently overridden.
+ removeHeader("Content-Type");
+ removeHeader("Content-Transfer-Encoding");
+ }
+
+ public void setContent(Object content, String type) throws MessagingException {
+ setDataHandler(new DataHandler(content, type));
+ }
+
+ public void setText(String text) throws MessagingException {
+ setText(text, null, "plain");
+ }
+
+ public void setText(String text, String charset) throws MessagingException {
+ setText(text, charset, "plain");
+ }
+
+
+ public void setText(String text, String charset, String subtype) throws MessagingException {
+ // we need to sort out the character set if one is not provided.
+ if (charset == null) {
+ // if we have non us-ascii characters here, we need to adjust this.
+ if (!ASCIIUtil.isAscii(text)) {
+ charset = MimeUtility.getDefaultMIMECharset();
+ }
+ else {
+ charset = "us-ascii";
+ }
+ }
+ setContent(text, "text/" + subtype + "; charset=" + MimeUtility.quote(charset, HeaderTokenizer.MIME));
+ }
+
+ public void setContent(Multipart part) throws MessagingException {
+ setDataHandler(new DataHandler(part, part.getContentType()));
+ part.setParent(this);
+ }
+
+ public Message reply(boolean replyToAll) throws MessagingException {
+ // create a new message in this session.
+ MimeMessage reply = createMimeMessage(session);
+
+ // get the header and add the "Re:" bit, if necessary.
+ String newSubject = getSubject();
+ if (newSubject != null) {
+ // check to see if it already begins with "Re: " (in any case).
+ // Add one on if we don't have it yet.
+ if (!newSubject.regionMatches(true, 0, "Re: ", 0, 4)) {
+ newSubject = "Re: " + newSubject;
+ }
+ reply.setSubject(newSubject);
+ }
+
+ Address[] toRecipients = getReplyTo();
+
+ // set the target recipients the replyTo value
+ reply.setRecipients(Message.RecipientType.TO, getReplyTo());
+
+ // need to reply to everybody? More things to add.
+ if (replyToAll) {
+ // when replying, we want to remove "duplicates" in the final list.
+
+ HashMap masterList = new HashMap();
+
+ // reply to all implies add the local sender. Add this to the list if resolveable.
+ InternetAddress localMail = InternetAddress.getLocalAddress(session);
+ if (localMail != null) {
+ masterList.put(localMail.getAddress(), localMail);
+ }
+ // see if we have some local aliases to deal with.
+ String alternates = session.getProperty(MAIL_ALTERNATES);
+ if (alternates != null) {
+ // parse this string list and merge with our set.
+ Address[] alternateList = InternetAddress.parse(alternates, false);
+ mergeAddressList(masterList, alternateList);
+ }
+
+ // the master list now contains an a list of addresses we will exclude from
+ // the addresses. From this point on, we're going to prune any additional addresses
+ // against this list, AND add any new addresses to the list
+
+ // now merge in the main recipients, and merge in the other recipents as well
+ Address[] toList = pruneAddresses(masterList, getRecipients(Message.RecipientType.TO));
+ if (toList.length != 0) {
+ // now check to see what sort of reply we've been asked to send.
+ // if replying to all as a CC, then we need to add to the CC list, otherwise they are
+ // TO recipients.
+ if (SessionUtil.getBooleanProperty(session, MAIL_REPLYALLCC, false)) {
+ reply.addRecipients(Message.RecipientType.CC, toList);
+ }
+ else {
+ reply.addRecipients(Message.RecipientType.TO, toList);
+ }
+ }
+ // and repeat for the CC list.
+ toList = pruneAddresses(masterList, getRecipients(Message.RecipientType.CC));
+ if (toList.length != 0) {
+ reply.addRecipients(Message.RecipientType.CC, toList);
+ }
+
+ // a news group list is separate from the normal addresses. We just take these recepients
+ // asis without trying to prune duplicates.
+ toList = getRecipients(RecipientType.NEWSGROUPS);
+ if (toList != null && toList.length != 0) {
+ reply.addRecipients(RecipientType.NEWSGROUPS, toList);
+ }
+ }
+
+ // this is a bit of a pain. We can't set the flags here by specifying the system flag, we need to
+ // construct a flag item instance inorder to set it.
+
+ // this is an answered email.
+ setFlags(new Flags(Flags.Flag.ANSWERED), true);
+ // all done, return the constructed Message object.
+ return reply;
+ }
+
+
+ /**
+ * Merge a set of addresses into a master accumulator list, eliminating
+ * duplicates.
+ *
+ * @param master The set of addresses we've accumulated so far.
+ * @param list The list of addresses to merge in.
+ */
+ private void mergeAddressList(Map master, Address[] list) {
+ // make sure we have a list.
+ if (list == null) {
+ return;
+ }
+ for (int i = 0; i < list.length; i++) {
+ InternetAddress address = (InternetAddress)list[i];
+
+ // if not in the master list already, add it now.
+ if (!master.containsKey(address.getAddress())) {
+ master.put(address.getAddress(), address);
+ }
+ }
+ }
+
+
+ /**
+ * Prune a list of addresses against our master address list,
+ * returning the "new" addresses. The master list will be
+ * updated with this new set of addresses.
+ *
+ * @param master The master address list of addresses we've seen before.
+ * @param list The new list of addresses to prune.
+ *
+ * @return An array of addresses pruned of any duplicate addresses.
+ */
+ private Address[] pruneAddresses(Map master, Address[] list) {
+ // return an empy array if we don't get an input list.
+ if (list == null) {
+ return new Address[0];
+ }
+
+ // optimistically assume there are no addresses to eliminate (common).
+ ArrayList prunedList = new ArrayList(list.length);
+ for (int i = 0; i < list.length; i++) {
+ InternetAddress address = (InternetAddress)list[i];
+
+ // if not in the master list, this is a new one. Add to both the master list and
+ // the pruned list.
+ if (!master.containsKey(address.getAddress())) {
+ master.put(address.getAddress(), address);
+ prunedList.add(address);
+ }
+ }
+ // convert back to list form.
+ return (Address[])prunedList.toArray(new Address[0]);
+ }
+
+
+ /**
+ * Write the message out to a stream in RFC 822 format.
+ *
+ * @param out The target output stream.
+ *
+ * @exception MessagingException
+ * @exception IOException
+ */
+ public void writeTo(OutputStream out) throws MessagingException, IOException {
+ writeTo(out, null);
+ }
+
+ /**
+ * Write the message out to a target output stream, excluding the
+ * specified message headers.
+ *
+ * @param out The target output stream.
+ * @param ignoreHeaders
+ * An array of header types to ignore. This can be null, which means
+ * write out all headers.
+ *
+ * @exception MessagingException
+ * @exception IOException
+ */
+ public void writeTo(OutputStream out, String[] ignoreHeaders) throws MessagingException, IOException {
+ // make sure everything is saved before we write
+ if (!saved) {
+ saveChanges();
+ }
+
+ // write out the headers first
+ headers.writeTo(out, ignoreHeaders);
+ // add the separater between the headers and the data portion.
+ out.write('\r');
+ out.write('\n');
+
+ // if the modfied flag, we don't have current content, so the data handler needs to
+ // take care of writing this data out.
+ if (modified) {
+ dh.writeTo(MimeUtility.encode(out, getEncoding()));
+ } else {
+ // if we have content directly, we can write this out now.
+ if (content != null) {
+ out.write(content);
+ }
+ else {
+ // see if we can get a content stream for this message. We might have had one
+ // explicitly set, or a subclass might override the get method to provide one.
+ InputStream in = getContentStream();
+
+ byte[] buffer = new byte[8192];
+ int length = in.read(buffer);
+ // copy the data stream-to-stream.
+ while (length > 0) {
+ out.write(buffer, 0, length);
+ length = in.read(buffer);
+ }
+ in.close();
+ }
+ }
+
+ // flush any data we wrote out, but do not close the stream. That's the caller's duty.
+ out.flush();
+ }
+
+
+ /**
+ * Retrieve all headers that match a given name.
+ *
+ * @param name The target name.
+ *
+ * @return The set of headers that match the given name. These headers
+ * will be the decoded() header values if these are RFC 2047
+ * encoded.
+ * @exception MessagingException
+ */
+ public String[] getHeader(String name) throws MessagingException {
+ return headers.getHeader(name);
+ }
+
+ /**
+ * Get all headers that match a particular name, as a single string.
+ * Individual headers are separated by the provided delimiter. If
+ * the delimiter is null, only the first header is returned.
+ *
+ * @param name The source header name.
+ * @param delimiter The delimiter string to be used between headers. If null, only
+ * the first is returned.
+ *
+ * @return The headers concatenated as a single string.
+ * @exception MessagingException
+ */
+ public String getHeader(String name, String delimiter) throws MessagingException {
+ return headers.getHeader(name, delimiter);
+ }
+
+ /**
+ * Set a new value for a named header.
+ *
+ * @param name The name of the target header.
+ * @param value The new value for the header.
+ *
+ * @exception MessagingException
+ */
+ public void setHeader(String name, String value) throws MessagingException {
+ headers.setHeader(name, value);
+ }
+
+ /**
+ * Conditionally set or remove a named header. If the new value
+ * is null, the header is removed.
+ *
+ * @param name The header name.
+ * @param value The new header value. A null value causes the header to be
+ * removed.
+ *
+ * @exception MessagingException
+ */
+ private void setOrRemoveHeader(String name, String value) throws MessagingException {
+ if (value == null) {
+ headers.removeHeader(name);
+ }
+ else {
+ headers.setHeader(name, value);
+ }
+ }
+
+ /**
+ * Add a new value to an existing header. The added value is
+ * created as an additional header of the same type and value.
+ *
+ * @param name The name of the target header.
+ * @param value The removed header.
+ *
+ * @exception MessagingException
+ */
+ public void addHeader(String name, String value) throws MessagingException {
+ headers.addHeader(name, value);
+ }
+
+ /**
+ * Remove a header with the given name.
+ *
+ * @param name The name of the removed header.
+ *
+ * @exception MessagingException
+ */
+ public void removeHeader(String name) throws MessagingException {
+ headers.removeHeader(name);
+ }
+
+ /**
+ * Retrieve the complete list of message headers, as an enumeration.
+ *
+ * @return An Enumeration of the message headers.
+ * @exception MessagingException
+ */
+ public Enumeration getAllHeaders() throws MessagingException {
+ return headers.getAllHeaders();
+ }
+
+ public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
+ return headers.getMatchingHeaders(names);
+ }
+
+ public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
+ return headers.getNonMatchingHeaders(names);
+ }
+
+ public void addHeaderLine(String line) throws MessagingException {
+ headers.addHeaderLine(line);
+ }
+
+ public Enumeration getAllHeaderLines() throws MessagingException {
+ return headers.getAllHeaderLines();
+ }
+
+ public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
+ return headers.getMatchingHeaderLines(names);
+ }
+
+ public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
+ return headers.getNonMatchingHeaderLines(names);
+ }
+
+ public synchronized Flags getFlags() throws MessagingException {
+ return (Flags) flags.clone();
+ }
+
+ public synchronized boolean isSet(Flags.Flag flag) throws MessagingException {
+ return flags.contains(flag);
+ }
+
+ /**
+ * Set or clear a flag value.
+ *
+ * @param flags The set of flags to effect.
+ * @param set The value to set the flag to (true or false).
+ *
+ * @exception MessagingException
+ */
+ public synchronized void setFlags(Flags flag, boolean set) throws MessagingException {
+ if (set) {
+ flags.add(flag);
+ }
+ else {
+ flags.remove(flag);
+ }
+ }
+
+ /**
+ * Saves any changes on this message. When called, the modified
+ * and saved flags are set to true and updateHeaders() is called
+ * to force updates.
+ *
+ * @exception MessagingException
+ */
+ public void saveChanges() throws MessagingException {
+ // setting modified invalidates the current content.
+ modified = true;
+ saved = true;
+ // update message headers from the content.
+ updateHeaders();
+ }
+
+ /**
+ * Update the internet headers so that they make sense. This
+ * will attempt to make sense of the message content type
+ * given the state of the content.
+ *
+ * @exception MessagingException
+ */
+ protected void updateHeaders() throws MessagingException {
+
+ // make sure we set the MIME version
+ setHeader("MIME-Version", "1.0");
+
+ DataHandler handler = getDataHandler();
+
+ try {
+ // figure out the content type. If not set, we'll need to figure this out.
+ String type = dh.getContentType();
+ // parse this content type out so we can do matches/compares.
+ ContentType content = new ContentType(type);
+
+ // is this a multipart content?
+ if (content.match("multipart/*")) {
+ // the content is suppose to be a MimeMultipart. Ping it to update it's headers as well.
+ try {
+ MimeMultipart part = (MimeMultipart)handler.getContent();
+ part.updateHeaders();
+ } catch (ClassCastException e) {
+ throw new MessagingException("Message content is not MimeMultipart", e);
+ }
+ }
+ else if (!content.match("message/rfc822")) {
+ // simple part, we need to update the header type information
+ // if no encoding is set yet, figure this out from the data handler content.
+ if (getSingleHeader("Content-Transfer-Encoding") == null) {
+ setHeader("Content-Transfer-Encoding", MimeUtility.getEncoding(handler));
+ }
+
+ // is a content type header set? Check the property to see if we need to set this.
+ if (getSingleHeader("Content-Type") == null) {
+ if (SessionUtil.getBooleanProperty(session, "MIME_MAIL_SETDEFAULTTEXTCHARSET", true)) {
+ // is this a text type? Figure out the encoding and make sure it is set.
+ if (content.match("text/*")) {
+ // the charset should be specified as a parameter on the MIME type. If not there,
+ // try to figure one out.
+ if (content.getParameter("charset") == null) {
+
+ String encoding = getEncoding();
+ // if we're sending this as 7-bit ASCII, our character set need to be
+ // compatible.
+ if (encoding != null && encoding.equalsIgnoreCase("7bit")) {
+ content.setParameter("charset", "us-ascii");
+ }
+ else {
+ // get the global default.
+ content.setParameter("charset", MimeUtility.getDefaultMIMECharset());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // if we don't have a content type header, then create one.
+ if (getSingleHeader("Content-Type") == null) {
+ // get the disposition header, and if it is there, copy the filename parameter into the
+ // name parameter of the type.
+ String disp = getSingleHeader("Content-Disposition");
+ if (disp != null) {
+ // parse up the string value of the disposition
+ ContentDisposition disposition = new ContentDisposition(disp);
+ // now check for a filename value
+ String filename = disposition.getParameter("filename");
+ // copy and rename the parameter, if it exists.
+ if (filename != null) {
+ content.setParameter("name", filename);
+ }
+ }
+ // set the header with the updated content type information.
+ setHeader("Content-Type", content.toString());
+ }
+
+ // new javamail 1.4 requirement.
+ updateMessageID();
+
+ } catch (IOException e) {
+ throw new MessagingException("Error updating message headers", e);
+ }
+ }
+
+
+ protected InternetHeaders createInternetHeaders(InputStream in) throws MessagingException {
+ // internet headers has a constructor for just this purpose
+ return new InternetHeaders(in);
+ }
+
+ /**
+ * Convert a header into an array of NewsAddress items.
+ *
+ * @param header The name of the source header.
+ *
+ * @return The parsed array of addresses.
+ * @exception MessagingException
+ */
+ private Address[] getHeaderAsNewsAddresses(String header) throws MessagingException {
+ // NB: We're using getHeader() here to allow subclasses an opportunity to perform lazy loading
+ // of the headers.
+ String mergedHeader = getHeader(header, ",");
+ if (mergedHeader != null) {
+ return NewsAddress.parse(mergedHeader);
+ }
+ return null;
+ }
+
+ private Address[] getHeaderAsInternetAddresses(String header, boolean strict) throws MessagingException {
+ // NB: We're using getHeader() here to allow subclasses an opportunity to perform lazy loading
+ // of the headers.
+ String mergedHeader = getHeader(header, ",");
+
+ if (mergedHeader != null) {
+ return InternetAddress.parseHeader(mergedHeader, strict);
+ }
+ return null;
+ }
+
+ /**
+ * Check to see if we require strict addressing on parsing
+ * internet headers.
+ *
+ * @return The current value of the "mail.mime.address.strict" session
+ * property, or true, if the property is not set.
+ */
+ private boolean isStrictAddressing() {
+ return SessionUtil.getBooleanProperty(session, MIME_ADDRESS_STRICT, true);
+ }
+
+ /**
+ * Set a named header to the value of an address field.
+ *
+ * @param header The header name.
+ * @param address The address value. If the address is null, the header is removed.
+ *
+ * @exception MessagingException
+ */
+ private void setHeader(String header, Address address) throws MessagingException {
+ if (address == null) {
+ removeHeader(header);
+ }
+ else {
+ setHeader(header, address.toString());
+ }
+ }
+
+ /**
+ * Set a header to a list of addresses.
+ *
+ * @param header The header name.
+ * @param addresses An array of addresses to set the header to. If null, the
+ * header is removed.
+ */
+ private void setHeader(String header, Address[] addresses) {
+ if (addresses == null) {
+ headers.removeHeader(header);
+ }
+ else {
+ headers.setHeader(header, addresses);
+ }
+ }
+
+ private void addHeader(String header, Address[] addresses) throws MessagingException {
+ headers.addHeader(header, InternetAddress.toString(addresses));
+ }
+
+ private String getHeaderForRecipientType(Message.RecipientType type) throws MessagingException {
+ if (RecipientType.TO == type) {
+ return "To";
+ } else if (RecipientType.CC == type) {
+ return "Cc";
+ } else if (RecipientType.BCC == type) {
+ return "Bcc";
+ } else if (RecipientType.NEWSGROUPS == type) {
+ return "Newsgroups";
+ } else {
+ throw new MessagingException("Unsupported recipient type: " + type.toString());
+ }
+ }
+
+ /**
+ * Utility routine to get a header as a single string value
+ * rather than an array of headers.
+ *
+ * @param name The name of the header.
+ *
+ * @return The single string header value. If multiple headers exist,
+ * the additional ones are ignored.
+ * @exception MessagingException
+ */
+ private String getSingleHeader(String name) throws MessagingException {
+ String[] values = getHeader(name);
+ if (values == null || values.length == 0) {
+ return null;
+ } else {
+ return values[0];
+ }
+ }
+
+ /**
+ * Update the message identifier after headers have been updated.
+ *
+ * The default message id is composed of the following items:
+ *
+ * 1) A newly created object's hash code.
+ * 2) A uniqueness counter
+ * 3) The current time in milliseconds
+ * 4) The string JavaMail
+ * 5) The user's local address as returned by InternetAddress.getLocalAddress().
+ *
+ * @exception MessagingException
+ */
+ public void updateMessageID() throws MessagingException {
+ StringBuffer id = new StringBuffer();
+
+ id.append('<');
+ id.append(new Object().hashCode());
+ id.append('.');
+ id.append(messageID++);
+ id.append(System.currentTimeMillis());
+ id.append('.');
+ id.append("JavaMail.");
+
+ // get the local address and apply a suitable default.
+
+ InternetAddress localAddress = InternetAddress.getLocalAddress(session);
+ if (localAddress != null) {
+ id.append(localAddress.getAddress());
+ }
+ else {
+ id.append("javamailuser@localhost");
+ }
+ id.append('>');
+
+ setHeader("Message-ID", id.toString());
+ }
+
+ /**
+ * Method used to create a new MimeMessage instance. This method
+ * is used whenever the MimeMessage class needs to create a new
+ * Message instance (e.g, reply()). This method allows subclasses
+ * to override the class of message that gets created or set
+ * default values, if needed.
+ *
+ * @param session The session associated with this message.
+ *
+ * @return A newly create MimeMessage instance.
+ */
+ protected MimeMessage createMimeMessage(Session session) {
+ return new MimeMessage(session);
+ }
+
+}
Propchange: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimeMessage.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimeMessage.java
------------------------------------------------------------------------------
svn:keywords = Date Revision
Propchange: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimeMessage.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimeMultipart.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimeMultipart.java?rev=421852&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimeMultipart.java (added)
+++ geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimeMultipart.java Fri Jul 14 03:02:19 2006
@@ -0,0 +1,374 @@
+/**
+ *
+ * Copyright 2003-2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PushbackInputStream;
+
+import javax.activation.DataSource;
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.MultipartDataSource;
+
+import org.apache.geronimo.mail.util.SessionUtil;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class MimeMultipart extends Multipart {
+ private static final String MIME_IGNORE_MISSING_BOUNDARY = "mail.mime.multipart.ignoremissingendboundary";
+
+ /**
+ * DataSource that provides our InputStream.
+ */
+ protected DataSource ds;
+ /**
+ * Indicates if the data has been parsed.
+ */
+ protected boolean parsed = true;
+
+ // the content type information
+ private transient ContentType type;
+
+ // indicates if we've seen the final boundary line when parsing.
+ private boolean complete = true;
+
+ // MIME multipart preable text that can appear before the first boundary line.
+ private String preamble = null;
+
+ /**
+ * Create an empty MimeMultipart with content type "multipart/mixed"
+ */
+ public MimeMultipart() {
+ this("mixed");
+ }
+
+ /**
+ * Create an empty MimeMultipart with the subtype supplied.
+ *
+ * @param subtype the subtype
+ */
+ public MimeMultipart(String subtype) {
+ type = new ContentType("multipart", subtype, null);
+ type.setParameter("boundary", getBoundary());
+ contentType = type.toString();
+ }
+
+ /**
+ * Create a MimeMultipart from the supplied DataSource.
+ *
+ * @param dataSource the DataSource to use
+ * @throws MessagingException
+ */
+ public MimeMultipart(DataSource dataSource) throws MessagingException {
+ ds = dataSource;
+ if (dataSource instanceof MultipartDataSource) {
+ super.setMultipartDataSource((MultipartDataSource) dataSource);
+ parsed = true;
+ } else {
+ type = new ContentType(ds.getContentType());
+ contentType = type.toString();
+ parsed = false;
+ }
+ }
+
+ public void setSubType(String subtype) throws MessagingException {
+ type.setSubType(subtype);
+ contentType = type.toString();
+ }
+
+ public int getCount() throws MessagingException {
+ parse();
+ return super.getCount();
+ }
+
+ public synchronized BodyPart getBodyPart(int part) throws MessagingException {
+ parse();
+ return super.getBodyPart(part);
+ }
+
+ public BodyPart getBodyPart(String cid) throws MessagingException {
+ parse();
+ for (int i = 0; i < parts.size(); i++) {
+ MimeBodyPart bodyPart = (MimeBodyPart) parts.get(i);
+ if (cid.equals(bodyPart.getContentID())) {
+ return bodyPart;
+ }
+ }
+ return null;
+ }
+
+ protected void updateHeaders() throws MessagingException {
+ parse();
+ for (int i = 0; i < parts.size(); i++) {
+ MimeBodyPart bodyPart = (MimeBodyPart) parts.get(i);
+ bodyPart.updateHeaders();
+ }
+ }
+
+ private static byte[] dash = { '-', '-' };
+ private static byte[] crlf = { 13, 10 };
+
+ public void writeTo(OutputStream out) throws IOException, MessagingException {
+ parse();
+ String boundary = type.getParameter("boundary");
+ byte[] bytes = boundary.getBytes();
+
+ if (preamble != null) {
+ byte[] preambleBytes = preamble.getBytes();
+ // write this out, followed by a line break.
+ out.write(preambleBytes);
+ out.write(crlf);
+ }
+
+ for (int i = 0; i < parts.size(); i++) {
+ BodyPart bodyPart = (BodyPart) parts.get(i);
+ out.write(dash);
+ out.write(bytes);
+ out.write(crlf);
+ bodyPart.writeTo(out);
+ out.write(crlf);
+ }
+ out.write(dash);
+ out.write(bytes);
+ out.write(dash);
+ out.write(crlf);
+ out.flush();
+ }
+
+ protected void parse() throws MessagingException {
+ if (parsed) {
+ return;
+ }
+ try {
+ ContentType cType = new ContentType(contentType);
+ byte[] boundary = ("--" + cType.getParameter("boundary")).getBytes();
+ InputStream is = new BufferedInputStream(ds.getInputStream());
+ PushbackInputStream pushbackInStream = new PushbackInputStream(is,
+ (boundary.length + 2));
+ readTillFirstBoundary(pushbackInStream, boundary);
+ while (pushbackInStream.available()>0){
+ MimeBodyPartInputStream partStream;
+ partStream = new MimeBodyPartInputStream(pushbackInStream,
+ boundary);
+ addBodyPart(new MimeBodyPart(partStream));
+
+ // terminated by an EOF rather than a proper boundary?
+ if (!partStream.boundaryFound) {
+ if (!SessionUtil.getBooleanProperty(MIME_IGNORE_MISSING_BOUNDARY, true)) {
+ throw new MessagingException("Missing Multi-part end boundary");
+ }
+ complete = false;
+ }
+ }
+ } catch (Exception e){
+ throw new MessagingException(e.toString(),e);
+ }
+ parsed = true;
+ }
+
+ /**
+ * Move the read pointer to the begining of the first part
+ * read till the end of first boundary. Any data read before this point are
+ * saved as the preamble.
+ *
+ * @param pushbackInStream
+ * @param boundary
+ * @throws MessagingException
+ */
+ private boolean readTillFirstBoundary(PushbackInputStream pushbackInStream, byte[] boundary) throws MessagingException {
+ ByteArrayOutputStream preambleStream = new ByteArrayOutputStream();
+
+ try {
+ while (pushbackInStream.available() > 0) {
+ int value = pushbackInStream.read();
+ if ((byte) value == boundary[0]) {
+ int boundaryIndex = 0;
+ while (pushbackInStream.available() > 0 && (boundaryIndex < boundary.length)
+ && ((byte) value == boundary[boundaryIndex])) {
+ value = pushbackInStream.read();
+ if (value == -1)
+ throw new MessagingException(
+ "Unexpected End of Stream while searching for first Mime Boundary");
+ boundaryIndex++;
+ }
+ if (boundaryIndex == boundary.length) { // boundary found
+ pushbackInStream.read();
+
+ // save the preamble, if there is one.
+ byte[] preambleBytes = preambleStream.toByteArray();
+ if (preambleBytes.length > 0) {
+ preamble = new String(preambleBytes);
+ }
+ return true;
+ }
+ else {
+ // we need to add this to the preamble. We write the part of the boundary that
+ // actually matched, followed by the character that was the mismatch.
+ preambleStream.write(boundary, 0, boundaryIndex);
+ preambleStream.write((byte)value);
+ }
+ }
+ else {
+ // this is part of the preamble.
+ preambleStream.write((byte)value);
+ }
+ }
+ } catch (IOException ioe) {
+ throw new MessagingException(ioe.toString(), ioe);
+ }
+ return false;
+ }
+
+ protected InternetHeaders createInternetHeaders(InputStream in) throws MessagingException {
+ return new InternetHeaders(in);
+ }
+
+ protected MimeBodyPart createMimeBodyPart(InternetHeaders headers, byte[] data) throws MessagingException {
+ return new MimeBodyPart(headers, data);
+ }
+
+ protected MimeBodyPart createMimeBodyPart(InputStream in) throws MessagingException {
+ return new MimeBodyPart(in);
+ }
+
+ // static used to track boudary value allocations to help ensure uniqueness.
+ private static int part;
+
+ private synchronized static String getBoundary() {
+ int i;
+ synchronized(MimeMultipart.class) {
+ i = part++;
+ }
+ StringBuffer buf = new StringBuffer(64);
+ buf.append("----=_Part_").append(i).append('_').append((new Object()).hashCode()).append('.').append(System.currentTimeMillis());
+ return buf.toString();
+ }
+
+ private class MimeBodyPartInputStream extends InputStream {
+ PushbackInputStream inStream;
+ public boolean boundaryFound = false;
+ byte[] boundary;
+
+ public MimeBodyPartInputStream(PushbackInputStream inStream,
+ byte[] boundary) {
+ super();
+ this.inStream = inStream;
+ this.boundary = boundary;
+ }
+
+ public int read() throws IOException {
+ if (boundaryFound) {
+ return -1;
+ }
+ // read the next value from stream
+ int value = inStream.read();
+ // A problem occured because all the mime parts tends to have a /r/n at the end. Making it hard to transform them to correct DataSources.
+ // This logic introduced to handle it
+ //TODO look more in to this && for a better way to do this
+ if (value == 13) {
+ value = inStream.read();
+ if (value != 10) {
+ inStream.unread(value);
+ return 13;
+ } else {
+ value = inStream.read();
+ if ((byte) value != boundary[0]) {
+ inStream.unread(value);
+ inStream.unread(10);
+ return 13;
+ }
+ }
+ } else if ((byte) value != boundary[0]) {
+ return value;
+ }
+ // read value is the first byte of the boundary. Start matching the
+ // next characters to find a boundary
+ int boundaryIndex = 0;
+ while ((boundaryIndex < boundary.length)
+ && ((byte) value == boundary[boundaryIndex])) {
+ value = inStream.read();
+ boundaryIndex++;
+ }
+ if (boundaryIndex == boundary.length) { // boundary found
+ boundaryFound = true;
+ // read the end of line character
+ if (inStream.read() == '-' && value == '-') {
+ //Last mime boundary should have a succeeding "--"
+ //as we are on it, read the terminating CRLF
+ inStream.read();
+ inStream.read();
+ }
+ return -1;
+ }
+ // Boundary not found. Restoring bytes skipped.
+ // write first skipped byte, push back the rest
+ if (value != -1) { // Stream might have ended
+ inStream.unread(value);
+ }
+ inStream.unread(boundary, 1, boundaryIndex - 1);
+ return boundary[0];
+ }
+ }
+
+ /**
+ * Return true if the final boundary line for this multipart was
+ * seen when parsing the data.
+ *
+ * @return
+ * @exception MessagingException
+ */
+ public boolean isComplete() throws MessagingException {
+ // make sure we've parsed this
+ parse();
+ return complete;
+ }
+
+
+ /**
+ * Returns the preamble text that appears before the first bady
+ * part of a MIME multi part. The preamble is optional, so this
+ * might be null.
+ *
+ * @return The preamble text string.
+ * @exception MessagingException
+ */
+ public String getPreamble() throws MessagingException {
+ parse();
+ return preamble;
+ }
+
+ /**
+ * Set the message preamble text. This will be written before
+ * the first boundary of a multi-part message.
+ *
+ * @param preamble The new boundary text. This is complete lines of text, including
+ * new lines.
+ *
+ * @exception MessagingException
+ */
+ public void setPreamble(String preamble) throws MessagingException {
+ this.preamble = preamble;
+ }
+}
Propchange: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimeMultipart.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimeMultipart.java
------------------------------------------------------------------------------
svn:keywords = Date Revision
Propchange: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimeMultipart.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimePart.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimePart.java?rev=421852&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimePart.java (added)
+++ geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimePart.java Fri Jul 14 03:02:19 2006
@@ -0,0 +1,62 @@
+/**
+ *
+ * Copyright 2003-2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.mail.internet;
+
+import java.util.Enumeration;
+import javax.mail.MessagingException;
+import javax.mail.Part;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public interface MimePart extends Part {
+ public abstract void addHeaderLine(String line) throws MessagingException;
+
+ public abstract Enumeration getAllHeaderLines() throws MessagingException;
+
+ public abstract String getContentID() throws MessagingException;
+
+ public abstract String[] getContentLanguage() throws MessagingException;
+
+ public abstract String getContentMD5() throws MessagingException;
+
+ public abstract String getEncoding() throws MessagingException;
+
+ public abstract String getHeader(String header, String delimiter)
+ throws MessagingException;
+
+ public abstract Enumeration getMatchingHeaderLines(String[] names)
+ throws MessagingException;
+
+ public abstract Enumeration getNonMatchingHeaderLines(String[] names)
+ throws MessagingException;
+
+ public abstract void setContentLanguage(String[] languages)
+ throws MessagingException;
+
+ public abstract void setContentMD5(String content)
+ throws MessagingException;
+
+ public abstract void setText(String text) throws MessagingException;
+
+ public abstract void setText(String text, String charset)
+ throws MessagingException;
+
+ public abstract void setText(String text, String charset, String subType)
+ throws MessagingException;
+}
Propchange: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimePart.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimePart.java
------------------------------------------------------------------------------
svn:keywords = Date Revision
Propchange: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimePart.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimePartDataSource.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimePartDataSource.java?rev=421852&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimePartDataSource.java (added)
+++ geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimePartDataSource.java Fri Jul 14 03:02:19 2006
@@ -0,0 +1,76 @@
+/**
+ *
+ * Copyright 2003-2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.UnknownServiceException;
+import javax.activation.DataSource;
+import javax.mail.MessageAware;
+import javax.mail.MessageContext;
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class MimePartDataSource implements DataSource, MessageAware {
+ // the part that provides the data form this data source.
+ protected MimePart part;
+
+ public MimePartDataSource(MimePart part) {
+ this.part = part;
+ }
+
+ public InputStream getInputStream() throws IOException {
+ try {
+ InputStream stream;
+ if (part instanceof MimeMessage) {
+ stream = ((MimeMessage) part).getContentStream();
+ } else if (part instanceof MimeBodyPart) {
+ stream = ((MimeBodyPart) part).getContentStream();
+ } else {
+ throw new MessagingException("Unknown part");
+ }
+ String encoding = part.getEncoding();
+ return encoding == null ? stream : MimeUtility.decode(stream, encoding);
+ } catch (MessagingException e) {
+ throw (IOException) new IOException(e.getMessage()).initCause(e);
+ }
+ }
+
+ public OutputStream getOutputStream() throws IOException {
+ throw new UnknownServiceException();
+ }
+
+ public String getContentType() {
+ try {
+ return part.getContentType();
+ } catch (MessagingException e) {
+ return null;
+ }
+ }
+
+ public String getName() {
+ return "";
+ }
+
+ public synchronized MessageContext getMessageContext() {
+ return new MessageContext(part);
+ }
+}
Propchange: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimePartDataSource.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimePartDataSource.java
------------------------------------------------------------------------------
svn:keywords = Date Revision
Propchange: geronimo/specs/trunk/geronimo-spec-javamail-1.4/src/main/java/javax/mail/internet/MimePartDataSource.java
------------------------------------------------------------------------------
svn:mime-type = text/plain