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