Skip to content

Package: MimeBodyPart$EncodedFileDataSource

MimeBodyPart$EncodedFileDataSource

nameinstructionbranchcomplexitylinemethod
MimeBodyPart.EncodedFileDataSource(File, String, String)
M: 10 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
getContentType()
M: 9 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getEncoding()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%

Coverage

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