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