1 /*
   2  * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  * @(#)MimeBodyPart.java      1.52 03/02/12
  28  */
  29 
  30 
  31 
  32 package com.sun.xml.internal.messaging.saaj.packaging.mime.internet;
  33 
  34 
  35 import com.sun.xml.internal.messaging.saaj.packaging.mime.MessagingException;
  36 import com.sun.xml.internal.messaging.saaj.packaging.mime.util.OutputUtil;
  37 import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
  38 import com.sun.xml.internal.messaging.saaj.util.FinalArrayList;
  39 
  40 import java.util.logging.Level;
  41 import java.util.logging.Logger;
  42 import javax.activation.DataHandler;
  43 import java.io.BufferedInputStream;
  44 import java.io.ByteArrayInputStream;
  45 import java.io.IOException;
  46 import java.io.InputStream;
  47 import java.io.OutputStream;
  48 import java.io.UnsupportedEncodingException;
  49 import java.util.List;
  50 import javax.activation.DataSource;
  51 import com.sun.xml.internal.org.jvnet.mimepull.MIMEPart;
  52 
  53 /**
  54  * This class represents a MIME body part.
  55  * MimeBodyParts are contained in <code>MimeMultipart</code>
  56  * objects. <p>
  57  *
  58  * MimeBodyPart uses the <code>InternetHeaders</code> class to parse
  59  * and store the headers of that body part. <p>
  60  *
  61  * <hr><strong>A note on RFC 822 and MIME headers</strong><p>
  62  *
  63  * RFC 822 header fields <strong>must</strong> contain only
  64  * US-ASCII characters. MIME allows non ASCII characters to be present
  65  * in certain portions of certain headers, by encoding those characters.
  66  * RFC 2047 specifies the rules for doing this. The MimeUtility
  67  * class provided in this package can be used to to achieve this.
  68  * Callers of the <code>setHeader</code>, <code>addHeader</code>, and
  69  * <code>addHeaderLine</code> methods are responsible for enforcing
  70  * the MIME requirements for the specified headers.  In addition, these
  71  * header fields must be folded (wrapped) before being sent if they
  72  * exceed the line length limitation for the transport (1000 bytes for
  73  * SMTP).  Received headers may have been folded.  The application is
  74  * responsible for folding and unfolding headers as appropriate. <p>
  75  *
  76  * @author John Mani
  77  * @author Bill Shannon
  78  * @see MimeUtility
  79  */
  80 
  81 public final class MimeBodyPart {
  82 
  83     /**
  84      * This part should be presented as an attachment.
  85      * @see #getDisposition
  86      * @see #setDisposition
  87      */
  88     public static final String ATTACHMENT = "attachment";
  89 
  90     /**
  91      * This part should be presented inline.
  92      * @see #getDisposition
  93      * @see #setDisposition
  94      */
  95     public static final String INLINE = "inline";
  96 
  97 
  98     // Paranoia:
  99     // allow this last minute change to be disabled if it causes problems
 100     private static boolean setDefaultTextCharset = true;
 101 
 102     static {
 103         try {
 104             String s = System.getProperty("mail.mime.setdefaulttextcharset");
 105             // default to true
 106             setDefaultTextCharset = s == null || !s.equalsIgnoreCase("false");
 107         } catch (SecurityException sex) {
 108             // ignore it
 109         }
 110     }
 111 
 112     /*
 113         Data is represented in one of three forms.
 114         Either we have a DataHandler, or byte[] as the raw content image, or the contentStream.
 115         It's OK to have more than one of them, provided that they are identical.
 116     */
 117 
 118     /**
 119      * The DataHandler object representing this MimeBodyPart's content.
 120      */
 121     private DataHandler dh;
 122 
 123     /**
 124      * Byte array that holds the bytes of the content of this MimeBodyPart.
 125      * Used in a pair with {@link #contentLength} to denote a regision of a buffer
 126      * as a valid data.
 127      */
 128     private byte[] content;
 129     private int contentLength;
 130     private int start = 0;
 131 
 132     /**
 133      * If the data for this body part was supplied by an
 134      * InputStream that implements the SharedInputStream interface,
 135      * <code>contentStream</code> is another such stream representing
 136      * the content of this body part.  In this case, <code>content</code>
 137      * will be null.
 138      *
 139      * @since   JavaMail 1.2
 140      */
 141     private InputStream contentStream;
 142 
 143 
 144 
 145     /**
 146      * The InternetHeaders object that stores all the headers
 147      * of this body part.
 148      */
 149     private final InternetHeaders headers;
 150 
 151     /**
 152      * The <code>MimeMultipart</code> object containing this <code>MimeBodyPart</code>,
 153      * if known.
 154      * @since   JavaMail 1.1
 155      */
 156     private MimeMultipart parent;
 157 
 158     private MIMEPart mimePart;
 159 
 160     /**
 161      * An empty MimeBodyPart object is created.
 162      * This body part maybe filled in by a client constructing a multipart
 163      * message.
 164      */
 165     public MimeBodyPart() {
 166         headers = new InternetHeaders();
 167     }
 168 
 169     /**
 170      * Constructs a MimeBodyPart by reading and parsing the data from
 171      * the specified input stream. The parser consumes data till the end
 172      * of the given input stream.  The input stream must start at the
 173      * beginning of a valid MIME body part and must terminate at the end
 174      * of that body part. <p>
 175      *
 176      * Note that the "boundary" string that delimits body parts must
 177      * <strong>not</strong> be included in the input stream. The intention
 178      * is that the MimeMultipart parser will extract each body part's bytes
 179      * from a multipart stream and feed them into this constructor, without
 180      * the delimiter strings.
 181      *
 182      * @param   is      the body part Input Stream
 183      */
 184     public MimeBodyPart(InputStream is) throws MessagingException {
 185         if (!(is instanceof ByteArrayInputStream) &&
 186                 !(is instanceof BufferedInputStream) &&
 187                 !(is instanceof SharedInputStream))
 188             is = new BufferedInputStream(is);
 189 
 190         headers = new InternetHeaders(is);
 191 
 192         if (is instanceof SharedInputStream) {
 193             SharedInputStream sis = (SharedInputStream) is;
 194             contentStream = sis.newStream(sis.getPosition(), -1);
 195         } else {
 196             try {
 197                 ByteOutputStream bos = new ByteOutputStream();
 198                 bos.write(is);
 199                 content = bos.getBytes();
 200                 contentLength = bos.getCount();
 201             } catch (IOException ioex) {
 202                 throw new MessagingException("Error reading input stream", ioex);
 203             }
 204         }
 205 
 206     }
 207 
 208     /**
 209      * Constructs a MimeBodyPart using the given header and
 210      * content bytes. <p>
 211      *
 212      * Used by providers.
 213      *
 214      * @param   headers The header of this part
 215      * @param   content bytes representing the body of this part.
 216      */
 217     public MimeBodyPart(InternetHeaders headers, byte[] content, int len) {
 218         this.headers = headers;
 219         this.content = content;
 220         this.contentLength = len;
 221     }
 222 
 223     public MimeBodyPart(
 224         InternetHeaders headers, byte[] content, int start,  int len) {
 225         this.headers = headers;
 226         this.content = content;
 227         this.start = start;
 228         this.contentLength = len;
 229     }
 230 
 231     public MimeBodyPart(MIMEPart part) {
 232        mimePart = part;
 233        headers = new InternetHeaders();
 234        List<? extends com.sun.xml.internal.org.jvnet.mimepull.Header> hdrs = mimePart.getAllHeaders();
 235         for (com.sun.xml.internal.org.jvnet.mimepull.Header hd : hdrs) {
 236             headers.addHeader(hd.getName(), hd.getValue());
 237         }
 238     }
 239     /**
 240      * Return the containing <code>MimeMultipart</code> object,
 241      * or <code>null</code> if not known.
 242      */
 243     public MimeMultipart getParent() {
 244         return parent;
 245     }
 246 
 247     /**
 248      * Set the parent of this <code>MimeBodyPart</code> to be the specified
 249      * <code>MimeMultipart</code>.  Normally called by <code>MimeMultipart</code>'s
 250      * <code>addBodyPart</code> method.  <code>parent</code> may be
 251      * <code>null</code> if the <code>MimeBodyPart</code> is being removed
 252      * from its containing <code>MimeMultipart</code>.
 253      * @since   JavaMail 1.1
 254      */
 255     public void setParent(MimeMultipart parent) {
 256         this.parent = parent;
 257     }
 258 
 259     /**
 260      * Return the size of the content of this body part in bytes.
 261      * Return -1 if the size cannot be determined. <p>
 262      *
 263      * Note that this number may not be an exact measure of the
 264      * content size and may or may not account for any transfer
 265      * encoding of the content. <p>
 266      *
 267      * This implementation returns the size of the <code>content</code>
 268      * array (if not null), or, if <code>contentStream</code> is not
 269      * null, and the <code>available</code> method returns a positive
 270      * number, it returns that number as the size.  Otherwise, it returns
 271      * -1.
 272      *
 273      * @return size in bytes, or -1 if not known
 274      */
 275     public int getSize() {
 276 
 277         if (mimePart != null) {
 278             try {
 279                 return mimePart.read().available();
 280             } catch (IOException ex) {
 281                 return -1;
 282             }
 283         }
 284         if (content != null)
 285             return contentLength;
 286         if (contentStream != null) {
 287             try {
 288                 int size = contentStream.available();
 289                 // only believe the size if it's greate than zero, since zero
 290                 // is the default returned by the InputStream class itself
 291                 if (size > 0)
 292                     return size;
 293             } catch (IOException ex) {
 294                 // ignore it
 295             }
 296         }
 297         return -1;
 298     }
 299 
 300     /**
 301      * Return the number of lines for the content of this MimeBodyPart.
 302      * Return -1 if this number cannot be determined. <p>
 303      *
 304      * Note that this number may not be an exact measure of the
 305      * content length and may or may not account for any transfer
 306      * encoding of the content. <p>
 307      *
 308      * This implementation returns -1.
 309      *
 310      * @return number of lines, or -1 if not known
 311      */
 312      public int getLineCount() {
 313         return -1;
 314      }
 315 
 316     /**
 317      * Returns the value of the RFC 822 "Content-Type" header field.
 318      * This represents the content type of the content of this
 319      * body part. This value must not be null. If this field is
 320      * unavailable, "text/plain" should be returned. <p>
 321      *
 322      * This implementation uses <code>getHeader(name)</code>
 323      * to obtain the requisite header field.
 324      *
 325      * @return  Content-Type of this body part
 326      */
 327     public String getContentType() {
 328         if (mimePart != null) {
 329             return mimePart.getContentType();
 330         }
 331         String s = getHeader("Content-Type", null);
 332         if (s == null)
 333             s = "text/plain";
 334 
 335         return s;
 336     }
 337 
 338     /**
 339      * Is this MimeBodyPart of the specified MIME type?  This method
 340      * compares <strong>only the <code>primaryType</code> and
 341      * <code>subType</code></strong>.
 342      * The parameters of the content types are ignored. <p>
 343      *
 344      * For example, this method will return <code>true</code> when
 345      * comparing a MimeBodyPart of content type <strong>"text/plain"</strong>
 346      * with <strong>"text/plain; charset=foobar"</strong>. <p>
 347      *
 348      * If the <code>subType</code> of <code>mimeType</code> is the
 349      * special character '*', then the subtype is ignored during the
 350      * comparison.
 351      */
 352     public boolean isMimeType(String mimeType) {
 353         boolean result;
 354         // XXX - lots of room for optimization here!
 355         try {
 356             ContentType ct = new ContentType(getContentType());
 357             result = ct.match(mimeType);
 358         } catch (ParseException ex) {
 359             result = getContentType().equalsIgnoreCase(mimeType);
 360         }
 361         return result;
 362     }
 363 
 364     /**
 365      * Returns the value of the "Content-Disposition" header field.
 366      * This represents the disposition of this part. The disposition
 367      * describes how the part should be presented to the user. <p>
 368      *
 369      * If the Content-Disposition field is unavailable,
 370      * null is returned. <p>
 371      *
 372      * This implementation uses <code>getHeader(name)</code>
 373      * to obtain the requisite header field.
 374      *
 375      * @see #headers
 376      */
 377     public String getDisposition() throws MessagingException {
 378         String s = getHeader("Content-Disposition", null);
 379 
 380         if (s == null)
 381             return null;
 382 
 383         ContentDisposition cd = new ContentDisposition(s);
 384         return cd.getDisposition();
 385     }
 386 
 387     /**
 388      * Set the "Content-Disposition" header field of this body part.
 389      * If the disposition is null, any existing "Content-Disposition"
 390      * header field is removed.
 391      *
 392      * @exception       IllegalStateException if this body part is
 393      *                  obtained from a READ_ONLY folder.
 394      */
 395     public void setDisposition(String disposition) throws MessagingException {
 396         if (disposition == null)
 397             removeHeader("Content-Disposition");
 398         else {
 399             String s = getHeader("Content-Disposition", null);
 400             if (s != null) {
 401                 /* A Content-Disposition header already exists ..
 402                  *
 403                  * Override disposition, but attempt to retain
 404                  * existing disposition parameters
 405                  */
 406                 ContentDisposition cd = new ContentDisposition(s);
 407                 cd.setDisposition(disposition);
 408                 disposition = cd.toString();
 409             }
 410             setHeader("Content-Disposition", disposition);
 411         }
 412     }
 413 
 414     /**
 415      * Returns the content transfer encoding from the
 416      * "Content-Transfer-Encoding" header
 417      * field. Returns <code>null</code> if the header is unavailable
 418      * or its value is absent. <p>
 419      *
 420      * This implementation uses <code>getHeader(name)</code>
 421      * to obtain the requisite header field.
 422      *
 423      * @see #headers
 424      */
 425     public String getEncoding() throws MessagingException {
 426         String s = getHeader("Content-Transfer-Encoding", null);
 427 
 428         if (s == null)
 429             return null;
 430 
 431         s = s.trim();   // get rid of trailing spaces
 432         // quick check for known values to avoid unnecessary use
 433         // of tokenizer.
 434         if (s.equalsIgnoreCase("7bit") || s.equalsIgnoreCase("8bit") ||
 435             s.equalsIgnoreCase("quoted-printable") ||
 436             s.equalsIgnoreCase("base64"))
 437             return s;
 438 
 439         // Tokenize the header to obtain the encoding (skip comments)
 440         HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
 441 
 442         HeaderTokenizer.Token tk;
 443         int tkType;
 444 
 445         for (;;) {
 446             tk = h.next(); // get a token
 447             tkType = tk.getType();
 448             if (tkType == HeaderTokenizer.Token.EOF)
 449             break; // done
 450             else if (tkType == HeaderTokenizer.Token.ATOM)
 451             return tk.getValue();
 452             else // invalid token, skip it.
 453             continue;
 454         }
 455         return s;
 456     }
 457 
 458     /**
 459      * Returns the value of the "Content-ID" header field. Returns
 460      * <code>null</code> if the field is unavailable or its value is
 461      * absent. <p>
 462      *
 463      * This implementation uses <code>getHeader(name)</code>
 464      * to obtain the requisite header field.
 465      */
 466     public String getContentID() {
 467         return getHeader("Content-ID", null);
 468     }
 469 
 470     /**
 471      * Set the "Content-ID" header field of this body part.
 472      * If the <code>cid</code> parameter is null, any existing
 473      * "Content-ID" is removed.
 474      *
 475      * @exception       IllegalStateException if this body part is
 476      *                  obtained from a READ_ONLY folder.
 477      * @since           JavaMail 1.3
 478      */
 479     public void setContentID(String cid) {
 480         if (cid == null)
 481             removeHeader("Content-ID");
 482         else
 483             setHeader("Content-ID", cid);
 484     }
 485 
 486     /**
 487      * Return the value of the "Content-MD5" header field. Returns
 488      * <code>null</code> if this field is unavailable or its value
 489      * is absent. <p>
 490      *
 491      * This implementation uses <code>getHeader(name)</code>
 492      * to obtain the requisite header field.
 493      */
 494     public String getContentMD5() {
 495         return getHeader("Content-MD5", null);
 496     }
 497 
 498     /**
 499      * Set the "Content-MD5" header field of this body part.
 500      *
 501      * @exception       IllegalStateException if this body part is
 502      *                  obtained from a READ_ONLY folder.
 503      */
 504     public void setContentMD5(String md5) {
 505         setHeader("Content-MD5", md5);
 506     }
 507 
 508     /**
 509      * Get the languages specified in the Content-Language header
 510      * of this MimeBodyPart. The Content-Language header is defined by
 511      * RFC 1766. Returns <code>null</code> if this header is not
 512      * available or its value is absent. <p>
 513      *
 514      * This implementation uses <code>getHeader(name)</code>
 515      * to obtain the requisite header field.
 516      */
 517     public String[] getContentLanguage() throws MessagingException {
 518         String s = getHeader("Content-Language", null);
 519 
 520         if (s == null)
 521             return null;
 522 
 523         // Tokenize the header to obtain the Language-tags (skip comments)
 524         HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
 525         FinalArrayList v = new FinalArrayList();
 526 
 527         HeaderTokenizer.Token tk;
 528         int tkType;
 529 
 530         while (true) {
 531             tk = h.next(); // get a language-tag
 532             tkType = tk.getType();
 533             if (tkType == HeaderTokenizer.Token.EOF)
 534             break; // done
 535             else if (tkType == HeaderTokenizer.Token.ATOM) v.add(tk.getValue());
 536             else // invalid token, skip it.
 537             continue;
 538         }
 539 
 540         if (v.size() == 0)
 541             return null;
 542 
 543         return (String[])v.toArray(new String[v.size()]);
 544     }
 545 
 546     /**
 547      * Set the Content-Language header of this MimeBodyPart. The
 548      * Content-Language header is defined by RFC 1766.
 549      *
 550      * @param languages         array of language tags
 551      */
 552     public void setContentLanguage(String[] languages) {
 553         StringBuffer sb = new StringBuffer(languages[0]);
 554         for (int i = 1; i < languages.length; i++)
 555             sb.append(',').append(languages[i]);
 556         setHeader("Content-Language", sb.toString());
 557     }
 558 
 559     /**
 560      * Returns the "Content-Description" header field of this body part.
 561      * This typically associates some descriptive information with
 562      * this part. Returns null if this field is unavailable or its
 563      * value is absent. <p>
 564      *
 565      * If the Content-Description field is encoded as per RFC 2047,
 566      * it is decoded and converted into Unicode. If the decoding or
 567      * conversion fails, the raw data is returned as is. <p>
 568      *
 569      * This implementation uses <code>getHeader(name)</code>
 570      * to obtain the requisite header field.
 571      *
 572      * @return  content description
 573      */
 574     public String getDescription() {
 575         String rawvalue = getHeader("Content-Description", null);
 576 
 577         if (rawvalue == null)
 578             return null;
 579 
 580         try {
 581             return MimeUtility.decodeText(MimeUtility.unfold(rawvalue));
 582         } catch (UnsupportedEncodingException ex) {
 583             return rawvalue;
 584         }
 585     }
 586 
 587     /**
 588      * Set the "Content-Description" header field for this body part.
 589      * If the description parameter is <code>null</code>, then any
 590      * existing "Content-Description" fields are removed. <p>
 591      *
 592      * If the description contains non US-ASCII characters, it will
 593      * be encoded using the platform's default charset. If the
 594      * description contains only US-ASCII characters, no encoding
 595      * is done and it is used as is. <p>
 596      *
 597      * Note that if the charset encoding process fails, a
 598      * MessagingException is thrown, and an UnsupportedEncodingException
 599      * is included in the chain of nested exceptions within the
 600      * MessagingException.
 601      *
 602      * @param description content description
 603      * @exception       IllegalStateException if this body part is
 604      *                  obtained from a READ_ONLY folder.
 605      * @exception       MessagingException An
 606      *                  UnsupportedEncodingException may be included
 607      *                  in the exception chain if the charset
 608      *                  conversion fails.
 609      */
 610     public void setDescription(String description) throws MessagingException {
 611         setDescription(description, null);
 612     }
 613 
 614     /**
 615      * Set the "Content-Description" header field for this body part.
 616      * If the description parameter is <code>null</code>, then any
 617      * existing "Content-Description" fields are removed. <p>
 618      *
 619      * If the description contains non US-ASCII characters, it will
 620      * be encoded using the specified charset. If the description
 621      * contains only US-ASCII characters, no encoding  is done and
 622      * it is used as is. <p>
 623      *
 624      * Note that if the charset encoding process fails, a
 625      * MessagingException is thrown, and an UnsupportedEncodingException
 626      * is included in the chain of nested exceptions within the
 627      * MessagingException.
 628      *
 629      * @param   description     Description
 630      * @param   charset         Charset for encoding
 631      * @exception       IllegalStateException if this body part is
 632      *                  obtained from a READ_ONLY folder.
 633      * @exception       MessagingException An
 634      *                  UnsupportedEncodingException may be included
 635      *                  in the exception chain if the charset
 636      *                  conversion fails.
 637      */
 638     public void setDescription(String description, String charset)
 639                 throws MessagingException {
 640         if (description == null) {
 641             removeHeader("Content-Description");
 642             return;
 643         }
 644 
 645         try {
 646             setHeader("Content-Description", MimeUtility.fold(21,
 647             MimeUtility.encodeText(description, charset, null)));
 648         } catch (UnsupportedEncodingException uex) {
 649             throw new MessagingException("Encoding error", uex);
 650         }
 651     }
 652 
 653     /**
 654      * Get the filename associated with this body part. <p>
 655      *
 656      * Returns the value of the "filename" parameter from the
 657      * "Content-Disposition" header field of this body part. If its
 658      * not available, returns the value of the "name" parameter from
 659      * the "Content-Type" header field of this body part.
 660      * Returns <code>null</code> if both are absent.
 661      *
 662      * @return  filename
 663      */
 664     public String getFileName() throws MessagingException {
 665         String filename = null;
 666         String s = getHeader("Content-Disposition", null);
 667 
 668         if (s != null) {
 669             // Parse the header ..
 670             ContentDisposition cd = new ContentDisposition(s);
 671             filename = cd.getParameter("filename");
 672         }
 673         if (filename == null) {
 674             // Still no filename ? Try the "name" ContentType parameter
 675             s = getHeader("Content-Type", null);
 676             if (s != null) {
 677             try {
 678                 ContentType ct = new ContentType(s);
 679                 filename = ct.getParameter("name");
 680             } catch (ParseException pex) { }    // ignore it
 681             }
 682         }
 683         return filename;
 684     }
 685 
 686     /**
 687      * Set the filename associated with this body part, if possible. <p>
 688      *
 689      * Sets the "filename" parameter of the "Content-Disposition"
 690      * header field of this body part.
 691      *
 692      * @exception       IllegalStateException if this body part is
 693      *                  obtained from a READ_ONLY folder.
 694      */
 695     public void setFileName(String filename) throws MessagingException {
 696         // Set the Content-Disposition "filename" parameter
 697         String s = getHeader("Content-Disposition", null);
 698         ContentDisposition cd =
 699             new ContentDisposition(s == null ? ATTACHMENT : s);
 700         cd.setParameter("filename", filename);
 701         setHeader("Content-Disposition", cd.toString());
 702 
 703         /* Also attempt to set the Content-Type "name" parameter,
 704          * to satisfy ancient MUAs.
 705          * XXX: This is not RFC compliant, and hence should really
 706          * be conditional based on some property. Fix this once we
 707          * figure out how to get at Properties from here !
 708          */
 709         s = getHeader("Content-Type", null);
 710         if (s != null) {
 711             try {
 712             ContentType cType = new ContentType(s);
 713             cType.setParameter("name", filename);
 714             setHeader("Content-Type", cType.toString());
 715             } catch (ParseException pex) { }    // ignore it
 716         }
 717     }
 718 
 719     /**
 720      * Return a decoded input stream for this body part's "content". <p>
 721      *
 722      * This implementation obtains the input stream from the DataHandler.
 723      * That is, it invokes getDataHandler().getInputStream();
 724      *
 725      * @return          an InputStream
 726      * @exception       IOException this is typically thrown by the
 727      *                  DataHandler. Refer to the documentation for
 728      *                  javax.activation.DataHandler for more details.
 729      *
 730      * @see     #getContentStream
 731      * @see     DataHandler#getInputStream
 732      */
 733     public InputStream getInputStream()
 734                 throws IOException {
 735         return getDataHandler().getInputStream();
 736     }
 737 
 738    /**
 739      * Produce the raw bytes of the content. This method is used
 740      * when creating a DataHandler object for the content. Subclasses
 741      * that can provide a separate input stream for just the MimeBodyPart
 742      * content might want to override this method. <p>
 743      *
 744      * @see #content
 745      */
 746     /*package*/ InputStream getContentStream() throws MessagingException {
 747         if (mimePart != null) {
 748             return mimePart.read();
 749         }
 750         if (contentStream != null)
 751             return ((SharedInputStream)contentStream).newStream(0, -1);
 752         if (content != null)
 753             return new ByteArrayInputStream(content,start,contentLength);
 754 
 755         throw new MessagingException("No content");
 756     }
 757 
 758     /**
 759      * Return an InputStream to the raw data with any Content-Transfer-Encoding
 760      * intact.  This method is useful if the "Content-Transfer-Encoding"
 761      * header is incorrect or corrupt, which would prevent the
 762      * <code>getInputStream</code> method or <code>getContent</code> method
 763      * from returning the correct data.  In such a case the application may
 764      * use this method and attempt to decode the raw data itself. <p>
 765      *
 766      * This implementation simply calls the <code>getContentStream</code>
 767      * method.
 768      *
 769      * @see     #getInputStream
 770      * @see     #getContentStream
 771      * @since   JavaMail 1.2
 772      */
 773     public InputStream getRawInputStream() throws MessagingException {
 774         return getContentStream();
 775     }
 776 
 777     /**
 778      * Return a DataHandler for this body part's content. <p>
 779      *
 780      * The implementation provided here works just like the
 781      * the implementation in MimeMessage.
 782      */
 783     public DataHandler getDataHandler() {
 784         if (mimePart != null) {
 785             //return an inputstream
 786             return new DataHandler(new DataSource() {
 787 
 788                 public InputStream getInputStream() throws IOException {
 789                     return mimePart.read();
 790                 }
 791 
 792                 public OutputStream getOutputStream() throws IOException {
 793                     throw new UnsupportedOperationException("getOutputStream cannot be supported : You have enabled LazyAttachments Option");
 794                 }
 795 
 796                 public String getContentType() {
 797                     return mimePart.getContentType();
 798                 }
 799 
 800                 public String getName() {
 801                     return "MIMEPart Wrapped DataSource";
 802                 }
 803             });
 804         }
 805         if (dh == null)
 806             dh = new DataHandler(new MimePartDataSource(this));
 807         return dh;
 808     }
 809 
 810     /**
 811      * Return the content as a java object. The type of the object
 812      * returned is of course dependent on the content itself. For
 813      * example, the native format of a text/plain content is usually
 814      * a String object. The native format for a "multipart"
 815      * content is always a MimeMultipart subclass. For content types that are
 816      * unknown to the DataHandler system, an input stream is returned
 817      * as the content. <p>
 818      *
 819      * This implementation obtains the content from the DataHandler.
 820      * That is, it invokes getDataHandler().getContent();
 821      *
 822      * @return          Object
 823      * @exception       IOException this is typically thrown by the
 824      *                  DataHandler. Refer to the documentation for
 825      *                  javax.activation.DataHandler for more details.
 826      */
 827     public Object getContent() throws IOException {
 828         return getDataHandler().getContent();
 829     }
 830 
 831     /**
 832      * This method provides the mechanism to set this body part's content.
 833      * The given DataHandler object should wrap the actual content.
 834      *
 835      * @param   dh      The DataHandler for the content
 836      * @exception       IllegalStateException if this body part is
 837      *                  obtained from a READ_ONLY folder.
 838      */
 839     public void setDataHandler(DataHandler dh) {
 840         if (mimePart != null) {
 841             mimePart = null;
 842         }
 843         this.dh = dh;
 844         this.content = null;
 845         this.contentStream = null;
 846         removeHeader("Content-Type");
 847         removeHeader("Content-Transfer-Encoding");
 848     }
 849 
 850     /**
 851      * A convenience method for setting this body part's content. <p>
 852      *
 853      * The content is wrapped in a DataHandler object. Note that a
 854      * DataContentHandler class for the specified type should be
 855      * available to the JavaMail implementation for this to work right.
 856      * That is, to do <code>setContent(foobar, "application/x-foobar")</code>,
 857      * a DataContentHandler for "application/x-foobar" should be installed.
 858      * Refer to the Java Activation Framework for more information.
 859      *
 860      * @param   o       the content object
 861      * @param   type    Mime type of the object
 862      * @exception       IllegalStateException if this body part is
 863      *                  obtained from a READ_ONLY folder.
 864      */
 865     public void setContent(Object o, String type) {
 866         if (mimePart != null) {
 867             mimePart = null;
 868         }
 869         if (o instanceof MimeMultipart) {
 870             setContent((MimeMultipart)o);
 871         } else {
 872             setDataHandler(new DataHandler(o, type));
 873         }
 874     }
 875 
 876     /**
 877      * Convenience method that sets the given String as this
 878      * part's content, with a MIME type of "text/plain". If the
 879      * string contains non US-ASCII characters, it will be encoded
 880      * using the platform's default charset. The charset is also
 881      * used to set the "charset" parameter. <p>
 882      *
 883      * Note that there may be a performance penalty if
 884      * <code>text</code> is large, since this method may have
 885      * to scan all the characters to determine what charset to
 886      * use. <p>
 887      * If the charset is already known, use the
 888      * setText() version that takes the charset parameter.
 889      *
 890      * @see     #setText(String text, String charset)
 891      */
 892     public void setText(String text) {
 893         setText(text, null);
 894     }
 895 
 896     /**
 897      * Convenience method that sets the given String as this part's
 898      * content, with a MIME type of "text/plain" and the specified
 899      * charset. The given Unicode string will be charset-encoded
 900      * using the specified charset. The charset is also used to set
 901      * the "charset" parameter.
 902      */
 903     public void setText(String text, String charset) {
 904         if (charset == null) {
 905             if (MimeUtility.checkAscii(text) != MimeUtility.ALL_ASCII)
 906                 charset = MimeUtility.getDefaultMIMECharset();
 907             else
 908                 charset = "us-ascii";
 909         }
 910         setContent(text, "text/plain; charset=" +
 911                 MimeUtility.quote(charset, HeaderTokenizer.MIME));
 912     }
 913 
 914     /**
 915      * This method sets the body part's content to a MimeMultipart object.
 916      *
 917      * @param  mp       The multipart object that is the Message's content
 918      * @exception       IllegalStateException if this body part is
 919      *                  obtained from a READ_ONLY folder.
 920      */
 921     public void setContent(MimeMultipart mp) {
 922         if (mimePart != null) {
 923             mimePart = null;
 924         }
 925         setDataHandler(new DataHandler(mp, mp.getContentType().toString()));
 926         mp.setParent(this);
 927     }
 928 
 929     /**
 930      * Output the body part as an RFC 822 format stream.
 931      *
 932      * @exception MessagingException
 933      * @exception IOException   if an error occurs writing to the
 934      *                          stream or if an error is generated
 935      *                          by the javax.activation layer.
 936      * @see DataHandler#writeTo
 937      */
 938     public void writeTo(OutputStream os)
 939                                 throws IOException, MessagingException {
 940 
 941         // First, write out the header
 942         List hdrLines = headers.getAllHeaderLines();
 943         int sz = hdrLines.size();
 944         for( int i=0; i<sz; i++ )
 945             OutputUtil.writeln((String)hdrLines.get(i),os);
 946 
 947         // The CRLF separator between header and content
 948         OutputUtil.writeln(os);
 949 
 950         // Finally, the content.
 951         // XXX: May need to account for ESMTP ?
 952         if (contentStream != null) {
 953             ((SharedInputStream)contentStream).writeTo(0,-1,os);
 954         } else
 955         if (content != null) {
 956             os.write(content,start,contentLength);
 957         } else
 958         if (dh!=null) {
 959             // this is the slowest route, so try it as the last resort
 960             OutputStream wos = MimeUtility.encode(os, getEncoding());
 961             getDataHandler().writeTo(wos);
 962             if(os!=wos)
 963                 wos.flush(); // Needed to complete encoding
 964         } else if (mimePart != null) {
 965             OutputStream wos = MimeUtility.encode(os, getEncoding());
 966             getDataHandler().writeTo(wos);
 967             if(os!=wos)
 968                 wos.flush(); // Needed to complete encoding
 969         }else {
 970             throw new MessagingException("no content");
 971         }
 972     }
 973 
 974     /**
 975      * Get all the headers for this header_name. Note that certain
 976      * headers may be encoded as per RFC 2047 if they contain
 977      * non US-ASCII characters and these should be decoded.
 978      *
 979      * @param   name    name of header
 980      * @return  array of headers
 981      * @see     MimeUtility
 982      */
 983     public String[] getHeader(String name) {
 984         return headers.getHeader(name);
 985     }
 986 
 987     /**
 988      * Get all the headers for this header name, returned as a single
 989      * String, with headers separated by the delimiter. If the
 990      * delimiter is <code>null</code>, only the first header is
 991      * returned.
 992      *
 993      * @param name              the name of this header
 994      * @param delimiter         delimiter between fields in returned string
 995      * @return                  the value fields for all headers with
 996      *                          this name
 997      */
 998     public String getHeader(String name, String delimiter) {
 999         return headers.getHeader(name, delimiter);
1000     }
1001 
1002     /**
1003      * Set the value for this header_name. Replaces all existing
1004      * header values with this new value. Note that RFC 822 headers
1005      * must contain only US-ASCII characters, so a header that
1006      * contains non US-ASCII characters must be encoded as per the
1007      * rules of RFC 2047.
1008      *
1009      * @param   name    header name
1010      * @param   value   header value
1011      * @see     MimeUtility
1012      */
1013     public void setHeader(String name, String value) {
1014         headers.setHeader(name, value);
1015     }
1016 
1017     /**
1018      * Add this value to the existing values for this header_name.
1019      * Note that RFC 822 headers must contain only US-ASCII
1020      * characters, so a header that contains non US-ASCII characters
1021      * must be encoded as per the rules of RFC 2047.
1022      *
1023      * @param   name    header name
1024      * @param   value   header value
1025      * @see     MimeUtility
1026      */
1027     public void addHeader(String name, String value) {
1028         headers.addHeader(name, value);
1029     }
1030 
1031     /**
1032      * Remove all headers with this name.
1033      */
1034     public void removeHeader(String name) {
1035         headers.removeHeader(name);
1036     }
1037 
1038     /**
1039      * Return all the headers from this Message as an Enumeration of
1040      * Header objects.
1041      */
1042     public FinalArrayList getAllHeaders() {
1043         return headers.getAllHeaders();
1044     }
1045 
1046 
1047     /**
1048      * Add a header line to this body part
1049      */
1050     public void addHeaderLine(String line) {
1051         headers.addHeaderLine(line);
1052     }
1053 
1054     /**
1055      * Examine the content of this body part and update the appropriate
1056      * MIME headers.  Typical headers that get set here are
1057      * <code>Content-Type</code> and <code>Content-Transfer-Encoding</code>.
1058      * Headers might need to be updated in two cases:
1059      *
1060      * <br>
1061      * - A message being crafted by a mail application will certainly
1062      * need to activate this method at some point to fill up its internal
1063      * headers.
1064      *
1065      * <br>
1066      * - A message read in from a Store will have obtained
1067      * all its headers from the store, and so doesn't need this.
1068      * However, if this message is editable and if any edits have
1069      * been made to either the content or message structure, we might
1070      * need to resync our headers.
1071      *
1072      * <br>
1073      * In both cases this method is typically called by the
1074      * <code>Message.saveChanges</code> method.
1075      */
1076     protected void updateHeaders() throws MessagingException {
1077         DataHandler dh = getDataHandler();
1078         if (dh == null) // Huh ?
1079             return;
1080 
1081         try {
1082             String type = dh.getContentType();
1083             boolean composite = false;
1084             boolean needCTHeader = getHeader("Content-Type") == null;
1085 
1086             ContentType cType = new ContentType(type);
1087             if (cType.match("multipart/*")) {
1088                 // If multipart, recurse
1089                 composite = true;
1090                 Object o = dh.getContent();
1091                 ((MimeMultipart) o).updateHeaders();
1092             } else if (cType.match("message/rfc822")) {
1093                 composite = true;
1094             }
1095 
1096             // Content-Transfer-Encoding, but only if we don't
1097             // already have one
1098             if (!composite) {   // not allowed on composite parts
1099                 if (getHeader("Content-Transfer-Encoding") == null)
1100                     setEncoding(MimeUtility.getEncoding(dh));
1101 
1102                 if (needCTHeader && setDefaultTextCharset &&
1103                         cType.match("text/*") &&
1104                         cType.getParameter("charset") == null) {
1105                     /*
1106                      * Set a default charset for text parts.
1107                      * We really should examine the data to determine
1108                      * whether or not it's all ASCII, but that's too
1109                      * expensive so we make an assumption:  If we
1110                      * chose 7bit encoding for this data, it's probably
1111                      * ASCII.  (MimeUtility.getEncoding will choose
1112                      * 7bit only in this case, but someone might've
1113                      * set the Content-Transfer-Encoding header manually.)
1114                      */
1115                     String charset;
1116                     String enc = getEncoding();
1117                     if (enc != null && enc.equalsIgnoreCase("7bit"))
1118                         charset = "us-ascii";
1119                     else
1120                         charset = MimeUtility.getDefaultMIMECharset();
1121                     cType.setParameter("charset", charset);
1122                     type = cType.toString();
1123                 }
1124             }
1125 
1126             // Now, let's update our own headers ...
1127 
1128             // Content-type, but only if we don't already have one
1129             if (needCTHeader) {
1130                 /*
1131                  * Pull out "filename" from Content-Disposition, and
1132                  * use that to set the "name" parameter. This is to
1133                  * satisfy older MUAs (DtMail, Roam and probably
1134                  * a bunch of others).
1135                  */
1136                 String s = getHeader("Content-Disposition", null);
1137                 if (s != null) {
1138                     // Parse the header ..
1139                     ContentDisposition cd = new ContentDisposition(s);
1140                     String filename = cd.getParameter("filename");
1141                     if (filename != null) {
1142                         cType.setParameter("name", filename);
1143                         type = cType.toString();
1144                     }
1145                 }
1146 
1147                 setHeader("Content-Type", type);
1148             }
1149         } catch (IOException ex) {
1150             throw new MessagingException("IOException updating headers", ex);
1151         }
1152     }
1153 
1154     private void setEncoding(String encoding) {
1155             setHeader("Content-Transfer-Encoding", encoding);
1156     }
1157 }