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