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