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