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