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