1 /*
2 * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 /*
27 * @(#)MimeBodyPart.java 1.52 03/02/12
28 */
29
30
31
32 package com.sun.xml.internal.messaging.saaj.packaging.mime.internet;
33
34
35 import com.sun.xml.internal.messaging.saaj.packaging.mime.Header;
36 import com.sun.xml.internal.messaging.saaj.packaging.mime.MessagingException;
37 import com.sun.xml.internal.messaging.saaj.packaging.mime.util.OutputUtil;
38 import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
39 import com.sun.xml.internal.messaging.saaj.util.FinalArrayList;
40
41 import javax.activation.DataHandler;
42 import java.io.BufferedInputStream;
43 import java.io.ByteArrayInputStream;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.OutputStream;
47 import java.io.UnsupportedEncodingException;
48 import java.util.List;
49 import javax.activation.DataSource;
50 import com.sun.xml.internal.org.jvnet.mimepull.MIMEPart;
51
52 /**
53 * This class represents a MIME body part.
54 * MimeBodyParts are contained in <code>MimeMultipart</code>
55 * objects. <p>
56 *
57 * MimeBodyPart uses the <code>InternetHeaders</code> class to parse
58 * and store the headers of that body part. <p>
59 *
60 * <hr><strong>A note on RFC 822 and MIME headers</strong><p>
61 *
62 * RFC 822 header fields <strong>must</strong> contain only
63 * US-ASCII characters. MIME allows non ASCII characters to be present
64 * in certain portions of certain headers, by encoding those characters.
65 * RFC 2047 specifies the rules for doing this. The MimeUtility
66 * class provided in this package can be used to to achieve this.
67 * Callers of the <code>setHeader</code>, <code>addHeader</code>, and
68 * <code>addHeaderLine</code> methods are responsible for enforcing
69 * the MIME requirements for the specified headers. In addition, these
70 * header fields must be folded (wrapped) before being sent if they
71 * exceed the line length limitation for the transport (1000 bytes for
72 * SMTP). Received headers may have been folded. The application is
73 * responsible for folding and unfolding headers as appropriate. <p>
74 *
75 * @author John Mani
76 * @author Bill Shannon
77 * @see MimeUtility
78 */
79
80 public final class MimeBodyPart {
81
82 /**
83 * This part should be presented as an attachment.
84 * @see #getDisposition
85 * @see #setDisposition
86 */
87 public static final String ATTACHMENT = "attachment";
88
89 /**
90 * This part should be presented inline.
91 * @see #getDisposition
92 * @see #setDisposition
93 */
162 * message.
163 */
164 public MimeBodyPart() {
165 headers = new InternetHeaders();
166 }
167
168 /**
169 * Constructs a MimeBodyPart by reading and parsing the data from
170 * the specified input stream. The parser consumes data till the end
171 * of the given input stream. The input stream must start at the
172 * beginning of a valid MIME body part and must terminate at the end
173 * of that body part. <p>
174 *
175 * Note that the "boundary" string that delimits body parts must
176 * <strong>not</strong> be included in the input stream. The intention
177 * is that the MimeMultipart parser will extract each body part's bytes
178 * from a multipart stream and feed them into this constructor, without
179 * the delimiter strings.
180 *
181 * @param is the body part Input Stream
182 */
183 public MimeBodyPart(InputStream is) throws MessagingException {
184 if (!(is instanceof ByteArrayInputStream) &&
185 !(is instanceof BufferedInputStream) &&
186 !(is instanceof SharedInputStream))
187 is = new BufferedInputStream(is);
188
189 headers = new InternetHeaders(is);
190
191 if (is instanceof SharedInputStream) {
192 SharedInputStream sis = (SharedInputStream) is;
193 contentStream = sis.newStream(sis.getPosition(), -1);
194 } else {
195 ByteOutputStream bos = null;
196 try {
197 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 } finally {
204 if (bos != null)
205 bos.close();
206 }
207 }
208
209 }
210
211 /**
212 * Constructs a MimeBodyPart using the given header and
213 * content bytes. <p>
214 *
215 * Used by providers.
216 *
217 * @param headers The header of this part
218 * @param content bytes representing the body of this part.
219 */
220 public MimeBodyPart(InternetHeaders headers, byte[] content, int len) {
221 this.headers = headers;
222 this.content = content;
223 this.contentLength = len;
224 }
225
226 public MimeBodyPart(
227 InternetHeaders headers, byte[] content, int start, int len) {
228 this.headers = headers;
229 this.content = content;
230 this.start = start;
231 this.contentLength = len;
232 }
233
234 public MimeBodyPart(MIMEPart part) {
235 mimePart = part;
236 headers = new InternetHeaders();
237 List<? extends com.sun.xml.internal.org.jvnet.mimepull.Header> hdrs = mimePart.getAllHeaders();
238 for (com.sun.xml.internal.org.jvnet.mimepull.Header hd : hdrs) {
239 headers.addHeader(hd.getName(), hd.getValue());
240 }
241 }
242 /**
243 * Return the containing <code>MimeMultipart</code> object,
244 * or <code>null</code> if not known.
245 */
246 public MimeMultipart getParent() {
247 return parent;
248 }
249
250 /**
251 * Set the parent of this <code>MimeBodyPart</code> to be the specified
252 * <code>MimeMultipart</code>. Normally called by <code>MimeMultipart</code>'s
253 * <code>addBodyPart</code> method. <code>parent</code> may be
254 * <code>null</code> if the <code>MimeBodyPart</code> is being removed
255 * from its containing <code>MimeMultipart</code>.
256 * @since JavaMail 1.1
257 */
258 public void setParent(MimeMultipart parent) {
259 this.parent = parent;
260 }
261
262 /**
263 * Return the size of the content of this body part in bytes.
264 * Return -1 if the size cannot be determined. <p>
265 *
266 * Note that this number may not be an exact measure of the
267 * content size and may or may not account for any transfer
268 * encoding of the content. <p>
269 *
270 * This implementation returns the size of the <code>content</code>
271 * array (if not null), or, if <code>contentStream</code> is not
272 * null, and the <code>available</code> method returns a positive
273 * number, it returns that number as the size. Otherwise, it returns
274 * -1.
275 *
334 String s = getHeader("Content-Type", null);
335 if (s == null)
336 s = "text/plain";
337
338 return s;
339 }
340
341 /**
342 * Is this MimeBodyPart of the specified MIME type? This method
343 * compares <strong>only the <code>primaryType</code> and
344 * <code>subType</code></strong>.
345 * The parameters of the content types are ignored. <p>
346 *
347 * For example, this method will return <code>true</code> when
348 * comparing a MimeBodyPart of content type <strong>"text/plain"</strong>
349 * with <strong>"text/plain; charset=foobar"</strong>. <p>
350 *
351 * If the <code>subType</code> of <code>mimeType</code> is the
352 * special character '*', then the subtype is ignored during the
353 * comparison.
354 */
355 public boolean isMimeType(String mimeType) {
356 boolean result;
357 // XXX - lots of room for optimization here!
358 try {
359 ContentType ct = new ContentType(getContentType());
360 result = ct.match(mimeType);
361 } catch (ParseException ex) {
362 result = getContentType().equalsIgnoreCase(mimeType);
363 }
364 return result;
365 }
366
367 /**
368 * Returns the value of the "Content-Disposition" header field.
369 * This represents the disposition of this part. The disposition
370 * describes how the part should be presented to the user. <p>
371 *
372 * If the Content-Disposition field is unavailable,
373 * null is returned. <p>
374 *
375 * This implementation uses <code>getHeader(name)</code>
376 * to obtain the requisite header field.
377 *
378 * @see #headers
379 */
380 public String getDisposition() throws MessagingException {
381 String s = getHeader("Content-Disposition", null);
382
383 if (s == null)
384 return null;
385
386 ContentDisposition cd = new ContentDisposition(s);
387 return cd.getDisposition();
388 }
389
390 /**
391 * Set the "Content-Disposition" header field of this body part.
392 * If the disposition is null, any existing "Content-Disposition"
393 * header field is removed.
394 *
395 * @exception IllegalStateException if this body part is
396 * obtained from a READ_ONLY folder.
397 */
398 public void setDisposition(String disposition) throws MessagingException {
399 if (disposition == null)
400 removeHeader("Content-Disposition");
401 else {
402 String s = getHeader("Content-Disposition", null);
403 if (s != null) {
404 /* A Content-Disposition header already exists ..
405 *
406 * Override disposition, but attempt to retain
407 * existing disposition parameters
408 */
409 ContentDisposition cd = new ContentDisposition(s);
410 cd.setDisposition(disposition);
411 disposition = cd.toString();
412 }
413 setHeader("Content-Disposition", disposition);
414 }
415 }
416
417 /**
418 * Returns the content transfer encoding from the
419 * "Content-Transfer-Encoding" header
420 * field. Returns <code>null</code> if the header is unavailable
421 * or its value is absent. <p>
422 *
423 * This implementation uses <code>getHeader(name)</code>
424 * to obtain the requisite header field.
425 *
426 * @see #headers
427 */
428 public String getEncoding() throws MessagingException {
429 String s = getHeader("Content-Transfer-Encoding", null);
430
431 if (s == null)
432 return null;
433
434 s = s.trim(); // get rid of trailing spaces
435 // quick check for known values to avoid unnecessary use
436 // of tokenizer.
437 if (s.equalsIgnoreCase("7bit") || s.equalsIgnoreCase("8bit") ||
438 s.equalsIgnoreCase("quoted-printable") ||
439 s.equalsIgnoreCase("base64"))
440 return s;
441
442 // Tokenize the header to obtain the encoding (skip comments)
443 HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
444
445 HeaderTokenizer.Token tk;
448 for (;;) {
449 tk = h.next(); // get a token
450 tkType = tk.getType();
451 if (tkType == HeaderTokenizer.Token.EOF)
452 break; // done
453 else if (tkType == HeaderTokenizer.Token.ATOM)
454 return tk.getValue();
455 else // invalid token, skip it.
456 continue;
457 }
458 return s;
459 }
460
461 /**
462 * Returns the value of the "Content-ID" header field. Returns
463 * <code>null</code> if the field is unavailable or its value is
464 * absent. <p>
465 *
466 * This implementation uses <code>getHeader(name)</code>
467 * to obtain the requisite header field.
468 */
469 public String getContentID() {
470 return getHeader("Content-ID", null);
471 }
472
473 /**
474 * Set the "Content-ID" header field of this body part.
475 * If the <code>cid</code> parameter is null, any existing
476 * "Content-ID" is removed.
477 *
478 * @exception IllegalStateException if this body part is
479 * obtained from a READ_ONLY folder.
480 * @since JavaMail 1.3
481 */
482 public void setContentID(String cid) {
483 if (cid == null)
484 removeHeader("Content-ID");
485 else
486 setHeader("Content-ID", cid);
487 }
488
489 /**
490 * Return the value of the "Content-MD5" header field. Returns
491 * <code>null</code> if this field is unavailable or its value
492 * is absent. <p>
493 *
494 * This implementation uses <code>getHeader(name)</code>
495 * to obtain the requisite header field.
496 */
497 public String getContentMD5() {
498 return getHeader("Content-MD5", null);
499 }
500
501 /**
502 * Set the "Content-MD5" header field of this body part.
503 *
504 * @exception IllegalStateException if this body part is
505 * obtained from a READ_ONLY folder.
506 */
507 public void setContentMD5(String md5) {
508 setHeader("Content-MD5", md5);
509 }
510
511 /**
512 * Get the languages specified in the Content-Language header
513 * of this MimeBodyPart. The Content-Language header is defined by
514 * RFC 1766. Returns <code>null</code> if this header is not
515 * available or its value is absent. <p>
516 *
517 * This implementation uses <code>getHeader(name)</code>
518 * to obtain the requisite header field.
519 */
520 public String[] getContentLanguage() throws MessagingException {
521 String s = getHeader("Content-Language", null);
522
523 if (s == null)
524 return null;
525
526 // Tokenize the header to obtain the Language-tags (skip comments)
527 HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
528 FinalArrayList<String> v = new FinalArrayList<String>();
529
530 HeaderTokenizer.Token tk;
531 int tkType;
532
533 while (true) {
534 tk = h.next(); // get a language-tag
535 tkType = tk.getType();
536 if (tkType == HeaderTokenizer.Token.EOF)
537 break; // done
538 else if (tkType == HeaderTokenizer.Token.ATOM) v.add(tk.getValue());
646 }
647
648 try {
649 setHeader("Content-Description", MimeUtility.fold(21,
650 MimeUtility.encodeText(description, charset, null)));
651 } catch (UnsupportedEncodingException uex) {
652 throw new MessagingException("Encoding error", uex);
653 }
654 }
655
656 /**
657 * Get the filename associated with this body part. <p>
658 *
659 * Returns the value of the "filename" parameter from the
660 * "Content-Disposition" header field of this body part. If its
661 * not available, returns the value of the "name" parameter from
662 * the "Content-Type" header field of this body part.
663 * Returns <code>null</code> if both are absent.
664 *
665 * @return filename
666 */
667 public String getFileName() throws MessagingException {
668 String filename = null;
669 String s = getHeader("Content-Disposition", null);
670
671 if (s != null) {
672 // Parse the header ..
673 ContentDisposition cd = new ContentDisposition(s);
674 filename = cd.getParameter("filename");
675 }
676 if (filename == null) {
677 // Still no filename ? Try the "name" ContentType parameter
678 s = getHeader("Content-Type", null);
679 if (s != null) {
680 try {
681 ContentType ct = new ContentType(s);
682 filename = ct.getParameter("name");
683 } catch (ParseException pex) { } // ignore it
684 }
685 }
686 return filename;
687 }
688
689 /**
690 * Set the filename associated with this body part, if possible. <p>
691 *
692 * Sets the "filename" parameter of the "Content-Disposition"
693 * header field of this body part.
694 *
695 * @exception IllegalStateException if this body part is
696 * obtained from a READ_ONLY folder.
697 */
698 public void setFileName(String filename) throws MessagingException {
699 // Set the Content-Disposition "filename" parameter
700 String s = getHeader("Content-Disposition", null);
701 ContentDisposition cd =
702 new ContentDisposition(s == null ? ATTACHMENT : s);
703 cd.setParameter("filename", filename);
704 setHeader("Content-Disposition", cd.toString());
705
706 /* Also attempt to set the Content-Type "name" parameter,
707 * to satisfy ancient MUAs.
708 * XXX: This is not RFC compliant, and hence should really
709 * be conditional based on some property. Fix this once we
710 * figure out how to get at Properties from here !
711 */
712 s = getHeader("Content-Type", null);
713 if (s != null) {
714 try {
752 }
753 if (contentStream != null)
754 return ((SharedInputStream)contentStream).newStream(0, -1);
755 if (content != null)
756 return new ByteArrayInputStream(content,start,contentLength);
757
758 throw new MessagingException("No content");
759 }
760
761 /**
762 * Return an InputStream to the raw data with any Content-Transfer-Encoding
763 * intact. This method is useful if the "Content-Transfer-Encoding"
764 * header is incorrect or corrupt, which would prevent the
765 * <code>getInputStream</code> method or <code>getContent</code> method
766 * from returning the correct data. In such a case the application may
767 * use this method and attempt to decode the raw data itself. <p>
768 *
769 * This implementation simply calls the <code>getContentStream</code>
770 * method.
771 *
772 * @see #getInputStream
773 * @see #getContentStream
774 * @since JavaMail 1.2
775 */
776 public InputStream getRawInputStream() throws MessagingException {
777 return getContentStream();
778 }
779
780 /**
781 * Return a DataHandler for this body part's content. <p>
782 *
783 * The implementation provided here works just like the
784 * the implementation in MimeMessage.
785 */
786 public DataHandler getDataHandler() {
787 if (mimePart != null) {
788 //return an inputstream
789 return new DataHandler(new DataSource() {
790
791 public InputStream getInputStream() throws IOException {
792 return mimePart.read();
793 }
794
795 public OutputStream getOutputStream() throws IOException {
796 throw new UnsupportedOperationException("getOutputStream cannot be supported : You have enabled LazyAttachments Option");
797 }
798
799 public String getContentType() {
800 return mimePart.getContentType();
801 }
802
803 public String getName() {
804 return "MIMEPart Wrapped DataSource";
805 }
806 });
807 }
808 if (dh == null)
809 dh = new DataHandler(new MimePartDataSource(this));
810 return dh;
811 }
812
813 /**
814 * Return the content as a java object. The type of the object
815 * returned is of course dependent on the content itself. For
816 * example, the native format of a text/plain content is usually
817 * a String object. The native format for a "multipart"
818 * content is always a MimeMultipart subclass. For content types that are
819 * unknown to the DataHandler system, an input stream is returned
820 * as the content. <p>
821 *
822 * This implementation obtains the content from the DataHandler.
873 setContent((MimeMultipart)o);
874 } else {
875 setDataHandler(new DataHandler(o, type));
876 }
877 }
878
879 /**
880 * Convenience method that sets the given String as this
881 * part's content, with a MIME type of "text/plain". If the
882 * string contains non US-ASCII characters, it will be encoded
883 * using the platform's default charset. The charset is also
884 * used to set the "charset" parameter. <p>
885 *
886 * Note that there may be a performance penalty if
887 * <code>text</code> is large, since this method may have
888 * to scan all the characters to determine what charset to
889 * use. <p>
890 * If the charset is already known, use the
891 * setText() version that takes the charset parameter.
892 *
893 * @see #setText(String text, String charset)
894 */
895 public void setText(String text) {
896 setText(text, null);
897 }
898
899 /**
900 * Convenience method that sets the given String as this part's
901 * content, with a MIME type of "text/plain" and the specified
902 * charset. The given Unicode string will be charset-encoded
903 * using the specified charset. The charset is also used to set
904 * the "charset" parameter.
905 */
906 public void setText(String text, String charset) {
907 if (charset == null) {
908 if (MimeUtility.checkAscii(text) != MimeUtility.ALL_ASCII)
909 charset = MimeUtility.getDefaultMIMECharset();
910 else
911 charset = "us-ascii";
912 }
913 setContent(text, "text/plain; charset=" +
914 MimeUtility.quote(charset, HeaderTokenizer.MIME));
915 }
916
917 /**
918 * This method sets the body part's content to a MimeMultipart object.
919 *
920 * @param mp The multipart object that is the Message's content
921 * @exception IllegalStateException if this body part is
922 * obtained from a READ_ONLY folder.
923 */
924 public void setContent(MimeMultipart mp) {
925 if (mimePart != null) {
926 mimePart = null;
927 }
928 setDataHandler(new DataHandler(mp, mp.getContentType().toString()));
929 mp.setParent(this);
930 }
931
932 /**
933 * Output the body part as an RFC 822 format stream.
934 *
935 * @exception MessagingException
936 * @exception IOException if an error occurs writing to the
937 * stream or if an error is generated
938 * by the javax.activation layer.
939 * @see DataHandler#writeTo
940 */
941 public void writeTo(OutputStream os)
942 throws IOException, MessagingException {
943
944 // First, write out the header
945 List<String> hdrLines = headers.getAllHeaderLines();
946 int sz = hdrLines.size();
947 for( int i=0; i<sz; i++ )
948 OutputUtil.writeln(hdrLines.get(i),os);
949
950 // The CRLF separator between header and content
951 OutputUtil.writeln(os);
952
953 // Finally, the content.
954 // XXX: May need to account for ESMTP ?
955 if (contentStream != null) {
1016 public void setHeader(String name, String value) {
1017 headers.setHeader(name, value);
1018 }
1019
1020 /**
1021 * Add this value to the existing values for this header_name.
1022 * Note that RFC 822 headers must contain only US-ASCII
1023 * characters, so a header that contains non US-ASCII characters
1024 * must be encoded as per the rules of RFC 2047.
1025 *
1026 * @param name header name
1027 * @param value header value
1028 * @see MimeUtility
1029 */
1030 public void addHeader(String name, String value) {
1031 headers.addHeader(name, value);
1032 }
1033
1034 /**
1035 * Remove all headers with this name.
1036 */
1037 public void removeHeader(String name) {
1038 headers.removeHeader(name);
1039 }
1040
1041 /**
1042 * Return all the headers from this Message as an Enumeration of
1043 * Header objects.
1044 */
1045 public List<? extends Header> getAllHeaders() {
1046 return headers.getAllHeaders();
1047 }
1048
1049
1050 /**
1051 * Add a header line to this body part
1052 */
1053 public void addHeaderLine(String line) {
1054 headers.addHeaderLine(line);
1055 }
1056
1057 /**
1058 * Examine the content of this body part and update the appropriate
1059 * MIME headers. Typical headers that get set here are
1060 * <code>Content-Type</code> and <code>Content-Transfer-Encoding</code>.
1061 * Headers might need to be updated in two cases:
1062 *
1063 * <br>
1064 * - A message being crafted by a mail application will certainly
1065 * need to activate this method at some point to fill up its internal
1066 * headers.
1067 *
1068 * <br>
1069 * - A message read in from a Store will have obtained
1070 * all its headers from the store, and so doesn't need this.
1071 * However, if this message is editable and if any edits have
1072 * been made to either the content or message structure, we might
1073 * need to resync our headers.
1074 *
1075 * <br>
1076 * In both cases this method is typically called by the
1077 * <code>Message.saveChanges</code> method.
1078 */
1079 protected void updateHeaders() throws MessagingException {
1080 DataHandler dh = getDataHandler();
1081 /*
1082 * Code flow indicates null is never returned from
1083 * getdataHandler() - findbugs
1084 */
1085 //if (dh == null) // Huh ?
1086 // return;
1087
1088 try {
1089 String type = dh.getContentType();
1090 boolean composite = false;
1091 boolean needCTHeader = getHeader("Content-Type") == null;
1092
1093 ContentType cType = new ContentType(type);
1094 if (cType.match("multipart/*")) {
1095 // If multipart, recurse
1096 composite = true;
1097 Object o = dh.getContent();
|
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
32 package com.sun.xml.internal.messaging.saaj.packaging.mime.internet;
33
34
35 import com.sun.xml.internal.messaging.saaj.packaging.mime.MessagingException;
36 import com.sun.xml.internal.messaging.saaj.packaging.mime.util.OutputUtil;
37 import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
38 import com.sun.xml.internal.messaging.saaj.util.FinalArrayList;
39
40 import javax.activation.DataHandler;
41 import java.io.BufferedInputStream;
42 import java.io.ByteArrayInputStream;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.OutputStream;
46 import java.io.UnsupportedEncodingException;
47 import java.util.List;
48 import javax.activation.DataSource;
49 import com.sun.xml.internal.org.jvnet.mimepull.MIMEPart;
50
51 /**
52 * This class represents a MIME body part.
53 * MimeBodyParts are contained in <code>MimeMultipart</code>
54 * objects.
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
79 public final class MimeBodyPart {
80
81 /**
82 * This part should be presented as an attachment.
83 * @see #getDisposition
84 * @see #setDisposition
85 */
86 public static final String ATTACHMENT = "attachment";
87
88 /**
89 * This part should be presented inline.
90 * @see #getDisposition
91 * @see #setDisposition
92 */
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 *
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;
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());
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 {
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.
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) {
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();
|