Clover coverage report - Maven Clover report
Coverage timestamp: Sun Aug 20 2006 04:01:44 PDT
file stats: LOC: 1,584   Methods: 94
NCLOC: 751   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
MimeMessage.java 41.4% 53.3% 60.6% 51.3%
coverage coverage
 1    /**
 2    *
 3    * Copyright 2003-2006 The Apache Software Foundation
 4    *
 5    * Licensed under the Apache License, Version 2.0 (the "License");
 6    * you may not use this file except in compliance with the License.
 7    * You may obtain a copy of the License at
 8    *
 9    * http://www.apache.org/licenses/LICENSE-2.0
 10    *
 11    * Unless required by applicable law or agreed to in writing, software
 12    * distributed under the License is distributed on an "AS IS" BASIS,
 13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14    * See the License for the specific language governing permissions and
 15    * limitations under the License.
 16    */
 17   
 18    package javax.mail.internet;
 19   
 20    import java.io.BufferedInputStream;
 21    import java.io.ByteArrayInputStream;
 22    import java.io.ByteArrayOutputStream;
 23    import java.io.IOException;
 24    import java.io.InputStream;
 25    import java.io.ObjectStreamException;
 26    import java.io.OutputStream;
 27    import java.io.UnsupportedEncodingException;
 28    import java.util.ArrayList;
 29    import java.util.Arrays;
 30    import java.util.Date;
 31    import java.util.Enumeration;
 32    import java.util.HashMap;
 33    import java.util.List;
 34    import java.util.Map;
 35   
 36    import javax.activation.DataHandler;
 37    import javax.mail.Address;
 38    import javax.mail.Flags;
 39    import javax.mail.Folder;
 40    import javax.mail.Message;
 41    import javax.mail.MessagingException;
 42    import javax.mail.Multipart;
 43    import javax.mail.Part;
 44    import javax.mail.Session;
 45    import javax.mail.internet.HeaderTokenizer.Token;
 46   
 47    import org.apache.geronimo.mail.util.ASCIIUtil;
 48    import org.apache.geronimo.mail.util.SessionUtil;
 49   
 50    /**
 51    * @version $Rev: 421852 $ $Date: 2006-07-14 03:02:19 -0700 (Fri, 14 Jul 2006) $
 52    */
 53    public class MimeMessage extends Message implements MimePart {
 54    private static final String MIME_ADDRESS_STRICT = "mail.mime.address.strict";
 55    private static final String MIME_DECODEFILENAME = "mail.mime.decodefilename";
 56    private static final String MIME_ENCODEFILENAME = "mail.mime.encodefilename";
 57   
 58    private static final String MAIL_ALTERNATES = "mail.alternates";
 59    private static final String MAIL_REPLYALLCC = "mail.replyallcc";
 60   
 61    // static used to ensure message ID uniqueness
 62    private static int messageID = 0;
 63   
 64   
 65    /**
 66    * Extends {@link javax.mail.Message.RecipientType} to support addition recipient types.
 67    */
 68    public static class RecipientType extends Message.RecipientType {
 69    /**
 70    * Recipient type for Usenet news.
 71    */
 72    public static final RecipientType NEWSGROUPS = new RecipientType("Newsgroups");
 73   
 74  1 protected RecipientType(String type) {
 75  1 super(type);
 76    }
 77   
 78    /**
 79    * Ensure the singleton is returned.
 80    *
 81    * @return resolved object
 82    */
 83  0 protected Object readResolve() throws ObjectStreamException {
 84  0 if (this.type.equals("Newsgroups")) {
 85  0 return NEWSGROUPS;
 86    } else {
 87  0 return super.readResolve();
 88    }
 89    }
 90    }
 91   
 92    /**
 93    * The {@link DataHandler} for this Message's content.
 94    */
 95    protected DataHandler dh;
 96    /**
 97    * This message's content (unless sourced from a SharedInputStream).
 98    */
 99    protected byte[] content;
 100    /**
 101    * If the data for this message was supplied by a {@link SharedInputStream}
 102    * then this is another such stream representing the content of this message;
 103    * if this field is non-null, then {@link #content} will be null.
 104    */
 105    protected InputStream contentStream;
 106    /**
 107    * This message's headers.
 108    */
 109    protected InternetHeaders headers;
 110    /**
 111    * This message's flags.
 112    */
 113    protected Flags flags;
 114    /**
 115    * Flag indicating that the message has been modified; set to true when
 116    * an empty message is created or when {@link #saveChanges()} is called.
 117    */
 118    protected boolean modified;
 119    /**
 120    * Flag indicating that the message has been saved.
 121    */
 122    protected boolean saved;
 123   
 124    private final MailDateFormat dateFormat = new MailDateFormat();
 125   
 126    /**
 127    * Create a new MimeMessage.
 128    * An empty message is created, with empty {@link #headers} and empty {@link #flags}.
 129    * The {@link #modified} flag is set.
 130    *
 131    * @param session the session for this message
 132    */
 133  20 public MimeMessage(Session session) {
 134  20 super(session);
 135  20 headers = new InternetHeaders();
 136  20 flags = new Flags();
 137    // empty messages are modified, because the content is not there, and require saving before use.
 138  20 modified = true;
 139  20 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  3 public MimeMessage(Session session, InputStream in) throws MessagingException {
 150  3 this(session);
 151  3 parse(in);
 152    // this message is complete, so marked as unmodified.
 153  3 modified = false;
 154    // and no saving required
 155  3 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  1 public MimeMessage(MimeMessage message) throws MessagingException {
 165  1 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  1 ByteArrayOutputStream copy = new ByteArrayOutputStream();
 175   
 176  1 try {
 177    // write this out the stream.
 178  1 message.writeTo(copy);
 179  1 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  1 ByteArrayInputStream inData = new ByteArrayInputStream(copy.toByteArray());
 183    // now reparse this message into this object.
 184  1 inData.close();
 185  1 parse (inData);
 186    // writing out the source data requires saving it, so we should consider this one saved also.
 187  1 saved = true;
 188    // this message is complete, so marked as unmodified.
 189  1 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  0 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  3 protected MimeMessage(Folder folder, int number) {
 205  3 super(folder, number);
 206  3 headers = new InternetHeaders();
 207  3 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  3 saved = true;
 211    // we've not filled in the content yet, so this needs to be marked as modified
 212  3 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  0 protected MimeMessage(Folder folder, InputStream in, int number) throws MessagingException {
 224  0 this(folder, number);
 225  0 parse(in);
 226    // this message is complete, so marked as unmodified.
 227  0 modified = false;
 228    // and no saving required
 229  0 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  0 protected MimeMessage(Folder folder, InternetHeaders headers, byte[] content, int number) throws MessagingException {
 243  0 this(folder, number);
 244  0 this.headers = headers;
 245  0 this.content = content;
 246    // this message is complete, so marked as unmodified.
 247  0 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  4 protected void parse(InputStream in) throws MessagingException {
 257  4 in = new BufferedInputStream(in);
 258    // create the headers first from the stream
 259  4 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  4 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 264  4 try {
 265  4 byte buffer[] = new byte[1024];
 266  4 int count;
 267  ? while ((count = in.read(buffer, 0, 1024)) != -1) {
 268  4 baos.write(buffer, 0, count);
 269    }
 270    } catch (Exception e) {
 271  0 throw new MessagingException(e.toString(), e);
 272    }
 273    // and finally extract the content as a byte array.
 274  4 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  8 public Address[] getFrom() throws MessagingException {
 287    // strict addressing controls this.
 288  8 boolean strict = isStrictAddressing();
 289  8 Address[] result = getHeaderAsInternetAddresses("From", strict);
 290  8 if (result == null) {
 291  5 result = getHeaderAsInternetAddresses("Sender", strict);
 292    }
 293  8 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  4 public void setFrom(Address address) throws MessagingException {
 306  4 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  1 public void setFrom() throws MessagingException {
 315  1 InternetAddress address = InternetAddress.getLocalAddress(session);
 316    // no local address resolvable? This is an error.
 317  1 if (address == null) {
 318  0 throw new MessagingException("No local address defined");
 319    }
 320  1 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  1 public void addFrom(Address[] addresses) throws MessagingException {
 331  1 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  4 public Address getSender() throws MessagingException {
 341  4 Address[] addrs = getHeaderAsInternetAddresses("Sender", isStrictAddressing());
 342  4 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  5 public void setSender(Address address) throws MessagingException {
 355  5 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  32 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  32 if (type == RecipientType.NEWSGROUPS) {
 376  8 return getHeaderAsNewsAddresses(getHeaderForRecipientType(type));
 377    }
 378    // the other types are all internet addresses.
 379  24 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  6 public Address[] getAllRecipients() throws MessagingException {
 397  6 List recipients = new ArrayList();
 398  6 addRecipientsToList(recipients, RecipientType.TO);
 399  6 addRecipientsToList(recipients, RecipientType.CC);
 400  6 addRecipientsToList(recipients, RecipientType.BCC);
 401  6 addRecipientsToList(recipients, RecipientType.NEWSGROUPS);
 402   
 403    // this is supposed to return null if nothing is there.
 404  6 if (recipients.isEmpty()) {
 405  1 return null;
 406    }
 407  5 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  24 private void addRecipientsToList(List list, Message.RecipientType type) throws MessagingException {
 420   
 421  24 Address[] recipients;
 422  24 if (type == RecipientType.NEWSGROUPS) {
 423  6 recipients = getHeaderAsNewsAddresses(getHeaderForRecipientType(type));
 424    }
 425    else {
 426  18 recipients = getHeaderAsInternetAddresses(getHeaderForRecipientType(type), isStrictAddressing());
 427    }
 428  24 if (recipients != null) {
 429  13 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  14 public void setRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException {
 443  14 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  9 public void setRecipients(Message.RecipientType type, String address) throws MessagingException {
 458  9 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  7 public void addRecipients(Message.RecipientType type, Address[] address) throws MessagingException {
 471  7 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  4 public void addRecipients(Message.RecipientType type, String address) throws MessagingException {
 483  4 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  3 public Address[] getReplyTo() throws MessagingException {
 495  3 Address[] addresses = getHeaderAsInternetAddresses("Reply-To", isStrictAddressing());
 496  3 if (addresses == null) {
 497  1 addresses = getFrom();
 498    }
 499  3 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  3 public void setReplyTo(Address[] address) throws MessagingException {
 511  3 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  3 public String getSubject() throws MessagingException {
 524  3 String subject = getSingleHeader("Subject");
 525  3 if (subject == null) {
 526  1 return null;
 527    } else {
 528  2 try {
 529    // this needs to be unfolded before decodeing.
 530  2 return MimeUtility.decodeText(MimeUtility.unfold(subject));
 531    } catch (UnsupportedEncodingException e) {
 532    // ignored.
 533    }
 534    }
 535   
 536  0 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  3 public void setSubject(String subject) throws MessagingException {
 551    // just set this using the default character set.
 552  3 setSubject(subject, null);
 553    }
 554   
 555  4 public void setSubject(String subject, String charset) throws MessagingException {
 556    // standard null removal (yada, yada, yada....)
 557  4 if (subject == null) {
 558  1 removeHeader("Subject");
 559    }
 560    else {
 561  3 try {
 562  3 String s = MimeUtility.fold(9, MimeUtility.encodeText(subject, charset, null));
 563    // encode this, and then fold to fit the line lengths.
 564  3 setHeader("Subject", MimeUtility.fold(9, MimeUtility.encodeText(subject, charset, null)));
 565    } catch (UnsupportedEncodingException e) {
 566  0 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  0 public Date getSentDate() throws MessagingException {
 579  0 String value = getSingleHeader("Date");
 580  0 if (value == null) {
 581  0 return null;
 582    }
 583  0 try {
 584  0 return dateFormat.parse(value);
 585    } catch (java.text.ParseException e) {
 586  0 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  0 public void setSentDate(Date sent) throws MessagingException {
 599  0 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  0 public Date getReceivedDate() throws MessagingException {
 610  0 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  0 public int getSize() throws MessagingException {
 623  0 if (content != null) {
 624  0 return content.length;
 625    }
 626  0 if (contentStream != null) {
 627  0 try {
 628  0 int size = contentStream.available();
 629  0 if (size > 0) {
 630  0 return size;
 631    }
 632    } catch (IOException e) {
 633    // ignore
 634    }
 635    }
 636  0 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  0 public int getLineCount() throws MessagingException {
 650  0 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  14 public String getContentType() throws MessagingException {
 661  14 String value = getSingleHeader("Content-Type");
 662  14 if (value == null) {
 663  1 value = "text/plain";
 664    }
 665  14 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  0 public boolean isMimeType(String type) throws MessagingException {
 679  0 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  0 public String getDisposition() throws MessagingException {
 691  0 String disp = getSingleHeader("Content-Disposition");
 692  0 if (disp != null) {
 693  0 return new ContentDisposition(disp).getDisposition();
 694    }
 695  0 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  0 public void setDisposition(String disposition) throws MessagingException {
 709  0 if (disposition == null) {
 710  0 removeHeader("Content-Disposition");
 711    }
 712    else {
 713    // the disposition has parameters, which we'll attempt to preserve in any existing header.
 714  0 String currentHeader = getSingleHeader("Content-Disposition");
 715  0 if (currentHeader != null) {
 716  0 ContentDisposition content = new ContentDisposition(currentHeader);
 717  0 content.setDisposition(disposition);
 718  0 setHeader("Content-Disposition", content.toString());
 719    }
 720    else {
 721    // set using the raw string.
 722  0 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  6 public String getEncoding() throws MessagingException {
 735    // this might require some parsing to sort out.
 736  6 String encoding = getSingleHeader("Content-Transfer-Encoding");
 737  6 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  0 HeaderTokenizer tokenizer = new HeaderTokenizer(encoding, HeaderTokenizer.MIME);
 741   
 742  0 Token token = tokenizer.next();
 743  0 while (token.getType() != Token.EOF) {
 744    // if this is an ATOM type, return it.
 745  0 if (token.getType() == Token.ATOM) {
 746  0 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  0 return encoding;
 752    }
 753    // no header, nothing to return.
 754  6 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  0 public String getContentID() throws MessagingException {
 765  0 return getSingleHeader("Content-ID");
 766    }
 767   
 768  0 public void setContentID(String cid) throws MessagingException {
 769  0 setOrRemoveHeader("Content-ID", cid);
 770    }
 771   
 772  0 public String getContentMD5() throws MessagingException {
 773  0 return getSingleHeader("Content-MD5");
 774    }
 775   
 776  0 public void setContentMD5(String md5) throws MessagingException {
 777  0 setOrRemoveHeader("Content-MD5", md5);
 778    }
 779   
 780  3 public String getDescription() throws MessagingException {
 781  3 String description = getSingleHeader("Content-Description");
 782  3 if (description != null) {
 783  2 try {
 784    // this could be both folded and encoded. Return this to usable form.
 785  2 return MimeUtility.decodeText(MimeUtility.unfold(description));
 786    } catch (UnsupportedEncodingException e) {
 787    // ignore
 788    }
 789    }
 790    // return the raw version for any errors.
 791  1 return description;
 792    }
 793   
 794  2 public void setDescription(String description) throws MessagingException {
 795  2 setDescription(description, null);
 796    }
 797   
 798  3 public void setDescription(String description, String charset) throws MessagingException {
 799  3 if (description == null) {
 800  1 removeHeader("Content-Description");
 801    }
 802    else {
 803  2 try {
 804  2 setHeader("Content-Description", MimeUtility.fold(21, MimeUtility.encodeText(description, charset, null)));
 805    } catch (UnsupportedEncodingException e) {
 806  0 throw new MessagingException(e.getMessage(), e);
 807    }
 808    }
 809   
 810    }
 811   
 812  0 public String[] getContentLanguage() throws MessagingException {
 813  0 return getHeader("Content-Language");
 814    }
 815   
 816  0 public void setContentLanguage(String[] languages) throws MessagingException {
 817  0 if (languages == null) {
 818  0 removeHeader("Content-Language");
 819  0 } else if (languages.length == 1) {
 820  0 setHeader("Content-Language", languages[0]);
 821    } else {
 822  0 StringBuffer buf = new StringBuffer(languages.length * 20);
 823  0 buf.append(languages[0]);
 824  0 for (int i = 1; i < languages.length; i++) {
 825  0 buf.append(',').append(languages[i]);
 826    }
 827  0 setHeader("Content-Language", buf.toString());
 828    }
 829    }
 830   
 831  0 public String getMessageID() throws MessagingException {
 832  0 return getSingleHeader("Message-ID");
 833    }
 834   
 835  0 public String getFileName() throws MessagingException {
 836    // see if there is a disposition. If there is, parse off the filename parameter.
 837  0 String disposition = getDisposition();
 838  0 String filename = null;
 839   
 840  0 if (disposition != null) {
 841  0 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  0 if (filename == null) {
 847  0 String type = getContentType();
 848  0 if (type != null) {
 849  0 try {
 850  0 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  0 if (filename != null && SessionUtil.getBooleanProperty(session, MIME_DECODEFILENAME, false)) {
 857  0 try {
 858  0 filename = MimeUtility.decodeText(filename);
 859    } catch (UnsupportedEncodingException e) {
 860  0 throw new MessagingException("Unable to decode filename", e);
 861    }
 862    }
 863   
 864  0 return filename;
 865    }
 866   
 867   
 868  0 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  0 if (name != null && SessionUtil.getBooleanProperty(session, MIME_ENCODEFILENAME, false)) {
 872  0 try {
 873  0 name = MimeUtility.encodeText(name);
 874    } catch (UnsupportedEncodingException e) {
 875  0 throw new MessagingException("Unable to encode filename", e);
 876    }
 877    }
 878   
 879    // get the disposition string.
 880  0 String disposition = getDisposition();
 881    // if not there, then this is an attachment.
 882  0 if (disposition == null) {
 883  0 disposition = Part.ATTACHMENT;
 884    }
 885    // now create a disposition object and set the parameter.
 886  0 ContentDisposition contentDisposition = new ContentDisposition(disposition);
 887  0 contentDisposition.setParameter("filename", name);
 888   
 889    // serialize this back out and reset.
 890  0 setDisposition(contentDisposition.toString());
 891    }
 892   
 893  0 public InputStream getInputStream() throws MessagingException, IOException {
 894  0 return getDataHandler().getInputStream();
 895    }
 896   
 897  2 protected InputStream getContentStream() throws MessagingException {
 898  2 if (contentStream != null) {
 899  0 return contentStream;
 900    }
 901   
 902  2 if (content != null) {
 903  2 return new ByteArrayInputStream(content);
 904    } else {
 905  0 throw new MessagingException("No content");
 906    }
 907    }
 908   
 909  0 public InputStream getRawInputStream() throws MessagingException {
 910  0 return getContentStream();
 911    }
 912   
 913  9 public synchronized DataHandler getDataHandler() throws MessagingException {
 914  9 if (dh == null) {
 915  2 dh = new DataHandler(new MimePartDataSource(this));
 916    }
 917  9 return dh;
 918    }
 919   
 920  3 public Object getContent() throws MessagingException, IOException {
 921  3 return getDataHandler().getContent();
 922    }
 923   
 924  6 public void setDataHandler(DataHandler handler) throws MessagingException {
 925  6 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  6 removeHeader("Content-Type");
 930  6 removeHeader("Content-Transfer-Encoding");
 931    }
 932   
 933  3 public void setContent(Object content, String type) throws MessagingException {
 934  3 setDataHandler(new DataHandler(content, type));
 935    }
 936   
 937  1 public void setText(String text) throws MessagingException {
 938  1 setText(text, null, "plain");
 939    }
 940   
 941  1 public void setText(String text, String charset) throws MessagingException {
 942  1 setText(text, charset, "plain");
 943    }
 944   
 945   
 946  3 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  3 if (charset == null) {
 949    // if we have non us-ascii characters here, we need to adjust this.
 950  1 if (!ASCIIUtil.isAscii(text)) {
 951  0 charset = MimeUtility.getDefaultMIMECharset();
 952    }
 953    else {
 954  1 charset = "us-ascii";
 955    }
 956    }
 957  3 setContent(text, "text/" + subtype + "; charset=" + MimeUtility.quote(charset, HeaderTokenizer.MIME));
 958    }
 959   
 960  3 public void setContent(Multipart part) throws MessagingException {
 961  3 setDataHandler(new DataHandler(part, part.getContentType()));
 962  3 part.setParent(this);
 963    }
 964   
 965  0 public Message reply(boolean replyToAll) throws MessagingException {
 966    // create a new message in this session.
 967  0 MimeMessage reply = createMimeMessage(session);
 968   
 969    // get the header and add the "Re:" bit, if necessary.
 970  0 String newSubject = getSubject();
 971  0 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  0 if (!newSubject.regionMatches(true, 0, "Re: ", 0, 4)) {
 975  0 newSubject = "Re: " + newSubject;
 976    }
 977  0 reply.setSubject(newSubject);
 978    }
 979   
 980  0 Address[] toRecipients = getReplyTo();
 981   
 982    // set the target recipients the replyTo value
 983  0 reply.setRecipients(Message.RecipientType.TO, getReplyTo());
 984   
 985    // need to reply to everybody? More things to add.
 986  0 if (replyToAll) {
 987    // when replying, we want to remove "duplicates" in the final list.
 988   
 989  0 HashMap masterList = new HashMap();
 990   
 991    // reply to all implies add the local sender. Add this to the list if resolveable.
 992  0 InternetAddress localMail = InternetAddress.getLocalAddress(session);
 993  0 if (localMail != null) {
 994  0 masterList.put(localMail.getAddress(), localMail);
 995    }
 996    // see if we have some local aliases to deal with.
 997  0 String alternates = session.getProperty(MAIL_ALTERNATES);
 998  0 if (alternates != null) {
 999    // parse this string list and merge with our set.
 1000  0 Address[] alternateList = InternetAddress.parse(alternates, false);
 1001  0 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  0 Address[] toList = pruneAddresses(masterList, getRecipients(Message.RecipientType.TO));
 1010  0 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  0 if (SessionUtil.getBooleanProperty(session, MAIL_REPLYALLCC, false)) {
 1015  0 reply.addRecipients(Message.RecipientType.CC, toList);
 1016    }
 1017    else {
 1018  0 reply.addRecipients(Message.RecipientType.TO, toList);
 1019    }
 1020    }
 1021    // and repeat for the CC list.
 1022  0 toList = pruneAddresses(masterList, getRecipients(Message.RecipientType.CC));
 1023  0 if (toList.length != 0) {
 1024  0 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  0 toList = getRecipients(RecipientType.NEWSGROUPS);
 1030  0 if (toList != null && toList.length != 0) {
 1031  0 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  0 setFlags(new Flags(Flags.Flag.ANSWERED), true);
 1040    // all done, return the constructed Message object.
 1041  0 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  0 private void mergeAddressList(Map master, Address[] list) {
 1053    // make sure we have a list.
 1054  0 if (list == null) {
 1055  0 return;
 1056    }
 1057  0 for (int i = 0; i < list.length; i++) {
 1058  0 InternetAddress address = (InternetAddress)list[i];
 1059   
 1060    // if not in the master list already, add it now.
 1061  0 if (!master.containsKey(address.getAddress())) {
 1062  0 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  0 private Address[] pruneAddresses(Map master, Address[] list) {
 1079    // return an empy array if we don't get an input list.
 1080  0 if (list == null) {
 1081  0 return new Address[0];
 1082    }
 1083   
 1084    // optimistically assume there are no addresses to eliminate (common).
 1085  0 ArrayList prunedList = new ArrayList(list.length);
 1086  0 for (int i = 0; i < list.length; i++) {
 1087  0 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  0 if (!master.containsKey(address.getAddress())) {
 1092  0 master.put(address.getAddress(), address);
 1093  0 prunedList.add(address);
 1094    }
 1095    }
 1096    // convert back to list form.
 1097  0 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  4 public void writeTo(OutputStream out) throws MessagingException, IOException {
 1110  4 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  4 public void writeTo(OutputStream out, String[] ignoreHeaders) throws MessagingException, IOException {
 1126    // make sure everything is saved before we write
 1127  4 if (!saved) {
 1128  3 saveChanges();
 1129    }
 1130   
 1131    // write out the headers first
 1132  4 headers.writeTo(out, ignoreHeaders);
 1133    // add the separater between the headers and the data portion.
 1134  4 out.write('\r');
 1135  4 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  4 if (modified) {
 1140  4 dh.writeTo(MimeUtility.encode(out, getEncoding()));
 1141    } else {
 1142    // if we have content directly, we can write this out now.
 1143  0 if (content != null) {
 1144  0 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  0 InputStream in = getContentStream();
 1150   
 1151  0 byte[] buffer = new byte[8192];
 1152  0 int length = in.read(buffer);
 1153    // copy the data stream-to-stream.
 1154  0 while (length > 0) {
 1155  0 out.write(buffer, 0, length);
 1156  0 length = in.read(buffer);
 1157    }
 1158  0 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  4 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  45 public String[] getHeader(String name) throws MessagingException {
 1178  45 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  76 public String getHeader(String name, String delimiter) throws MessagingException {
 1194  76 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  34 public void setHeader(String name, String value) throws MessagingException {
 1206  34 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  9 private void setOrRemoveHeader(String name, String value) throws MessagingException {
 1220  9 if (value == null) {
 1221  5 headers.removeHeader(name);
 1222    }
 1223    else {
 1224  4 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  4 public void addHeader(String name, String value) throws MessagingException {
 1238  4 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  17 public void removeHeader(String name) throws MessagingException {
 1249  17 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  0 public Enumeration getAllHeaders() throws MessagingException {
 1259  0 return headers.getAllHeaders();
 1260    }
 1261   
 1262  0 public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
 1263  0 return headers.getMatchingHeaders(names);
 1264    }
 1265   
 1266  0 public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
 1267  0 return headers.getNonMatchingHeaders(names);
 1268    }
 1269   
 1270  0 public void addHeaderLine(String line) throws MessagingException {
 1271  0 headers.addHeaderLine(line);
 1272    }
 1273   
 1274  0 public Enumeration getAllHeaderLines() throws MessagingException {
 1275  0 return headers.getAllHeaderLines();
 1276    }
 1277   
 1278  0 public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
 1279  0 return headers.getMatchingHeaderLines(names);
 1280    }
 1281   
 1282  0 public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
 1283  0 return headers.getNonMatchingHeaderLines(names);
 1284    }
 1285   
 1286  0 public synchronized Flags getFlags() throws MessagingException {
 1287  0 return (Flags) flags.clone();
 1288    }
 1289   
 1290  0 public synchronized boolean isSet(Flags.Flag flag) throws MessagingException {
 1291  0 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  0 public synchronized void setFlags(Flags flag, boolean set) throws MessagingException {
 1303  0 if (set) {
 1304  0 flags.add(flag);
 1305    }
 1306    else {
 1307  0 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  6 public void saveChanges() throws MessagingException {
 1319    // setting modified invalidates the current content.
 1320  6 modified = true;
 1321  6 saved = true;
 1322    // update message headers from the content.
 1323  6 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  6 protected void updateHeaders() throws MessagingException {
 1334   
 1335    // make sure we set the MIME version
 1336  6 setHeader("MIME-Version", "1.0");
 1337   
 1338  6 DataHandler handler = getDataHandler();
 1339   
 1340  6 try {
 1341    // figure out the content type. If not set, we'll need to figure this out.
 1342  6 String type = dh.getContentType();
 1343    // parse this content type out so we can do matches/compares.
 1344  6 ContentType content = new ContentType(type);
 1345   
 1346    // is this a multipart content?
 1347  6 if (content.match("multipart/*")) {
 1348    // the content is suppose to be a MimeMultipart. Ping it to update it's headers as well.
 1349  3 try {
 1350  3 MimeMultipart part = (MimeMultipart)handler.getContent();
 1351  3 part.updateHeaders();
 1352    } catch (ClassCastException e) {
 1353  0 throw new MessagingException("Message content is not MimeMultipart", e);
 1354    }
 1355    }
 1356  3 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  3 if (getSingleHeader("Content-Transfer-Encoding") == null) {
 1360  3 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  3 if (getSingleHeader("Content-Type") == null) {
 1365  3 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  3 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  3 if (content.getParameter("charset") == null) {
 1371   
 1372  0 String encoding = getEncoding();
 1373    // if we're sending this as 7-bit ASCII, our character set need to be
 1374    // compatible.
 1375  0 if (encoding != null && encoding.equalsIgnoreCase("7bit")) {
 1376  0 content.setParameter("charset", "us-ascii");
 1377    }
 1378    else {
 1379    // get the global default.
 1380  0 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  6 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  5 String disp = getSingleHeader("Content-Disposition");
 1393  5 if (disp != null) {
 1394    // parse up the string value of the disposition
 1395  0 ContentDisposition disposition = new ContentDisposition(disp);
 1396    // now check for a filename value
 1397  0 String filename = disposition.getParameter("filename");
 1398    // copy and rename the parameter, if it exists.
 1399  0 if (filename != null) {
 1400  0 content.setParameter("name", filename);
 1401    }
 1402    }
 1403    // set the header with the updated content type information.
 1404  5 setHeader("Content-Type", content.toString());
 1405    }
 1406   
 1407    // new javamail 1.4 requirement.
 1408  6 updateMessageID();
 1409   
 1410    } catch (IOException e) {
 1411  0 throw new MessagingException("Error updating message headers", e);
 1412    }
 1413    }
 1414   
 1415   
 1416  0 protected InternetHeaders createInternetHeaders(InputStream in) throws MessagingException {
 1417    // internet headers has a constructor for just this purpose
 1418  0 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  14 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  14 String mergedHeader = getHeader(header, ",");
 1433  14 if (mergedHeader != null) {
 1434  7 return NewsAddress.parse(mergedHeader);
 1435    }
 1436  7 return null;
 1437    }
 1438   
 1439  62 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  62 String mergedHeader = getHeader(header, ",");
 1443   
 1444  62 if (mergedHeader != null) {
 1445  37 return InternetAddress.parseHeader(mergedHeader, strict);
 1446    }
 1447  25 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  57 private boolean isStrictAddressing() {
 1458  57 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  9 private void setHeader(String header, Address address) throws MessagingException {
 1470  9 if (address == null) {
 1471  3 removeHeader(header);
 1472    }
 1473    else {
 1474  6 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  17 private void setHeader(String header, Address[] addresses) {
 1486  17 if (addresses == null) {
 1487  5 headers.removeHeader(header);
 1488    }
 1489    else {
 1490  12 headers.setHeader(header, addresses);
 1491    }
 1492    }
 1493   
 1494  8 private void addHeader(String header, Address[] addresses) throws MessagingException {
 1495  8 headers.addHeader(header, InternetAddress.toString(addresses));
 1496    }
 1497   
 1498  90 private String getHeaderForRecipientType(Message.RecipientType type) throws MessagingException {
 1499  90 if (RecipientType.TO == type) {
 1500  23 return "To";
 1501  67 } else if (RecipientType.CC == type) {
 1502  23 return "Cc";
 1503  44 } else if (RecipientType.BCC == type) {
 1504  22 return "Bcc";
 1505  22 } else if (RecipientType.NEWSGROUPS == type) {
 1506  22 return "Newsgroups";
 1507    } else {
 1508  0 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  43 private String getSingleHeader(String name) throws MessagingException {
 1523  43 String[] values = getHeader(name);
 1524  43 if (values == null || values.length == 0) {
 1525  25 return null;
 1526    } else {
 1527  18 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  6 public void updateMessageID() throws MessagingException {
 1545  6 StringBuffer id = new StringBuffer();
 1546   
 1547  6 id.append('<');
 1548  6 id.append(new Object().hashCode());
 1549  6 id.append('.');
 1550  6 id.append(messageID++);
 1551  6 id.append(System.currentTimeMillis());
 1552  6 id.append('.');
 1553  6 id.append("JavaMail.");
 1554   
 1555    // get the local address and apply a suitable default.
 1556   
 1557  6 InternetAddress localAddress = InternetAddress.getLocalAddress(session);
 1558  6 if (localAddress != null) {
 1559  6 id.append(localAddress.getAddress());
 1560    }
 1561    else {
 1562  0 id.append("javamailuser@localhost");
 1563    }
 1564  6 id.append('>');
 1565   
 1566  6 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  0 protected MimeMessage createMimeMessage(Session session) {
 1581  0 return new MimeMessage(session);
 1582    }
 1583   
 1584    }