1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5 *
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common Development
8 * and Distribution License("CDDL") (collectively, the "License"). You
9 * may not use this file except in compliance with the License. You can obtain
10 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
11 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
12 * language governing permissions and limitations under the License.
13 *
14 * When distributing the software, include this License Header Notice in each
15 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
16 * Sun designates this particular file as subject to the "Classpath" exception
17 * as provided by Sun in the GPL Version 2 section of the License file that
18 * accompanied this code. If applicable, add the following below the License
19 * Header, with the fields enclosed by brackets [] replaced by your own
20 * identifying information: "Portions Copyrighted [year]
21 * [name of copyright owner]"
22 *
23 * Contributor(s):
24 *
25 * If you wish your version of this file to be governed by only the CDDL or
26 * only the GPL Version 2, indicate your decision by adding "[Contributor]
27 * elects to include this software in this distribution under the [CDDL or GPL
28 * Version 2] license." If you don't indicate a single choice of license, a
29 * recipient has the option to distribute your version of this file under
30 * either the CDDL, the GPL Version 2 or to extend the choice of license to
31 * its licensees as provided above. However, if you add GPL Version 2 code
32 * and therefore, elected the GPL Version 2 license, then the option applies
33 * only if the new code is made subject to such option by the copyright
34 * holder.
35 */
36
37 /*
38 * @(#)MimeBodyPart.java 1.67 07/05/04
39 */
40
41 package javax.mail.internet;
42
43 import javax.mail;
44 import javax.activation;
45 import java.io;
46 import java.util;
47 import com.sun.mail.util;
48
49 /**
50 * This class represents a MIME body part. It implements the
51 * <code>BodyPart</code> abstract class and the <code>MimePart</code>
52 * interface. MimeBodyParts are contained in <code>MimeMultipart</code>
53 * objects. <p>
54 *
55 * MimeBodyPart uses the <code>InternetHeaders</code> class to parse
56 * and store the headers of that body part. <p>
57 *
58 * <hr><strong>A note on RFC 822 and MIME headers</strong><p>
59 *
60 * RFC 822 header fields <strong>must</strong> contain only
61 * US-ASCII characters. MIME allows non ASCII characters to be present
62 * in certain portions of certain headers, by encoding those characters.
63 * RFC 2047 specifies the rules for doing this. The MimeUtility
64 * class provided in this package can be used to to achieve this.
65 * Callers of the <code>setHeader</code>, <code>addHeader</code>, and
66 * <code>addHeaderLine</code> methods are responsible for enforcing
67 * the MIME requirements for the specified headers. In addition, these
68 * header fields must be folded (wrapped) before being sent if they
69 * exceed the line length limitation for the transport (1000 bytes for
70 * SMTP). Received headers may have been folded. The application is
71 * responsible for folding and unfolding headers as appropriate. <p>
72 *
73 * @author John Mani
74 * @author Bill Shannon
75 * @author Kanwar Oberoi
76 * @see javax.mail.Part
77 * @see javax.mail.internet.MimePart
78 * @see javax.mail.internet.MimeUtility
79 */
80
81 public class MimeBodyPart extends BodyPart implements MimePart {
82
83 // Paranoia:
84 // allow this last minute change to be disabled if it causes problems
85 private static boolean setDefaultTextCharset = true;
86
87 private static boolean setContentTypeFileName = true;
88
89 private static boolean encodeFileName = false;
90 private static boolean decodeFileName = false;
91
92 // Paranoia:
93 // allow this last minute change to be disabled if it causes problems
94 static boolean cacheMultipart = true; // accessed by MimeMessage
95
96 static {
97 try {
98 String s = System.getProperty("mail.mime.setdefaulttextcharset");
99 // default to true
100 setDefaultTextCharset = s == null || !s.equalsIgnoreCase("false");
101
102 s = System.getProperty("mail.mime.setcontenttypefilename");
103 // default to true
104 setContentTypeFileName = s == null || !s.equalsIgnoreCase("false");
105
106 s = System.getProperty("mail.mime.encodefilename");
107 // default to false
108 encodeFileName = s != null && !s.equalsIgnoreCase("false");
109
110 s = System.getProperty("mail.mime.decodefilename");
111 // default to false
112 decodeFileName = s != null && !s.equalsIgnoreCase("false");
113
114 s = System.getProperty("mail.mime.cachemultipart");
115 // default to true
116 cacheMultipart = s == null || !s.equalsIgnoreCase("false");
117 } catch (SecurityException sex) {
118 // ignore it
119 }
120 }
121
122 /**
123 * The DataHandler object representing this Part's content.
124 */
125 protected DataHandler dh;
126
127 /**
128 * Byte array that holds the bytes of the content of this Part.
129 */
130 protected byte[] content;
131
132 /**
133 * If the data for this body part was supplied by an
134 * InputStream that implements the SharedInputStream interface,
135 * <code>contentStream</code> is another such stream representing
136 * the content of this body part. In this case, <code>content</code>
137 * will be null.
138 *
139 * @since JavaMail 1.2
140 */
141 protected InputStream contentStream;
142
143 /**
144 * The InternetHeaders object that stores all the headers
145 * of this body part.
146 */
147 protected InternetHeaders headers;
148
149 /**
150 * If our content is a Multipart of Message object, we save it
151 * the first time it's created by parsing a stream so that changes
152 * to the contained objects will not be lost.
153 */
154 private Object cachedContent;
155
156 /**
157 * An empty MimeBodyPart object is created.
158 * This body part maybe filled in by a client constructing a multipart
159 * message.
160 */
161 public MimeBodyPart() {
162 super();
163 headers = new InternetHeaders();
164 }
165
166 /**
167 * Constructs a MimeBodyPart by reading and parsing the data from
168 * the specified input stream. The parser consumes data till the end
169 * of the given input stream. The input stream must start at the
170 * beginning of a valid MIME body part and must terminate at the end
171 * of that body part. <p>
172 *
173 * Note that the "boundary" string that delimits body parts must
174 * <strong>not</strong> be included in the input stream. The intention
175 * is that the MimeMultipart parser will extract each body part's bytes
176 * from a multipart stream and feed them into this constructor, without
177 * the delimiter strings.
178 *
179 * @param is the body part Input Stream
180 */
181 public MimeBodyPart(InputStream is) throws MessagingException {
182 if (!(is instanceof ByteArrayInputStream) &&
183 !(is instanceof BufferedInputStream) &&
184 !(is instanceof SharedInputStream))
185 is = new BufferedInputStream(is);
186
187 headers = new InternetHeaders(is);
188
189 if (is instanceof SharedInputStream) {
190 SharedInputStream sis = (SharedInputStream)is;
191 contentStream = sis.newStream(sis.getPosition(), -1);
192 } else {
193 try {
194 content = ASCIIUtility.getBytes(is);
195 } catch (IOException ioex) {
196 throw new MessagingException("Error reading input stream", ioex);
197 }
198 }
199
200 }
201
202 /**
203 * Constructs a MimeBodyPart using the given header and
204 * content bytes. <p>
205 *
206 * Used by providers.
207 *
208 * @param headers The header of this part
209 * @param content bytes representing the body of this part.
210 */
211 public MimeBodyPart(InternetHeaders headers, byte[] content)
212 throws MessagingException {
213 super();
214 this.headers = headers;
215 this.content = content;
216 }
217
218 /**
219 * Return the size of the content of this body part in bytes.
220 * Return -1 if the size cannot be determined. <p>
221 *
222 * Note that this number may not be an exact measure of the
223 * content size and may or may not account for any transfer
224 * encoding of the content. <p>
225 *
226 * This implementation returns the size of the <code>content</code>
227 * array (if not null), or, if <code>contentStream</code> is not
228 * null, and the <code>available</code> method returns a positive
229 * number, it returns that number as the size. Otherwise, it returns
230 * -1.
231 *
232 * @return size in bytes, or -1 if not known
233 */
234 public int getSize() throws MessagingException {
235 if (content != null)
236 return content.length;
237 if (contentStream != null) {
238 try {
239 int size = contentStream.available();
240 // only believe the size if it's greate than zero, since zero
241 // is the default returned by the InputStream class itself
242 if (size > 0)
243 return size;
244 } catch (IOException ex) {
245 // ignore it
246 }
247 }
248 return -1;
249 }
250
251 /**
252 * Return the number of lines for the content of this Part.
253 * Return -1 if this number cannot be determined. <p>
254 *
255 * Note that this number may not be an exact measure of the
256 * content length and may or may not account for any transfer
257 * encoding of the content. <p>
258 *
259 * This implementation returns -1.
260 *
261 * @return number of lines, or -1 if not known
262 */
263 public int getLineCount() throws MessagingException {
264 return -1;
265 }
266
267 /**
268 * Returns the value of the RFC 822 "Content-Type" header field.
269 * This represents the content type of the content of this
270 * body part. This value must not be null. If this field is
271 * unavailable, "text/plain" should be returned. <p>
272 *
273 * This implementation uses <code>getHeader(name)</code>
274 * to obtain the requisite header field.
275 *
276 * @return Content-Type of this body part
277 */
278 public String getContentType() throws MessagingException {
279 String s = getHeader("Content-Type", null);
280 if (s == null)
281 s = "text/plain";
282
283 return s;
284 }
285
286 /**
287 * Is this Part of the specified MIME type? This method
288 * compares <strong>only the <code>primaryType</code> and
289 * <code>subType</code></strong>.
290 * The parameters of the content types are ignored. <p>
291 *
292 * For example, this method will return <code>true</code> when
293 * comparing a Part of content type <strong>"text/plain"</strong>
294 * with <strong>"text/plain; charset=foobar"</strong>. <p>
295 *
296 * If the <code>subType</code> of <code>mimeType</code> is the
297 * special character '*', then the subtype is ignored during the
298 * comparison.
299 */
300 public boolean isMimeType(String mimeType) throws MessagingException {
301 return isMimeType(this, mimeType);
302 }
303
304 /**
305 * Returns the value of the "Content-Disposition" header field.
306 * This represents the disposition of this part. The disposition
307 * describes how the part should be presented to the user. <p>
308 *
309 * If the Content-Disposition field is unavailable,
310 * null is returned. <p>
311 *
312 * This implementation uses <code>getHeader(name)</code>
313 * to obtain the requisite header field.
314 *
315 * @see #headers
316 */
317 public String getDisposition() throws MessagingException {
318 return getDisposition(this);
319 }
320
321 /**
322 * Set the "Content-Disposition" header field of this body part.
323 * If the disposition is null, any existing "Content-Disposition"
324 * header field is removed.
325 *
326 * @exception IllegalWriteException if the underlying
327 * implementation does not support modification
328 * @exception IllegalStateException if this body part is
329 * obtained from a READ_ONLY folder.
330 */
331 public void setDisposition(String disposition) throws MessagingException {
332 setDisposition(this, disposition);
333 }
334
335 /**
336 * Returns the content transfer encoding from the
337 * "Content-Transfer-Encoding" header
338 * field. Returns <code>null</code> if the header is unavailable
339 * or its value is absent. <p>
340 *
341 * This implementation uses <code>getHeader(name)</code>
342 * to obtain the requisite header field.
343 *
344 * @see #headers
345 */
346 public String getEncoding() throws MessagingException {
347 return getEncoding(this);
348 }
349
350 /**
351 * Returns the value of the "Content-ID" header field. Returns
352 * <code>null</code> if the field is unavailable or its value is
353 * absent. <p>
354 *
355 * This implementation uses <code>getHeader(name)</code>
356 * to obtain the requisite header field.
357 */
358 public String getContentID() throws MessagingException {
359 return getHeader("Content-Id", null);
360 }
361
362 /**
363 * Set the "Content-ID" header field of this body part.
364 * If the <code>cid</code> parameter is null, any existing
365 * "Content-ID" is removed.
366 *
367 * @exception IllegalWriteException if the underlying
368 * implementation does not support modification
369 * @exception IllegalStateException if this body part is
370 * obtained from a READ_ONLY folder.
371 * @exception MessagingException
372 * @since JavaMail 1.3
373 */
374 public void setContentID(String cid) throws MessagingException {
375 if (cid == null)
376 removeHeader("Content-ID");
377 else
378 setHeader("Content-ID", cid);
379 }
380
381 /**
382 * Return the value of the "Content-MD5" header field. Returns
383 * <code>null</code> if this field is unavailable or its value
384 * is absent. <p>
385 *
386 * This implementation uses <code>getHeader(name)</code>
387 * to obtain the requisite header field.
388 */
389 public String getContentMD5() throws MessagingException {
390 return getHeader("Content-MD5", null);
391 }
392
393 /**
394 * Set the "Content-MD5" header field of this body part.
395 *
396 * @exception IllegalWriteException if the underlying
397 * implementation does not support modification
398 * @exception IllegalStateException if this body part is
399 * obtained from a READ_ONLY folder.
400 */
401 public void setContentMD5(String md5) throws MessagingException {
402 setHeader("Content-MD5", md5);
403 }
404
405 /**
406 * Get the languages specified in the Content-Language header
407 * of this MimePart. The Content-Language header is defined by
408 * RFC 1766. Returns <code>null</code> if this header is not
409 * available or its value is absent. <p>
410 *
411 * This implementation uses <code>getHeader(name)</code>
412 * to obtain the requisite header field.
413 */
414 public String[] getContentLanguage() throws MessagingException {
415 return getContentLanguage(this);
416 }
417
418 /**
419 * Set the Content-Language header of this MimePart. The
420 * Content-Language header is defined by RFC 1766.
421 *
422 * @param languages array of language tags
423 */
424 public void setContentLanguage(String[] languages)
425 throws MessagingException {
426 setContentLanguage(this, languages);
427 }
428
429 /**
430 * Returns the "Content-Description" header field of this body part.
431 * This typically associates some descriptive information with
432 * this part. Returns null if this field is unavailable or its
433 * value is absent. <p>
434 *
435 * If the Content-Description field is encoded as per RFC 2047,
436 * it is decoded and converted into Unicode. If the decoding or
437 * conversion fails, the raw data is returned as is. <p>
438 *
439 * This implementation uses <code>getHeader(name)</code>
440 * to obtain the requisite header field.
441 *
442 * @return content description
443 */
444 public String getDescription() throws MessagingException {
445 return getDescription(this);
446 }
447
448 /**
449 * Set the "Content-Description" header field for this body part.
450 * If the description parameter is <code>null</code>, then any
451 * existing "Content-Description" fields are removed. <p>
452 *
453 * If the description contains non US-ASCII characters, it will
454 * be encoded using the platform's default charset. If the
455 * description contains only US-ASCII characters, no encoding
456 * is done and it is used as is. <p>
457 *
458 * Note that if the charset encoding process fails, a
459 * MessagingException is thrown, and an UnsupportedEncodingException
460 * is included in the chain of nested exceptions within the
461 * MessagingException.
462 *
463 * @param description content description
464 * @exception IllegalWriteException if the underlying
465 * implementation does not support modification
466 * @exception IllegalStateException if this body part is
467 * obtained from a READ_ONLY folder.
468 * @exception MessagingException otherwise; an
469 * UnsupportedEncodingException may be included
470 * in the exception chain if the charset
471 * conversion fails.
472 */
473 public void setDescription(String description) throws MessagingException {
474 setDescription(description, null);
475 }
476
477 /**
478 * Set the "Content-Description" header field for this body part.
479 * If the description parameter is <code>null</code>, then any
480 * existing "Content-Description" fields are removed. <p>
481 *
482 * If the description contains non US-ASCII characters, it will
483 * be encoded using the specified charset. If the description
484 * contains only US-ASCII characters, no encoding is done and
485 * it is used as is. <p>
486 *
487 * Note that if the charset encoding process fails, a
488 * MessagingException is thrown, and an UnsupportedEncodingException
489 * is included in the chain of nested exceptions within the
490 * MessagingException.
491 *
492 * @param description Description
493 * @param charset Charset for encoding
494 * @exception IllegalWriteException if the underlying
495 * implementation does not support modification
496 * @exception IllegalStateException if this body part is
497 * obtained from a READ_ONLY folder.
498 * @exception MessagingException otherwise; an
499 * UnsupportedEncodingException may be included
500 * in the exception chain if the charset
501 * conversion fails.
502 */
503 public void setDescription(String description, String charset)
504 throws MessagingException {
505 setDescription(this, description, charset);
506 }
507
508 /**
509 * Get the filename associated with this body part. <p>
510 *
511 * Returns the value of the "filename" parameter from the
512 * "Content-Disposition" header field of this body part. If its
513 * not available, returns the value of the "name" parameter from
514 * the "Content-Type" header field of this body part.
515 * Returns <code>null</code> if both are absent. <p>
516 *
517 * If the <code>mail.mime.encodefilename</code> System property
518 * is set to true, the {@link MimeUtility#decodeText
519 * MimeUtility.decodeText} method will be used to decode the
520 * filename. While such encoding is not supported by the MIME
521 * spec, many mailers use this technique to support non-ASCII
522 * characters in filenames. The default value of this property
523 * is false.
524 *
525 * @return filename
526 */
527 public String getFileName() throws MessagingException {
528 return getFileName(this);
529 }
530
531 /**
532 * Set the filename associated with this body part, if possible. <p>
533 *
534 * Sets the "filename" parameter of the "Content-Disposition"
535 * header field of this body part. For compatibility with older
536 * mailers, the "name" parameter of the "Content-Type" header is
537 * also set. <p>
538 *
539 * If the <code>mail.mime.encodefilename</code> System property
540 * is set to true, the {@link MimeUtility#encodeText
541 * MimeUtility.encodeText} method will be used to encode the
542 * filename. While such encoding is not supported by the MIME
543 * spec, many mailers use this technique to support non-ASCII
544 * characters in filenames. The default value of this property
545 * is false.
546 *
547 * @exception IllegalWriteException if the underlying
548 * implementation does not support modification
549 * @exception IllegalStateException if this body part is
550 * obtained from a READ_ONLY folder.
551 */
552 public void setFileName(String filename) throws MessagingException {
553 setFileName(this, filename);
554 }
555
556 /**
557 * Return a decoded input stream for this body part's "content". <p>
558 *
559 * This implementation obtains the input stream from the DataHandler.
560 * That is, it invokes getDataHandler().getInputStream();
561 *
562 * @return an InputStream
563 * @exception MessagingException
564 * @exception IOException this is typically thrown by the
565 * DataHandler. Refer to the documentation for
566 * javax.activation.DataHandler for more details.
567 *
568 * @see #getContentStream
569 * @see javax.activation.DataHandler#getInputStream
570 */
571 public InputStream getInputStream()
572 throws IOException, MessagingException {
573 return getDataHandler().getInputStream();
574 }
575
576 /**
577 * Produce the raw bytes of the content. This method is used
578 * when creating a DataHandler object for the content. Subclasses
579 * that can provide a separate input stream for just the Part
580 * content might want to override this method. <p>
581 *
582 * @see #content
583 * @see MimeMessage#getContentStream
584 */
585 protected InputStream getContentStream() throws MessagingException {
586 if (contentStream != null)
587 return ((SharedInputStream)contentStream).newStream(0, -1);
588 if (content != null)
589 return new ByteArrayInputStream(content);
590
591 throw new MessagingException("No content");
592 }
593
594 /**
595 * Return an InputStream to the raw data with any Content-Transfer-Encoding
596 * intact. This method is useful if the "Content-Transfer-Encoding"
597 * header is incorrect or corrupt, which would prevent the
598 * <code>getInputStream</code> method or <code>getContent</code> method
599 * from returning the correct data. In such a case the application may
600 * use this method and attempt to decode the raw data itself. <p>
601 *
602 * This implementation simply calls the <code>getContentStream</code>
603 * method.
604 *
605 * @see #getInputStream
606 * @see #getContentStream
607 * @since JavaMail 1.2
608 */
609 public InputStream getRawInputStream() throws MessagingException {
610 return getContentStream();
611 }
612
613 /**
614 * Return a DataHandler for this body part's content. <p>
615 *
616 * The implementation provided here works just like the
617 * the implementation in MimeMessage.
618 * @see MimeMessage#getDataHandler
619 */
620 public DataHandler getDataHandler() throws MessagingException {
621 if (dh == null)
622 dh = new DataHandler(new MimePartDataSource(this));
623 return dh;
624 }
625
626 /**
627 * Return the content as a Java object. The type of the object
628 * returned is of course dependent on the content itself. For
629 * example, the native format of a text/plain content is usually
630 * a String object. The native format for a "multipart"
631 * content is always a Multipart subclass. For content types that are
632 * unknown to the DataHandler system, an input stream is returned
633 * as the content. <p>
634 *
635 * This implementation obtains the content from the DataHandler.
636 * That is, it invokes getDataHandler().getContent();
637 * If the content is a Multipart or Message object and was created by
638 * parsing a stream, the object is cached and returned in subsequent
639 * calls so that modifications to the content will not be lost.
640 *
641 * @return Object
642 * @exception MessagingException
643 * @exception IOException this is typically thrown by the
644 * DataHandler. Refer to the documentation for
645 * javax.activation.DataHandler for more details.
646 */
647 public Object getContent() throws IOException, MessagingException {
648 if (cachedContent != null)
649 return cachedContent;
650 Object c;
651 try {
652 c = getDataHandler().getContent();
653 } catch (FolderClosedIOException fex) {
654 throw new FolderClosedException(fex.getFolder(), fex.getMessage());
655 } catch (MessageRemovedIOException mex) {
656 throw new MessageRemovedException(mex.getMessage());
657 }
658 if (cacheMultipart &&
659 (c instanceof Multipart || c instanceof Message) &&
660 (content != null || contentStream != null)) {
661 cachedContent = c;
662 }
663 return c;
664 }
665
666 /**
667 * This method provides the mechanism to set this body part's content.
668 * The given DataHandler object should wrap the actual content.
669 *
670 * @param dh The DataHandler for the content
671 * @exception IllegalWriteException if the underlying
672 * implementation does not support modification
673 * @exception IllegalStateException if this body part is
674 * obtained from a READ_ONLY folder.
675 */
676 public void setDataHandler(DataHandler dh)
677 throws MessagingException {
678 this.dh = dh;
679 cachedContent = null;
680 MimeBodyPart.invalidateContentHeaders(this);
681 }
682
683 /**
684 * A convenience method for setting this body part's content. <p>
685 *
686 * The content is wrapped in a DataHandler object. Note that a
687 * DataContentHandler class for the specified type should be
688 * available to the JavaMail implementation for this to work right.
689 * That is, to do <code>setContent(foobar, "application/x-foobar")</code>,
690 * a DataContentHandler for "application/x-foobar" should be installed.
691 * Refer to the Java Activation Framework for more information.
692 *
693 * @param o the content object
694 * @param type Mime type of the object
695 * @exception IllegalWriteException if the underlying
696 * implementation does not support modification of
697 * existing values
698 * @exception IllegalStateException if this body part is
699 * obtained from a READ_ONLY folder.
700 */
701 public void setContent(Object o, String type)
702 throws MessagingException {
703 if (o instanceof Multipart) {
704 setContent((Multipart)o);
705 } else {
706 setDataHandler(new DataHandler(o, type));
707 }
708 }
709
710 /**
711 * Convenience method that sets the given String as this
712 * part's content, with a MIME type of "text/plain". If the
713 * string contains non US-ASCII characters, it will be encoded
714 * using the platform's default charset. The charset is also
715 * used to set the "charset" parameter. <p>
716 *
717 * Note that there may be a performance penalty if
718 * <code>text</code> is large, since this method may have
719 * to scan all the characters to determine what charset to
720 * use. <p>
721 *
722 * If the charset is already known, use the
723 * <code>setText</code> method that takes the charset parameter.
724 *
725 * @param text the text content to set
726 * @exception MessagingException if an error occurs
727 * @see #setText(String text, String charset)
728 */
729 public void setText(String text) throws MessagingException {
730 setText(text, null);
731 }
732
733 /**
734 * Convenience method that sets the given String as this part's
735 * content, with a MIME type of "text/plain" and the specified
736 * charset. The given Unicode string will be charset-encoded
737 * using the specified charset. The charset is also used to set
738 * the "charset" parameter.
739 *
740 * @param text the text content to set
741 * @param charset the charset to use for the text
742 * @exception MessagingException if an error occurs
743 */
744 public void setText(String text, String charset)
745 throws MessagingException {
746 setText(this, text, charset, "plain");
747 }
748
749 /**
750 * Convenience method that sets the given String as this part's
751 * content, with a primary MIME type of "text" and the specified
752 * MIME subtype. The given Unicode string will be charset-encoded
753 * using the specified charset. The charset is also used to set
754 * the "charset" parameter.
755 *
756 * @param text the text content to set
757 * @param charset the charset to use for the text
758 * @param subtype the MIME subtype to use (e.g., "html")
759 * @exception MessagingException if an error occurs
760 * @since JavaMail 1.4
761 */
762 public void setText(String text, String charset, String subtype)
763 throws MessagingException {
764 setText(this, text, charset, subtype);
765 }
766
767 /**
768 * This method sets the body part's content to a Multipart object.
769 *
770 * @param mp The multipart object that is the Message's content
771 * @exception IllegalWriteException if the underlying
772 * implementation does not support modification of
773 * existing values.
774 * @exception IllegalStateException if this body part is
775 * obtained from a READ_ONLY folder.
776 */
777 public void setContent(Multipart mp) throws MessagingException {
778 setDataHandler(new DataHandler(mp, mp.getContentType()));
779 mp.setParent(this);
780 }
781
782 /**
783 * Use the specified file to provide the data for this part.
784 * The simple file name is used as the file name for this
785 * part and the data in the file is used as the data for this
786 * part. The encoding will be chosen appropriately for the
787 * file data.
788 *
789 * @param file the File object to attach
790 * @exception IOException errors related to accessing the file
791 * @exception MessagingException message related errors
792 * @since JavaMail 1.4
793 */
794 public void attachFile(File file) throws IOException, MessagingException {
795 FileDataSource fds = new FileDataSource(file);
796 this.setDataHandler(new DataHandler(fds));
797 this.setFileName(fds.getName());
798 }
799
800 /**
801 * Use the specified file to provide the data for this part.
802 * The simple file name is used as the file name for this
803 * part and the data in the file is used as the data for this
804 * part. The encoding will be chosen appropriately for the
805 * file data.
806 *
807 * @param file the name of the file to attach
808 * @exception IOException errors related to accessing the file
809 * @exception MessagingException message related errors
810 * @since JavaMail 1.4
811 */
812 public void attachFile(String file) throws IOException, MessagingException {
813 File f = new File(file);
814 attachFile(f);
815 }
816
817 /**
818 * Save the contents of this part in the specified file. The content
819 * is decoded and saved, without any of the MIME headers.
820 *
821 * @param file the File object to write to
822 * @exception IOException errors related to accessing the file
823 * @exception MessagingException message related errors
824 * @since JavaMail 1.4
825 */
826 public void saveFile(File file) throws IOException, MessagingException {
827 OutputStream out = null;
828 InputStream in = null;
829 try {
830 out = new BufferedOutputStream(new FileOutputStream(file));
831 in = this.getInputStream();
832 byte[] buf = new byte[8192];
833 int len;
834 while ((len = in.read(buf)) > 0)
835 out.write(buf, 0, len);
836 } finally {
837 // close streams, but don't mask original exception, if any
838 try {
839 if (in != null)
840 in.close();
841 } catch (IOException ex) { }
842 try {
843 if (out != null)
844 out.close();
845 } catch (IOException ex) { }
846 }
847 }
848
849 /**
850 * Save the contents of this part in the specified file. The content
851 * is decoded and saved, without any of the MIME headers.
852 *
853 * @param file the name of the file to write to
854 * @exception IOException errors related to accessing the file
855 * @exception MessagingException message related errors
856 * @since JavaMail 1.4
857 */
858 public void saveFile(String file) throws IOException, MessagingException {
859 File f = new File(file);
860 saveFile(f);
861 }
862
863 /**
864 * Output the body part as an RFC 822 format stream.
865 *
866 * @exception MessagingException
867 * @exception IOException if an error occurs writing to the
868 * stream or if an error is generated
869 * by the javax.activation layer.
870 * @see javax.activation.DataHandler#writeTo
871 */
872 public void writeTo(OutputStream os)
873 throws IOException, MessagingException {
874 writeTo(this, os, null);
875 }
876
877 /**
878 * Get all the headers for this header_name. Note that certain
879 * headers may be encoded as per RFC 2047 if they contain
880 * non US-ASCII characters and these should be decoded.
881 *
882 * @param name name of header
883 * @return array of headers
884 * @see javax.mail.internet.MimeUtility
885 */
886 public String[] getHeader(String name) throws MessagingException {
887 return headers.getHeader(name);
888 }
889
890 /**
891 * Get all the headers for this header name, returned as a single
892 * String, with headers separated by the delimiter. If the
893 * delimiter is <code>null</code>, only the first header is
894 * returned.
895 *
896 * @param name the name of this header
897 * @param delimiter delimiter between fields in returned string
898 * @return the value fields for all headers with
899 * this name
900 * @exception MessagingException
901 */
902 public String getHeader(String name, String delimiter)
903 throws MessagingException {
904 return headers.getHeader(name, delimiter);
905 }
906
907 /**
908 * Set the value for this header_name. Replaces all existing
909 * header values with this new value. Note that RFC 822 headers
910 * must contain only US-ASCII characters, so a header that
911 * contains non US-ASCII characters must be encoded as per the
912 * rules of RFC 2047.
913 *
914 * @param name header name
915 * @param value header value
916 * @see javax.mail.internet.MimeUtility
917 */
918 public void setHeader(String name, String value)
919 throws MessagingException {
920 headers.setHeader(name, value);
921 }
922
923 /**
924 * Add this value to the existing values for this header_name.
925 * Note that RFC 822 headers must contain only US-ASCII
926 * characters, so a header that contains non US-ASCII characters
927 * must be encoded as per the rules of RFC 2047.
928 *
929 * @param name header name
930 * @param value header value
931 * @see javax.mail.internet.MimeUtility
932 */
933 public void addHeader(String name, String value)
934 throws MessagingException {
935 headers.addHeader(name, value);
936 }
937
938 /**
939 * Remove all headers with this name.
940 */
941 public void removeHeader(String name) throws MessagingException {
942 headers.removeHeader(name);
943 }
944
945 /**
946 * Return all the headers from this Message as an Enumeration of
947 * Header objects.
948 */
949 public Enumeration getAllHeaders() throws MessagingException {
950 return headers.getAllHeaders();
951 }
952
953 /**
954 * Return matching headers from this Message as an Enumeration of
955 * Header objects. <p>
956 */
957 public Enumeration getMatchingHeaders(String[] names)
958 throws MessagingException {
959 return headers.getMatchingHeaders(names);
960 }
961
962 /**
963 * Return non-matching headers from this Message as an
964 * Enumeration of Header objects.
965 */
966 public Enumeration getNonMatchingHeaders(String[] names)
967 throws MessagingException {
968 return headers.getNonMatchingHeaders(names);
969 }
970
971 /**
972 * Add a header line to this body part
973 */
974 public void addHeaderLine(String line) throws MessagingException {
975 headers.addHeaderLine(line);
976 }
977
978 /**
979 * Get all header lines as an Enumeration of Strings. A Header
980 * line is a raw RFC 822 header line, containing both the "name"
981 * and "value" field.
982 */
983 public Enumeration getAllHeaderLines() throws MessagingException {
984 return headers.getAllHeaderLines();
985 }
986
987 /**
988 * Get matching header lines as an Enumeration of Strings.
989 * A Header line is a raw RFC 822 header line, containing both
990 * the "name" and "value" field.
991 */
992 public Enumeration getMatchingHeaderLines(String[] names)
993 throws MessagingException {
994 return headers.getMatchingHeaderLines(names);
995 }
996
997 /**
998 * Get non-matching header lines as an Enumeration of Strings.
999 * A Header line is a raw RFC 822 header line, containing both
1000 * the "name" and "value" field.
1001 */
1002 public Enumeration getNonMatchingHeaderLines(String[] names)
1003 throws MessagingException {
1004 return headers.getNonMatchingHeaderLines(names);
1005 }
1006
1007 /**
1008 * Examine the content of this body part and update the appropriate
1009 * MIME headers. Typical headers that get set here are
1010 * <code>Content-Type</code> and <code>Content-Transfer-Encoding</code>.
1011 * Headers might need to be updated in two cases:
1012 *
1013 * <br>
1014 * - A message being crafted by a mail application will certainly
1015 * need to activate this method at some point to fill up its internal
1016 * headers.
1017 *
1018 * <br>
1019 * - A message read in from a Store will have obtained
1020 * all its headers from the store, and so doesn't need this.
1021 * However, if this message is editable and if any edits have
1022 * been made to either the content or message structure, we might
1023 * need to resync our headers.
1024 *
1025 * <br>
1026 * In both cases this method is typically called by the
1027 * <code>Message.saveChanges</code> method.
1028 */
1029 protected void updateHeaders() throws MessagingException {
1030 updateHeaders(this);
1031 /*
1032 * If we've cached a Multipart or Message object then
1033 * we're now committed to using this instance of the
1034 * object and we discard any stream data used to create
1035 * this object.
1036 */
1037 if (cachedContent != null) {
1038 dh = new DataHandler(cachedContent, getContentType());
1039 cachedContent = null;
1040 content = null;
1041 if (contentStream != null) {
1042 try {
1043 contentStream.close();
1044 } catch (IOException ioex) { } // nothing to do
1045 }
1046 contentStream = null;
1047 }
1048 }
1049
1050 /////////////////////////////////////////////////////////////
1051 // Package private convenience methods to share code among //
1052 // MimeMessage and MimeBodyPart //
1053 /////////////////////////////////////////////////////////////
1054
1055 static boolean isMimeType(MimePart part, String mimeType)
1056 throws MessagingException {
1057 // XXX - lots of room for optimization here!
1058 try {
1059 ContentType ct = new ContentType(part.getContentType());
1060 return ct.match(mimeType);
1061 } catch (ParseException ex) {
1062 return part.getContentType().equalsIgnoreCase(mimeType);
1063 }
1064 }
1065
1066 static void setText(MimePart part, String text, String charset,
1067 String subtype) throws MessagingException {
1068 if (charset == null) {
1069 if (MimeUtility.checkAscii(text) != MimeUtility.ALL_ASCII)
1070 charset = MimeUtility.getDefaultMIMECharset();
1071 else
1072 charset = "us-ascii";
1073 }
1074 // XXX - should at least ensure that subtype is an atom
1075 part.setContent(text, "text/" + subtype + "; charset=" +
1076 MimeUtility.quote(charset, HeaderTokenizer.MIME));
1077 }
1078
1079 static String getDisposition(MimePart part) throws MessagingException {
1080 String s = part.getHeader("Content-Disposition", null);
1081
1082 if (s == null)
1083 return null;
1084
1085 ContentDisposition cd = new ContentDisposition(s);
1086 return cd.getDisposition();
1087 }
1088
1089 static void setDisposition(MimePart part, String disposition)
1090 throws MessagingException {
1091 if (disposition == null)
1092 part.removeHeader("Content-Disposition");
1093 else {
1094 String s = part.getHeader("Content-Disposition", null);
1095 if (s != null) {
1096 /* A Content-Disposition header already exists ..
1097 *
1098 * Override disposition, but attempt to retain
1099 * existing disposition parameters
1100 */
1101 ContentDisposition cd = new ContentDisposition(s);
1102 cd.setDisposition(disposition);
1103 disposition = cd.toString();
1104 }
1105 part.setHeader("Content-Disposition", disposition);
1106 }
1107 }
1108
1109 static String getDescription(MimePart part)
1110 throws MessagingException {
1111 String rawvalue = part.getHeader("Content-Description", null);
1112
1113 if (rawvalue == null)
1114 return null;
1115
1116 try {
1117 return MimeUtility.decodeText(MimeUtility.unfold(rawvalue));
1118 } catch (UnsupportedEncodingException ex) {
1119 return rawvalue;
1120 }
1121 }
1122
1123 static void
1124 setDescription(MimePart part, String description, String charset)
1125 throws MessagingException {
1126 if (description == null) {
1127 part.removeHeader("Content-Description");
1128 return;
1129 }
1130
1131 try {
1132 part.setHeader("Content-Description", MimeUtility.fold(21,
1133 MimeUtility.encodeText(description, charset, null)));
1134 } catch (UnsupportedEncodingException uex) {
1135 throw new MessagingException("Encoding error", uex);
1136 }
1137 }
1138
1139 static String getFileName(MimePart part) throws MessagingException {
1140 String filename = null;
1141 String s = part.getHeader("Content-Disposition", null);
1142
1143 if (s != null) {
1144 // Parse the header ..
1145 ContentDisposition cd = new ContentDisposition(s);
1146 filename = cd.getParameter("filename");
1147 }
1148 if (filename == null) {
1149 // Still no filename ? Try the "name" ContentType parameter
1150 s = part.getHeader("Content-Type", null);
1151 if (s != null) {
1152 try {
1153 ContentType ct = new ContentType(s);
1154 filename = ct.getParameter("name");
1155 } catch (ParseException pex) { } // ignore it
1156 }
1157 }
1158 if (decodeFileName && filename != null) {
1159 try {
1160 filename = MimeUtility.decodeText(filename);
1161 } catch (UnsupportedEncodingException ex) {
1162 throw new MessagingException("Can't decode filename", ex);
1163 }
1164 }
1165 return filename;
1166 }
1167
1168 static void setFileName(MimePart part, String name)
1169 throws MessagingException {
1170 if (encodeFileName && name != null) {
1171 try {
1172 name = MimeUtility.encodeText(name);
1173 } catch (UnsupportedEncodingException ex) {
1174 throw new MessagingException("Can't encode filename", ex);
1175 }
1176 }
1177
1178 // Set the Content-Disposition "filename" parameter
1179 String s = part.getHeader("Content-Disposition", null);
1180 ContentDisposition cd =
1181 new ContentDisposition(s == null ? Part.ATTACHMENT : s);
1182 cd.setParameter("filename", name);
1183 part.setHeader("Content-Disposition", cd.toString());
1184
1185 /*
1186 * Also attempt to set the Content-Type "name" parameter,
1187 * to satisfy ancient MUAs. XXX - This is not RFC compliant.
1188 */
1189 if (setContentTypeFileName) {
1190 s = part.getHeader("Content-Type", null);
1191 if (s != null) {
1192 try {
1193 ContentType cType = new ContentType(s);
1194 cType.setParameter("name", name);
1195 part.setHeader("Content-Type", cType.toString());
1196 } catch (ParseException pex) { } // ignore it
1197 }
1198 }
1199 }
1200
1201 static String[] getContentLanguage(MimePart part)
1202 throws MessagingException {
1203 String s = part.getHeader("Content-Language", null);
1204
1205 if (s == null)
1206 return null;
1207
1208 // Tokenize the header to obtain the Language-tags (skip comments)
1209 HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
1210 Vector v = new Vector();
1211
1212 HeaderTokenizer.Token tk;
1213 int tkType;
1214
1215 while (true) {
1216 tk = h.next(); // get a language-tag
1217 tkType = tk.getType();
1218 if (tkType == HeaderTokenizer.Token.EOF)
1219 break; // done
1220 else if (tkType == HeaderTokenizer.Token.ATOM)
1221 v.addElement(tk.getValue());
1222 else // invalid token, skip it.
1223 continue;
1224 }
1225
1226 if (v.size() == 0)
1227 return null;
1228
1229 String[] language = new String[v.size()];
1230 v.copyInto(language);
1231 return language;
1232 }
1233
1234 static void setContentLanguage(MimePart part, String[] languages)
1235 throws MessagingException {
1236 StringBuffer sb = new StringBuffer(languages[0]);
1237 for (int i = 1; i < languages.length; i++)
1238 sb.append(',').append(languages[i]);
1239 part.setHeader("Content-Language", sb.toString());
1240 }
1241
1242 static String getEncoding(MimePart part) throws MessagingException {
1243 String s = part.getHeader("Content-Transfer-Encoding", null);
1244
1245 if (s == null)
1246 return null;
1247
1248 s = s.trim(); // get rid of trailing spaces
1249 // quick check for known values to avoid unnecessary use
1250 // of tokenizer.
1251 if (s.equalsIgnoreCase("7bit") || s.equalsIgnoreCase("8bit") ||
1252 s.equalsIgnoreCase("quoted-printable") ||
1253 s.equalsIgnoreCase("binary") ||
1254 s.equalsIgnoreCase("base64"))
1255 return s;
1256
1257 // Tokenize the header to obtain the encoding (skip comments)
1258 HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
1259
1260 HeaderTokenizer.Token tk;
1261 int tkType;
1262
1263 for (;;) {
1264 tk = h.next(); // get a token
1265 tkType = tk.getType();
1266 if (tkType == HeaderTokenizer.Token.EOF)
1267 break; // done
1268 else if (tkType == HeaderTokenizer.Token.ATOM)
1269 return tk.getValue();
1270 else // invalid token, skip it.
1271 continue;
1272 }
1273 return s;
1274 }
1275
1276 static void setEncoding(MimePart part, String encoding)
1277 throws MessagingException {
1278 part.setHeader("Content-Transfer-Encoding", encoding);
1279 }
1280
1281 static void updateHeaders(MimePart part) throws MessagingException {
1282 DataHandler dh = part.getDataHandler();
1283 if (dh == null) // Huh ?
1284 return;
1285
1286 try {
1287 String type = dh.getContentType();
1288 boolean composite = false;
1289 boolean needCTHeader = part.getHeader("Content-Type") == null;
1290
1291 ContentType cType = new ContentType(type);
1292 if (cType.match("multipart/*")) {
1293 // If multipart, recurse
1294 composite = true;
1295 Object o;
1296 if (part instanceof MimeBodyPart) {
1297 MimeBodyPart mbp = (MimeBodyPart)part;
1298 o = mbp.cachedContent != null ?
1299 mbp.cachedContent : dh.getContent();
1300 } else if (part instanceof MimeMessage) {
1301 MimeMessage msg = (MimeMessage)part;
1302 o = msg.cachedContent != null ?
1303 msg.cachedContent : dh.getContent();
1304 } else
1305 o = dh.getContent();
1306 if (o instanceof MimeMultipart)
1307 ((MimeMultipart)o).updateHeaders();
1308 else
1309 throw new MessagingException("MIME part of type \"" +
1310 type + "\" contains object of type " +
1311 o.getClass().getName() + " instead of MimeMultipart");
1312 } else if (cType.match("message/rfc822")) {
1313 composite = true;
1314 // XXX - call MimeMessage.updateHeaders()?
1315 }
1316
1317 // Content-Transfer-Encoding, but only if we don't
1318 // already have one
1319 if (!composite) { // not allowed on composite parts
1320 if (part.getHeader("Content-Transfer-Encoding") == null)
1321 setEncoding(part, MimeUtility.getEncoding(dh));
1322
1323 if (needCTHeader && setDefaultTextCharset &&
1324 cType.match("text/*") &&
1325 cType.getParameter("charset") == null) {
1326 /*
1327 * Set a default charset for text parts.
1328 * We really should examine the data to determine
1329 * whether or not it's all ASCII, but that's too
1330 * expensive so we make an assumption: If we
1331 * chose 7bit encoding for this data, it's probably
1332 * ASCII. (MimeUtility.getEncoding will choose
1333 * 7bit only in this case, but someone might've
1334 * set the Content-Transfer-Encoding header manually.)
1335 */
1336 String charset;
1337 String enc = part.getEncoding();
1338 if (enc != null && enc.equalsIgnoreCase("7bit"))
1339 charset = "us-ascii";
1340 else
1341 charset = MimeUtility.getDefaultMIMECharset();
1342 cType.setParameter("charset", charset);
1343 type = cType.toString();
1344 }
1345 }
1346
1347 // Now, let's update our own headers ...
1348
1349 // Content-type, but only if we don't already have one
1350 if (needCTHeader) {
1351 /*
1352 * Pull out "filename" from Content-Disposition, and
1353 * use that to set the "name" parameter. This is to
1354 * satisfy older MUAs (DtMail, Roam and probably
1355 * a bunch of others).
1356 */
1357 String s = part.getHeader("Content-Disposition", null);
1358 if (s != null) {
1359 // Parse the header ..
1360 ContentDisposition cd = new ContentDisposition(s);
1361 String filename = cd.getParameter("filename");
1362 if (filename != null) {
1363 cType.setParameter("name", filename);
1364 type = cType.toString();
1365 }
1366 }
1367
1368 part.setHeader("Content-Type", type);
1369 }
1370 } catch (IOException ex) {
1371 throw new MessagingException("IOException updating headers", ex);
1372 }
1373 }
1374
1375 static void invalidateContentHeaders(MimePart part)
1376 throws MessagingException {
1377 part.removeHeader("Content-Type");
1378 part.removeHeader("Content-Transfer-Encoding");
1379 }
1380
1381 static void writeTo(MimePart part, OutputStream os, String[] ignoreList)
1382 throws IOException, MessagingException {
1383
1384 // see if we already have a LOS
1385 LineOutputStream los = null;
1386 if (os instanceof LineOutputStream) {
1387 los = (LineOutputStream) os;
1388 } else {
1389 los = new LineOutputStream(os);
1390 }
1391
1392 // First, write out the header
1393 Enumeration hdrLines = part.getNonMatchingHeaderLines(ignoreList);
1394 while (hdrLines.hasMoreElements())
1395 los.writeln((String)hdrLines.nextElement());
1396
1397 // The CRLF separator between header and content
1398 los.writeln();
1399
1400 // Finally, the content. Encode if required.
1401 // XXX: May need to account for ESMTP ?
1402 os = MimeUtility.encode(os, part.getEncoding());
1403 part.getDataHandler().writeTo(os);
1404 os.flush(); // Needed to complete encoding
1405 }
1406 }