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