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