Skip to content

Package: MimeBodyPart$MimePartDataHandler

MimeBodyPart$MimePartDataHandler

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