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