View Javadoc

1   /**
2    *
3    * Copyright 2003-2006 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package javax.mail.internet;
19  
20  import java.io.BufferedInputStream;
21  import java.io.ByteArrayInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.ObjectStreamException;
26  import java.io.OutputStream;
27  import java.io.UnsupportedEncodingException;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Date;
31  import java.util.Enumeration;
32  import java.util.HashMap;
33  import java.util.List;
34  import java.util.Map;
35  
36  import javax.activation.DataHandler;
37  import javax.mail.Address;
38  import javax.mail.Flags;
39  import javax.mail.Folder;
40  import javax.mail.Message;
41  import javax.mail.MessagingException;
42  import javax.mail.Multipart;
43  import javax.mail.Part;
44  import javax.mail.Session;
45  import javax.mail.internet.HeaderTokenizer.Token;
46  
47  import org.apache.geronimo.mail.util.ASCIIUtil;
48  import org.apache.geronimo.mail.util.SessionUtil;
49  
50  /**
51   * @version $Rev: 421852 $ $Date: 2006-07-14 03:02:19 -0700 (Fri, 14 Jul 2006) $
52   */
53  public class MimeMessage extends Message implements MimePart {
54  	private static final String MIME_ADDRESS_STRICT = "mail.mime.address.strict";
55  	private static final String MIME_DECODEFILENAME = "mail.mime.decodefilename";
56  	private static final String MIME_ENCODEFILENAME = "mail.mime.encodefilename";
57  
58  	private static final String MAIL_ALTERNATES = "mail.alternates";
59  	private static final String MAIL_REPLYALLCC = "mail.replyallcc";
60  
61      // static used to ensure message ID uniqueness
62      private static int messageID = 0;
63  
64  
65      /**
66       * Extends {@link javax.mail.Message.RecipientType} to support addition recipient types.
67       */
68      public static class RecipientType extends Message.RecipientType {
69          /**
70           * Recipient type for Usenet news.
71           */
72          public static final RecipientType NEWSGROUPS = new RecipientType("Newsgroups");
73  
74          protected RecipientType(String type) {
75              super(type);
76          }
77  
78          /**
79           * Ensure the singleton is returned.
80           *
81           * @return resolved object
82           */
83          protected Object readResolve() throws ObjectStreamException {
84              if (this.type.equals("Newsgroups")) {
85                  return NEWSGROUPS;
86              } else {
87                  return super.readResolve();
88              }
89          }
90      }
91  
92      /**
93       * The {@link DataHandler} for this Message's content.
94       */
95      protected DataHandler dh;
96      /**
97       * This message's content (unless sourced from a SharedInputStream).
98       */
99      protected byte[] content;
100     /**
101      * If the data for this message was supplied by a {@link SharedInputStream}
102      * then this is another such stream representing the content of this message;
103      * if this field is non-null, then {@link #content} will be null.
104      */
105     protected InputStream contentStream;
106     /**
107      * This message's headers.
108      */
109     protected InternetHeaders headers;
110     /**
111      * This message's flags.
112      */
113     protected Flags flags;
114     /**
115      * Flag indicating that the message has been modified; set to true when
116      * an empty message is created or when {@link #saveChanges()} is called.
117      */
118     protected boolean modified;
119     /**
120      * Flag indicating that the message has been saved.
121      */
122     protected boolean saved;
123 
124     private final MailDateFormat dateFormat = new MailDateFormat();
125 
126     /**
127      * Create a new MimeMessage.
128      * An empty message is created, with empty {@link #headers} and empty {@link #flags}.
129      * The {@link #modified} flag is set.
130      *
131      * @param session the session for this message
132      */
133     public MimeMessage(Session session) {
134         super(session);
135         headers = new InternetHeaders();
136         flags = new Flags();
137         // empty messages are modified, because the content is not there, and require saving before use.
138         modified = true;
139         saved = false;
140     }
141 
142     /**
143      * Create a MimeMessage by reading an parsing the data from the supplied stream.
144      *
145      * @param session the session for this message
146      * @param in      the stream to load from
147      * @throws MessagingException if there is a problem reading or parsing the stream
148      */
149     public MimeMessage(Session session, InputStream in) throws MessagingException {
150         this(session);
151         parse(in);
152         // this message is complete, so marked as unmodified.
153         modified = false;
154         // and no saving required
155         saved = true;
156     }
157 
158     /**
159      * Copy a MimeMessage.
160      *
161      * @param message the message to copy
162      * @throws MessagingException is there was a problem copying the message
163      */
164     public MimeMessage(MimeMessage message) throws MessagingException {
165         super(message.session);
166         // this is somewhat difficult to do.  There's a lot of data in both the superclass and this
167         // class that needs to undergo a "deep cloning" operation.  These operations don't really exist
168         // on the objects in question, so the only solution I can come up with is to serialize the
169         // message data of the source object using the write() method, then reparse the data in this
170         // object.  I've not found a lot of uses for this particular constructor, so perhaps that's not
171         // really all that bad of a solution.
172 
173         // serialized this out to an in-memory stream.
174         ByteArrayOutputStream copy = new ByteArrayOutputStream();
175 
176         try {
177             // write this out the stream.
178             message.writeTo(copy);
179             copy.close();
180             // I think this ends up creating a new array for the data, but I'm not aware of any more
181             // efficient options.
182             ByteArrayInputStream inData = new ByteArrayInputStream(copy.toByteArray());
183             // now reparse this message into this object.
184             inData.close();
185             parse (inData);
186             // writing out the source data requires saving it, so we should consider this one saved also.
187             saved = true;
188             // this message is complete, so marked as unmodified.
189             modified = false;
190         } catch (IOException e) {
191             // I'm not sure ByteArrayInput/OutputStream actually throws IOExceptions or not, but the method
192             // signatures declare it, so we need to deal with it.  Turning it into a messaging exception
193             // should fit the bill.
194             throw new MessagingException("Error copying MimeMessage data", e);
195         }
196     }
197 
198     /**
199      * Create an new MimeMessage in the supplied {@link Folder} and message number.
200      *
201      * @param folder the Folder that contains the new message
202      * @param number the message number of the new message
203      */
204     protected MimeMessage(Folder folder, int number) {
205         super(folder, number);
206         headers = new InternetHeaders();
207         flags = new Flags();
208         // saving primarly involves updates to the message header.  Since we're taking the header info
209         // from a message store in this context, we mark the message as saved.
210         saved = true;
211         // we've not filled in the content yet, so this needs to be marked as modified
212         modified = true;
213     }
214 
215     /**
216      * Create a MimeMessage by reading an parsing the data from the supplied stream.
217      *
218      * @param folder the folder for this message
219      * @param in     the stream to load from
220      * @param number the message number of the new message
221      * @throws MessagingException if there is a problem reading or parsing the stream
222      */
223     protected MimeMessage(Folder folder, InputStream in, int number) throws MessagingException {
224         this(folder, number);
225         parse(in);
226         // this message is complete, so marked as unmodified.
227         modified = false;
228         // and no saving required
229         saved = true;
230     }
231 
232 
233     /**
234      * Create a MimeMessage with the supplied headers and content.
235      *
236      * @param folder  the folder for this message
237      * @param headers the headers for the new message
238      * @param content the content of the new message
239      * @param number  the message number of the new message
240      * @throws MessagingException if there is a problem reading or parsing the stream
241      */
242     protected MimeMessage(Folder folder, InternetHeaders headers, byte[] content, int number) throws MessagingException {
243         this(folder, number);
244         this.headers = headers;
245         this.content = content;
246         // this message is complete, so marked as unmodified.
247         modified = false;
248     }
249 
250     /**
251      * Parse the supplied stream and initialize {@link #headers} and {@link #content} appropriately.
252      *
253      * @param in the stream to read
254      * @throws MessagingException if there was a problem parsing the stream
255      */
256     protected void parse(InputStream in) throws MessagingException {
257         in = new BufferedInputStream(in);
258         // create the headers first from the stream
259         headers = new InternetHeaders(in);
260 
261         // now we need to get the rest of the content as a byte array...this means reading from the current
262         // position in the stream until the end and writing it to an accumulator ByteArrayOutputStream.
263         ByteArrayOutputStream baos = new ByteArrayOutputStream();
264         try {
265             byte buffer[] = new byte[1024];
266             int count;
267             while ((count = in.read(buffer, 0, 1024)) != -1) {
268                 baos.write(buffer, 0, count);
269             }
270         } catch (Exception e) {
271             throw new MessagingException(e.toString(), e);
272         }
273         // and finally extract the content as a byte array.
274         content = baos.toByteArray();
275     }
276 
277     /**
278      * Get the message "From" addresses.  This looks first at the
279      * "From" headers, and no "From" header is found, the "Sender"
280      * header is checked.  Returns null if not found.
281      *
282      * @return An array of addresses identifying the message from target.  Returns
283      *         null if this is not resolveable from the headers.
284      * @exception MessagingException
285      */
286     public Address[] getFrom() throws MessagingException {
287         // strict addressing controls this.
288         boolean strict = isStrictAddressing();
289         Address[] result = getHeaderAsInternetAddresses("From", strict);
290         if (result == null) {
291             result = getHeaderAsInternetAddresses("Sender", strict);
292         }
293         return result;
294     }
295 
296     /**
297      * Set the current message "From" recipient.  This replaces any
298      * existing "From" header.  If the address is null, the header is
299      * removed.
300      *
301      * @param address The new "From" target.
302      *
303      * @exception MessagingException
304      */
305     public void setFrom(Address address) throws MessagingException {
306         setHeader("From", address);
307     }
308 
309     /**
310      * Set the "From" header using the value returned by {@link InternetAddress#getLocalAddress(javax.mail.Session)}.
311      *
312      * @throws MessagingException if there was a problem setting the header
313      */
314     public void setFrom() throws MessagingException {
315         InternetAddress address = InternetAddress.getLocalAddress(session);
316         // no local address resolvable?  This is an error.
317         if (address == null) {
318             throw new MessagingException("No local address defined");
319         }
320         setFrom(address);
321     }
322 
323     /**
324      * Add a set of addresses to the existing From header.
325      *
326      * @param addresses The list to add.
327      *
328      * @exception MessagingException
329      */
330     public void addFrom(Address[] addresses) throws MessagingException {
331         addHeader("From", addresses);
332     }
333 
334     /**
335      * Return the "Sender" header as an address.
336      *
337      * @return the "Sender" header as an address, or null if not present
338      * @throws MessagingException if there was a problem parsing the header
339      */
340     public Address getSender() throws MessagingException {
341         Address[] addrs = getHeaderAsInternetAddresses("Sender", isStrictAddressing());
342         return addrs != null && addrs.length > 0 ? addrs[0] : null;
343     }
344 
345     /**
346      * Set the "Sender" header.  If the address is null, this
347      * will remove the current sender header.
348      *
349      * @param address the new Sender address
350      *
351      * @throws MessagingException
352      *                if there was a problem setting the header
353      */
354     public void setSender(Address address) throws MessagingException {
355         setHeader("Sender", address);
356     }
357 
358     /**
359      * Gets the recipients by type.  Returns null if there are no
360      * headers of the specified type.  Acceptable RecipientTypes are:
361      *
362      *   javax.mail.Message.RecipientType.TO
363      *   javax.mail.Message.RecipientType.CC
364      *   javax.mail.Message.RecipientType.BCC
365      *   javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS
366      *
367      * @param type   The message RecipientType identifier.
368      *
369      * @return The array of addresses for the specified recipient types.
370      * @exception MessagingException
371      */
372     public Address[] getRecipients(Message.RecipientType type) throws MessagingException {
373         // is this a NEWSGROUP request?  We need to handle this as a special case here, because
374         // this needs to return NewsAddress instances instead of InternetAddress items.
375         if (type == RecipientType.NEWSGROUPS) {
376             return getHeaderAsNewsAddresses(getHeaderForRecipientType(type));
377         }
378         // the other types are all internet addresses.
379         return getHeaderAsInternetAddresses(getHeaderForRecipientType(type), isStrictAddressing());
380     }
381 
382     /**
383      * Retrieve all of the recipients defined for this message.  This
384      * returns a merged array of all possible message recipients
385      * extracted from the headers.  The relevant header types are:
386      *
387      *
388      *    javax.mail.Message.RecipientType.TO
389      *    javax.mail.Message.RecipientType.CC
390      *    javax.mail.Message.RecipientType.BCC
391      *    javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS
392      *
393      * @return An array of all target message recipients.
394      * @exception MessagingException
395      */
396     public Address[] getAllRecipients() throws MessagingException {
397         List recipients = new ArrayList();
398         addRecipientsToList(recipients, RecipientType.TO);
399         addRecipientsToList(recipients, RecipientType.CC);
400         addRecipientsToList(recipients, RecipientType.BCC);
401         addRecipientsToList(recipients, RecipientType.NEWSGROUPS);
402 
403         // this is supposed to return null if nothing is there.
404         if (recipients.isEmpty()) {
405             return null;
406         }
407         return (Address[]) recipients.toArray(new Address[recipients.size()]);
408     }
409 
410     /**
411      * Utility routine to merge different recipient types into a
412      * single list.
413      *
414      * @param list   The accumulator list.
415      * @param type   The recipient type to extract.
416      *
417      * @exception MessagingException
418      */
419     private void addRecipientsToList(List list, Message.RecipientType type) throws MessagingException {
420 
421         Address[] recipients;
422         if (type == RecipientType.NEWSGROUPS) {
423             recipients = getHeaderAsNewsAddresses(getHeaderForRecipientType(type));
424         }
425         else {
426             recipients = getHeaderAsInternetAddresses(getHeaderForRecipientType(type), isStrictAddressing());
427         }
428         if (recipients != null) {
429             list.addAll(Arrays.asList(recipients));
430         }
431     }
432 
433     /**
434      * Set a recipients list for a particular recipient type.  If the
435      * list is null, the corresponding header is removed.
436      *
437      * @param type      The type of recipient to set.
438      * @param addresses The list of addresses.
439      *
440      * @exception MessagingException
441      */
442     public void setRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException {
443         setHeader(getHeaderForRecipientType(type), addresses);
444     }
445 
446     /**
447      * Set a recipient field to a string address (which may be a
448      * list or group type).
449      *
450      * If the address is null, the field is removed.
451      *
452      * @param type    The type of recipient to set.
453      * @param address The address string.
454      *
455      * @exception MessagingException
456      */
457     public void setRecipients(Message.RecipientType type, String address) throws MessagingException {
458         setOrRemoveHeader(getHeaderForRecipientType(type), address);
459     }
460 
461 
462     /**
463      * Add a list of addresses to a target recipient list.
464      *
465      * @param type    The target recipient type.
466      * @param address An array of addresses to add.
467      *
468      * @exception MessagingException
469      */
470     public void addRecipients(Message.RecipientType type, Address[] address) throws MessagingException {
471         addHeader(getHeaderForRecipientType(type), address);
472     }
473 
474     /**
475      * Add an address to a target recipient list by string name.
476      *
477      * @param type    The target header type.
478      * @param address The address to add.
479      *
480      * @exception MessagingException
481      */
482     public void addRecipients(Message.RecipientType type, String address) throws MessagingException {
483         addHeader(getHeaderForRecipientType(type), address);
484     }
485 
486     /**
487      * Get the ReplyTo address information.  The headers are parsed
488      * using the "mail.mime.address.strict" setting.  If the "Reply-To" header does
489      * not have any addresses, then the value of the "From" field is used.
490      *
491      * @return An array of addresses obtained from parsing the header.
492      * @exception MessagingException
493      */
494     public Address[] getReplyTo() throws MessagingException {
495          Address[] addresses = getHeaderAsInternetAddresses("Reply-To", isStrictAddressing());
496          if (addresses == null) {
497              addresses = getFrom();
498          }
499          return addresses;
500     }
501 
502     /**
503      * Set the Reply-To field to the provided list of addresses.  If
504      * the address list is null, the header is removed.
505      *
506      * @param address The new field value.
507      *
508      * @exception MessagingException
509      */
510     public void setReplyTo(Address[] address) throws MessagingException {
511         setHeader("Reply-To", address);
512     }
513 
514     /**
515      * Returns the value of the "Subject" header.  If the subject
516      * is encoded as an RFC 2047 value, the value is decoded before
517      * return.  If decoding fails, the raw string value is
518      * returned.
519      *
520      * @return The String value of the subject field.
521      * @exception MessagingException
522      */
523     public String getSubject() throws MessagingException {
524         String subject = getSingleHeader("Subject");
525         if (subject == null) {
526             return null;
527         } else {
528             try {
529                 // this needs to be unfolded before decodeing.
530                 return MimeUtility.decodeText(MimeUtility.unfold(subject));
531             } catch (UnsupportedEncodingException e) {
532                 // ignored.
533             }
534         }
535 
536         return subject;
537     }
538 
539     /**
540      * Set the value for the "Subject" header.  If the subject
541      * contains non US-ASCII characters, it is encoded in RFC 2047
542      * fashion.
543      *
544      * If the subject value is null, the Subject field is removed.
545      *
546      * @param subject The new subject value.
547      *
548      * @exception MessagingException
549      */
550     public void setSubject(String subject) throws MessagingException {
551         // just set this using the default character set.
552         setSubject(subject, null);
553     }
554 
555     public void setSubject(String subject, String charset) throws MessagingException {
556         // standard null removal (yada, yada, yada....)
557         if (subject == null) {
558             removeHeader("Subject");
559         }
560         else {
561             try {
562                 String s = MimeUtility.fold(9, MimeUtility.encodeText(subject, charset, null));
563                 // encode this, and then fold to fit the line lengths.
564                 setHeader("Subject", MimeUtility.fold(9, MimeUtility.encodeText(subject, charset, null)));
565             } catch (UnsupportedEncodingException e) {
566                 throw new MessagingException("Encoding error", e);
567             }
568         }
569     }
570 
571     /**
572      * Get the value of the "Date" header field.  Returns null if
573      * if the field is absent or the date is not in a parseable format.
574      *
575      * @return A Date object parsed according to RFC 822.
576      * @exception MessagingException
577      */
578     public Date getSentDate() throws MessagingException {
579         String value = getSingleHeader("Date");
580         if (value == null) {
581             return null;
582         }
583         try {
584             return dateFormat.parse(value);
585         } catch (java.text.ParseException e) {
586             return null;
587         }
588     }
589 
590     /**
591      * Set the message sent date.  This updates the "Date" header.
592      * If the provided date is null, the header is removed.
593      *
594      * @param sent   The new sent date value.
595      *
596      * @exception MessagingException
597      */
598     public void setSentDate(Date sent) throws MessagingException {
599         setOrRemoveHeader("Date", dateFormat.format(sent));
600     }
601 
602     /**
603      * Get the message received date.  The Sun implementation is
604      * documented as always returning null, so this one does too.
605      *
606      * @return Always returns null.
607      * @exception MessagingException
608      */
609     public Date getReceivedDate() throws MessagingException {
610         return null;
611     }
612 
613     /**
614      * Return the content size of this message.  This is obtained
615      * either from the size of the content field (if available) or
616      * from the contentStream, IFF the contentStream returns a positive
617      * size.  Returns -1 if the size is not available.
618      *
619      * @return Size of the content in bytes.
620      * @exception MessagingException
621      */
622     public int getSize() throws MessagingException {
623         if (content != null) {
624             return content.length;
625         }
626         if (contentStream != null) {
627             try {
628                 int size = contentStream.available();
629                 if (size > 0) {
630                     return size;
631                 }
632             } catch (IOException e) {
633                 // ignore
634             }
635         }
636         return -1;
637     }
638 
639     /**
640      * Retrieve the line count for the current message.  Returns
641      * -1 if the count cannot be determined.
642      *
643      * The Sun implementation always returns -1, so this version
644      * does too.
645      *
646      * @return The content line count (always -1 in this implementation).
647      * @exception MessagingException
648      */
649     public int getLineCount() throws MessagingException {
650         return -1;
651     }
652 
653     /**
654      * Returns the current content type (defined in the "Content-Type"
655      * header.  If not available, "text/plain" is the default.
656      *
657      * @return The String name of the message content type.
658      * @exception MessagingException
659      */
660     public String getContentType() throws MessagingException {
661         String value = getSingleHeader("Content-Type");
662         if (value == null) {
663             value = "text/plain";
664         }
665         return value;
666     }
667 
668 
669     /**
670      * Tests to see if this message has a mime-type match with the
671      * given type name.
672      *
673      * @param type   The tested type name.
674      *
675      * @return If this is a type match on the primary and secondare portion of the types.
676      * @exception MessagingException
677      */
678     public boolean isMimeType(String type) throws MessagingException {
679         return new ContentType(getContentType()).match(type);
680     }
681 
682     /**
683      * Retrieve the message "Content-Disposition" header field.
684      * This value represents how the part should be represented to
685      * the user.
686      *
687      * @return The string value of the Content-Disposition field.
688      * @exception MessagingException
689      */
690     public String getDisposition() throws MessagingException {
691         String disp = getSingleHeader("Content-Disposition");
692         if (disp != null) {
693             return new ContentDisposition(disp).getDisposition();
694         }
695         return null;
696     }
697 
698 
699     /**
700      * Set a new dispostion value for the "Content-Disposition" field.
701      * If the new value is null, the header is removed.
702      *
703      * @param disposition
704      *               The new disposition value.
705      *
706      * @exception MessagingException
707      */
708     public void setDisposition(String disposition) throws MessagingException {
709         if (disposition == null) {
710             removeHeader("Content-Disposition");
711         }
712         else {
713             // the disposition has parameters, which we'll attempt to preserve in any existing header.
714             String currentHeader = getSingleHeader("Content-Disposition");
715             if (currentHeader != null) {
716                 ContentDisposition content = new ContentDisposition(currentHeader);
717                 content.setDisposition(disposition);
718                 setHeader("Content-Disposition", content.toString());
719             }
720             else {
721                 // set using the raw string.
722                 setHeader("Content-Disposition", disposition);
723             }
724         }
725     }
726 
727     /**
728      * Decode the Content-Transfer-Encoding header to determine
729      * the transfer encoding type.
730      *
731      * @return The string name of the required encoding.
732      * @exception MessagingException
733      */
734     public String getEncoding() throws MessagingException {
735         // this might require some parsing to sort out.
736         String encoding = getSingleHeader("Content-Transfer-Encoding");
737         if (encoding != null) {
738             // we need to parse this into ATOMs and other constituent parts.  We want the first
739             // ATOM token on the string.
740             HeaderTokenizer tokenizer = new HeaderTokenizer(encoding, HeaderTokenizer.MIME);
741 
742             Token token = tokenizer.next();
743             while (token.getType() != Token.EOF) {
744                 // if this is an ATOM type, return it.
745                 if (token.getType() == Token.ATOM) {
746                     return token.getValue();
747                 }
748             }
749             // not ATOMs found, just return the entire header value....somebody might be able to make sense of
750             // this.
751             return encoding;
752         }
753         // no header, nothing to return.
754         return null;
755     }
756 
757     /**
758      * Retrieve the value of the "Content-ID" header.  Returns null
759      * if the header does not exist.
760      *
761      * @return The current header value or null.
762      * @exception MessagingException
763      */
764     public String getContentID() throws MessagingException {
765         return getSingleHeader("Content-ID");
766     }
767 
768     public void setContentID(String cid) throws MessagingException {
769         setOrRemoveHeader("Content-ID", cid);
770     }
771 
772     public String getContentMD5() throws MessagingException {
773         return getSingleHeader("Content-MD5");
774     }
775 
776     public void setContentMD5(String md5) throws MessagingException {
777         setOrRemoveHeader("Content-MD5", md5);
778     }
779 
780     public String getDescription() throws MessagingException {
781         String description = getSingleHeader("Content-Description");
782         if (description != null) {
783             try {
784                 // this could be both folded and encoded.  Return this to usable form.
785                 return MimeUtility.decodeText(MimeUtility.unfold(description));
786             } catch (UnsupportedEncodingException e) {
787                 // ignore
788             }
789         }
790         // return the raw version for any errors.
791         return description;
792     }
793 
794     public void setDescription(String description) throws MessagingException {
795         setDescription(description, null);
796     }
797 
798     public void setDescription(String description, String charset) throws MessagingException {
799         if (description == null) {
800             removeHeader("Content-Description");
801         }
802         else {
803             try {
804                 setHeader("Content-Description", MimeUtility.fold(21, MimeUtility.encodeText(description, charset, null)));
805             } catch (UnsupportedEncodingException e) {
806                 throw new MessagingException(e.getMessage(), e);
807             }
808         }
809 
810     }
811 
812     public String[] getContentLanguage() throws MessagingException {
813         return getHeader("Content-Language");
814     }
815 
816     public void setContentLanguage(String[] languages) throws MessagingException {
817         if (languages == null) {
818             removeHeader("Content-Language");
819         } else if (languages.length == 1) {
820             setHeader("Content-Language", languages[0]);
821         } else {
822             StringBuffer buf = new StringBuffer(languages.length * 20);
823             buf.append(languages[0]);
824             for (int i = 1; i < languages.length; i++) {
825                 buf.append(',').append(languages[i]);
826             }
827             setHeader("Content-Language", buf.toString());
828         }
829     }
830 
831     public String getMessageID() throws MessagingException {
832         return getSingleHeader("Message-ID");
833     }
834 
835     public String getFileName() throws MessagingException {
836         // see if there is a disposition.  If there is, parse off the filename parameter.
837         String disposition = getDisposition();
838         String filename = null;
839 
840         if (disposition != null) {
841             filename = new ContentDisposition(disposition).getParameter("filename");
842         }
843 
844         // if there's no filename on the disposition, there might be a name parameter on a
845         // Content-Type header.
846         if (filename == null) {
847             String type = getContentType();
848             if (type != null) {
849                 try {
850                     filename = new ContentType(type).getParameter("name");
851                 } catch (ParseException e) {
852                 }
853             }
854         }
855         // if we have a name, we might need to decode this if an additional property is set.
856         if (filename != null && SessionUtil.getBooleanProperty(session, MIME_DECODEFILENAME, false)) {
857             try {
858                 filename = MimeUtility.decodeText(filename);
859             } catch (UnsupportedEncodingException e) {
860                 throw new MessagingException("Unable to decode filename", e);
861             }
862         }
863 
864         return filename;
865     }
866 
867 
868     public void setFileName(String name) throws MessagingException {
869         // there's an optional session property that requests file name encoding...we need to process this before
870         // setting the value.
871         if (name != null && SessionUtil.getBooleanProperty(session, MIME_ENCODEFILENAME, false)) {
872             try {
873                 name = MimeUtility.encodeText(name);
874             } catch (UnsupportedEncodingException e) {
875                 throw new MessagingException("Unable to encode filename", e);
876             }
877         }
878 
879         // get the disposition string.
880         String disposition = getDisposition();
881         // if not there, then this is an attachment.
882         if (disposition == null) {
883             disposition = Part.ATTACHMENT;
884         }
885         // now create a disposition object and set the parameter.
886         ContentDisposition contentDisposition = new ContentDisposition(disposition);
887         contentDisposition.setParameter("filename", name);
888 
889         // serialize this back out and reset.
890         setDisposition(contentDisposition.toString());
891     }
892 
893     public InputStream getInputStream() throws MessagingException, IOException {
894         return getDataHandler().getInputStream();
895     }
896 
897     protected InputStream getContentStream() throws MessagingException {
898         if (contentStream != null) {
899             return contentStream;
900         }
901 
902         if (content != null) {
903             return new ByteArrayInputStream(content);
904         } else {
905             throw new MessagingException("No content");
906         }
907     }
908 
909     public InputStream getRawInputStream() throws MessagingException {
910         return getContentStream();
911     }
912 
913     public synchronized DataHandler getDataHandler() throws MessagingException {
914         if (dh == null) {
915             dh = new DataHandler(new MimePartDataSource(this));
916         }
917         return dh;
918     }
919 
920     public Object getContent() throws MessagingException, IOException {
921         return getDataHandler().getContent();
922     }
923 
924     public void setDataHandler(DataHandler handler) throws MessagingException {
925         dh = handler;
926         // if we have a handler override, then we need to invalidate any content
927         // headers that define the types.  This information will be derived from the
928         // data heander unless subsequently overridden.
929         removeHeader("Content-Type");
930         removeHeader("Content-Transfer-Encoding");
931     }
932 
933     public void setContent(Object content, String type) throws MessagingException {
934         setDataHandler(new DataHandler(content, type));
935     }
936 
937     public void setText(String text) throws MessagingException {
938         setText(text, null, "plain");
939     }
940 
941     public void setText(String text, String charset) throws MessagingException {
942         setText(text, charset, "plain");
943     }
944 
945 
946     public void setText(String text, String charset, String subtype) throws MessagingException {
947         // we need to sort out the character set if one is not provided.
948         if (charset == null) {
949             // if we have non us-ascii characters here, we need to adjust this.
950             if (!ASCIIUtil.isAscii(text)) {
951                 charset = MimeUtility.getDefaultMIMECharset();
952             }
953             else {
954                 charset = "us-ascii";
955             }
956         }
957         setContent(text, "text/" + subtype + "; charset=" + MimeUtility.quote(charset, HeaderTokenizer.MIME));
958     }
959 
960     public void setContent(Multipart part) throws MessagingException {
961         setDataHandler(new DataHandler(part, part.getContentType()));
962         part.setParent(this);
963     }
964 
965     public Message reply(boolean replyToAll) throws MessagingException {
966         // create a new message in this session.
967         MimeMessage reply = createMimeMessage(session);
968 
969         // get the header and add the "Re:" bit, if necessary.
970         String newSubject = getSubject();
971         if (newSubject != null) {
972             // check to see if it already begins with "Re: " (in any case).
973             // Add one on if we don't have it yet.
974             if (!newSubject.regionMatches(true, 0, "Re: ", 0, 4)) {
975                 newSubject = "Re: " + newSubject;
976             }
977             reply.setSubject(newSubject);
978         }
979 
980         Address[] toRecipients = getReplyTo();
981 
982         // set the target recipients the replyTo value
983         reply.setRecipients(Message.RecipientType.TO, getReplyTo());
984 
985         // need to reply to everybody?  More things to add.
986         if (replyToAll) {
987             // when replying, we want to remove "duplicates" in the final list.
988 
989             HashMap masterList = new HashMap();
990 
991             // reply to all implies add the local sender.  Add this to the list if resolveable.
992             InternetAddress localMail = InternetAddress.getLocalAddress(session);
993             if (localMail != null) {
994                 masterList.put(localMail.getAddress(), localMail);
995             }
996             // see if we have some local aliases to deal with.
997             String alternates = session.getProperty(MAIL_ALTERNATES);
998             if (alternates != null) {
999                 // parse this string list and merge with our set.
1000                 Address[] alternateList = InternetAddress.parse(alternates, false);
1001                 mergeAddressList(masterList, alternateList);
1002             }
1003 
1004             // the master list now contains an a list of addresses we will exclude from
1005             // the addresses.  From this point on, we're going to prune any additional addresses
1006             // against this list, AND add any new addresses to the list
1007 
1008             // now merge in the main recipients, and merge in the other recipents as well
1009             Address[] toList = pruneAddresses(masterList, getRecipients(Message.RecipientType.TO));
1010             if (toList.length != 0) {
1011                 // now check to see what sort of reply we've been asked to send.
1012                 // if replying to all as a CC, then we need to add to the CC list, otherwise they are
1013                 // TO recipients.
1014                 if (SessionUtil.getBooleanProperty(session, MAIL_REPLYALLCC, false)) {
1015                     reply.addRecipients(Message.RecipientType.CC, toList);
1016                 }
1017                 else {
1018                     reply.addRecipients(Message.RecipientType.TO, toList);
1019                 }
1020             }
1021             // and repeat for the CC list.
1022             toList = pruneAddresses(masterList, getRecipients(Message.RecipientType.CC));
1023             if (toList.length != 0) {
1024                 reply.addRecipients(Message.RecipientType.CC, toList);
1025             }
1026 
1027             // a news group list is separate from the normal addresses.  We just take these recepients
1028             // asis without trying to prune duplicates.
1029             toList = getRecipients(RecipientType.NEWSGROUPS);
1030             if (toList != null && toList.length != 0) {
1031                 reply.addRecipients(RecipientType.NEWSGROUPS, toList);
1032             }
1033         }
1034 
1035         // this is a bit of a pain.  We can't set the flags here by specifying the system flag, we need to
1036         // construct a flag item instance inorder to set it.
1037 
1038         // this is an answered email.
1039         setFlags(new Flags(Flags.Flag.ANSWERED), true);
1040         // all done, return the constructed Message object.
1041         return reply;
1042     }
1043 
1044 
1045     /**
1046      * Merge a set of addresses into a master accumulator list, eliminating
1047      * duplicates.
1048      *
1049      * @param master The set of addresses we've accumulated so far.
1050      * @param list   The list of addresses to merge in.
1051      */
1052     private void mergeAddressList(Map master, Address[] list) {
1053         // make sure we have a list.
1054         if (list == null) {
1055             return;
1056         }
1057         for (int i = 0; i < list.length; i++) {
1058             InternetAddress address = (InternetAddress)list[i];
1059 
1060             // if not in the master list already, add it now.
1061             if (!master.containsKey(address.getAddress())) {
1062                 master.put(address.getAddress(), address);
1063             }
1064         }
1065     }
1066 
1067 
1068     /**
1069      * Prune a list of addresses against our master address list,
1070      * returning the "new" addresses.  The master list will be
1071      * updated with this new set of addresses.
1072      *
1073      * @param master The master address list of addresses we've seen before.
1074      * @param list   The new list of addresses to prune.
1075      *
1076      * @return An array of addresses pruned of any duplicate addresses.
1077      */
1078     private Address[] pruneAddresses(Map master, Address[] list) {
1079         // return an empy array if we don't get an input list.
1080         if (list == null) {
1081             return new Address[0];
1082         }
1083 
1084         // optimistically assume there are no addresses to eliminate (common).
1085         ArrayList prunedList = new ArrayList(list.length);
1086         for (int i = 0; i < list.length; i++) {
1087             InternetAddress address = (InternetAddress)list[i];
1088 
1089             // if not in the master list, this is a new one.  Add to both the master list and
1090             // the pruned list.
1091             if (!master.containsKey(address.getAddress())) {
1092                 master.put(address.getAddress(), address);
1093                 prunedList.add(address);
1094             }
1095         }
1096         // convert back to list form.
1097         return (Address[])prunedList.toArray(new Address[0]);
1098     }
1099 
1100 
1101     /**
1102      * Write the message out to a stream in RFC 822 format.
1103      *
1104      * @param out    The target output stream.
1105      *
1106      * @exception MessagingException
1107      * @exception IOException
1108      */
1109     public void writeTo(OutputStream out) throws MessagingException, IOException {
1110         writeTo(out, null);
1111     }
1112 
1113     /**
1114      * Write the message out to a target output stream, excluding the
1115      * specified message headers.
1116      *
1117      * @param out    The target output stream.
1118      * @param ignoreHeaders
1119      *               An array of header types to ignore.  This can be null, which means
1120      *               write out all headers.
1121      *
1122      * @exception MessagingException
1123      * @exception IOException
1124      */
1125     public void writeTo(OutputStream out, String[] ignoreHeaders) throws MessagingException, IOException {
1126         // make sure everything is saved before we write
1127         if (!saved) {
1128             saveChanges();
1129         }
1130 
1131         // write out the headers first
1132         headers.writeTo(out, ignoreHeaders);
1133         // add the separater between the headers and the data portion.
1134         out.write('\r');
1135         out.write('\n');
1136 
1137         // if the modfied flag, we don't have current content, so the data handler needs to
1138         // take care of writing this data out.
1139         if (modified) {
1140             dh.writeTo(MimeUtility.encode(out, getEncoding()));
1141         } else {
1142             // if we have content directly, we can write this out now.
1143             if (content != null) {
1144                 out.write(content);
1145             }
1146             else {
1147                 // see if we can get a content stream for this message.  We might have had one
1148                 // explicitly set, or a subclass might override the get method to provide one.
1149                 InputStream in = getContentStream();
1150 
1151                 byte[] buffer = new byte[8192];
1152                 int length = in.read(buffer);
1153                 // copy the data stream-to-stream.
1154                 while (length > 0) {
1155                     out.write(buffer, 0, length);
1156                     length = in.read(buffer);
1157                 }
1158                 in.close();
1159             }
1160         }
1161 
1162         // flush any data we wrote out, but do not close the stream.  That's the caller's duty.
1163         out.flush();
1164     }
1165 
1166 
1167     /**
1168      * Retrieve all headers that match a given name.
1169      *
1170      * @param name   The target name.
1171      *
1172      * @return The set of headers that match the given name.  These headers
1173      *         will be the decoded() header values if these are RFC 2047
1174      *         encoded.
1175      * @exception MessagingException
1176      */
1177     public String[] getHeader(String name) throws MessagingException {
1178         return headers.getHeader(name);
1179     }
1180 
1181     /**
1182      * Get all headers that match a particular name, as a single string.
1183      * Individual headers are separated by the provided delimiter.   If
1184      * the delimiter is null, only the first header is returned.
1185      *
1186      * @param name      The source header name.
1187      * @param delimiter The delimiter string to be used between headers.  If null, only
1188      *                  the first is returned.
1189      *
1190      * @return The headers concatenated as a single string.
1191      * @exception MessagingException
1192      */
1193     public String getHeader(String name, String delimiter) throws MessagingException {
1194         return headers.getHeader(name, delimiter);
1195     }
1196 
1197     /**
1198      * Set a new value for a named header.
1199      *
1200      * @param name   The name of the target header.
1201      * @param value  The new value for the header.
1202      *
1203      * @exception MessagingException
1204      */
1205     public void setHeader(String name, String value) throws MessagingException {
1206         headers.setHeader(name, value);
1207     }
1208 
1209     /**
1210      * Conditionally set or remove a named header.  If the new value
1211      * is null, the header is removed.
1212      *
1213      * @param name   The header name.
1214      * @param value  The new header value.  A null value causes the header to be
1215      *               removed.
1216      *
1217      * @exception MessagingException
1218      */
1219     private void setOrRemoveHeader(String name, String value) throws MessagingException {
1220         if (value == null) {
1221             headers.removeHeader(name);
1222         }
1223         else {
1224             headers.setHeader(name, value);
1225         }
1226     }
1227 
1228     /**
1229      * Add a new value to an existing header.  The added value is
1230      * created as an additional header of the same type and value.
1231      *
1232      * @param name   The name of the target header.
1233      * @param value  The removed header.
1234      *
1235      * @exception MessagingException
1236      */
1237     public void addHeader(String name, String value) throws MessagingException {
1238         headers.addHeader(name, value);
1239     }
1240 
1241     /**
1242      * Remove a header with the given name.
1243      *
1244      * @param name   The name of the removed header.
1245      *
1246      * @exception MessagingException
1247      */
1248     public void removeHeader(String name) throws MessagingException {
1249         headers.removeHeader(name);
1250     }
1251 
1252     /**
1253      * Retrieve the complete list of message headers, as an enumeration.
1254      *
1255      * @return An Enumeration of the message headers.
1256      * @exception MessagingException
1257      */
1258     public Enumeration getAllHeaders() throws MessagingException {
1259         return headers.getAllHeaders();
1260     }
1261 
1262     public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
1263         return headers.getMatchingHeaders(names);
1264     }
1265 
1266     public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
1267         return headers.getNonMatchingHeaders(names);
1268     }
1269 
1270     public void addHeaderLine(String line) throws MessagingException {
1271         headers.addHeaderLine(line);
1272     }
1273 
1274     public Enumeration getAllHeaderLines() throws MessagingException {
1275         return headers.getAllHeaderLines();
1276     }
1277 
1278     public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
1279         return headers.getMatchingHeaderLines(names);
1280     }
1281 
1282     public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
1283         return headers.getNonMatchingHeaderLines(names);
1284     }
1285 
1286     public synchronized Flags getFlags() throws MessagingException {
1287         return (Flags) flags.clone();
1288     }
1289 
1290     public synchronized boolean isSet(Flags.Flag flag) throws MessagingException {
1291         return flags.contains(flag);
1292     }
1293 
1294     /**
1295      * Set or clear a flag value.
1296      *
1297      * @param flags  The set of flags to effect.
1298      * @param set    The value to set the flag to (true or false).
1299      *
1300      * @exception MessagingException
1301      */
1302     public synchronized void setFlags(Flags flag, boolean set) throws MessagingException {
1303         if (set) {
1304             flags.add(flag);
1305         }
1306         else {
1307             flags.remove(flag);
1308         }
1309     }
1310 
1311     /**
1312      * Saves any changes on this message.  When called, the modified
1313      * and saved flags are set to true and updateHeaders() is called
1314      * to force updates.
1315      *
1316      * @exception MessagingException
1317      */
1318     public void saveChanges() throws MessagingException {
1319         // setting modified invalidates the current content.
1320         modified = true;
1321         saved = true;
1322         // update message headers from the content.
1323         updateHeaders();
1324     }
1325 
1326     /**
1327      * Update the internet headers so that they make sense.  This
1328      * will attempt to make sense of the message content type
1329      * given the state of the content.
1330      *
1331      * @exception MessagingException
1332      */
1333     protected void updateHeaders() throws MessagingException {
1334 
1335         // make sure we set the MIME version
1336         setHeader("MIME-Version", "1.0");
1337 
1338         DataHandler handler = getDataHandler();
1339 
1340         try {
1341             // figure out the content type.  If not set, we'll need to figure this out.
1342             String type = dh.getContentType();
1343             // parse this content type out so we can do matches/compares.
1344             ContentType content = new ContentType(type);
1345 
1346             // is this a multipart content?
1347             if (content.match("multipart/*")) {
1348                 // the content is suppose to be a MimeMultipart.  Ping it to update it's headers as well.
1349                 try {
1350                     MimeMultipart part = (MimeMultipart)handler.getContent();
1351                     part.updateHeaders();
1352                 } catch (ClassCastException e) {
1353                     throw new MessagingException("Message content is not MimeMultipart", e);
1354                 }
1355             }
1356             else if (!content.match("message/rfc822")) {
1357                 // simple part, we need to update the header type information
1358                 // if no encoding is set yet, figure this out from the data handler content.
1359                 if (getSingleHeader("Content-Transfer-Encoding") == null) {
1360                     setHeader("Content-Transfer-Encoding", MimeUtility.getEncoding(handler));
1361                 }
1362 
1363                 // is a content type header set?  Check the property to see if we need to set this.
1364                 if (getSingleHeader("Content-Type") == null) {
1365                     if (SessionUtil.getBooleanProperty(session, "MIME_MAIL_SETDEFAULTTEXTCHARSET", true)) {
1366                         // is this a text type?  Figure out the encoding and make sure it is set.
1367                         if (content.match("text/*")) {
1368                             // the charset should be specified as a parameter on the MIME type.  If not there,
1369                             // try to figure one out.
1370                             if (content.getParameter("charset") == null) {
1371 
1372                                 String encoding = getEncoding();
1373                                 // if we're sending this as 7-bit ASCII, our character set need to be
1374                                 // compatible.
1375                                 if (encoding != null && encoding.equalsIgnoreCase("7bit")) {
1376                                     content.setParameter("charset", "us-ascii");
1377                                 }
1378                                 else {
1379                                     // get the global default.
1380                                     content.setParameter("charset", MimeUtility.getDefaultMIMECharset());
1381                                 }
1382                             }
1383                         }
1384                     }
1385                 }
1386             }
1387 
1388             // if we don't have a content type header, then create one.
1389             if (getSingleHeader("Content-Type") == null) {
1390                 // get the disposition header, and if it is there, copy the filename parameter into the
1391                 // name parameter of the type.
1392                 String disp = getSingleHeader("Content-Disposition");
1393                 if (disp != null) {
1394                     // parse up the string value of the disposition
1395                     ContentDisposition disposition = new ContentDisposition(disp);
1396                     // now check for a filename value
1397                     String filename = disposition.getParameter("filename");
1398                     // copy and rename the parameter, if it exists.
1399                     if (filename != null) {
1400                         content.setParameter("name", filename);
1401                     }
1402                 }
1403                 // set the header with the updated content type information.
1404                 setHeader("Content-Type", content.toString());
1405             }
1406 
1407             // new javamail 1.4 requirement.
1408             updateMessageID();
1409 
1410         } catch (IOException e) {
1411             throw new MessagingException("Error updating message headers", e);
1412         }
1413     }
1414 
1415 
1416     protected InternetHeaders createInternetHeaders(InputStream in) throws MessagingException {
1417         // internet headers has a constructor for just this purpose
1418         return new InternetHeaders(in);
1419     }
1420 
1421     /**
1422      * Convert a header into an array of NewsAddress items.
1423      *
1424      * @param header The name of the source header.
1425      *
1426      * @return The parsed array of addresses.
1427      * @exception MessagingException
1428      */
1429     private Address[] getHeaderAsNewsAddresses(String header) throws MessagingException {
1430         // NB:  We're using getHeader() here to allow subclasses an opportunity to perform lazy loading
1431         // of the headers.
1432         String mergedHeader = getHeader(header, ",");
1433         if (mergedHeader != null) {
1434             return NewsAddress.parse(mergedHeader);
1435         }
1436         return null;
1437     }
1438 
1439     private Address[] getHeaderAsInternetAddresses(String header, boolean strict) throws MessagingException {
1440         // NB:  We're using getHeader() here to allow subclasses an opportunity to perform lazy loading
1441         // of the headers.
1442         String mergedHeader = getHeader(header, ",");
1443 
1444         if (mergedHeader != null) {
1445             return InternetAddress.parseHeader(mergedHeader, strict);
1446         }
1447         return null;
1448     }
1449 
1450     /**
1451      * Check to see if we require strict addressing on parsing
1452      * internet headers.
1453      *
1454      * @return The current value of the "mail.mime.address.strict" session
1455      *         property, or true, if the property is not set.
1456      */
1457     private boolean isStrictAddressing() {
1458         return SessionUtil.getBooleanProperty(session, MIME_ADDRESS_STRICT, true);
1459     }
1460 
1461     /**
1462      * Set a named header to the value of an address field.
1463      *
1464      * @param header  The header name.
1465      * @param address The address value.  If the address is null, the header is removed.
1466      *
1467      * @exception MessagingException
1468      */
1469     private void setHeader(String header, Address address) throws MessagingException {
1470         if (address == null) {
1471             removeHeader(header);
1472         }
1473         else {
1474             setHeader(header, address.toString());
1475         }
1476     }
1477 
1478     /**
1479      * Set a header to a list of addresses.
1480      *
1481      * @param header    The header name.
1482      * @param addresses An array of addresses to set the header to.  If null, the
1483      *                  header is removed.
1484      */
1485     private void setHeader(String header, Address[] addresses) {
1486         if (addresses == null) {
1487             headers.removeHeader(header);
1488         }
1489         else {
1490             headers.setHeader(header, addresses);
1491         }
1492     }
1493 
1494     private void addHeader(String header, Address[] addresses) throws MessagingException {
1495         headers.addHeader(header, InternetAddress.toString(addresses));
1496     }
1497 
1498     private String getHeaderForRecipientType(Message.RecipientType type) throws MessagingException {
1499         if (RecipientType.TO == type) {
1500             return "To";
1501         } else if (RecipientType.CC == type) {
1502             return "Cc";
1503         } else if (RecipientType.BCC == type) {
1504             return "Bcc";
1505         } else if (RecipientType.NEWSGROUPS == type) {
1506             return "Newsgroups";
1507         } else {
1508             throw new MessagingException("Unsupported recipient type: " + type.toString());
1509         }
1510     }
1511 
1512     /**
1513      * Utility routine to get a header as a single string value
1514      * rather than an array of headers.
1515      *
1516      * @param name   The name of the header.
1517      *
1518      * @return The single string header value.  If multiple headers exist,
1519      *         the additional ones are ignored.
1520      * @exception MessagingException
1521      */
1522     private String getSingleHeader(String name) throws MessagingException {
1523         String[] values = getHeader(name);
1524         if (values == null || values.length == 0) {
1525             return null;
1526         } else {
1527             return values[0];
1528         }
1529     }
1530 
1531     /**
1532      * Update the message identifier after headers have been updated.
1533      *
1534      * The default message id is composed of the following items:
1535      *
1536      * 1)  A newly created object's hash code.
1537      * 2)  A uniqueness counter
1538      * 3)  The current time in milliseconds
1539      * 4)  The string JavaMail
1540      * 5)  The user's local address as returned by InternetAddress.getLocalAddress().
1541      *
1542      * @exception MessagingException
1543      */
1544     public void updateMessageID() throws MessagingException {
1545         StringBuffer id = new StringBuffer();
1546 
1547         id.append('<');
1548         id.append(new Object().hashCode());
1549         id.append('.');
1550         id.append(messageID++);
1551         id.append(System.currentTimeMillis());
1552         id.append('.');
1553         id.append("JavaMail.");
1554 
1555         // get the local address and apply a suitable default.
1556 
1557         InternetAddress localAddress = InternetAddress.getLocalAddress(session);
1558         if (localAddress != null) {
1559             id.append(localAddress.getAddress());
1560         }
1561         else {
1562             id.append("javamailuser@localhost");
1563         }
1564         id.append('>');
1565 
1566         setHeader("Message-ID", id.toString());
1567     }
1568 
1569     /**
1570      * Method used to create a new MimeMessage instance.  This method
1571      * is used whenever the MimeMessage class needs to create a new
1572      * Message instance (e.g, reply()).  This method allows subclasses
1573      * to override the class of message that gets created or set
1574      * default values, if needed.
1575      *
1576      * @param session The session associated with this message.
1577      *
1578      * @return A newly create MimeMessage instance.
1579      */
1580     protected MimeMessage createMimeMessage(Session session) {
1581         return new MimeMessage(session);
1582     }
1583 
1584 }