001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020 package javax.mail.internet; 021 022 import java.io.BufferedInputStream; 023 import java.io.ByteArrayInputStream; 024 import java.io.ByteArrayOutputStream; 025 import java.io.IOException; 026 import java.io.InputStream; 027 import java.io.ObjectStreamException; 028 import java.io.OutputStream; 029 import java.io.UnsupportedEncodingException; 030 import java.util.ArrayList; 031 import java.util.Arrays; 032 import java.util.Date; 033 import java.util.Enumeration; 034 import java.util.HashMap; 035 import java.util.List; 036 import java.util.Map; 037 038 import javax.activation.DataHandler; 039 import javax.mail.Address; 040 import javax.mail.Flags; 041 import javax.mail.Folder; 042 import javax.mail.Message; 043 import javax.mail.MessagingException; 044 import javax.mail.Multipart; 045 import javax.mail.Part; 046 import javax.mail.Session; 047 import javax.mail.internet.HeaderTokenizer.Token; 048 049 import org.apache.geronimo.mail.util.ASCIIUtil; 050 import org.apache.geronimo.mail.util.SessionUtil; 051 052 /** 053 * @version $Rev: 702800 $ $Date: 2008-10-08 06:38:14 -0400 (Wed, 08 Oct 2008) $ 054 */ 055 public class MimeMessage extends Message implements MimePart { 056 private static final String MIME_ADDRESS_STRICT = "mail.mime.address.strict"; 057 private static final String MIME_DECODEFILENAME = "mail.mime.decodefilename"; 058 private static final String MIME_ENCODEFILENAME = "mail.mime.encodefilename"; 059 060 private static final String MAIL_ALTERNATES = "mail.alternates"; 061 private static final String MAIL_REPLYALLCC = "mail.replyallcc"; 062 063 // static used to ensure message ID uniqueness 064 private static int messageID = 0; 065 066 067 /** 068 * Extends {@link javax.mail.Message.RecipientType} to support addition recipient types. 069 */ 070 public static class RecipientType extends Message.RecipientType { 071 /** 072 * Recipient type for Usenet news. 073 */ 074 public static final RecipientType NEWSGROUPS = new RecipientType("Newsgroups"); 075 076 protected RecipientType(String type) { 077 super(type); 078 } 079 080 /** 081 * Ensure the singleton is returned. 082 * 083 * @return resolved object 084 */ 085 protected Object readResolve() throws ObjectStreamException { 086 if (this.type.equals("Newsgroups")) { 087 return NEWSGROUPS; 088 } else { 089 return super.readResolve(); 090 } 091 } 092 } 093 094 /** 095 * The {@link DataHandler} for this Message's content. 096 */ 097 protected DataHandler dh; 098 /** 099 * 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 OutputStream encoderStream = MimeUtility.encode(out, getEncoding()); 1166 dh.writeTo(encoderStream); 1167 encoderStream.flush(); 1168 } else { 1169 // if we have content directly, we can write this out now. 1170 if (content != null) { 1171 out.write(content); 1172 } 1173 else { 1174 // see if we can get a content stream for this message. We might have had one 1175 // explicitly set, or a subclass might override the get method to provide one. 1176 InputStream in = getContentStream(); 1177 1178 byte[] buffer = new byte[8192]; 1179 int length = in.read(buffer); 1180 // copy the data stream-to-stream. 1181 while (length > 0) { 1182 out.write(buffer, 0, length); 1183 length = in.read(buffer); 1184 } 1185 in.close(); 1186 } 1187 } 1188 1189 // flush any data we wrote out, but do not close the stream. That's the caller's duty. 1190 out.flush(); 1191 } 1192 1193 1194 /** 1195 * Retrieve all headers that match a given name. 1196 * 1197 * @param name The target name. 1198 * 1199 * @return The set of headers that match the given name. These headers 1200 * will be the decoded() header values if these are RFC 2047 1201 * encoded. 1202 * @exception MessagingException 1203 */ 1204 public String[] getHeader(String name) throws MessagingException { 1205 return headers.getHeader(name); 1206 } 1207 1208 /** 1209 * Get all headers that match a particular name, as a single string. 1210 * Individual headers are separated by the provided delimiter. If 1211 * the delimiter is null, only the first header is returned. 1212 * 1213 * @param name The source header name. 1214 * @param delimiter The delimiter string to be used between headers. If null, only 1215 * the first is returned. 1216 * 1217 * @return The headers concatenated as a single string. 1218 * @exception MessagingException 1219 */ 1220 public String getHeader(String name, String delimiter) throws MessagingException { 1221 return headers.getHeader(name, delimiter); 1222 } 1223 1224 /** 1225 * Set a new value for a named header. 1226 * 1227 * @param name The name of the target header. 1228 * @param value The new value for the header. 1229 * 1230 * @exception MessagingException 1231 */ 1232 public void setHeader(String name, String value) throws MessagingException { 1233 headers.setHeader(name, value); 1234 } 1235 1236 /** 1237 * Conditionally set or remove a named header. If the new value 1238 * is null, the header is removed. 1239 * 1240 * @param name The header name. 1241 * @param value The new header value. A null value causes the header to be 1242 * removed. 1243 * 1244 * @exception MessagingException 1245 */ 1246 private void setOrRemoveHeader(String name, String value) throws MessagingException { 1247 if (value == null) { 1248 headers.removeHeader(name); 1249 } 1250 else { 1251 headers.setHeader(name, value); 1252 } 1253 } 1254 1255 /** 1256 * Add a new value to an existing header. The added value is 1257 * created as an additional header of the same type and value. 1258 * 1259 * @param name The name of the target header. 1260 * @param value The removed header. 1261 * 1262 * @exception MessagingException 1263 */ 1264 public void addHeader(String name, String value) throws MessagingException { 1265 headers.addHeader(name, value); 1266 } 1267 1268 /** 1269 * Remove a header with the given name. 1270 * 1271 * @param name The name of the removed header. 1272 * 1273 * @exception MessagingException 1274 */ 1275 public void removeHeader(String name) throws MessagingException { 1276 headers.removeHeader(name); 1277 } 1278 1279 /** 1280 * Retrieve the complete list of message headers, as an enumeration. 1281 * 1282 * @return An Enumeration of the message headers. 1283 * @exception MessagingException 1284 */ 1285 public Enumeration getAllHeaders() throws MessagingException { 1286 return headers.getAllHeaders(); 1287 } 1288 1289 public Enumeration getMatchingHeaders(String[] names) throws MessagingException { 1290 return headers.getMatchingHeaders(names); 1291 } 1292 1293 public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException { 1294 return headers.getNonMatchingHeaders(names); 1295 } 1296 1297 public void addHeaderLine(String line) throws MessagingException { 1298 headers.addHeaderLine(line); 1299 } 1300 1301 public Enumeration getAllHeaderLines() throws MessagingException { 1302 return headers.getAllHeaderLines(); 1303 } 1304 1305 public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException { 1306 return headers.getMatchingHeaderLines(names); 1307 } 1308 1309 public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException { 1310 return headers.getNonMatchingHeaderLines(names); 1311 } 1312 1313 1314 /** 1315 * Return a copy the flags associated with this message. 1316 * 1317 * @return a copy of the flags for this message 1318 * @throws MessagingException if there was a problem accessing the Store 1319 */ 1320 public synchronized Flags getFlags() throws MessagingException { 1321 return (Flags) flags.clone(); 1322 } 1323 1324 1325 /** 1326 * Check whether the supplied flag is set. 1327 * The default implementation checks the flags returned by {@link #getFlags()}. 1328 * 1329 * @param flag the flags to check for 1330 * @return true if the flags is set 1331 * @throws MessagingException if there was a problem accessing the Store 1332 */ 1333 public synchronized boolean isSet(Flags.Flag flag) throws MessagingException { 1334 return flags.contains(flag); 1335 } 1336 1337 /** 1338 * Set or clear a flag value. 1339 * 1340 * @param flags The set of flags to effect. 1341 * @param set The value to set the flag to (true or false). 1342 * 1343 * @exception MessagingException 1344 */ 1345 public synchronized void setFlags(Flags flag, boolean set) throws MessagingException { 1346 if (set) { 1347 flags.add(flag); 1348 } 1349 else { 1350 flags.remove(flag); 1351 } 1352 } 1353 1354 /** 1355 * Saves any changes on this message. When called, the modified 1356 * and saved flags are set to true and updateHeaders() is called 1357 * to force updates. 1358 * 1359 * @exception MessagingException 1360 */ 1361 public void saveChanges() throws MessagingException { 1362 // setting modified invalidates the current content. 1363 modified = true; 1364 saved = true; 1365 // update message headers from the content. 1366 updateHeaders(); 1367 } 1368 1369 /** 1370 * Update the internet headers so that they make sense. This 1371 * will attempt to make sense of the message content type 1372 * given the state of the content. 1373 * 1374 * @exception MessagingException 1375 */ 1376 protected void updateHeaders() throws MessagingException { 1377 1378 DataHandler handler = getDataHandler(); 1379 1380 try { 1381 // figure out the content type. If not set, we'll need to figure this out. 1382 String type = dh.getContentType(); 1383 // we might need to reconcile the content type and our explicitly set type 1384 String explicitType = getSingleHeader("Content-Type"); 1385 // parse this content type out so we can do matches/compares. 1386 ContentType content = new ContentType(type); 1387 1388 // is this a multipart content? 1389 if (content.match("multipart/*")) { 1390 // the content is suppose to be a MimeMultipart. Ping it to update it's headers as well. 1391 try { 1392 MimeMultipart part = (MimeMultipart)handler.getContent(); 1393 part.updateHeaders(); 1394 } catch (ClassCastException e) { 1395 throw new MessagingException("Message content is not MimeMultipart", e); 1396 } 1397 } 1398 else if (!content.match("message/rfc822")) { 1399 // simple part, we need to update the header type information 1400 // if no encoding is set yet, figure this out from the data handler content. 1401 if (getSingleHeader("Content-Transfer-Encoding") == null) { 1402 setHeader("Content-Transfer-Encoding", MimeUtility.getEncoding(handler)); 1403 } 1404 1405 // is a content type header set? Check the property to see if we need to set this. 1406 if (explicitType == null) { 1407 if (SessionUtil.getBooleanProperty(session, "MIME_MAIL_SETDEFAULTTEXTCHARSET", true)) { 1408 // is this a text type? Figure out the encoding and make sure it is set. 1409 if (content.match("text/*")) { 1410 // the charset should be specified as a parameter on the MIME type. If not there, 1411 // try to figure one out. 1412 if (content.getParameter("charset") == null) { 1413 1414 String encoding = getEncoding(); 1415 // if we're sending this as 7-bit ASCII, our character set need to be 1416 // compatible. 1417 if (encoding != null && encoding.equalsIgnoreCase("7bit")) { 1418 content.setParameter("charset", "us-ascii"); 1419 } 1420 else { 1421 // get the global default. 1422 content.setParameter("charset", MimeUtility.getDefaultMIMECharset()); 1423 } 1424 // replace the original type string 1425 type = content.toString(); 1426 } 1427 } 1428 } 1429 } 1430 } 1431 1432 // if we don't have a content type header, then create one. 1433 if (explicitType == null) { 1434 // get the disposition header, and if it is there, copy the filename parameter into the 1435 // name parameter of the type. 1436 String disp = getSingleHeader("Content-Disposition"); 1437 if (disp != null) { 1438 // parse up the string value of the disposition 1439 ContentDisposition disposition = new ContentDisposition(disp); 1440 // now check for a filename value 1441 String filename = disposition.getParameter("filename"); 1442 // copy and rename the parameter, if it exists. 1443 if (filename != null) { 1444 content.setParameter("name", filename); 1445 // set the header with the updated content type information. 1446 type = content.toString(); 1447 } 1448 } 1449 // if no header has been set, then copy our current type string (which may 1450 // have been modified above) 1451 setHeader("Content-Type", type); 1452 } 1453 1454 // make sure we set the MIME version 1455 setHeader("MIME-Version", "1.0"); 1456 // new javamail 1.4 requirement. 1457 updateMessageID(); 1458 1459 } catch (IOException e) { 1460 throw new MessagingException("Error updating message headers", e); 1461 } 1462 } 1463 1464 1465 /** 1466 * Create a new set of internet headers from the 1467 * InputStream 1468 * 1469 * @param in The header source. 1470 * 1471 * @return A new InternetHeaders object containing the 1472 * appropriate headers. 1473 * @exception MessagingException 1474 */ 1475 protected InternetHeaders createInternetHeaders(InputStream in) throws MessagingException { 1476 // internet headers has a constructor for just this purpose 1477 return new InternetHeaders(in); 1478 } 1479 1480 /** 1481 * Convert a header into an array of NewsAddress items. 1482 * 1483 * @param header The name of the source header. 1484 * 1485 * @return The parsed array of addresses. 1486 * @exception MessagingException 1487 */ 1488 private Address[] getHeaderAsNewsAddresses(String header) throws MessagingException { 1489 // NB: We're using getHeader() here to allow subclasses an opportunity to perform lazy loading 1490 // of the headers. 1491 String mergedHeader = getHeader(header, ","); 1492 if (mergedHeader != null) { 1493 return NewsAddress.parse(mergedHeader); 1494 } 1495 return null; 1496 } 1497 1498 private Address[] getHeaderAsInternetAddresses(String header, boolean strict) throws MessagingException { 1499 // NB: We're using getHeader() here to allow subclasses an opportunity to perform lazy loading 1500 // of the headers. 1501 String mergedHeader = getHeader(header, ","); 1502 1503 if (mergedHeader != null) { 1504 return InternetAddress.parseHeader(mergedHeader, strict); 1505 } 1506 return null; 1507 } 1508 1509 /** 1510 * Check to see if we require strict addressing on parsing 1511 * internet headers. 1512 * 1513 * @return The current value of the "mail.mime.address.strict" session 1514 * property, or true, if the property is not set. 1515 */ 1516 private boolean isStrictAddressing() { 1517 return SessionUtil.getBooleanProperty(session, MIME_ADDRESS_STRICT, true); 1518 } 1519 1520 /** 1521 * Set a named header to the value of an address field. 1522 * 1523 * @param header The header name. 1524 * @param address The address value. If the address is null, the header is removed. 1525 * 1526 * @exception MessagingException 1527 */ 1528 private void setHeader(String header, Address address) throws MessagingException { 1529 if (address == null) { 1530 removeHeader(header); 1531 } 1532 else { 1533 setHeader(header, address.toString()); 1534 } 1535 } 1536 1537 /** 1538 * Set a header to a list of addresses. 1539 * 1540 * @param header The header name. 1541 * @param addresses An array of addresses to set the header to. If null, the 1542 * header is removed. 1543 */ 1544 private void setHeader(String header, Address[] addresses) { 1545 if (addresses == null) { 1546 headers.removeHeader(header); 1547 } 1548 else { 1549 headers.setHeader(header, addresses); 1550 } 1551 } 1552 1553 private void addHeader(String header, Address[] addresses) throws MessagingException { 1554 headers.addHeader(header, InternetAddress.toString(addresses)); 1555 } 1556 1557 private String getHeaderForRecipientType(Message.RecipientType type) throws MessagingException { 1558 if (RecipientType.TO == type) { 1559 return "To"; 1560 } else if (RecipientType.CC == type) { 1561 return "Cc"; 1562 } else if (RecipientType.BCC == type) { 1563 return "Bcc"; 1564 } else if (RecipientType.NEWSGROUPS == type) { 1565 return "Newsgroups"; 1566 } else { 1567 throw new MessagingException("Unsupported recipient type: " + type.toString()); 1568 } 1569 } 1570 1571 /** 1572 * Utility routine to get a header as a single string value 1573 * rather than an array of headers. 1574 * 1575 * @param name The name of the header. 1576 * 1577 * @return The single string header value. If multiple headers exist, 1578 * the additional ones are ignored. 1579 * @exception MessagingException 1580 */ 1581 private String getSingleHeader(String name) throws MessagingException { 1582 String[] values = getHeader(name); 1583 if (values == null || values.length == 0) { 1584 return null; 1585 } else { 1586 return values[0]; 1587 } 1588 } 1589 1590 /** 1591 * Update the message identifier after headers have been updated. 1592 * 1593 * The default message id is composed of the following items: 1594 * 1595 * 1) A newly created object's hash code. 1596 * 2) A uniqueness counter 1597 * 3) The current time in milliseconds 1598 * 4) The string JavaMail 1599 * 5) The user's local address as returned by InternetAddress.getLocalAddress(). 1600 * 1601 * @exception MessagingException 1602 */ 1603 protected void updateMessageID() throws MessagingException { 1604 StringBuffer id = new StringBuffer(); 1605 1606 id.append('<'); 1607 id.append(new Object().hashCode()); 1608 id.append('.'); 1609 id.append(messageID++); 1610 id.append(System.currentTimeMillis()); 1611 id.append('.'); 1612 id.append("JavaMail."); 1613 1614 // get the local address and apply a suitable default. 1615 1616 InternetAddress localAddress = InternetAddress.getLocalAddress(session); 1617 if (localAddress != null) { 1618 id.append(localAddress.getAddress()); 1619 } 1620 else { 1621 id.append("javamailuser@localhost"); 1622 } 1623 id.append('>'); 1624 1625 setHeader("Message-ID", id.toString()); 1626 } 1627 1628 /** 1629 * Method used to create a new MimeMessage instance. This method 1630 * is used whenever the MimeMessage class needs to create a new 1631 * Message instance (e.g, reply()). This method allows subclasses 1632 * to override the class of message that gets created or set 1633 * default values, if needed. 1634 * 1635 * @param session The session associated with this message. 1636 * 1637 * @return A newly create MimeMessage instance. 1638 * @throws javax.mail.MessagingException if the MimeMessage could not be created 1639 */ 1640 protected MimeMessage createMimeMessage(Session session) throws javax.mail.MessagingException { 1641 return new MimeMessage(session); 1642 } 1643 1644 }