src/share/jaxws_classes/com/sun/xml/internal/messaging/saaj/packaging/mime/internet/MimeBodyPart.java

Thu, 31 Aug 2017 15:18:52 +0800

author
aoqi
date
Thu, 31 Aug 2017 15:18:52 +0800
changeset 637
9c07ef4934dd
parent 368
0989ad8c0860
parent 0
373ffda63c9a
permissions
-rw-r--r--

merge

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

mercurial