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