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