1 /* 2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * @(#)MimeBodyPart.java 1.52 03/02/12 28 */ 29 30 31 32 package com.sun.xml.internal.messaging.saaj.packaging.mime.internet; 33 34 35 import com.sun.xml.internal.messaging.saaj.packaging.mime.MessagingException; 36 import com.sun.xml.internal.messaging.saaj.packaging.mime.util.OutputUtil; 37 import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream; 38 import com.sun.xml.internal.messaging.saaj.util.FinalArrayList; 39 40 import javax.activation.DataHandler; 41 import java.io.BufferedInputStream; 42 import java.io.ByteArrayInputStream; 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.io.OutputStream; 46 import java.io.UnsupportedEncodingException; 47 import java.util.List; 48 import javax.activation.DataSource; 49 import com.sun.xml.internal.org.jvnet.mimepull.MIMEPart; 50 51 /** 52 * This class represents a MIME body part. 53 * MimeBodyParts are contained in <code>MimeMultipart</code> 54 * objects. 55 * <p> 56 * MimeBodyPart uses the <code>InternetHeaders</code> class to parse 57 * and store the headers of that body part. 58 * 59 * <hr><strong>A note on RFC 822 and MIME headers</strong> 60 * 61 * RFC 822 header fields <strong>must</strong> contain only 62 * US-ASCII characters. MIME allows non ASCII characters to be present 63 * in certain portions of certain headers, by encoding those characters. 64 * RFC 2047 specifies the rules for doing this. The MimeUtility 65 * class provided in this package can be used to to achieve this. 66 * Callers of the <code>setHeader</code>, <code>addHeader</code>, and 67 * <code>addHeaderLine</code> methods are responsible for enforcing 68 * the MIME requirements for the specified headers. In addition, these 69 * header fields must be folded (wrapped) before being sent if they 70 * exceed the line length limitation for the transport (1000 bytes for 71 * SMTP). Received headers may have been folded. The application is 72 * responsible for folding and unfolding headers as appropriate. 73 * 74 * @author John Mani 75 * @author Bill Shannon 76 * @see MimeUtility 77 */ 78 79 public final class MimeBodyPart { 80 81 /** 82 * This part should be presented as an attachment. 83 * @see #getDisposition 84 * @see #setDisposition 85 */ 86 public static final String ATTACHMENT = "attachment"; 87 88 /** 89 * This part should be presented inline. 90 * @see #getDisposition 91 * @see #setDisposition 92 */ 93 public static final String INLINE = "inline"; 94 95 96 // Paranoia: 97 // allow this last minute change to be disabled if it causes problems 98 private static boolean setDefaultTextCharset = true; 99 100 static { 101 try { 102 String s = System.getProperty("mail.mime.setdefaulttextcharset"); 103 // default to true 104 setDefaultTextCharset = s == null || !s.equalsIgnoreCase("false"); 105 } catch (SecurityException sex) { 106 // ignore it 107 } 108 } 109 110 /* 111 Data is represented in one of three forms. 112 Either we have a DataHandler, or byte[] as the raw content image, or the contentStream. 113 It's OK to have more than one of them, provided that they are identical. 114 */ 115 116 /** 117 * The DataHandler object representing this MimeBodyPart's content. 118 */ 119 private DataHandler dh; 120 121 /** 122 * Byte array that holds the bytes of the content of this MimeBodyPart. 123 * Used in a pair with {@link #contentLength} to denote a regision of a buffer 124 * as a valid data. 125 */ 126 private byte[] content; 127 private int contentLength; 128 private int start = 0; 129 130 /** 131 * If the data for this body part was supplied by an 132 * InputStream that implements the SharedInputStream interface, 133 * <code>contentStream</code> is another such stream representing 134 * the content of this body part. In this case, <code>content</code> 135 * will be null. 136 * 137 * @since JavaMail 1.2 138 */ 139 private InputStream contentStream; 140 141 142 143 /** 144 * The InternetHeaders object that stores all the headers 145 * of this body part. 146 */ 147 private final InternetHeaders headers; 148 149 /** 150 * The <code>MimeMultipart</code> object containing this <code>MimeBodyPart</code>, 151 * if known. 152 * @since JavaMail 1.1 153 */ 154 private MimeMultipart parent; 155 156 private MIMEPart mimePart; 157 158 /** 159 * An empty MimeBodyPart object is created. 160 * This body part maybe filled in by a client constructing a multipart 161 * message. 162 */ 163 public MimeBodyPart() { 164 headers = new InternetHeaders(); 165 } 166 167 /** 168 * Constructs a MimeBodyPart by reading and parsing the data from 169 * the specified input stream. The parser consumes data till the end 170 * of the given input stream. The input stream must start at the 171 * beginning of a valid MIME body part and must terminate at the end 172 * of that body part. <p> 173 * 174 * Note that the "boundary" string that delimits body parts must 175 * <strong>not</strong> be included in the input stream. The intention 176 * is that the MimeMultipart parser will extract each body part's bytes 177 * from a multipart stream and feed them into this constructor, without 178 * the delimiter strings. 179 * 180 * @param is the body part Input Stream 181 * 182 * @exception MessagingException in case of error 183 */ 184 public MimeBodyPart(InputStream is) throws MessagingException { 185 if (!(is instanceof ByteArrayInputStream) && 186 !(is instanceof BufferedInputStream) && 187 !(is instanceof SharedInputStream)) 188 is = new BufferedInputStream(is); 189 190 headers = new InternetHeaders(is); 191 192 if (is instanceof SharedInputStream) { 193 SharedInputStream sis = (SharedInputStream) is; 194 contentStream = sis.newStream(sis.getPosition(), -1); 195 } else { 196 ByteOutputStream bos = null; 197 try { 198 bos = new ByteOutputStream(); 199 bos.write(is); 200 content = bos.getBytes(); 201 contentLength = bos.getCount(); 202 } catch (IOException ioex) { 203 throw new MessagingException("Error reading input stream", ioex); 204 } finally { 205 if (bos != null) 206 bos.close(); 207 } 208 } 209 210 } 211 212 /** 213 * Constructs a MimeBodyPart using the given header and 214 * content bytes. <p> 215 * 216 * Used by providers. 217 * 218 * @param headers The header of this part 219 * @param content bytes representing the body of this part. 220 * @param len content length. 221 */ 222 public MimeBodyPart(InternetHeaders headers, byte[] content, int len) { 223 this.headers = headers; 224 this.content = content; 225 this.contentLength = len; 226 } 227 228 public MimeBodyPart( 229 InternetHeaders headers, byte[] content, int start, int len) { 230 this.headers = headers; 231 this.content = content; 232 this.start = start; 233 this.contentLength = len; 234 } 235 236 public MimeBodyPart(MIMEPart part) { 237 mimePart = part; 238 headers = new InternetHeaders(); 239 List<? extends com.sun.xml.internal.org.jvnet.mimepull.Header> hdrs = mimePart.getAllHeaders(); 240 for (com.sun.xml.internal.org.jvnet.mimepull.Header hd : hdrs) { 241 headers.addHeader(hd.getName(), hd.getValue()); 242 } 243 } 244 /** 245 * Return the containing <code>MimeMultipart</code> object, 246 * or <code>null</code> if not known. 247 * @return parent part. 248 */ 249 public MimeMultipart getParent() { 250 return parent; 251 } 252 253 /** 254 * Set the parent of this <code>MimeBodyPart</code> to be the specified 255 * <code>MimeMultipart</code>. Normally called by <code>MimeMultipart</code>'s 256 * <code>addBodyPart</code> method. <code>parent</code> may be 257 * <code>null</code> if the <code>MimeBodyPart</code> is being removed 258 * from its containing <code>MimeMultipart</code>. 259 * @param parent parent part 260 * @since JavaMail 1.1 261 */ 262 public void setParent(MimeMultipart parent) { 263 this.parent = parent; 264 } 265 266 /** 267 * Return the size of the content of this body part in bytes. 268 * Return -1 if the size cannot be determined. <p> 269 * 270 * Note that this number may not be an exact measure of the 271 * content size and may or may not account for any transfer 272 * encoding of the content. <p> 273 * 274 * This implementation returns the size of the <code>content</code> 275 * array (if not null), or, if <code>contentStream</code> is not 276 * null, and the <code>available</code> method returns a positive 277 * number, it returns that number as the size. Otherwise, it returns 278 * -1. 279 * 280 * @return size in bytes, or -1 if not known 281 */ 282 public int getSize() { 283 284 if (mimePart != null) { 285 try { 286 return mimePart.read().available(); 287 } catch (IOException ex) { 288 return -1; 289 } 290 } 291 if (content != null) 292 return contentLength; 293 if (contentStream != null) { 294 try { 295 int size = contentStream.available(); 296 // only believe the size if it's greate than zero, since zero 297 // is the default returned by the InputStream class itself 298 if (size > 0) 299 return size; 300 } catch (IOException ex) { 301 // ignore it 302 } 303 } 304 return -1; 305 } 306 307 /** 308 * Return the number of lines for the content of this MimeBodyPart. 309 * Return -1 if this number cannot be determined. <p> 310 * 311 * Note that this number may not be an exact measure of the 312 * content length and may or may not account for any transfer 313 * encoding of the content. <p> 314 * 315 * This implementation returns -1. 316 * 317 * @return number of lines, or -1 if not known 318 */ 319 public int getLineCount() { 320 return -1; 321 } 322 323 /** 324 * Returns the value of the RFC 822 "Content-Type" header field. 325 * This represents the content type of the content of this 326 * body part. This value must not be null. If this field is 327 * unavailable, "text/plain" should be returned. <p> 328 * 329 * This implementation uses <code>getHeader(name)</code> 330 * to obtain the requisite header field. 331 * 332 * @return Content-Type of this body part 333 */ 334 public String getContentType() { 335 if (mimePart != null) { 336 return mimePart.getContentType(); 337 } 338 String s = getHeader("Content-Type", null); 339 if (s == null) 340 s = "text/plain"; 341 342 return s; 343 } 344 345 /** 346 * Is this MimeBodyPart of the specified MIME type? This method 347 * compares <strong>only the <code>primaryType</code> and 348 * <code>subType</code></strong>. 349 * The parameters of the content types are ignored. <p> 350 * 351 * For example, this method will return <code>true</code> when 352 * comparing a MimeBodyPart of content type <strong>"text/plain"</strong> 353 * with <strong>"text/plain; charset=foobar"</strong>. <p> 354 * 355 * If the <code>subType</code> of <code>mimeType</code> is the 356 * special character '*', then the subtype is ignored during the 357 * comparison. 358 * 359 * @param mimeType string 360 * @return true if it is valid mime type 361 */ 362 public boolean isMimeType(String mimeType) { 363 boolean result; 364 // XXX - lots of room for optimization here! 365 try { 366 ContentType ct = new ContentType(getContentType()); 367 result = ct.match(mimeType); 368 } catch (ParseException ex) { 369 result = getContentType().equalsIgnoreCase(mimeType); 370 } 371 return result; 372 } 373 374 /** 375 * Returns the value of the "Content-Disposition" header field. 376 * This represents the disposition of this part. The disposition 377 * describes how the part should be presented to the user. <p> 378 * 379 * If the Content-Disposition field is unavailable, 380 * null is returned. <p> 381 * 382 * This implementation uses <code>getHeader(name)</code> 383 * to obtain the requisite header field. 384 * 385 * @return content disposition 386 * @exception MessagingException in case of error 387 * 388 * @see #headers 389 */ 390 public String getDisposition() throws MessagingException { 391 String s = getHeader("Content-Disposition", null); 392 393 if (s == null) 394 return null; 395 396 ContentDisposition cd = new ContentDisposition(s); 397 return cd.getDisposition(); 398 } 399 400 /** 401 * Set the "Content-Disposition" header field of this body part. 402 * If the disposition is null, any existing "Content-Disposition" 403 * header field is removed. 404 * 405 * @param disposition value 406 * 407 * @exception MessagingException in case of error 408 * @exception IllegalStateException if this body part is 409 * obtained from a READ_ONLY folder. 410 */ 411 public void setDisposition(String disposition) throws MessagingException { 412 if (disposition == null) 413 removeHeader("Content-Disposition"); 414 else { 415 String s = getHeader("Content-Disposition", null); 416 if (s != null) { 417 /* A Content-Disposition header already exists .. 418 * 419 * Override disposition, but attempt to retain 420 * existing disposition parameters 421 */ 422 ContentDisposition cd = new ContentDisposition(s); 423 cd.setDisposition(disposition); 424 disposition = cd.toString(); 425 } 426 setHeader("Content-Disposition", disposition); 427 } 428 } 429 430 /** 431 * Returns the content transfer encoding from the 432 * "Content-Transfer-Encoding" header 433 * field. Returns <code>null</code> if the header is unavailable 434 * or its value is absent. <p> 435 * 436 * This implementation uses <code>getHeader(name)</code> 437 * to obtain the requisite header field. 438 * 439 * @return encoding 440 * @exception MessagingException in case of error 441 * 442 * @see #headers 443 */ 444 public String getEncoding() throws MessagingException { 445 String s = getHeader("Content-Transfer-Encoding", null); 446 447 if (s == null) 448 return null; 449 450 s = s.trim(); // get rid of trailing spaces 451 // quick check for known values to avoid unnecessary use 452 // of tokenizer. 453 if (s.equalsIgnoreCase("7bit") || s.equalsIgnoreCase("8bit") || 454 s.equalsIgnoreCase("quoted-printable") || 455 s.equalsIgnoreCase("base64")) 456 return s; 457 458 // Tokenize the header to obtain the encoding (skip comments) 459 HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME); 460 461 HeaderTokenizer.Token tk; 462 int tkType; 463 464 for (;;) { 465 tk = h.next(); // get a token 466 tkType = tk.getType(); 467 if (tkType == HeaderTokenizer.Token.EOF) 468 break; // done 469 else if (tkType == HeaderTokenizer.Token.ATOM) 470 return tk.getValue(); 471 else // invalid token, skip it. 472 continue; 473 } 474 return s; 475 } 476 477 /** 478 * Returns the value of the "Content-ID" header field. Returns 479 * <code>null</code> if the field is unavailable or its value is 480 * absent. <p> 481 * 482 * This implementation uses <code>getHeader(name)</code> 483 * to obtain the requisite header field. 484 * 485 * @return conent id 486 */ 487 public String getContentID() { 488 return getHeader("Content-ID", null); 489 } 490 491 /** 492 * Set the "Content-ID" header field of this body part. 493 * If the <code>cid</code> parameter is null, any existing 494 * "Content-ID" is removed. 495 * 496 * @param cid content id 497 * @exception IllegalStateException if this body part is 498 * obtained from a READ_ONLY folder. 499 * @since JavaMail 1.3 500 */ 501 public void setContentID(String cid) { 502 if (cid == null) 503 removeHeader("Content-ID"); 504 else 505 setHeader("Content-ID", cid); 506 } 507 508 /** 509 * Return the value of the "Content-MD5" header field. Returns 510 * <code>null</code> if this field is unavailable or its value 511 * is absent. <p> 512 * 513 * This implementation uses <code>getHeader(name)</code> 514 * to obtain the requisite header field. 515 * 516 * @return content MD5 sum 517 */ 518 public String getContentMD5() { 519 return getHeader("Content-MD5", null); 520 } 521 522 /** 523 * Set the "Content-MD5" header field of this body part. 524 * 525 * @param md5 content md5 sum 526 * 527 * @exception IllegalStateException if this body part is 528 * obtained from a READ_ONLY folder. 529 */ 530 public void setContentMD5(String md5) { 531 setHeader("Content-MD5", md5); 532 } 533 534 /** 535 * Get the languages specified in the Content-Language header 536 * of this MimeBodyPart. The Content-Language header is defined by 537 * RFC 1766. Returns <code>null</code> if this header is not 538 * available or its value is absent. <p> 539 * 540 * This implementation uses <code>getHeader(name)</code> 541 * to obtain the requisite header field. 542 * 543 * @return array of language tags 544 * @exception MessagingException in case of error 545 */ 546 public String[] getContentLanguage() throws MessagingException { 547 String s = getHeader("Content-Language", null); 548 549 if (s == null) 550 return null; 551 552 // Tokenize the header to obtain the Language-tags (skip comments) 553 HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME); 554 FinalArrayList<String> v = new FinalArrayList<String>(); 555 556 HeaderTokenizer.Token tk; 557 int tkType; 558 559 while (true) { 560 tk = h.next(); // get a language-tag 561 tkType = tk.getType(); 562 if (tkType == HeaderTokenizer.Token.EOF) 563 break; // done 564 else if (tkType == HeaderTokenizer.Token.ATOM) v.add(tk.getValue()); 565 else // invalid token, skip it. 566 continue; 567 } 568 569 if (v.size() == 0) 570 return null; 571 572 return v.toArray(new String[v.size()]); 573 } 574 575 /** 576 * Set the Content-Language header of this MimeBodyPart. The 577 * Content-Language header is defined by RFC 1766. 578 * 579 * @param languages array of language tags 580 */ 581 public void setContentLanguage(String[] languages) { 582 StringBuilder sb = new StringBuilder(languages[0]); 583 for (int i = 1; i < languages.length; i++) 584 sb.append(',').append(languages[i]); 585 setHeader("Content-Language", sb.toString()); 586 } 587 588 /** 589 * Returns the "Content-Description" header field of this body part. 590 * This typically associates some descriptive information with 591 * this part. Returns null if this field is unavailable or its 592 * value is absent. <p> 593 * 594 * If the Content-Description field is encoded as per RFC 2047, 595 * it is decoded and converted into Unicode. If the decoding or 596 * conversion fails, the raw data is returned as is. <p> 597 * 598 * This implementation uses <code>getHeader(name)</code> 599 * to obtain the requisite header field. 600 * 601 * @return content description 602 */ 603 public String getDescription() { 604 String rawvalue = getHeader("Content-Description", null); 605 606 if (rawvalue == null) 607 return null; 608 609 try { 610 return MimeUtility.decodeText(MimeUtility.unfold(rawvalue)); 611 } catch (UnsupportedEncodingException ex) { 612 return rawvalue; 613 } 614 } 615 616 /** 617 * Set the "Content-Description" header field for this body part. 618 * If the description parameter is <code>null</code>, then any 619 * existing "Content-Description" fields are removed. <p> 620 * 621 * If the description contains non US-ASCII characters, it will 622 * be encoded using the platform's default charset. If the 623 * description contains only US-ASCII characters, no encoding 624 * is done and it is used as is. <p> 625 * 626 * Note that if the charset encoding process fails, a 627 * MessagingException is thrown, and an UnsupportedEncodingException 628 * is included in the chain of nested exceptions within the 629 * MessagingException. 630 * 631 * @param description content description 632 * @exception IllegalStateException if this body part is 633 * obtained from a READ_ONLY folder. 634 * @exception MessagingException An 635 * UnsupportedEncodingException may be included 636 * in the exception chain if the charset 637 * conversion fails. 638 */ 639 public void setDescription(String description) throws MessagingException { 640 setDescription(description, null); 641 } 642 643 /** 644 * Set the "Content-Description" header field for this body part. 645 * If the description parameter is <code>null</code>, then any 646 * existing "Content-Description" fields are removed. <p> 647 * 648 * If the description contains non US-ASCII characters, it will 649 * be encoded using the specified charset. If the description 650 * contains only US-ASCII characters, no encoding is done and 651 * it is used as is. <p> 652 * 653 * Note that if the charset encoding process fails, a 654 * MessagingException is thrown, and an UnsupportedEncodingException 655 * is included in the chain of nested exceptions within the 656 * MessagingException. 657 * 658 * @param description Description 659 * @param charset Charset for encoding 660 * @exception IllegalStateException if this body part is 661 * obtained from a READ_ONLY folder. 662 * @exception MessagingException An 663 * UnsupportedEncodingException may be included 664 * in the exception chain if the charset 665 * conversion fails. 666 */ 667 public void setDescription(String description, String charset) 668 throws MessagingException { 669 if (description == null) { 670 removeHeader("Content-Description"); 671 return; 672 } 673 674 try { 675 setHeader("Content-Description", MimeUtility.fold(21, 676 MimeUtility.encodeText(description, charset, null))); 677 } catch (UnsupportedEncodingException uex) { 678 throw new MessagingException("Encoding error", uex); 679 } 680 } 681 682 /** 683 * Get the filename associated with this body part. <p> 684 * 685 * Returns the value of the "filename" parameter from the 686 * "Content-Disposition" header field of this body part. If its 687 * not available, returns the value of the "name" parameter from 688 * the "Content-Type" header field of this body part. 689 * Returns <code>null</code> if both are absent. 690 * 691 * @return filename 692 * @exception MessagingException in case of error 693 */ 694 public String getFileName() throws MessagingException { 695 String filename = null; 696 String s = getHeader("Content-Disposition", null); 697 698 if (s != null) { 699 // Parse the header .. 700 ContentDisposition cd = new ContentDisposition(s); 701 filename = cd.getParameter("filename"); 702 } 703 if (filename == null) { 704 // Still no filename ? Try the "name" ContentType parameter 705 s = getHeader("Content-Type", null); 706 if (s != null) { 707 try { 708 ContentType ct = new ContentType(s); 709 filename = ct.getParameter("name"); 710 } catch (ParseException pex) { } // ignore it 711 } 712 } 713 return filename; 714 } 715 716 /** 717 * Set the filename associated with this body part, if possible. <p> 718 * 719 * Sets the "filename" parameter of the "Content-Disposition" 720 * header field of this body part. 721 * 722 * @param filename filename 723 * 724 * @exception MessagingException in case of error 725 * @exception IllegalStateException if this body part is 726 * obtained from a READ_ONLY folder. 727 */ 728 public void setFileName(String filename) throws MessagingException { 729 // Set the Content-Disposition "filename" parameter 730 String s = getHeader("Content-Disposition", null); 731 ContentDisposition cd = 732 new ContentDisposition(s == null ? ATTACHMENT : s); 733 cd.setParameter("filename", filename); 734 setHeader("Content-Disposition", cd.toString()); 735 736 /* Also attempt to set the Content-Type "name" parameter, 737 * to satisfy ancient MUAs. 738 * XXX: This is not RFC compliant, and hence should really 739 * be conditional based on some property. Fix this once we 740 * figure out how to get at Properties from here ! 741 */ 742 s = getHeader("Content-Type", null); 743 if (s != null) { 744 try { 745 ContentType cType = new ContentType(s); 746 cType.setParameter("name", filename); 747 setHeader("Content-Type", cType.toString()); 748 } catch (ParseException pex) { } // ignore it 749 } 750 } 751 752 /** 753 * Return a decoded input stream for this body part's "content". <p> 754 * 755 * This implementation obtains the input stream from the DataHandler. 756 * That is, it invokes getDataHandler().getInputStream(); 757 * 758 * @return an InputStream 759 * @exception IOException this is typically thrown by the 760 * DataHandler. Refer to the documentation for 761 * javax.activation.DataHandler for more details. 762 * 763 * @see #getContentStream 764 * @see DataHandler#getInputStream 765 */ 766 public InputStream getInputStream() 767 throws IOException { 768 return getDataHandler().getInputStream(); 769 } 770 771 /** 772 * Produce the raw bytes of the content. This method is used 773 * when creating a DataHandler object for the content. Subclasses 774 * that can provide a separate input stream for just the MimeBodyPart 775 * content might want to override this method. <p> 776 * 777 * @see #content 778 */ 779 /*package*/ InputStream getContentStream() throws MessagingException { 780 if (mimePart != null) { 781 return mimePart.read(); 782 } 783 if (contentStream != null) 784 return ((SharedInputStream)contentStream).newStream(0, -1); 785 if (content != null) 786 return new ByteArrayInputStream(content,start,contentLength); 787 788 throw new MessagingException("No content"); 789 } 790 791 /** 792 * Return an InputStream to the raw data with any Content-Transfer-Encoding 793 * intact. This method is useful if the "Content-Transfer-Encoding" 794 * header is incorrect or corrupt, which would prevent the 795 * <code>getInputStream</code> method or <code>getContent</code> method 796 * from returning the correct data. In such a case the application may 797 * use this method and attempt to decode the raw data itself. <p> 798 * 799 * This implementation simply calls the <code>getContentStream</code> 800 * method. 801 * 802 * @return input stream 803 * 804 * @exception MessagingException in case of error 805 * 806 * @see #getInputStream 807 * @see #getContentStream 808 * @since JavaMail 1.2 809 * 810 */ 811 public InputStream getRawInputStream() throws MessagingException { 812 return getContentStream(); 813 } 814 815 /** 816 * Return a DataHandler for this body part's content. <p> 817 * 818 * The implementation provided here works just like the 819 * the implementation in MimeMessage. 820 * 821 * @return data handler 822 */ 823 public DataHandler getDataHandler() { 824 if (mimePart != null) { 825 //return an inputstream 826 return new DataHandler(new DataSource() { 827 828 @Override 829 public InputStream getInputStream() throws IOException { 830 return mimePart.read(); 831 } 832 833 @Override 834 public OutputStream getOutputStream() throws IOException { 835 throw new UnsupportedOperationException("getOutputStream cannot be supported : You have enabled LazyAttachments Option"); 836 } 837 838 @Override 839 public String getContentType() { 840 return mimePart.getContentType(); 841 } 842 843 @Override 844 public String getName() { 845 return "MIMEPart Wrapped DataSource"; 846 } 847 }); 848 } 849 if (dh == null) 850 dh = new DataHandler(new MimePartDataSource(this)); 851 return dh; 852 } 853 854 /** 855 * Return the content as a java object. The type of the object 856 * returned is of course dependent on the content itself. For 857 * example, the native format of a text/plain content is usually 858 * a String object. The native format for a "multipart" 859 * content is always a MimeMultipart subclass. For content types that are 860 * unknown to the DataHandler system, an input stream is returned 861 * as the content. <p> 862 * 863 * This implementation obtains the content from the DataHandler. 864 * That is, it invokes getDataHandler().getContent(); 865 * 866 * @return Object 867 * @exception IOException this is typically thrown by the 868 * DataHandler. Refer to the documentation for 869 * javax.activation.DataHandler for more details. 870 */ 871 public Object getContent() throws IOException { 872 return getDataHandler().getContent(); 873 } 874 875 /** 876 * This method provides the mechanism to set this body part's content. 877 * The given DataHandler object should wrap the actual content. 878 * 879 * @param dh The DataHandler for the content 880 * @exception IllegalStateException if this body part is 881 * obtained from a READ_ONLY folder. 882 */ 883 public void setDataHandler(DataHandler dh) { 884 if (mimePart != null) { 885 mimePart = null; 886 } 887 this.dh = dh; 888 this.content = null; 889 this.contentStream = null; 890 removeHeader("Content-Type"); 891 removeHeader("Content-Transfer-Encoding"); 892 } 893 894 /** 895 * A convenience method for setting this body part's content. <p> 896 * 897 * The content is wrapped in a DataHandler object. Note that a 898 * DataContentHandler class for the specified type should be 899 * available to the JavaMail implementation for this to work right. 900 * That is, to do <code>setContent(foobar, "application/x-foobar")</code>, 901 * a DataContentHandler for "application/x-foobar" should be installed. 902 * Refer to the Java Activation Framework for more information. 903 * 904 * @param o the content object 905 * @param type Mime type of the object 906 * @exception IllegalStateException if this body part is 907 * obtained from a READ_ONLY folder. 908 */ 909 public void setContent(Object o, String type) { 910 if (mimePart != null) { 911 mimePart = null; 912 } 913 if (o instanceof MimeMultipart) { 914 setContent((MimeMultipart)o); 915 } else { 916 setDataHandler(new DataHandler(o, type)); 917 } 918 } 919 920 /** 921 * Convenience method that sets the given String as this 922 * part's content, with a MIME type of "text/plain". If the 923 * string contains non US-ASCII characters, it will be encoded 924 * using the platform's default charset. The charset is also 925 * used to set the "charset" parameter. <p> 926 * 927 * Note that there may be a performance penalty if 928 * <code>text</code> is large, since this method may have 929 * to scan all the characters to determine what charset to 930 * use. <p> 931 * If the charset is already known, use the 932 * setText() version that takes the charset parameter. 933 * 934 * @param text string 935 * 936 * @see #setText(String text, String charset) 937 */ 938 public void setText(String text) { 939 setText(text, null); 940 } 941 942 /** 943 * Convenience method that sets the given String as this part's 944 * content, with a MIME type of "text/plain" and the specified 945 * charset. The given Unicode string will be charset-encoded 946 * using the specified charset. The charset is also used to set 947 * the "charset" parameter. 948 * 949 * @param text string 950 * @param charset character set 951 */ 952 public void setText(String text, String charset) { 953 if (charset == null) { 954 if (MimeUtility.checkAscii(text) != MimeUtility.ALL_ASCII) 955 charset = MimeUtility.getDefaultMIMECharset(); 956 else 957 charset = "us-ascii"; 958 } 959 setContent(text, "text/plain; charset=" + 960 MimeUtility.quote(charset, HeaderTokenizer.MIME)); 961 } 962 963 /** 964 * This method sets the body part's content to a MimeMultipart object. 965 * 966 * @param mp The multipart object that is the Message's content 967 * @exception IllegalStateException if this body part is 968 * obtained from a READ_ONLY folder. 969 */ 970 public void setContent(MimeMultipart mp) { 971 if (mimePart != null) { 972 mimePart = null; 973 } 974 setDataHandler(new DataHandler(mp, mp.getContentType().toString())); 975 mp.setParent(this); 976 } 977 978 /** 979 * Output the body part as an RFC 822 format stream. 980 * 981 * @param os output stream 982 * 983 * @exception MessagingException in case of error 984 * @exception IOException if an error occurs writing to the 985 * stream or if an error is generated 986 * by the javax.activation layer. 987 * @see DataHandler#writeTo 988 */ 989 public void writeTo(OutputStream os) 990 throws IOException, MessagingException { 991 992 // First, write out the header 993 List<String> hdrLines = headers.getAllHeaderLines(); 994 int sz = hdrLines.size(); 995 for( int i=0; i<sz; i++ ) 996 OutputUtil.writeln(hdrLines.get(i),os); 997 998 // The CRLF separator between header and content 999 OutputUtil.writeln(os); 1000 1001 // Finally, the content. 1002 // XXX: May need to account for ESMTP ? 1003 if (contentStream != null) { 1004 ((SharedInputStream)contentStream).writeTo(0,-1,os); 1005 } else 1006 if (content != null) { 1007 os.write(content,start,contentLength); 1008 } else 1009 if (dh!=null) { 1010 // this is the slowest route, so try it as the last resort 1011 OutputStream wos = MimeUtility.encode(os, getEncoding()); 1012 getDataHandler().writeTo(wos); 1013 if(os!=wos) 1014 wos.flush(); // Needed to complete encoding 1015 } else if (mimePart != null) { 1016 OutputStream wos = MimeUtility.encode(os, getEncoding()); 1017 getDataHandler().writeTo(wos); 1018 if(os!=wos) 1019 wos.flush(); // Needed to complete encoding 1020 }else { 1021 throw new MessagingException("no content"); 1022 } 1023 } 1024 1025 /** 1026 * Get all the headers for this header_name. Note that certain 1027 * headers may be encoded as per RFC 2047 if they contain 1028 * non US-ASCII characters and these should be decoded. 1029 * 1030 * @param name name of header 1031 * @return array of headers 1032 * @see MimeUtility 1033 */ 1034 public String[] getHeader(String name) { 1035 return headers.getHeader(name); 1036 } 1037 1038 /** 1039 * Get all the headers for this header name, returned as a single 1040 * String, with headers separated by the delimiter. If the 1041 * delimiter is <code>null</code>, only the first header is 1042 * returned. 1043 * 1044 * @param name the name of this header 1045 * @param delimiter delimiter between fields in returned string 1046 * @return the value fields for all headers with 1047 * this name 1048 */ 1049 public String getHeader(String name, String delimiter) { 1050 return headers.getHeader(name, delimiter); 1051 } 1052 1053 /** 1054 * Set the value for this header_name. Replaces all existing 1055 * header values with this new value. Note that RFC 822 headers 1056 * must contain only US-ASCII characters, so a header that 1057 * contains non US-ASCII characters must be encoded as per the 1058 * rules of RFC 2047. 1059 * 1060 * @param name header name 1061 * @param value header value 1062 * @see MimeUtility 1063 */ 1064 public void setHeader(String name, String value) { 1065 headers.setHeader(name, value); 1066 } 1067 1068 /** 1069 * Add this value to the existing values for this header_name. 1070 * Note that RFC 822 headers must contain only US-ASCII 1071 * characters, so a header that contains non US-ASCII characters 1072 * must be encoded as per the rules of RFC 2047. 1073 * 1074 * @param name header name 1075 * @param value header value 1076 * @see MimeUtility 1077 */ 1078 public void addHeader(String name, String value) { 1079 headers.addHeader(name, value); 1080 } 1081 1082 /** 1083 * Remove all headers with this name. 1084 * 1085 * @param name header name 1086 */ 1087 public void removeHeader(String name) { 1088 headers.removeHeader(name); 1089 } 1090 1091 /** 1092 * Return all the headers from this Message as an Enumeration of 1093 * Header objects. 1094 * 1095 * @return all headers 1096 */ 1097 public FinalArrayList<hdr> getAllHeaders() { 1098 return headers.getAllHeaders(); 1099 } 1100 1101 1102 /** 1103 * Add a header line to this body part 1104 * 1105 * @param line header line to add 1106 */ 1107 public void addHeaderLine(String line) { 1108 headers.addHeaderLine(line); 1109 } 1110 1111 /** 1112 * Examine the content of this body part and update the appropriate 1113 * MIME headers. Typical headers that get set here are 1114 * <code>Content-Type</code> and <code>Content-Transfer-Encoding</code>. 1115 * Headers might need to be updated in two cases: 1116 * 1117 * <br> 1118 * - A message being crafted by a mail application will certainly 1119 * need to activate this method at some point to fill up its internal 1120 * headers. 1121 * 1122 * <br> 1123 * - A message read in from a Store will have obtained 1124 * all its headers from the store, and so doesn't need this. 1125 * However, if this message is editable and if any edits have 1126 * been made to either the content or message structure, we might 1127 * need to resync our headers. 1128 * 1129 * <br> 1130 * In both cases this method is typically called by the 1131 * <code>Message.saveChanges</code> method. 1132 * 1133 * @exception MessagingException in case of error. 1134 */ 1135 protected void updateHeaders() throws MessagingException { 1136 DataHandler dh = getDataHandler(); 1137 /* 1138 * Code flow indicates null is never returned from 1139 * getdataHandler() - findbugs 1140 */ 1141 //if (dh == null) // Huh ? 1142 // return; 1143 1144 try { 1145 String type = dh.getContentType(); 1146 boolean composite = false; 1147 boolean needCTHeader = getHeader("Content-Type") == null; 1148 1149 ContentType cType = new ContentType(type); 1150 if (cType.match("multipart/*")) { 1151 // If multipart, recurse 1152 composite = true; 1153 Object o = dh.getContent(); 1154 ((MimeMultipart) o).updateHeaders(); 1155 } else if (cType.match("message/rfc822")) { 1156 composite = true; 1157 } 1158 1159 // Content-Transfer-Encoding, but only if we don't 1160 // already have one 1161 if (!composite) { // not allowed on composite parts 1162 if (getHeader("Content-Transfer-Encoding") == null) 1163 setEncoding(MimeUtility.getEncoding(dh)); 1164 1165 if (needCTHeader && setDefaultTextCharset && 1166 cType.match("text/*") && 1167 cType.getParameter("charset") == null) { 1168 /* 1169 * Set a default charset for text parts. 1170 * We really should examine the data to determine 1171 * whether or not it's all ASCII, but that's too 1172 * expensive so we make an assumption: If we 1173 * chose 7bit encoding for this data, it's probably 1174 * ASCII. (MimeUtility.getEncoding will choose 1175 * 7bit only in this case, but someone might've 1176 * set the Content-Transfer-Encoding header manually.) 1177 */ 1178 String charset; 1179 String enc = getEncoding(); 1180 if (enc != null && enc.equalsIgnoreCase("7bit")) 1181 charset = "us-ascii"; 1182 else 1183 charset = MimeUtility.getDefaultMIMECharset(); 1184 cType.setParameter("charset", charset); 1185 type = cType.toString(); 1186 } 1187 } 1188 1189 // Now, let's update our own headers ... 1190 1191 // Content-type, but only if we don't already have one 1192 if (needCTHeader) { 1193 /* 1194 * Pull out "filename" from Content-Disposition, and 1195 * use that to set the "name" parameter. This is to 1196 * satisfy older MUAs (DtMail, Roam and probably 1197 * a bunch of others). 1198 */ 1199 String s = getHeader("Content-Disposition", null); 1200 if (s != null) { 1201 // Parse the header .. 1202 ContentDisposition cd = new ContentDisposition(s); 1203 String filename = cd.getParameter("filename"); 1204 if (filename != null) { 1205 cType.setParameter("name", filename); 1206 type = cType.toString(); 1207 } 1208 } 1209 1210 setHeader("Content-Type", type); 1211 } 1212 } catch (IOException ex) { 1213 throw new MessagingException("IOException updating headers", ex); 1214 } 1215 } 1216 1217 private void setEncoding(String encoding) { 1218 setHeader("Content-Transfer-Encoding", encoding); 1219 } 1220 }