1. /*
  2. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  3. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  4. */
  5. package javax.mail.internet;
  6. import javax.mail.*;
  7. import javax.activation.*;
  8. import java.io.*;
  9. import java.util.*;
  10. import com.sun.mail.util.ASCIIUtility;
  11. import com.sun.mail.util.LineOutputStream;
  12. /**
  13. * This class represents a MIME body part. It implements the
  14. * <code>BodyPart</code> abstract class and the <code>MimePart</code>
  15. * interface. MimeBodyParts are contained in <code>MimeMultipart</code>
  16. * objects. <p>
  17. *
  18. * MimeBodyPart uses the <code>InternetHeaders</code> class to parse
  19. * and store the headers of that body part. <p>
  20. *
  21. * <hr><strong>A note on RFC 822 and MIME headers</strong><p>
  22. *
  23. * RFC 822 header fields <strong>must</strong> contain only
  24. * US-ASCII characters. MIME allows non ASCII characters to be present
  25. * in certain portions of certain headers, by encoding those characters.
  26. * RFC 2047 specifies the rules for doing this. The MimeUtility
  27. * class provided in this package can be used to to achieve this.
  28. * Callers of the <code>setHeader</code>, <code>addHeader</code>, and
  29. * <code>addHeaderLine</code> methods are responsible for enforcing
  30. * the MIME requirements for the specified headers. In addition, these
  31. * header fields must be folded (wrapped) before being sent if they
  32. * exceed the line length limitation for the transport (1000 bytes for
  33. * SMTP). Received headers may have been folded. The application is
  34. * responsible for folding and unfolding headers as appropriate. <p>
  35. *
  36. * @author John Mani
  37. * @author Bill Shannon
  38. * @see javax.mail.Part
  39. * @see javax.mail.internet.MimePart
  40. * @see javax.mail.internet.MimeUtility
  41. */
  42. public class MimeBodyPart extends BodyPart implements MimePart {
  43. /**
  44. * The DataHandler object representing this Part's content.
  45. */
  46. protected DataHandler dh;
  47. /**
  48. * Byte array that holds the bytes of the content of this Part.
  49. */
  50. protected byte[] content;
  51. /**
  52. * If the data for this body part was supplied by an
  53. * InputStream that implements the SharedInputStream interface,
  54. * <code>contentStream</code> is another such stream representing
  55. * the content of this body part. In this case, <code>content</code>
  56. * will be null.
  57. *
  58. * @since JavaMail 1.2
  59. */
  60. protected InputStream contentStream;
  61. /**
  62. * The InternetHeaders object that stores all the headers
  63. * of this body part.
  64. */
  65. protected InternetHeaders headers;
  66. /**
  67. * An empty MimeBodyPart object is created.
  68. * This body part maybe filled in by a client constructing a multipart
  69. * message.
  70. */
  71. public MimeBodyPart() {
  72. super();
  73. headers = new InternetHeaders();
  74. }
  75. /**
  76. * Constructs a MimeBodyPart by reading and parsing the data from
  77. * the specified input stream. The parser consumes data till the end
  78. * of the given input stream. The input stream must start at the
  79. * beginning of a valid MIME body part and must terminate at the end
  80. * of that body part. <p>
  81. *
  82. * Note that the "boundary" string that delimits body parts must
  83. * <strong>not</strong> be included in the input stream. The intention
  84. * is that the MimeMultipart parser will extract each body part's bytes
  85. * from a multipart stream and feed them into this constructor, without
  86. * the delimiter strings.
  87. *
  88. * @param is the body part Input Stream
  89. */
  90. public MimeBodyPart(InputStream is) throws MessagingException {
  91. if (!(is instanceof ByteArrayInputStream) &&
  92. !(is instanceof BufferedInputStream))
  93. is = new BufferedInputStream(is);
  94. headers = new InternetHeaders(is);
  95. if (is instanceof SharedInputStream) {
  96. SharedInputStream sis = (SharedInputStream)is;
  97. contentStream = sis.newStream(sis.getPosition(), -1);
  98. } else {
  99. try {
  100. content = ASCIIUtility.getBytes(is);
  101. } catch (IOException ioex) {
  102. throw new MessagingException("Error reading input stream", ioex);
  103. }
  104. }
  105. }
  106. /**
  107. * Constructs a MimeBodyPart using the given header and
  108. * content bytes. <p>
  109. *
  110. * Used by providers.
  111. *
  112. * @param headers The header of this part
  113. * @param content bytes representing the body of this part.
  114. */
  115. public MimeBodyPart(InternetHeaders headers, byte[] content)
  116. throws MessagingException {
  117. super();
  118. this.headers = headers;
  119. this.content = content;
  120. }
  121. /**
  122. * Return the size of the content of this body part in bytes.
  123. * Return -1 if the size cannot be determined. <p>
  124. *
  125. * Note that this number may not be an exact measure of the
  126. * content size and may or may not account for any transfer
  127. * encoding of the content. <p>
  128. *
  129. * This implementation returns the size of the <code>content</code>
  130. * array (if not null), or, if <code>contentStream</code> is not
  131. * null, and the <code>available</code> method returns a positive
  132. * number, it returns that number as the size. Otherwise, it returns
  133. * -1.
  134. *
  135. * @return size in bytes, or -1 if not known
  136. */
  137. public int getSize() throws MessagingException {
  138. if (content != null)
  139. return content.length;
  140. if (contentStream != null) {
  141. try {
  142. int size = contentStream.available();
  143. // only believe the size if it's greate than zero, since zero
  144. // is the default returned by the InputStream class itself
  145. if (size > 0)
  146. return size;
  147. } catch (IOException ex) {
  148. // ignore it
  149. }
  150. }
  151. return -1;
  152. }
  153. /**
  154. * Return the number of lines for the content of this Part.
  155. * Return -1 if this number cannot be determined. <p>
  156. *
  157. * Note that this number may not be an exact measure of the
  158. * content length and may or may not account for any transfer
  159. * encoding of the content. <p>
  160. *
  161. * This implementation returns -1.
  162. *
  163. * @return number of lines, or -1 if not known
  164. */
  165. public int getLineCount() throws MessagingException {
  166. return -1;
  167. }
  168. /**
  169. * Returns the value of the RFC 822 "Content-Type" header field.
  170. * This represents the content type of the content of this
  171. * body part. This value must not be null. If this field is
  172. * unavailable, "text/plain" should be returned. <p>
  173. *
  174. * This implementation uses <code>getHeader(name)</code>
  175. * to obtain the requisite header field.
  176. *
  177. * @return Content-Type of this body part
  178. */
  179. public String getContentType() throws MessagingException {
  180. String s = getHeader("Content-Type", null);
  181. if (s == null)
  182. s = "text/plain";
  183. return s;
  184. }
  185. /**
  186. * Is this Part of the specified MIME type? This method
  187. * compares <strong>only the <code>primaryType</code> and
  188. * <code>subType</code></strong>.
  189. * The parameters of the content types are ignored. <p>
  190. *
  191. * For example, this method will return <code>true</code> when
  192. * comparing a Part of content type <strong>"text/plain"</strong>
  193. * with <strong>"text/plain; charset=foobar"</strong>. <p>
  194. *
  195. * If the <code>subType</code> of <code>mimeType</code> is the
  196. * special character '*', then the subtype is ignored during the
  197. * comparison.
  198. */
  199. public boolean isMimeType(String mimeType) throws MessagingException {
  200. return isMimeType(this, mimeType);
  201. }
  202. /**
  203. * Returns the value of the "Content-Disposition" header field.
  204. * This represents the disposition of this part. The disposition
  205. * describes how the part should be presented to the user. <p>
  206. *
  207. * If the Content-Disposition field is unavailable,
  208. * null is returned. <p>
  209. *
  210. * This implementation uses <code>getHeader(name)</code>
  211. * to obtain the requisite header field.
  212. *
  213. * @see #headers
  214. */
  215. public String getDisposition() throws MessagingException {
  216. return getDisposition(this);
  217. }
  218. /**
  219. * Set the "Content-Disposition" header field of this body part.
  220. * If the disposition is null, any existing "Content-Disposition"
  221. * header field is removed.
  222. *
  223. * @exception IllegalWriteException if the underlying
  224. * implementation does not support modification
  225. * @exception IllegalStateException if this body part is
  226. * obtained from a READ_ONLY folder.
  227. */
  228. public void setDisposition(String disposition) throws MessagingException {
  229. setDisposition(this, disposition);
  230. }
  231. /**
  232. * Returns the content transfer encoding from the
  233. * "Content-Transfer-Encoding" header
  234. * field. Returns <code>null</code> if the header is unavailable
  235. * or its value is absent. <p>
  236. *
  237. * This implementation uses <code>getHeader(name)</code>
  238. * to obtain the requisite header field.
  239. *
  240. * @see #headers
  241. */
  242. public String getEncoding() throws MessagingException {
  243. return getEncoding(this);
  244. }
  245. /**
  246. * Returns the value of the "Content-ID" header field. Returns
  247. * <code>null</code> if the field is unavailable or its value is
  248. * absent. <p>
  249. *
  250. * This implementation uses <code>getHeader(name)</code>
  251. * to obtain the requisite header field.
  252. */
  253. public String getContentID() throws MessagingException {
  254. return getHeader("Content-Id", null);
  255. }
  256. /**
  257. * Return the value of the "Content-MD5" header field. Returns
  258. * <code>null</code> if this field is unavailable or its value
  259. * is absent. <p>
  260. *
  261. * This implementation uses <code>getHeader(name)</code>
  262. * to obtain the requisite header field.
  263. */
  264. public String getContentMD5() throws MessagingException {
  265. return getHeader("Content-MD5", null);
  266. }
  267. /**
  268. * Set the "Content-MD5" header field of this body part.
  269. *
  270. * @exception IllegalWriteException if the underlying
  271. * implementation does not support modification
  272. * @exception IllegalStateException if this body part is
  273. * obtained from a READ_ONLY folder.
  274. */
  275. public void setContentMD5(String md5) throws MessagingException {
  276. setHeader("Content-MD5", md5);
  277. }
  278. /**
  279. * Get the languages specified in the Content-Language header
  280. * of this MimePart. The Content-Language header is defined by
  281. * RFC 1766. Returns <code>null</code> if this header is not
  282. * available or its value is absent. <p>
  283. *
  284. * This implementation uses <code>getHeader(name)</code>
  285. * to obtain the requisite header field.
  286. */
  287. public String[] getContentLanguage() throws MessagingException {
  288. return getContentLanguage(this);
  289. }
  290. /**
  291. * Set the Content-Language header of this MimePart. The
  292. * Content-Language header is defined by RFC 1766.
  293. *
  294. * @param languages array of language tags
  295. */
  296. public void setContentLanguage(String[] languages)
  297. throws MessagingException {
  298. setContentLanguage(this, languages);
  299. }
  300. /**
  301. * Returns the "Content-Description" header field of this body part.
  302. * This typically associates some descriptive information with
  303. * this part. Returns null if this field is unavailable or its
  304. * value is absent. <p>
  305. *
  306. * If the Content-Description field is encoded as per RFC 2047,
  307. * it is decoded and converted into Unicode. If the decoding or
  308. * conversion fails, the raw data is returned as is. <p>
  309. *
  310. * This implementation uses <code>getHeader(name)</code>
  311. * to obtain the requisite header field.
  312. *
  313. * @return content description
  314. */
  315. public String getDescription() throws MessagingException {
  316. return getDescription(this);
  317. }
  318. /**
  319. * Set the "Content-Description" header field for this body part.
  320. * If the description parameter is <code>null</code>, then any
  321. * existing "Content-Description" fields are removed. <p>
  322. *
  323. * If the description contains non US-ASCII characters, it will
  324. * be encoded using the platform's default charset. If the
  325. * description contains only US-ASCII characters, no encoding
  326. * is done and it is used as is. <p>
  327. *
  328. * Note that if the charset encoding process fails, a
  329. * MessagingException is thrown, and an UnsupportedEncodingException
  330. * is included in the chain of nested exceptions within the
  331. * MessagingException.
  332. *
  333. * @param description content description
  334. * @exception IllegalWriteException if the underlying
  335. * implementation does not support modification
  336. * @exception IllegalStateException if this body part is
  337. * obtained from a READ_ONLY folder.
  338. * @exception MessagingException. An
  339. * UnsupportedEncodingException may be included
  340. * in the exception chain if the charset
  341. * conversion fails.
  342. */
  343. public void setDescription(String description) throws MessagingException {
  344. setDescription(description, null);
  345. }
  346. /**
  347. * Set the "Content-Description" header field for this body part.
  348. * If the description parameter is <code>null</code>, then any
  349. * existing "Content-Description" fields are removed. <p>
  350. *
  351. * If the description contains non US-ASCII characters, it will
  352. * be encoded using the specified charset. If the description
  353. * contains only US-ASCII characters, no encoding is done and
  354. * it is used as is. <p>
  355. *
  356. * Note that if the charset encoding process fails, a
  357. * MessagingException is thrown, and an UnsupportedEncodingException
  358. * is included in the chain of nested exceptions within the
  359. * MessagingException.
  360. *
  361. * @param description Description
  362. * @param charset Charset for encoding
  363. * @exception IllegalWriteException if the underlying
  364. * implementation does not support modification
  365. * @exception IllegalStateException if this body part is
  366. * obtained from a READ_ONLY folder.
  367. * @exception MessagingException. An
  368. * UnsupportedEncodingException may be included
  369. * in the exception chain if the charset
  370. * conversion fails.
  371. */
  372. public void setDescription(String description, String charset)
  373. throws MessagingException {
  374. setDescription(this, description, charset);
  375. }
  376. /**
  377. * Get the filename associated with this body part. <p>
  378. *
  379. * Returns the value of the "filename" parameter from the
  380. * "Content-Disposition" header field of this body part. If its
  381. * not available, returns the value of the "name" parameter from
  382. * the "Content-Type" header field of this body part.
  383. * Returns <code>null</code> if both are absent.
  384. *
  385. * @return filename
  386. */
  387. public String getFileName() throws MessagingException {
  388. return getFileName(this);
  389. }
  390. /**
  391. * Set the filename associated with this body part, if possible. <p>
  392. *
  393. * Sets the "filename" parameter of the "Content-Disposition"
  394. * header field of this body part.
  395. *
  396. * @exception IllegalWriteException if the underlying
  397. * implementation does not support modification
  398. * @exception IllegalStateException if this body part is
  399. * obtained from a READ_ONLY folder.
  400. */
  401. public void setFileName(String filename) throws MessagingException {
  402. setFileName(this, filename);
  403. }
  404. /**
  405. * Return a decoded input stream for this body part's "content". <p>
  406. *
  407. * This implementation obtains the input stream from the DataHandler.
  408. * That is, it invokes getDataHandler().getInputStream();
  409. *
  410. * @return an InputStream
  411. * @exception MessagingException
  412. * @exception IOException this is typically thrown by the
  413. * DataHandler. Refer to the documentation for
  414. * javax.activation.DataHandler for more details.
  415. *
  416. * @see #getContentStream
  417. * @see javax.activation.DataHandler#getInputStream
  418. */
  419. public InputStream getInputStream()
  420. throws IOException, MessagingException {
  421. return getDataHandler().getInputStream();
  422. }
  423. /**
  424. * Produce the raw bytes of the content. This method is used
  425. * when creating a DataHandler object for the content. Subclasses
  426. * that can provide a separate input stream for just the Part
  427. * content might want to override this method. <p>
  428. *
  429. * @see #content
  430. * @see MimeMessage#getContentStream
  431. */
  432. protected InputStream getContentStream() throws MessagingException {
  433. if (contentStream != null)
  434. return ((SharedInputStream)contentStream).newStream(0, -1);
  435. if (content != null)
  436. return new ByteArrayInputStream(content);
  437. throw new MessagingException("No content");
  438. }
  439. /**
  440. * Return an InputStream to the raw data with any Content-Transfer-Encoding
  441. * intact. This method is useful if the "Content-Transfer-Encoding"
  442. * header is incorrect or corrupt, which would prevent the
  443. * <code>getInputStream</code> method or <code>getContent</code> method
  444. * from returning the correct data. In such a case the application may
  445. * use this method and attempt to decode the raw data itself. <p>
  446. *
  447. * This implementation simply calls the <code>getContentStream</code>
  448. * method.
  449. *
  450. * @see #getInputStream
  451. * @see #getContentStream
  452. * @since JavaMail 1.2
  453. */
  454. public InputStream getRawInputStream() throws MessagingException {
  455. return getContentStream();
  456. }
  457. /**
  458. * Return a DataHandler for this body part's content. <p>
  459. *
  460. * The implementation provided here works just like the
  461. * the implementation in MimeMessage.
  462. * @see MimeMessage#getDataHandler
  463. */
  464. public DataHandler getDataHandler() throws MessagingException {
  465. if (dh == null)
  466. dh = new DataHandler(new MimePartDataSource(this));
  467. return dh;
  468. }
  469. /**
  470. * Return the content as a java object. The type of the object
  471. * returned is of course dependent on the content itself. For
  472. * example, the native format of a text/plain content is usually
  473. * a String object. The native format for a "multipart"
  474. * content is always a Multipart subclass. For content types that are
  475. * unknown to the DataHandler system, an input stream is returned
  476. * as the content. <p>
  477. *
  478. * This implementation obtains the content from the DataHandler.
  479. * That is, it invokes getDataHandler().getContent();
  480. *
  481. * @return Object
  482. * @exception MessagingException
  483. * @exception IOException this is typically thrown by the
  484. * DataHandler. Refer to the documentation for
  485. * javax.activation.DataHandler for more details.
  486. */
  487. public Object getContent() throws IOException, MessagingException {
  488. return getDataHandler().getContent();
  489. }
  490. /**
  491. * This method provides the mechanism to set this body part's content.
  492. * The given DataHandler object should wrap the actual content.
  493. *
  494. * @param dh The DataHandler for the content
  495. * @exception IllegalWriteException if the underlying
  496. * implementation does not support modification
  497. * @exception IllegalStateException if this body part is
  498. * obtained from a READ_ONLY folder.
  499. */
  500. public void setDataHandler(DataHandler dh)
  501. throws MessagingException {
  502. this.dh = dh;
  503. MimeBodyPart.invalidateContentHeaders(this);
  504. }
  505. /**
  506. * A convenience method for setting this body part's content. <p>
  507. *
  508. * The content is wrapped in a DataHandler object. Note that a
  509. * DataContentHandler class for the specified type should be
  510. * available to the JavaMail implementation for this to work right.
  511. * That is, to do <code>setContent(foobar, "application/x-foobar")</code>,
  512. * a DataContentHandler for "application/x-foobar" should be installed.
  513. * Refer to the Java Activation Framework for more information.
  514. *
  515. * @param o the content object
  516. * @param type Mime type of the object
  517. * @exception IllegalWriteException if the underlying
  518. * implementation does not support modification of
  519. * existing values
  520. * @exception IllegalStateException if this body part is
  521. * obtained from a READ_ONLY folder.
  522. */
  523. public void setContent(Object o, String type)
  524. throws MessagingException {
  525. if (o instanceof Multipart) {
  526. setContent((Multipart)o);
  527. } else {
  528. setDataHandler(new DataHandler(o, type));
  529. }
  530. }
  531. /**
  532. * Convenience method that sets the given String as this
  533. * part's content, with a MIME type of "text/plain". If the
  534. * string contains non US-ASCII characters, it will be encoded
  535. * using the platform's default charset. The charset is also
  536. * used to set the "charset" parameter. <p>
  537. *
  538. * Note that there may be a performance penalty if
  539. * <code>text</code> is large, since this method may have
  540. * to scan all the characters to determine what charset to
  541. * use. <p>
  542. * If the charset is already known, use the
  543. * setText() version that takes the charset parameter.
  544. *
  545. * @see #setText(String text, String charset)
  546. */
  547. public void setText(String text) throws MessagingException {
  548. setText(text, null);
  549. }
  550. /**
  551. * Convenience method that sets the given String as this part's
  552. * content, with a MIME type of "text/plain" and the specified
  553. * charset. The given Unicode string will be charset-encoded
  554. * using the specified charset. The charset is also used to set
  555. * the "charset" parameter.
  556. */
  557. public void setText(String text, String charset)
  558. throws MessagingException {
  559. setText(this, text, charset);
  560. }
  561. /**
  562. * This method sets the body part's content to a Multipart object.
  563. *
  564. * @param mp The multipart object that is the Message's content
  565. * @exception IllegalWriteException if the underlying
  566. * implementation does not support modification of
  567. * existing values.
  568. * @exception IllegalStateException if this body part is
  569. * obtained from a READ_ONLY folder.
  570. */
  571. public void setContent(Multipart mp) throws MessagingException {
  572. setDataHandler(new DataHandler(mp, mp.getContentType()));
  573. mp.setParent(this);
  574. }
  575. /**
  576. * Output the body part as an RFC 822 format stream.
  577. *
  578. * @exception MessagingException
  579. * @exception IOException if an error occurs writing to the
  580. * stream or if an error is generated
  581. * by the javax.activation layer.
  582. * @see javax.activation.DataHandler#writeTo
  583. */
  584. public void writeTo(OutputStream os)
  585. throws IOException, MessagingException {
  586. writeTo(this, os, null);
  587. }
  588. /**
  589. * Get all the headers for this header_name. Note that certain
  590. * headers may be encoded as per RFC 2047 if they contain
  591. * non US-ASCII characters and these should be decoded.
  592. *
  593. * @param name name of header
  594. * @return array of headers
  595. * @see javax.mail.internet.MimeUtility
  596. */
  597. public String[] getHeader(String name) throws MessagingException {
  598. return headers.getHeader(name);
  599. }
  600. /**
  601. * Get all the headers for this header name, returned as a single
  602. * String, with headers separated by the delimiter. If the
  603. * delimiter is <code>null</code>, only the first header is
  604. * returned.
  605. *
  606. * @param header_name the name of this header
  607. * @return the value fields for all headers with
  608. * this name
  609. * @exception MessagingException
  610. */
  611. public String getHeader(String name, String delimiter)
  612. throws MessagingException {
  613. return headers.getHeader(name, delimiter);
  614. }
  615. /**
  616. * Set the value for this header_name. Replaces all existing
  617. * header values with this new value. Note that RFC 822 headers
  618. * must contain only US-ASCII characters, so a header that
  619. * contains non US-ASCII characters must be encoded as per the
  620. * rules of RFC 2047.
  621. *
  622. * @param name header name
  623. * @param value header value
  624. * @see javax.mail.internet.MimeUtility
  625. */
  626. public void setHeader(String name, String value)
  627. throws MessagingException {
  628. headers.setHeader(name, value);
  629. }
  630. /**
  631. * Add this value to the existing values for this header_name.
  632. * Note that RFC 822 headers must contain only US-ASCII
  633. * characters, so a header that contains non US-ASCII characters
  634. * must be encoded as per the rules of RFC 2047.
  635. *
  636. * @param name header name
  637. * @param value header value
  638. * @see javax.mail.internet.MimeUtility
  639. */
  640. public void addHeader(String name, String value)
  641. throws MessagingException {
  642. headers.addHeader(name, value);
  643. }
  644. /**
  645. * Remove all headers with this name.
  646. */
  647. public void removeHeader(String name) throws MessagingException {
  648. headers.removeHeader(name);
  649. }
  650. /**
  651. * Return all the headers from this Message as an Enumeration of
  652. * Header objects.
  653. */
  654. public Enumeration getAllHeaders() throws MessagingException {
  655. return headers.getAllHeaders();
  656. }
  657. /**
  658. * Return matching headers from this Message as an Enumeration of
  659. * Header objects. <p>
  660. */
  661. public Enumeration getMatchingHeaders(String[] names)
  662. throws MessagingException {
  663. return headers.getMatchingHeaders(names);
  664. }
  665. /**
  666. * Return non-matching headers from this Message as an
  667. * Enumeration of Header objects.
  668. */
  669. public Enumeration getNonMatchingHeaders(String[] names)
  670. throws MessagingException {
  671. return headers.getNonMatchingHeaders(names);
  672. }
  673. /**
  674. * Add a header line to this body part
  675. */
  676. public void addHeaderLine(String line) throws MessagingException {
  677. headers.addHeaderLine(line);
  678. }
  679. /**
  680. * Get all header lines as an Enumeration of Strings. A Header
  681. * line is a raw RFC 822 header line, containing both the "name"
  682. * and "value" field.
  683. */
  684. public Enumeration getAllHeaderLines() throws MessagingException {
  685. return headers.getAllHeaderLines();
  686. }
  687. /**
  688. * Get matching header lines as an Enumeration of Strings.
  689. * A Header line is a raw RFC 822 header line, containing both
  690. * the "name" and "value" field.
  691. */
  692. public Enumeration getMatchingHeaderLines(String[] names)
  693. throws MessagingException {
  694. return headers.getMatchingHeaderLines(names);
  695. }
  696. /**
  697. * Get non-matching header lines as an Enumeration of Strings.
  698. * A Header line is a raw RFC 822 header line, containing both
  699. * the "name" and "value" field.
  700. */
  701. public Enumeration getNonMatchingHeaderLines(String[] names)
  702. throws MessagingException {
  703. return headers.getNonMatchingHeaderLines(names);
  704. }
  705. /**
  706. * Examine the content of this body part and update the appropriate
  707. * MIME headers. Typical headers that get set here are
  708. * <code>Content-Type</code> and <code>Content-Transfer-Encoding</code>.
  709. * Headers might need to be updated in two cases:
  710. *
  711. * <br>
  712. * - A message being crafted by a mail application will certainly
  713. * need to activate this method at some point to fill up its internal
  714. * headers.
  715. *
  716. * <br>
  717. * - A message read in from a Store will have obtained
  718. * all its headers from the store, and so doesn't need this.
  719. * However, if this message is editable and if any edits have
  720. * been made to either the content or message structure, we might
  721. * need to resync our headers.
  722. *
  723. * <br>
  724. * In both cases this method is typically called by the
  725. * <code>Message.saveChanges</code> method.
  726. */
  727. protected void updateHeaders() throws MessagingException {
  728. updateHeaders(this);
  729. }
  730. /////////////////////////////////////////////////////////////
  731. // Package private convenience methods to share code among //
  732. // MimeMessage and MimeBodyPart //
  733. /////////////////////////////////////////////////////////////
  734. static boolean isMimeType(MimePart part, String mimeType)
  735. throws MessagingException {
  736. // XXX - lots of room for optimization here!
  737. try {
  738. ContentType ct = new ContentType(part.getContentType());
  739. return ct.match(mimeType);
  740. } catch (ParseException ex) {
  741. return part.getContentType().equalsIgnoreCase(mimeType);
  742. }
  743. }
  744. static void setText(MimePart part, String text, String charset)
  745. throws MessagingException {
  746. if (charset == null) {
  747. if (MimeUtility.checkAscii(text) != MimeUtility.ALL_ASCII)
  748. charset = MimeUtility.getDefaultMIMECharset();
  749. else
  750. charset = "us-ascii";
  751. }
  752. part.setContent(text, "text/plain; charset=" +
  753. MimeUtility.quote(charset, HeaderTokenizer.MIME));
  754. }
  755. static String getDisposition(MimePart part) throws MessagingException {
  756. String s = part.getHeader("Content-Disposition", null);
  757. if (s == null)
  758. return null;
  759. ContentDisposition cd = new ContentDisposition(s);
  760. return cd.getDisposition();
  761. }
  762. static void setDisposition(MimePart part, String disposition)
  763. throws MessagingException {
  764. if (disposition == null)
  765. part.removeHeader("Content-Disposition");
  766. else {
  767. String s = part.getHeader("Content-Disposition", null);
  768. if (s != null) {
  769. /* A Content-Disposition header already exists ..
  770. *
  771. * Override disposition, but attempt to retain
  772. * existing disposition parameters
  773. */
  774. ContentDisposition cd = new ContentDisposition(s);
  775. cd.setDisposition(disposition);
  776. disposition = cd.toString();
  777. }
  778. part.setHeader("Content-Disposition", disposition);
  779. }
  780. }
  781. static String getDescription(MimePart part)
  782. throws MessagingException {
  783. String rawvalue = part.getHeader("Content-Description", null);
  784. if (rawvalue == null)
  785. return null;
  786. try {
  787. return MimeUtility.decodeText(rawvalue);
  788. } catch (UnsupportedEncodingException ex) {
  789. return rawvalue;
  790. }
  791. }
  792. static void
  793. setDescription(MimePart part, String description, String charset)
  794. throws MessagingException {
  795. if (description == null) {
  796. part.removeHeader("Content-Description");
  797. return;
  798. }
  799. try {
  800. part.setHeader("Content-Description",
  801. MimeUtility.encodeText(description, charset, null));
  802. } catch (UnsupportedEncodingException uex) {
  803. throw new MessagingException("Encoding error", uex);
  804. }
  805. }
  806. static String getFileName(MimePart part) throws MessagingException {
  807. String filename = null;
  808. String s = part.getHeader("Content-Disposition", null);
  809. if (s != null) {
  810. // Parse the header ..
  811. ContentDisposition cd = new ContentDisposition(s);
  812. filename = cd.getParameter("filename");
  813. }
  814. if (filename == null) {
  815. // Still no filename ? Try the "name" ContentType parameter
  816. s = part.getHeader("Content-Type", null);
  817. if (s != null) {
  818. try {
  819. ContentType ct = new ContentType(s);
  820. filename = ct.getParameter("name");
  821. } catch (ParseException pex) { } // ignore it
  822. }
  823. }
  824. return filename;
  825. }
  826. static void setFileName(MimePart part, String name)
  827. throws MessagingException {
  828. // Set the Content-Disposition "filename" parameter
  829. String s = part.getHeader("Content-Disposition", null);
  830. ContentDisposition cd =
  831. new ContentDisposition(s == null ? Part.ATTACHMENT : s);
  832. cd.setParameter("filename", name);
  833. part.setHeader("Content-Disposition", cd.toString());
  834. /* Also attempt to set the Content-Type "name" parameter,
  835. * to satisfy ancient MUAs.
  836. * XXX: This is not RFC compliant, and hence should really
  837. * be conditional based on some property. Fix this once we
  838. * figure out how to get at Properties from here !
  839. */
  840. s = part.getHeader("Content-Type", null);
  841. if (s != null) {
  842. try {
  843. ContentType cType = new ContentType(s);
  844. cType.setParameter("name", name);
  845. part.setHeader("Content-Type", cType.toString());
  846. } catch (ParseException pex) { } // ignore it
  847. }
  848. }
  849. static String[] getContentLanguage(MimePart part)
  850. throws MessagingException {
  851. String s = part.getHeader("Content-Language", null);
  852. if (s == null)
  853. return null;
  854. // Tokenize the header to obtain the Language-tags (skip comments)
  855. HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
  856. Vector v = new Vector();
  857. HeaderTokenizer.Token tk;
  858. int tkType;
  859. while (true) {
  860. tk = h.next(); // get a language-tag
  861. tkType = tk.getType();
  862. if (tkType == HeaderTokenizer.Token.EOF)
  863. break; // done
  864. else if (tkType == HeaderTokenizer.Token.ATOM)
  865. v.addElement(tk.getValue());
  866. else // invalid token, skip it.
  867. continue;
  868. }
  869. if (v.size() == 0)
  870. return null;
  871. String[] language = new String[v.size()];
  872. v.copyInto(language);
  873. return language;
  874. }
  875. static void setContentLanguage(MimePart part, String[] languages)
  876. throws MessagingException {
  877. StringBuffer sb = new StringBuffer(languages[0]);
  878. for (int i = 1; i < languages.length; i++)
  879. sb.append(',').append(languages[i]);
  880. part.setHeader("Content-Language", sb.toString());
  881. }
  882. static String getEncoding(MimePart part) throws MessagingException {
  883. String s = part.getHeader("Content-Transfer-Encoding", null);
  884. if (s == null)
  885. return null;
  886. s = s.trim(); // get rid of trailing spaces
  887. // quick check for known values to avoid unnecessary use
  888. // of tokenizer.
  889. if (s.equalsIgnoreCase("7bit") || s.equalsIgnoreCase("8bit") ||
  890. s.equalsIgnoreCase("quoted-printable") ||
  891. s.equalsIgnoreCase("base64"))
  892. return s;
  893. // Tokenize the header to obtain the encoding (skip comments)
  894. HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
  895. HeaderTokenizer.Token tk;
  896. int tkType;
  897. for (;;) {
  898. tk = h.next(); // get a token
  899. tkType = tk.getType();
  900. if (tkType == HeaderTokenizer.Token.EOF)
  901. break; // done
  902. else if (tkType == HeaderTokenizer.Token.ATOM)
  903. return tk.getValue();
  904. else // invalid token, skip it.
  905. continue;
  906. }
  907. return s;
  908. }
  909. static void setEncoding(MimePart part, String encoding)
  910. throws MessagingException {
  911. part.setHeader("Content-Transfer-Encoding", encoding);
  912. }
  913. static void updateHeaders(MimePart part) throws MessagingException {
  914. DataHandler dh = part.getDataHandler();
  915. if (dh == null) // Huh ?
  916. return;
  917. try {
  918. String type = dh.getContentType();
  919. boolean composite = false;
  920. ContentType cType = new ContentType(type);
  921. if (cType.match("multipart/*")) {
  922. // If multipart, recurse
  923. composite = true;
  924. Object o = dh.getContent();
  925. ((MimeMultipart)o).updateHeaders();
  926. }
  927. else if (cType.match("message/rfc822")) {
  928. composite = true;
  929. }
  930. // Now, let's update our own headers ...
  931. // Content-type, but only if we don't already have one
  932. if (part.getHeader("Content-Type") == null) {
  933. /*
  934. * Pull out "filename" from Content-Disposition, and
  935. * use that to set the "name" parameter. This is to
  936. * satisfy older MUAs (DtMail, Roam and probably
  937. * a bunch of others).
  938. */
  939. String s = part.getHeader("Content-Disposition", null);
  940. if (s != null) {
  941. // Parse the header ..
  942. ContentDisposition cd = new ContentDisposition(s);
  943. String filename = cd.getParameter("filename");
  944. if (filename != null) {
  945. cType.setParameter("name", filename);
  946. type = cType.toString();
  947. }
  948. }
  949. part.setHeader("Content-Type", type);
  950. }
  951. // Content-Transfer-Encoding, but only if we don't
  952. // already have one
  953. if (!composite && // not allowed on composite parts
  954. (part.getHeader("Content-Transfer-Encoding") == null))
  955. setEncoding(part, MimeUtility.getEncoding(dh));
  956. } catch (IOException ex) {
  957. throw new MessagingException("IOException updating headers", ex);
  958. }
  959. }
  960. static void invalidateContentHeaders(MimePart part)
  961. throws MessagingException {
  962. part.removeHeader("Content-Type");
  963. part.removeHeader("Content-Transfer-Encoding");
  964. }
  965. static void writeTo(MimePart part, OutputStream os, String[] ignoreList)
  966. throws IOException, MessagingException {
  967. // see if we already have a LOS
  968. LineOutputStream los = null;
  969. if (os instanceof LineOutputStream) {
  970. los = (LineOutputStream) os;
  971. } else {
  972. los = new LineOutputStream(os);
  973. }
  974. // First, write out the header
  975. Enumeration hdrLines = part.getNonMatchingHeaderLines(ignoreList);
  976. while (hdrLines.hasMoreElements())
  977. los.writeln((String)hdrLines.nextElement());
  978. // The CRLF separator between header and content
  979. los.writeln();
  980. // Finally, the content. Encode if required.
  981. // XXX: May need to account for ESMTP ?
  982. os = MimeUtility.encode(os, part.getEncoding());
  983. part.getDataHandler().writeTo(os);
  984. os.flush(); // Needed to complete encoding
  985. }
  986. }