jaxws/src/java.xml.soap/share/classes/com/sun/xml/internal/messaging/saaj/packaging/mime/internet/MimeBodyPart.java
changeset 28977 d7609b65606b
parent 28976 8c912c147654
parent 28344 722378bc599e
child 28978 8431abc709c0
equal deleted inserted replaced
28976:8c912c147654 28977:d7609b65606b
     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 }