Skip to content

Method: getFileName(MimePart)

1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package jakarta.mail.internet;
18:
19: import jakarta.activation.DataHandler;
20: import jakarta.activation.DataSource;
21: import jakarta.activation.FileDataSource;
22: import jakarta.mail.BodyPart;
23: import jakarta.mail.EncodingAware;
24: import jakarta.mail.FolderClosedException;
25: import jakarta.mail.Header;
26: import jakarta.mail.IllegalWriteException;
27: import jakarta.mail.Message;
28: import jakarta.mail.MessageRemovedException;
29: import jakarta.mail.MessagingException;
30: import jakarta.mail.Multipart;
31: import jakarta.mail.Part;
32: import jakarta.mail.util.LineOutputStream;
33: import jakarta.mail.util.StreamProvider;
34: import jakarta.mail.util.StreamProvider.EncoderTypes;
35:
36: import java.io.BufferedInputStream;
37: import java.io.ByteArrayInputStream;
38: import java.io.File;
39: import java.io.IOException;
40: import java.io.InputStream;
41: import java.io.OutputStream;
42: import java.io.UnsupportedEncodingException;
43: import java.nio.file.Files;
44: import java.util.ArrayList;
45: import java.util.Enumeration;
46: import java.util.HashMap;
47: import java.util.List;
48: import java.util.Map;
49:
50:
51: /**
52: * This class represents a MIME body part. It implements the
53: * <code>BodyPart</code> abstract class and the <code>MimePart</code>
54: * interface. 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.
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.
74: *
75: * @author John Mani
76: * @author Bill Shannon
77: * @author Kanwar Oberoi
78: * @see jakarta.mail.Part
79: * @see jakarta.mail.internet.MimePart
80: * @see jakarta.mail.internet.MimeUtility
81: */
82:
83: public class MimeBodyPart extends BodyPart implements MimePart {
84:
85: // Paranoia:
86: // allow this last minute change to be disabled if it causes problems
87: private static final boolean setDefaultTextCharset =
88: MimeUtility.getBooleanSystemProperty(
89: "mail.mime.setdefaulttextcharset", true);
90:
91: private static final boolean setContentTypeFileName =
92: MimeUtility.getBooleanSystemProperty(
93: "mail.mime.setcontenttypefilename", true);
94:
95: private static final boolean encodeFileName =
96: MimeUtility.getBooleanSystemProperty("mail.mime.encodefilename", false);
97: private static final boolean decodeFileName =
98: MimeUtility.getBooleanSystemProperty("mail.mime.decodefilename", false);
99: private static final boolean ignoreMultipartEncoding =
100: MimeUtility.getBooleanSystemProperty(
101: "mail.mime.ignoremultipartencoding", true);
102: private static final boolean allowutf8 =
103: MimeUtility.getBooleanSystemProperty("mail.mime.allowutf8", true);
104:
105: // Paranoia:
106: // allow this last minute change to be disabled if it causes problems
107: static final boolean cacheMultipart = // accessed by MimeMessage
108: MimeUtility.getBooleanSystemProperty("mail.mime.cachemultipart", true);
109:
110:
111: /**
112: * The DataHandler object representing this Part's content.
113: */
114: protected DataHandler dh;
115:
116: /**
117: * Byte array that holds the bytes of the content of this Part.
118: */
119: protected byte[] content;
120:
121: /**
122: * If the data for this body part was supplied by an
123: * InputStream that implements the SharedInputStream interface,
124: * <code>contentStream</code> is another such stream representing
125: * the content of this body part. In this case, <code>content</code>
126: * will be null.
127: *
128: * @since JavaMail 1.2
129: */
130: protected InputStream contentStream;
131:
132: /**
133: * The InternetHeaders object that stores all the headers
134: * of this body part.
135: */
136: protected InternetHeaders headers;
137:
138: /**
139: * If our content is a Multipart of Message object, we save it
140: * the first time it's created by parsing a stream so that changes
141: * to the contained objects will not be lost.
142: *
143: * If this field is not null, it's return by the {@link #getContent}
144: * method. The {@link #getContent} method sets this field if it
145: * would return a Multipart or MimeMessage object. This field is
146: * is cleared by the {@link #setDataHandler} method.
147: *
148: * @since JavaMail 1.5
149: */
150: protected Object cachedContent;
151:
152: /**
153: * An empty MimeBodyPart object is created.
154: * This body part maybe filled in by a client constructing a multipart
155: * message.
156: */
157: public MimeBodyPart() {
158: super();
159: headers = new InternetHeaders();
160: }
161:
162: /**
163: * Constructs a MimeBodyPart by reading and parsing the data from
164: * the specified input stream. The parser consumes data till the end
165: * of the given input stream. The input stream must start at the
166: * beginning of a valid MIME body part and must terminate at the end
167: * of that body part. <p>
168: *
169: * Note that the "boundary" string that delimits body parts must
170: * <strong>not</strong> be included in the input stream. The intention
171: * is that the MimeMultipart parser will extract each body part's bytes
172: * from a multipart stream and feed them into this constructor, without
173: * the delimiter strings.
174: *
175: * @param is the body part Input Stream
176: * @throws MessagingException for failures
177: */
178: public MimeBodyPart(InputStream is) throws MessagingException {
179: if (!(is instanceof ByteArrayInputStream) &&
180: !(is instanceof BufferedInputStream) &&
181: !(is instanceof SharedInputStream))
182: is = new BufferedInputStream(is);
183:
184: headers = new InternetHeaders(is);
185:
186: if (is instanceof SharedInputStream) {
187: SharedInputStream sis = (SharedInputStream) is;
188: contentStream = sis.newStream(sis.getPosition(), -1);
189: } else {
190: try {
191: content = MimeUtility.getBytes(is);
192: } catch (IOException ioex) {
193: throw new MessagingException("Error reading input stream", ioex);
194: }
195: }
196:
197: }
198:
199: /**
200: * Constructs a MimeBodyPart using the given header and
201: * content bytes. <p>
202: *
203: * Used by providers.
204: *
205: * @param headers The header of this part
206: * @param content bytes representing the body of this part.
207: * @throws MessagingException for failures
208: */
209: public MimeBodyPart(InternetHeaders headers, byte[] content)
210: throws MessagingException {
211: super();
212: this.headers = headers;
213: this.content = content;
214: }
215:
216: /**
217: * Return the size of the content of this body part in bytes.
218: * Return -1 if the size cannot be determined. <p>
219: *
220: * Note that this number may not be an exact measure of the
221: * content size and may or may not account for any transfer
222: * encoding of the content. <p>
223: *
224: * This implementation returns the size of the <code>content</code>
225: * array (if not null), or, if <code>contentStream</code> is not
226: * null, and the <code>available</code> method returns a positive
227: * number, it returns that number as the size. Otherwise, it returns
228: * -1.
229: *
230: * @return size in bytes, or -1 if not known
231: */
232: @Override
233: public int getSize() throws MessagingException {
234: if (content != null)
235: return content.length;
236: if (contentStream != null) {
237: try {
238: int size = contentStream.available();
239: // only believe the size if it's greate than zero, since zero
240: // is the default returned by the InputStream class itself
241: if (size > 0)
242: return size;
243: } catch (IOException ex) {
244: // ignore it
245: }
246: }
247: return -1;
248: }
249:
250: /**
251: * Return the number of lines for the content of this Part.
252: * Return -1 if this number cannot be determined. <p>
253: *
254: * Note that this number may not be an exact measure of the
255: * content length and may or may not account for any transfer
256: * encoding of the content. <p>
257: *
258: * This implementation returns -1.
259: *
260: * @return number of lines, or -1 if not known
261: */
262: @Override
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: @Override
279: public String getContentType() throws MessagingException {
280: String s = getHeader("Content-Type", null);
281: s = MimeUtil.cleanContentType(this, s);
282: if (s == null)
283: s = "text/plain";
284: return s;
285: }
286:
287: /**
288: * Is this Part of the specified MIME type? This method
289: * compares <strong>only the <code>primaryType</code> and
290: * <code>subType</code></strong>.
291: * The parameters of the content types are ignored. <p>
292: *
293: * For example, this method will return <code>true</code> when
294: * comparing a Part of content type <strong>"text/plain"</strong>
295: * with <strong>"text/plain; charset=foobar"</strong>. <p>
296: *
297: * If the <code>subType</code> of <code>mimeType</code> is the
298: * special character '*', then the subtype is ignored during the
299: * comparison.
300: *
301: * @throws MessagingException for failures
302: */
303: @Override
304: public boolean isMimeType(String mimeType) throws MessagingException {
305: return isMimeType(this, mimeType);
306: }
307:
308: /**
309: * Returns the disposition from the "Content-Disposition" header field.
310: * This represents the disposition of this part. The disposition
311: * describes how the part should be presented to the user. <p>
312: *
313: * If the Content-Disposition field is unavailable,
314: * null is returned. <p>
315: *
316: * This implementation uses <code>getHeader(name)</code>
317: * to obtain the requisite header field.
318: *
319: * @throws MessagingException for failures
320: * @see #headers
321: */
322: @Override
323: public String getDisposition() throws MessagingException {
324: return getDisposition(this);
325: }
326:
327: /**
328: * Set the disposition in the "Content-Disposition" header field
329: * of this body part. If the disposition is null, any existing
330: * "Content-Disposition" header field is removed.
331: *
332: * @throws IllegalWriteException if the underlying
333: * implementation does not support modification
334: * @throws IllegalStateException if this body part is
335: * obtained from a READ_ONLY folder.
336: * @throws MessagingException for other failures
337: */
338: @Override
339: public void setDisposition(String disposition) throws MessagingException {
340: setDisposition(this, disposition);
341: }
342:
343: /**
344: * Returns the content transfer encoding from the
345: * "Content-Transfer-Encoding" header
346: * field. Returns <code>null</code> if the header is unavailable
347: * or its value is absent. <p>
348: *
349: * This implementation uses <code>getHeader(name)</code>
350: * to obtain the requisite header field.
351: *
352: * @see #headers
353: */
354: @Override
355: public String getEncoding() throws MessagingException {
356: return getEncoding(this);
357: }
358:
359: /**
360: * Returns the value of the "Content-ID" header field. Returns
361: * <code>null</code> if the field is unavailable or its value is
362: * absent. <p>
363: *
364: * This implementation uses <code>getHeader(name)</code>
365: * to obtain the requisite header field.
366: */
367: @Override
368: public String getContentID() throws MessagingException {
369: return getHeader("Content-Id", null);
370: }
371:
372: /**
373: * Set the "Content-ID" header field of this body part.
374: * If the <code>cid</code> parameter is null, any existing
375: * "Content-ID" is removed.
376: *
377: * @param cid the Content-ID
378: * @throws IllegalWriteException if the underlying
379: * implementation does not support modification
380: * @throws IllegalStateException if this body part is
381: * obtained from a READ_ONLY folder.
382: * @throws MessagingException for other failures
383: * @since JavaMail 1.3
384: */
385: public void setContentID(String cid) throws MessagingException {
386: if (cid == null)
387: removeHeader("Content-ID");
388: else
389: setHeader("Content-ID", cid);
390: }
391:
392: /**
393: * Return the value of the "Content-MD5" header field. Returns
394: * <code>null</code> if this field is unavailable or its value
395: * is absent. <p>
396: *
397: * This implementation uses <code>getHeader(name)</code>
398: * to obtain the requisite header field.
399: */
400: @Override
401: public String getContentMD5() throws MessagingException {
402: return getHeader("Content-MD5", null);
403: }
404:
405: /**
406: * Set the "Content-MD5" header field of this body part.
407: *
408: * @throws IllegalWriteException if the underlying
409: * implementation does not support modification
410: * @throws IllegalStateException if this body part is
411: * obtained from a READ_ONLY folder.
412: */
413: @Override
414: public void setContentMD5(String md5) throws MessagingException {
415: setHeader("Content-MD5", md5);
416: }
417:
418: /**
419: * Get the languages specified in the Content-Language header
420: * of this MimePart. The Content-Language header is defined by
421: * RFC 1766. Returns <code>null</code> if this header is not
422: * available or its value is absent. <p>
423: *
424: * This implementation uses <code>getHeader(name)</code>
425: * to obtain the requisite header field.
426: */
427: @Override
428: public String[] getContentLanguage() throws MessagingException {
429: return getContentLanguage(this);
430: }
431:
432: /**
433: * Set the Content-Language header of this MimePart. The
434: * Content-Language header is defined by RFC 1766.
435: *
436: * @param languages array of language tags
437: */
438: @Override
439: public void setContentLanguage(String[] languages)
440: throws MessagingException {
441: setContentLanguage(this, languages);
442: }
443:
444: /**
445: * Returns the "Content-Description" header field of this body part.
446: * This typically associates some descriptive information with
447: * this part. Returns null if this field is unavailable or its
448: * value is absent. <p>
449: *
450: * If the Content-Description field is encoded as per RFC 2047,
451: * it is decoded and converted into Unicode. If the decoding or
452: * conversion fails, the raw data is returned as is. <p>
453: *
454: * This implementation uses <code>getHeader(name)</code>
455: * to obtain the requisite header field.
456: *
457: * @return content description
458: */
459: @Override
460: public String getDescription() throws MessagingException {
461: return getDescription(this);
462: }
463:
464: /**
465: * Set the "Content-Description" header field for this body part.
466: * If the description parameter is <code>null</code>, then any
467: * existing "Content-Description" fields are removed. <p>
468: *
469: * If the description contains non US-ASCII characters, it will
470: * be encoded using the platform's default charset. If the
471: * description contains only US-ASCII characters, no encoding
472: * is done and it is used as is. <p>
473: *
474: * Note that if the charset encoding process fails, a
475: * MessagingException is thrown, and an UnsupportedEncodingException
476: * is included in the chain of nested exceptions within the
477: * MessagingException.
478: *
479: * @param description content description
480: * @throws MessagingException otherwise; an
481: * UnsupportedEncodingException may be included
482: * in the exception chain if the charset
483: * conversion fails.
484: * @throws IllegalWriteException if the underlying
485: * implementation does not support modification
486: * @throws IllegalStateException if this body part is
487: * obtained from a READ_ONLY folder.
488: */
489: @Override
490: public void setDescription(String description) throws MessagingException {
491: setDescription(description, null);
492: }
493:
494: /**
495: * Set the "Content-Description" header field for this body part.
496: * If the description parameter is <code>null</code>, then any
497: * existing "Content-Description" fields are removed. <p>
498: *
499: * If the description contains non US-ASCII characters, it will
500: * be encoded using the specified charset. If the description
501: * contains only US-ASCII characters, no encoding is done and
502: * it is used as is. <p>
503: *
504: * Note that if the charset encoding process fails, a
505: * MessagingException is thrown, and an UnsupportedEncodingException
506: * is included in the chain of nested exceptions within the
507: * MessagingException.
508: *
509: * @param description Description
510: * @param charset Charset for encoding
511: * @throws MessagingException otherwise; an
512: * UnsupportedEncodingException may be included
513: * in the exception chain if the charset
514: * conversion fails.
515: * @throws IllegalWriteException if the underlying
516: * implementation does not support modification
517: * @throws IllegalStateException if this body part is
518: * obtained from a READ_ONLY folder.
519: */
520: public void setDescription(String description, String charset)
521: throws MessagingException {
522: setDescription(this, description, charset);
523: }
524:
525: /**
526: * Get the filename associated with this body part. <p>
527: *
528: * Returns the value of the "filename" parameter from the
529: * "Content-Disposition" header field of this body part. If its
530: * not available, returns the value of the "name" parameter from
531: * the "Content-Type" header field of this body part.
532: * Returns <code>null</code> if both are absent. <p>
533: *
534: * If the <code>mail.mime.decodefilename</code> System property
535: * is set to true, the {@link MimeUtility#decodeText
536: * MimeUtility.decodeText} method will be used to decode the
537: * filename. While such encoding is not supported by the MIME
538: * spec, many mailers use this technique to support non-ASCII
539: * characters in filenames. The default value of this property
540: * is false.
541: *
542: * @return filename
543: */
544: @Override
545: public String getFileName() throws MessagingException {
546: return getFileName(this);
547: }
548:
549: /**
550: * Set the filename associated with this body part, if possible. <p>
551: *
552: * Sets the "filename" parameter of the "Content-Disposition"
553: * header field of this body part. For compatibility with older
554: * mailers, the "name" parameter of the "Content-Type" header is
555: * also set. <p>
556: *
557: * If the <code>mail.mime.encodefilename</code> System property
558: * is set to true, the {@link MimeUtility#encodeText
559: * MimeUtility.encodeText} method will be used to encode the
560: * filename. While such encoding is not supported by the MIME
561: * spec, many mailers use this technique to support non-ASCII
562: * characters in filenames. The default value of this property
563: * is false.
564: *
565: * @param filename the file name
566: * @throws IllegalWriteException if the underlying
567: * implementation does not support modification
568: * @throws IllegalStateException if this body part is
569: * obtained from a READ_ONLY folder.
570: * @throws MessagingException for other failures
571: */
572: @Override
573: public void setFileName(String filename) throws MessagingException {
574: setFileName(this, filename);
575: }
576:
577: /**
578: * Return a decoded input stream for this body part's "content". <p>
579: *
580: * This implementation obtains the input stream from the DataHandler.
581: * That is, it invokes getDataHandler().getInputStream();
582: *
583: * @return an InputStream
584: * @throws IOException this is typically thrown by the
585: * DataHandler. Refer to the documentation for
586: * jakarta.activation.DataHandler for more details.
587: * @throws MessagingException for other failures
588: * @see #getContentStream
589: * @see jakarta.activation.DataHandler#getInputStream
590: */
591: @Override
592: public InputStream getInputStream()
593: throws IOException, MessagingException {
594: return getDataHandler().getInputStream();
595: }
596:
597: /**
598: * Produce the raw bytes of the content. This method is used
599: * when creating a DataHandler object for the content. Subclasses
600: * that can provide a separate input stream for just the Part
601: * content might want to override this method.
602: *
603: * @return an InputStream containing the raw bytes
604: * @throws MessagingException for failures
605: * @see #content
606: * @see MimeMessage#getContentStream
607: */
608: protected InputStream getContentStream() throws MessagingException {
609: if (contentStream != null)
610: return ((SharedInputStream) contentStream).newStream(0, -1);
611: if (content != null)
612: return new ByteArrayInputStream(content);
613:
614: throw new MessagingException("No MimeBodyPart content");
615: }
616:
617: /**
618: * Return an InputStream to the raw data with any Content-Transfer-Encoding
619: * intact. This method is useful if the "Content-Transfer-Encoding"
620: * header is incorrect or corrupt, which would prevent the
621: * <code>getInputStream</code> method or <code>getContent</code> method
622: * from returning the correct data. In such a case the application may
623: * use this method and attempt to decode the raw data itself. <p>
624: *
625: * This implementation simply calls the <code>getContentStream</code>
626: * method.
627: *
628: * @return an InputStream containing the raw bytes
629: * @throws MessagingException for failures
630: * @see #getInputStream
631: * @see #getContentStream
632: * @since JavaMail 1.2
633: */
634: public InputStream getRawInputStream() throws MessagingException {
635: return getContentStream();
636: }
637:
638: /**
639: * Return a DataHandler for this body part's content. <p>
640: *
641: * The implementation provided here works just like the
642: * the implementation in MimeMessage.
643: *
644: * @see MimeMessage#getDataHandler
645: */
646: @Override
647: public DataHandler getDataHandler() throws MessagingException {
648: if (dh == null)
649: dh = new MimePartDataHandler(this);
650: return dh;
651: }
652:
653: /**
654: * Return the content as a Java object. The type of the object
655: * returned is of course dependent on the content itself. For
656: * example, the native format of a text/plain content is usually
657: * a String object. The native format for a "multipart"
658: * content is always a Multipart subclass. For content types that are
659: * unknown to the DataHandler system, an input stream is returned
660: * as the content. <p>
661: *
662: * This implementation obtains the content from the DataHandler.
663: * That is, it invokes getDataHandler().getContent();
664: * If the content is a Multipart or Message object and was created by
665: * parsing a stream, the object is cached and returned in subsequent
666: * calls so that modifications to the content will not be lost.
667: *
668: * @return Object
669: * @throws IOException this is typically thrown by the
670: * DataHandler. Refer to the documentation for
671: * jakarta.activation.DataHandler for more details.
672: * @throws MessagingException for other failures
673: */
674: @Override
675: public Object getContent() throws IOException, MessagingException {
676: if (cachedContent != null)
677: return cachedContent;
678: Object c;
679: try {
680: c = getDataHandler().getContent();
681: } catch (IOException e) {
682: if (e.getCause() instanceof FolderClosedException) {
683: FolderClosedException fce = (FolderClosedException) e.getCause();
684: throw new FolderClosedException(fce.getFolder(), e.getMessage());
685: } else if (e.getCause() instanceof MessagingException) {
686: throw new MessageRemovedException(e.getMessage());
687: } else {
688: throw e;
689: }
690: }
691: if (cacheMultipart &&
692: (c instanceof Multipart || c instanceof Message) &&
693: (content != null || contentStream != null)) {
694: cachedContent = c;
695: /*
696: * We may abandon the input stream so make sure
697: * the MimeMultipart has consumed the stream.
698: */
699: if (c instanceof MimeMultipart)
700: ((MimeMultipart) c).parse();
701: }
702: return c;
703: }
704:
705: /**
706: * This method provides the mechanism to set this body part's content.
707: * The given DataHandler object should wrap the actual content.
708: *
709: * @param dh The DataHandler for the content
710: * @throws IllegalWriteException if the underlying
711: * implementation does not support modification
712: * @throws IllegalStateException if this body part is
713: * obtained from a READ_ONLY folder.
714: */
715: @Override
716: public void setDataHandler(DataHandler dh)
717: throws MessagingException {
718: this.dh = dh;
719: cachedContent = null;
720: MimeBodyPart.invalidateContentHeaders(this);
721: }
722:
723: /**
724: * A convenience method for setting this body part's content. <p>
725: *
726: * The content is wrapped in a DataHandler object. Note that a
727: * DataContentHandler class for the specified type should be
728: * available to the Jakarta Mail implementation for this to work right.
729: * That is, to do <code>setContent(foobar, "application/x-foobar")</code>,
730: * a DataContentHandler for "application/x-foobar" should be installed.
731: * Refer to the Java Activation Framework for more information.
732: *
733: * @param o the content object
734: * @param type Mime type of the object
735: * @throws IllegalWriteException if the underlying
736: * implementation does not support modification of
737: * existing values
738: * @throws IllegalStateException if this body part is
739: * obtained from a READ_ONLY folder.
740: */
741: @Override
742: public void setContent(Object o, String type)
743: throws MessagingException {
744: if (o instanceof Multipart) {
745: setContent((Multipart) o);
746: } else {
747: setDataHandler(new DataHandler(o, type));
748: }
749: }
750:
751: /**
752: * Convenience method that sets the given String as this
753: * part's content, with a MIME type of "text/plain". If the
754: * string contains non US-ASCII characters, it will be encoded
755: * using the platform's default charset. The charset is also
756: * used to set the "charset" parameter. <p>
757: *
758: * Note that there may be a performance penalty if
759: * <code>text</code> is large, since this method may have
760: * to scan all the characters to determine what charset to
761: * use. <p>
762: *
763: * If the charset is already known, use the
764: * <code>setText</code> method that takes the charset parameter.
765: *
766: * @param text the text content to set
767: * @throws MessagingException if an error occurs
768: * @see #setText(String text, String charset)
769: */
770: @Override
771: public void setText(String text) throws MessagingException {
772: setText(text, null);
773: }
774:
775: /**
776: * Convenience method that sets the given String as this part's
777: * content, with a MIME type of "text/plain" and the specified
778: * charset. The given Unicode string will be charset-encoded
779: * using the specified charset. The charset is also used to set
780: * the "charset" parameter.
781: *
782: * @param text the text content to set
783: * @param charset the charset to use for the text
784: * @throws MessagingException if an error occurs
785: */
786: @Override
787: public void setText(String text, String charset)
788: throws MessagingException {
789: setText(this, text, charset, "plain");
790: }
791:
792: /**
793: * Convenience method that sets the given String as this part's
794: * content, with a primary MIME type of "text" and the specified
795: * MIME subtype. The given Unicode string will be charset-encoded
796: * using the specified charset. The charset is also used to set
797: * the "charset" parameter.
798: *
799: * @param text the text content to set
800: * @param charset the charset to use for the text
801: * @param subtype the MIME subtype to use (e.g., "html")
802: * @throws MessagingException if an error occurs
803: * @since JavaMail 1.4
804: */
805: @Override
806: public void setText(String text, String charset, String subtype)
807: throws MessagingException {
808: setText(this, text, charset, subtype);
809: }
810:
811: /**
812: * This method sets the body part's content to a Multipart object.
813: *
814: * @param mp The multipart object that is the Message's content
815: * @throws IllegalWriteException if the underlying
816: * implementation does not support modification of
817: * existing values.
818: * @throws IllegalStateException if this body part is
819: * obtained from a READ_ONLY folder.
820: */
821: @Override
822: public void setContent(Multipart mp) throws MessagingException {
823: setDataHandler(new DataHandler(mp, mp.getContentType()));
824: mp.setParent(this);
825: }
826:
827: /**
828: * Use the specified file to provide the data for this part.
829: * The simple file name is used as the file name for this
830: * part and the data in the file is used as the data for this
831: * part. The encoding will be chosen appropriately for the
832: * file data. The disposition of this part is set to
833: * {@link Part#ATTACHMENT Part.ATTACHMENT}.
834: *
835: * @param file the File object to attach
836: * @throws IOException errors related to accessing the file
837: * @throws MessagingException message related errors
838: * @since JavaMail 1.4
839: */
840: public void attachFile(File file) throws IOException, MessagingException {
841: FileDataSource fds = new FileDataSource(file);
842: this.setDataHandler(new DataHandler(fds));
843: this.setFileName(fds.getName());
844: this.setDisposition(ATTACHMENT);
845: }
846:
847: /**
848: * Use the specified file to provide the data for this part.
849: * The simple file name is used as the file name for this
850: * part and the data in the file is used as the data for this
851: * part. The encoding will be chosen appropriately for the
852: * file data.
853: *
854: * @param file the name of the file to attach
855: * @throws IOException errors related to accessing the file
856: * @throws MessagingException message related errors
857: * @since JavaMail 1.4
858: */
859: public void attachFile(String file) throws IOException, MessagingException {
860: File f = new File(file);
861: attachFile(f);
862: }
863:
864: /**
865: * Use the specified file with the specified Content-Type and
866: * Content-Transfer-Encoding to provide the data for this part.
867: * If contentType or encoding are null, appropriate values will
868: * be chosen.
869: * The simple file name is used as the file name for this
870: * part and the data in the file is used as the data for this
871: * part. The disposition of this part is set to
872: * {@link Part#ATTACHMENT Part.ATTACHMENT}.
873: *
874: * @param file the File object to attach
875: * @param contentType the Content-Type, or null
876: * @param encoding the Content-Transfer-Encoding, or null
877: * @throws IOException errors related to accessing the file
878: * @throws MessagingException message related errors
879: * @since JavaMail 1.5
880: */
881: public void attachFile(File file, String contentType, String encoding)
882: throws IOException, MessagingException {
883: DataSource fds = new EncodedFileDataSource(file, contentType, encoding);
884: this.setDataHandler(new DataHandler(fds));
885: this.setFileName(fds.getName());
886: this.setDisposition(ATTACHMENT);
887: }
888:
889: /**
890: * Use the specified file with the specified Content-Type and
891: * Content-Transfer-Encoding to provide the data for this part.
892: * If contentType or encoding are null, appropriate values will
893: * be chosen.
894: * The simple file name is used as the file name for this
895: * part and the data in the file is used as the data for this
896: * part. The disposition of this part is set to
897: * {@link Part#ATTACHMENT Part.ATTACHMENT}.
898: *
899: * @param file the name of the file
900: * @param contentType the Content-Type, or null
901: * @param encoding the Content-Transfer-Encoding, or null
902: * @throws IOException errors related to accessing the file
903: * @throws MessagingException message related errors
904: * @since JavaMail 1.5
905: */
906: public void attachFile(String file, String contentType, String encoding)
907: throws IOException, MessagingException {
908: attachFile(new File(file), contentType, encoding);
909: }
910:
911: /**
912: * A FileDataSource class that allows us to specify the
913: * Content-Type and Content-Transfer-Encoding.
914: */
915: private static class EncodedFileDataSource extends FileDataSource
916: implements EncodingAware {
917: private String contentType;
918: private String encoding;
919:
920: public EncodedFileDataSource(File file, String contentType,
921: String encoding) {
922: super(file);
923: this.contentType = contentType;
924: this.encoding = encoding;
925: }
926:
927: // overrides DataSource.getContentType()
928: @Override
929: public String getContentType() {
930: return contentType != null ? contentType : super.getContentType();
931: }
932:
933: // implements EncodingAware.getEncoding()
934: @Override
935: public String getEncoding() {
936: return encoding;
937: }
938: }
939:
940: /**
941: * Save the contents of this part in the specified file. The content
942: * is decoded and saved, without any of the MIME headers.
943: *
944: * @param file the File object to write to
945: * @throws IOException errors related to accessing the file
946: * @throws MessagingException message related errors
947: * @since JavaMail 1.4
948: */
949: public void saveFile(File file) throws IOException, MessagingException {
950: Files.copy(getInputStream(), file.toPath());
951: }
952:
953: /**
954: * Save the contents of this part in the specified file. The content
955: * is decoded and saved, without any of the MIME headers.
956: *
957: * @param file the name of the file to write to
958: * @throws IOException errors related to accessing the file
959: * @throws MessagingException message related errors
960: * @since JavaMail 1.4
961: */
962: public void saveFile(String file) throws IOException, MessagingException {
963: File f = new File(file);
964: saveFile(f);
965: }
966:
967: /**
968: * Output the body part as an RFC 822 format stream.
969: *
970: * @throws IOException if an error occurs writing to the
971: * stream or if an error is generated
972: * by the jakarta.activation layer.
973: * @throws MessagingException for other failures
974: * @see jakarta.activation.DataHandler#writeTo
975: */
976: @Override
977: public void writeTo(OutputStream os)
978: throws IOException, MessagingException {
979: writeTo(this, os, null);
980: }
981:
982: /**
983: * Get all the headers for this header_name. Note that certain
984: * headers may be encoded as per RFC 2047 if they contain
985: * non US-ASCII characters and these should be decoded.
986: *
987: * @param name name of header
988: * @return array of headers
989: * @see jakarta.mail.internet.MimeUtility
990: */
991: @Override
992: public String[] getHeader(String name) throws MessagingException {
993: return headers.getHeader(name);
994: }
995:
996: /**
997: * Get all the headers for this header name, returned as a single
998: * String, with headers separated by the delimiter. If the
999: * delimiter is <code>null</code>, only the first header is
1000: * returned.
1001: *
1002: * @param name the name of this header
1003: * @param delimiter delimiter between fields in returned string
1004: * @return the value fields for all headers with
1005: * this name
1006: * @throws MessagingException for failures
1007: */
1008: @Override
1009: public String getHeader(String name, String delimiter)
1010: throws MessagingException {
1011: return headers.getHeader(name, delimiter);
1012: }
1013:
1014: /**
1015: * Set the value for this header_name. Replaces all existing
1016: * header values with this new value. Note that RFC 822 headers
1017: * must contain only US-ASCII characters, so a header that
1018: * contains non US-ASCII characters must be encoded as per the
1019: * rules of RFC 2047.
1020: *
1021: * @param name header name
1022: * @param value header value
1023: * @see jakarta.mail.internet.MimeUtility
1024: */
1025: @Override
1026: public void setHeader(String name, String value)
1027: throws MessagingException {
1028: headers.setHeader(name, value);
1029: }
1030:
1031: /**
1032: * Add this value to the existing values for this header_name.
1033: * Note that RFC 822 headers must contain only US-ASCII
1034: * characters, so a header that contains non US-ASCII characters
1035: * must be encoded as per the rules of RFC 2047.
1036: *
1037: * @param name header name
1038: * @param value header value
1039: * @see jakarta.mail.internet.MimeUtility
1040: */
1041: @Override
1042: public void addHeader(String name, String value)
1043: throws MessagingException {
1044: headers.addHeader(name, value);
1045: }
1046:
1047: /**
1048: * Remove all headers with this name.
1049: */
1050: @Override
1051: public void removeHeader(String name) throws MessagingException {
1052: headers.removeHeader(name);
1053: }
1054:
1055: /**
1056: * Return all the headers from this Message as an Enumeration of
1057: * Header objects.
1058: */
1059: @Override
1060: public Enumeration<Header> getAllHeaders() throws MessagingException {
1061: return headers.getAllHeaders();
1062: }
1063:
1064: /**
1065: * Return matching headers from this Message as an Enumeration of
1066: * Header objects.
1067: */
1068: @Override
1069: public Enumeration<Header> getMatchingHeaders(String[] names)
1070: throws MessagingException {
1071: return headers.getMatchingHeaders(names);
1072: }
1073:
1074: /**
1075: * Return non-matching headers from this Message as an
1076: * Enumeration of Header objects.
1077: */
1078: @Override
1079: public Enumeration<Header> getNonMatchingHeaders(String[] names)
1080: throws MessagingException {
1081: return headers.getNonMatchingHeaders(names);
1082: }
1083:
1084: /**
1085: * Add a header line to this body part
1086: */
1087: @Override
1088: public void addHeaderLine(String line) throws MessagingException {
1089: headers.addHeaderLine(line);
1090: }
1091:
1092: /**
1093: * Get all header lines as an Enumeration of Strings. A Header
1094: * line is a raw RFC 822 header line, containing both the "name"
1095: * and "value" field.
1096: */
1097: @Override
1098: public Enumeration<String> getAllHeaderLines() throws MessagingException {
1099: return headers.getAllHeaderLines();
1100: }
1101:
1102: /**
1103: * Get matching header lines as an Enumeration of Strings.
1104: * A Header line is a raw RFC 822 header line, containing both
1105: * the "name" and "value" field.
1106: */
1107: @Override
1108: public Enumeration<String> getMatchingHeaderLines(String[] names)
1109: throws MessagingException {
1110: return headers.getMatchingHeaderLines(names);
1111: }
1112:
1113: /**
1114: * Get non-matching header lines as an Enumeration of Strings.
1115: * A Header line is a raw RFC 822 header line, containing both
1116: * the "name" and "value" field.
1117: */
1118: @Override
1119: public Enumeration<String> getNonMatchingHeaderLines(String[] names)
1120: throws MessagingException {
1121: return headers.getNonMatchingHeaderLines(names);
1122: }
1123:
1124: /**
1125: * Examine the content of this body part and update the appropriate
1126: * MIME headers. Typical headers that get set here are
1127: * <code>Content-Type</code> and <code>Content-Transfer-Encoding</code>.
1128: * Headers might need to be updated in two cases:
1129: *
1130: * <br>
1131: * - A message being crafted by a mail application will certainly
1132: * need to activate this method at some point to fill up its internal
1133: * headers.
1134: *
1135: * <br>
1136: * - A message read in from a Store will have obtained
1137: * all its headers from the store, and so doesn't need this.
1138: * However, if this message is editable and if any edits have
1139: * been made to either the content or message structure, we might
1140: * need to resync our headers.
1141: *
1142: * <br>
1143: * In both cases this method is typically called by the
1144: * <code>Message.saveChanges</code> method. <p>
1145: *
1146: * If the {@link #cachedContent} field is not null (that is,
1147: * it references a Multipart or Message object), then
1148: * that object is used to set a new DataHandler, any
1149: * stream data used to create this object is discarded,
1150: * and the {@link #cachedContent} field is cleared.
1151: *
1152: * @throws MessagingException for failures
1153: */
1154: protected void updateHeaders() throws MessagingException {
1155: updateHeaders(this);
1156: /*
1157: * If we've cached a Multipart or Message object then
1158: * we're now committed to using this instance of the
1159: * object and we discard any stream data used to create
1160: * this object.
1161: */
1162: if (cachedContent != null) {
1163: dh = new DataHandler(cachedContent, getContentType());
1164: cachedContent = null;
1165: content = null;
1166: if (contentStream != null) {
1167: try {
1168: contentStream.close();
1169: } catch (IOException ioex) {
1170: } // nothing to do
1171: }
1172: contentStream = null;
1173: }
1174: }
1175:
1176: /////////////////////////////////////////////////////////////
1177: // Package private convenience methods to share code among //
1178: // MimeMessage and MimeBodyPart //
1179: /////////////////////////////////////////////////////////////
1180:
1181: static boolean isMimeType(MimePart part, String mimeType)
1182: throws MessagingException {
1183: // XXX - lots of room for optimization here!
1184: String type = part.getContentType();
1185: try {
1186: return new ContentType(type).match(mimeType);
1187: } catch (ParseException ex) {
1188: // we only need the type and subtype so throw away the rest
1189: try {
1190: int i = type.indexOf(';');
1191: if (i > 0)
1192: return new ContentType(type.substring(0, i)).match(mimeType);
1193: } catch (ParseException pex2) {
1194: }
1195: return type.equalsIgnoreCase(mimeType);
1196: }
1197: }
1198:
1199: static void setText(MimePart part, String text, String charset,
1200: String subtype) throws MessagingException {
1201: if (charset == null) {
1202: if (MimeUtility.checkAscii(text) != MimeUtility.ALL_ASCII)
1203: charset = MimeUtility.getDefaultMIMECharset();
1204: else
1205: charset = "us-ascii";
1206: }
1207: // XXX - should at least ensure that subtype is an atom
1208: part.setContent(text, "text/" + subtype + "; charset=" +
1209: MimeUtility.quote(charset, HeaderTokenizer.MIME));
1210: }
1211:
1212: static String getDisposition(MimePart part) throws MessagingException {
1213: String s = part.getHeader("Content-Disposition", null);
1214:
1215: if (s == null)
1216: return null;
1217:
1218: ContentDisposition cd = new ContentDisposition(s);
1219: return cd.getDisposition();
1220: }
1221:
1222: static void setDisposition(MimePart part, String disposition)
1223: throws MessagingException {
1224: if (disposition == null)
1225: part.removeHeader("Content-Disposition");
1226: else {
1227: String s = part.getHeader("Content-Disposition", null);
1228: if (s != null) {
1229: /* A Content-Disposition header already exists ..
1230: *
1231: * Override disposition, but attempt to retain
1232: * existing disposition parameters
1233: */
1234: ContentDisposition cd = new ContentDisposition(s);
1235: cd.setDisposition(disposition);
1236: disposition = cd.toString();
1237: }
1238: part.setHeader("Content-Disposition", disposition);
1239: }
1240: }
1241:
1242: static String getDescription(MimePart part)
1243: throws MessagingException {
1244: String rawvalue = part.getHeader("Content-Description", null);
1245:
1246: if (rawvalue == null)
1247: return null;
1248:
1249: try {
1250: return MimeUtility.decodeText(MimeUtility.unfold(rawvalue));
1251: } catch (UnsupportedEncodingException ex) {
1252: return rawvalue;
1253: }
1254: }
1255:
1256: static void
1257: setDescription(MimePart part, String description, String charset)
1258: throws MessagingException {
1259: if (description == null) {
1260: part.removeHeader("Content-Description");
1261: return;
1262: }
1263:
1264: try {
1265: part.setHeader("Content-Description", MimeUtility.fold(21,
1266: MimeUtility.encodeText(description, charset, null)));
1267: } catch (UnsupportedEncodingException uex) {
1268: throw new MessagingException("Encoding error", uex);
1269: }
1270: }
1271:
1272: static String getFileName(MimePart part) throws MessagingException {
1273: String filename = null;
1274: String s = part.getHeader("Content-Disposition", null);
1275:
1276:• if (s != null) {
1277: // Parse the header ..
1278: ContentDisposition cd = new ContentDisposition(s);
1279: filename = cd.getParameter("filename");
1280: }
1281:• if (filename == null) {
1282: // Still no filename ? Try the "name" ContentType parameter
1283: s = part.getHeader("Content-Type", null);
1284: s = MimeUtil.cleanContentType(part, s);
1285:• if (s != null) {
1286: try {
1287: ContentType ct = new ContentType(s);
1288: filename = ct.getParameter("name");
1289: } catch (ParseException pex) {
1290: } // ignore it
1291: }
1292: }
1293:• if (decodeFileName && filename != null) {
1294: try {
1295: filename = MimeUtility.decodeText(filename);
1296: } catch (UnsupportedEncodingException ex) {
1297: throw new MessagingException("Can't decode filename", ex);
1298: }
1299: }
1300: return filename;
1301: }
1302:
1303: static void setFileName(MimePart part, String name)
1304: throws MessagingException {
1305: if (encodeFileName && name != null) {
1306: try {
1307: name = MimeUtility.encodeText(name);
1308: } catch (UnsupportedEncodingException ex) {
1309: throw new MessagingException("Can't encode filename", ex);
1310: }
1311: }
1312:
1313: // Set the Content-Disposition "filename" parameter
1314: String s = part.getHeader("Content-Disposition", null);
1315: ContentDisposition cd =
1316: new ContentDisposition(s == null ? Part.ATTACHMENT : s);
1317: // ensure that the filename is encoded if necessary
1318: String charset = MimeUtility.getDefaultMIMECharset();
1319: ParameterList p = cd.getParameterList();
1320: if (p == null) {
1321: p = new ParameterList();
1322: cd.setParameterList(p);
1323: }
1324: if (encodeFileName)
1325: p.setLiteral("filename", name);
1326: else
1327: p.set("filename", name, charset);
1328: part.setHeader("Content-Disposition", cd.toString());
1329:
1330: /*
1331: * Also attempt to set the Content-Type "name" parameter,
1332: * to satisfy ancient MUAs. XXX - This is not RFC compliant.
1333: */
1334: if (setContentTypeFileName) {
1335: s = part.getHeader("Content-Type", null);
1336: s = MimeUtil.cleanContentType(part, s);
1337: if (s != null) {
1338: try {
1339: ContentType cType = new ContentType(s);
1340: // ensure that the filename is encoded if necessary
1341: p = cType.getParameterList();
1342: if (p == null) {
1343: p = new ParameterList();
1344: cType.setParameterList(p);
1345: }
1346: if (encodeFileName)
1347: p.setLiteral("name", name);
1348: else
1349: p.set("name", name, charset);
1350: part.setHeader("Content-Type", cType.toString());
1351: } catch (ParseException pex) {
1352: } // ignore it
1353: }
1354: }
1355: }
1356:
1357: static String[] getContentLanguage(MimePart part)
1358: throws MessagingException {
1359: String s = part.getHeader("Content-Language", null);
1360:
1361: if (s == null)
1362: return null;
1363:
1364: // Tokenize the header to obtain the Language-tags (skip comments)
1365: HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
1366: List<String> v = new ArrayList<>();
1367:
1368: HeaderTokenizer.Token tk;
1369: int tkType;
1370:
1371: while (true) {
1372: tk = h.next(); // get a language-tag
1373: tkType = tk.getType();
1374: if (tkType == HeaderTokenizer.Token.EOF)
1375: break; // done
1376: else if (tkType == HeaderTokenizer.Token.ATOM)
1377: v.add(tk.getValue());
1378: else // invalid token, skip it.
1379: continue;
1380: }
1381:
1382: if (v.isEmpty())
1383: return null;
1384:
1385: String[] language = new String[v.size()];
1386: v.toArray(language);
1387: return language;
1388: }
1389:
1390: static void setContentLanguage(MimePart part, String[] languages)
1391: throws MessagingException {
1392: StringBuilder sb = new StringBuilder(languages[0]);
1393: int len = "Content-Language".length() + 2 + languages[0].length();
1394: for (int i = 1; i < languages.length; i++) {
1395: sb.append(',');
1396: len++;
1397: if (len > 76) {
1398: sb.append("\r\n\t");
1399: len = 8;
1400: }
1401: sb.append(languages[i]);
1402: len += languages[i].length();
1403: }
1404: part.setHeader("Content-Language", sb.toString());
1405: }
1406:
1407: static String getEncoding(MimePart part) throws MessagingException {
1408: String s = part.getHeader("Content-Transfer-Encoding", null);
1409:
1410: if (s == null)
1411: return null;
1412:
1413: s = s.trim(); // get rid of trailing spaces
1414: if (s.length() == 0)
1415: return null;
1416: // quick check for known values to avoid unnecessary use
1417: // of tokenizer.
1418: if (s.equalsIgnoreCase(EncoderTypes.BIT7_ENCODER.getEncoder()) || s.equalsIgnoreCase(EncoderTypes.BIT8_ENCODER.getEncoder()) ||
1419: s.equalsIgnoreCase(EncoderTypes.QUOTED_PRINTABLE_ENCODER.getEncoder()) ||
1420: s.equalsIgnoreCase(EncoderTypes.BINARY_ENCODER.getEncoder()) ||
1421: s.equalsIgnoreCase(EncoderTypes.BASE_64.getEncoder()))
1422: return s;
1423:
1424: // Tokenize the header to obtain the encoding (skip comments)
1425: HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
1426:
1427: HeaderTokenizer.Token tk;
1428: int tkType;
1429:
1430: for (; ; ) {
1431: tk = h.next(); // get a token
1432: tkType = tk.getType();
1433: if (tkType == HeaderTokenizer.Token.EOF)
1434: break; // done
1435: else if (tkType == HeaderTokenizer.Token.ATOM)
1436: return tk.getValue();
1437: else // invalid token, skip it.
1438: continue;
1439: }
1440: return s;
1441: }
1442:
1443: static void setEncoding(MimePart part, String encoding)
1444: throws MessagingException {
1445: part.setHeader("Content-Transfer-Encoding", encoding);
1446: }
1447:
1448: /**
1449: * Restrict the encoding to values allowed for the
1450: * Content-Type of the specified MimePart. Returns
1451: * either the original encoding or null.
1452: */
1453: static String restrictEncoding(MimePart part, String encoding)
1454: throws MessagingException {
1455: if (!ignoreMultipartEncoding || encoding == null)
1456: return encoding;
1457:
1458: if (encoding.equalsIgnoreCase(EncoderTypes.BIT7_ENCODER.getEncoder()) ||
1459: encoding.equalsIgnoreCase(EncoderTypes.BIT8_ENCODER.getEncoder()) ||
1460: encoding.equalsIgnoreCase(EncoderTypes.BINARY_ENCODER.getEncoder()))
1461: return encoding; // these encodings are always valid
1462:
1463: String type = part.getContentType();
1464: if (type == null)
1465: return encoding;
1466:
1467: try {
1468: /*
1469: * multipart and message types aren't allowed to have
1470: * encodings except for the three mentioned above.
1471: * If it's one of these types, ignore the encoding.
1472: */
1473: ContentType cType = new ContentType(type);
1474: if (cType.match("multipart/*"))
1475: return null;
1476: if (cType.match("message/*") &&
1477: !MimeUtility.getBooleanSystemProperty(
1478: "mail.mime.allowencodedmessages", false))
1479: return null;
1480: } catch (ParseException pex) {
1481: // ignore it
1482: }
1483: return encoding;
1484: }
1485:
1486: static void updateHeaders(MimePart part) throws MessagingException {
1487: DataHandler dh = part.getDataHandler();
1488: if (dh == null) // Huh ?
1489: return;
1490:
1491: try {
1492: String type = dh.getContentType();
1493: boolean composite = false;
1494: boolean needCTHeader = part.getHeader("Content-Type") == null;
1495:
1496: ContentType cType = new ContentType(type);
1497:
1498: /*
1499: * If this is a multipart, give sub-parts a chance to
1500: * update their headers. Even though the data for this
1501: * multipart may have come from a stream, one of the
1502: * sub-parts may have been updated.
1503: */
1504: if (cType.match("multipart/*")) {
1505: // If multipart, recurse
1506: composite = true;
1507: Object o;
1508: if (part instanceof MimeBodyPart) {
1509: MimeBodyPart mbp = (MimeBodyPart) part;
1510: o = mbp.cachedContent != null ?
1511: mbp.cachedContent : dh.getContent();
1512: } else if (part instanceof MimeMessage) {
1513: MimeMessage msg = (MimeMessage) part;
1514: o = msg.cachedContent != null ?
1515: msg.cachedContent : dh.getContent();
1516: } else
1517: o = dh.getContent();
1518: if (o instanceof MimeMultipart)
1519: ((MimeMultipart) o).updateHeaders();
1520: else
1521: throw new MessagingException("MIME part of type \"" +
1522: type + "\" contains object of type " +
1523: o.getClass().getName() + " instead of MimeMultipart");
1524: } else if (cType.match("message/rfc822")) {
1525: composite = true;
1526: // XXX - call MimeMessage.updateHeaders()?
1527: }
1528:
1529: /*
1530: * If this is our own MimePartDataHandler, we can't update any
1531: * of the headers.
1532: *
1533: * If this is a MimePartDataHandler coming from another part,
1534: * we need to copy over the content headers from the other part.
1535: * Note that the MimePartDataHandler still refers to the original
1536: * data and the original MimePart.
1537: */
1538: if (dh instanceof MimePartDataHandler) {
1539: MimePartDataHandler mdh = (MimePartDataHandler) dh;
1540: MimePart mpart = mdh.getPart();
1541: if (mpart != part) {
1542: if (needCTHeader)
1543: part.setHeader("Content-Type", mpart.getContentType());
1544: // XXX - can't change the encoding of the data from the
1545: // other part without decoding and reencoding it, so
1546: // we just force it to match the original, but if the
1547: // original has no encoding we'll consider reencoding it
1548: String enc = mpart.getEncoding();
1549: if (enc != null) {
1550: setEncoding(part, enc);
1551: return;
1552: }
1553: } else
1554: return;
1555: }
1556:
1557: // Content-Transfer-Encoding, but only if we don't
1558: // already have one
1559: if (!composite) { // not allowed on composite parts
1560: if (part.getHeader("Content-Transfer-Encoding") == null)
1561: setEncoding(part, MimeUtility.getEncoding(dh));
1562:
1563: if (needCTHeader && setDefaultTextCharset &&
1564: cType.match("text/*") &&
1565: cType.getParameter("charset") == null) {
1566: /*
1567: * Set a default charset for text parts.
1568: * We really should examine the data to determine
1569: * whether or not it's all ASCII, but that's too
1570: * expensive so we make an assumption: If we
1571: * chose 7bit encoding for this data, it's probably
1572: * ASCII. (MimeUtility.getEncoding will choose
1573: * 7bit only in this case, but someone might've
1574: * set the Content-Transfer-Encoding header manually.)
1575: */
1576: String charset;
1577: String enc = part.getEncoding();
1578: if (enc != null && enc.equalsIgnoreCase(EncoderTypes.BIT7_ENCODER.getEncoder()))
1579: charset = "us-ascii";
1580: else
1581: charset = MimeUtility.getDefaultMIMECharset();
1582: cType.setParameter("charset", charset);
1583: type = cType.toString();
1584: }
1585: }
1586:
1587: // Now, let's update our own headers ...
1588:
1589: // Content-type, but only if we don't already have one
1590: if (needCTHeader) {
1591: /*
1592: * Pull out "filename" from Content-Disposition, and
1593: * use that to set the "name" parameter. This is to
1594: * satisfy older MUAs (DtMail, Roam and probably
1595: * a bunch of others).
1596: */
1597: if (setContentTypeFileName) {
1598: String s = part.getHeader("Content-Disposition", null);
1599: if (s != null) {
1600: // Parse the header ..
1601: ContentDisposition cd = new ContentDisposition(s);
1602: String filename = cd.getParameter("filename");
1603: if (filename != null) {
1604: ParameterList p = cType.getParameterList();
1605: if (p == null) {
1606: p = new ParameterList();
1607: cType.setParameterList(p);
1608: }
1609: if (encodeFileName)
1610: p.setLiteral("name",
1611: MimeUtility.encodeText(filename));
1612: else
1613: p.set("name", filename,
1614: MimeUtility.getDefaultMIMECharset());
1615: type = cType.toString();
1616: }
1617: }
1618: }
1619:
1620: part.setHeader("Content-Type", type);
1621: }
1622: } catch (IOException ex) {
1623: throw new MessagingException("IOException updating headers", ex);
1624: }
1625: }
1626:
1627: static void invalidateContentHeaders(MimePart part)
1628: throws MessagingException {
1629: part.removeHeader("Content-Type");
1630: part.removeHeader("Content-Transfer-Encoding");
1631: }
1632:
1633: static void writeTo(MimePart part, OutputStream os, String[] ignoreList)
1634: throws IOException, MessagingException {
1635:
1636: // see if we already have a LOS
1637: LineOutputStream los = null;
1638: if (os instanceof LineOutputStream) {
1639: los = (LineOutputStream) os;
1640: } else {
1641: Map<String, Object> params = new HashMap<>();
1642: params.put("allowutf8", allowutf8);
1643: los = StreamProvider.provider().outputLineStream(os, allowutf8);
1644: }
1645:
1646: // First, write out the header
1647: Enumeration<String> hdrLines
1648: = part.getNonMatchingHeaderLines(ignoreList);
1649: while (hdrLines.hasMoreElements())
1650: los.writeln(hdrLines.nextElement());
1651:
1652: // The CRLF separator between header and content
1653: los.writeln();
1654:
1655: // Finally, the content. Encode if required.
1656: // XXX: May need to account for ESMTP ?
1657: InputStream is = null;
1658: byte[] buf = null;
1659: try {
1660: /*
1661: * If the data for this part comes from a stream,
1662: * and is already encoded,
1663: * just copy it to the output stream without decoding
1664: * and reencoding it.
1665: */
1666: DataHandler dh = part.getDataHandler();
1667: if (dh instanceof MimePartDataHandler) {
1668: MimePartDataHandler mpdh = (MimePartDataHandler) dh;
1669: MimePart mpart = mpdh.getPart();
1670: if (mpart.getEncoding() != null)
1671: is = mpdh.getContentStream();
1672: }
1673: if (is != null) {
1674: // now copy the data to the output stream
1675: buf = new byte[8192];
1676: int len;
1677: while ((len = is.read(buf)) > 0)
1678: os.write(buf, 0, len);
1679: } else {
1680: os = MimeUtility.encode(os,
1681: restrictEncoding(part, part.getEncoding()));
1682: part.getDataHandler().writeTo(os);
1683: }
1684: } finally {
1685: if (is != null)
1686: is.close();
1687: buf = null;
1688: }
1689: os.flush(); // Needed to complete encoding
1690: }
1691:
1692: /**
1693: * A special DataHandler used only as a marker to indicate that
1694: * the source of the data is a MimePart (that is, a byte array
1695: * or a stream). This prevents updateHeaders from trying to
1696: * change the headers for such data. In particular, the original
1697: * Content-Transfer-Encoding for the data must be preserved.
1698: * Otherwise the data would need to be decoded and reencoded.
1699: */
1700: static class MimePartDataHandler extends DataHandler {
1701: MimePart part;
1702:
1703: public MimePartDataHandler(MimePart part) {
1704: super(new MimePartDataSource(part));
1705: this.part = part;
1706: }
1707:
1708: InputStream getContentStream() throws MessagingException {
1709: InputStream is = null;
1710:
1711: if (part instanceof MimeBodyPart) {
1712: MimeBodyPart mbp = (MimeBodyPart) part;
1713: is = mbp.getContentStream();
1714: } else if (part instanceof MimeMessage) {
1715: MimeMessage msg = (MimeMessage) part;
1716: is = msg.getContentStream();
1717: }
1718: return is;
1719: }
1720:
1721: MimePart getPart() {
1722: return part;
1723: }
1724: }
1725: }