Clover coverage report - Maven Clover report
Coverage timestamp: Sun Aug 20 2006 04:01:44 PDT
file stats: LOC: 676   Methods: 52
NCLOC: 413   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
MimeBodyPart.java 60% 72.6% 71.2% 69%
coverage coverage
 1    /**
 2    *
 3    * Copyright 2003-2006 The Apache Software Foundation
 4    *
 5    * Licensed under the Apache License, Version 2.0 (the "License");
 6    * you may not use this file except in compliance with the License.
 7    * You may obtain a copy of the License at
 8    *
 9    * http://www.apache.org/licenses/LICENSE-2.0
 10    *
 11    * Unless required by applicable law or agreed to in writing, software
 12    * distributed under the License is distributed on an "AS IS" BASIS,
 13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14    * See the License for the specific language governing permissions and
 15    * limitations under the License.
 16    */
 17   
 18    package javax.mail.internet;
 19   
 20    import java.io.BufferedOutputStream;
 21    import java.io.ByteArrayInputStream;
 22    import java.io.ByteArrayOutputStream;
 23    import java.io.FileOutputStream;
 24    import java.io.File;
 25    import java.io.IOException;
 26    import java.io.InputStream;
 27    import java.io.OutputStream;
 28    import java.io.UnsupportedEncodingException;
 29    import java.util.Enumeration;
 30   
 31    import javax.activation.DataHandler;
 32    import javax.activation.FileDataSource;
 33    import javax.mail.BodyPart;
 34    import javax.mail.MessagingException;
 35    import javax.mail.Multipart;
 36    import javax.mail.Part;
 37    import javax.mail.internet.HeaderTokenizer.Token;
 38    import javax.swing.text.AbstractDocument.Content;
 39   
 40    import org.apache.geronimo.mail.util.ASCIIUtil;
 41    import org.apache.geronimo.mail.util.SessionUtil;
 42   
 43    /**
 44    * @version $Rev: 421852 $ $Date: 2006-07-14 03:02:19 -0700 (Fri, 14 Jul 2006) $
 45    */
 46    public class MimeBodyPart extends BodyPart implements MimePart {
 47    // constants for accessed properties
 48    private static final String MIME_DECODEFILENAME = "mail.mime.decodefilename";
 49    private static final String MIME_ENCODEFILENAME = "mail.mime.encodefilename";
 50    private static final String MIME_SETDEFAULTTEXTCHARSET = "mail.mime.setdefaulttextcharset";
 51    private static final String MIME_SETCONTENTTYPEFILENAME = "mail.mime.setcontenttypefilename";
 52   
 53   
 54    /**
 55    * The {@link DataHandler} for this Message's content.
 56    */
 57    protected DataHandler dh;
 58    /**
 59    * This message's content (unless sourced from a SharedInputStream).
 60    */
 61    protected byte content[];
 62    /**
 63    * If the data for this message was supplied by a {@link SharedInputStream}
 64    * then this is another such stream representing the content of this message;
 65    * if this field is non-null, then {@link #content} will be null.
 66    */
 67    protected InputStream contentStream;
 68    /**
 69    * This message's headers.
 70    */
 71    protected InternetHeaders headers;
 72   
 73  21 public MimeBodyPart() {
 74  21 headers = new InternetHeaders();
 75    }
 76   
 77  5 public MimeBodyPart(InputStream in) throws MessagingException {
 78  5 headers = new InternetHeaders(in);
 79  5 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 80  5 byte[] buffer = new byte[1024];
 81  5 int count;
 82  5 try {
 83  ? while((count = in.read(buffer, 0, 1024)) != -1)
 84  5 baos.write(buffer, 0, count);
 85    } catch (IOException e) {
 86  0 throw new MessagingException(e.toString(),e);
 87    }
 88  5 content = baos.toByteArray();
 89    }
 90   
 91  2 public MimeBodyPart(InternetHeaders headers, byte[] content) throws MessagingException {
 92  2 this.headers = headers;
 93  2 this.content = content;
 94    }
 95   
 96    /**
 97    * Return the content size of this message. This is obtained
 98    * either from the size of the content field (if available) or
 99    * from the contentStream, IFF the contentStream returns a positive
 100    * size. Returns -1 if the size is not available.
 101    *
 102    * @return Size of the content in bytes.
 103    * @exception MessagingException
 104    */
 105  2 public int getSize() throws MessagingException {
 106  2 if (content != null) {
 107  1 return content.length;
 108    }
 109  1 if (contentStream != null) {
 110  0 try {
 111  0 int size = contentStream.available();
 112  0 if (size > 0) {
 113  0 return size;
 114    }
 115    } catch (IOException e) {
 116    }
 117    }
 118  1 return -1;
 119    }
 120   
 121  2 public int getLineCount() throws MessagingException {
 122  2 return -1;
 123    }
 124   
 125  17 public String getContentType() throws MessagingException {
 126  17 String value = getSingleHeader("Content-Type");
 127  17 if (value == null) {
 128  10 value = "text/plain";
 129    }
 130  17 return value;
 131    }
 132   
 133    /**
 134    * Tests to see if this message has a mime-type match with the
 135    * given type name.
 136    *
 137    * @param type The tested type name.
 138    *
 139    * @return If this is a type match on the primary and secondare portion of the types.
 140    * @exception MessagingException
 141    */
 142  4 public boolean isMimeType(String type) throws MessagingException {
 143  4 return new ContentType(getContentType()).match(type);
 144    }
 145   
 146    /**
 147    * Retrieve the message "Content-Disposition" header field.
 148    * This value represents how the part should be represented to
 149    * the user.
 150    *
 151    * @return The string value of the Content-Disposition field.
 152    * @exception MessagingException
 153    */
 154  5 public String getDisposition() throws MessagingException {
 155  5 String disp = getSingleHeader("Content-Disposition");
 156  5 if (disp != null) {
 157  1 return new ContentDisposition(disp).getDisposition();
 158    }
 159  4 return null;
 160    }
 161   
 162    /**
 163    * Set a new dispostion value for the "Content-Disposition" field.
 164    * If the new value is null, the header is removed.
 165    *
 166    * @param disposition
 167    * The new disposition value.
 168    *
 169    * @exception MessagingException
 170    */
 171  1 public void setDisposition(String disposition) throws MessagingException {
 172  1 if (disposition == null) {
 173  0 removeHeader("Content-Disposition");
 174    }
 175    else {
 176    // the disposition has parameters, which we'll attempt to preserve in any existing header.
 177  1 String currentHeader = getSingleHeader("Content-Disposition");
 178  1 if (currentHeader != null) {
 179  0 ContentDisposition content = new ContentDisposition(currentHeader);
 180  0 content.setDisposition(disposition);
 181  0 setHeader("Content-Disposition", content.toString());
 182    }
 183    else {
 184    // set using the raw string.
 185  1 setHeader("Content-Disposition", disposition);
 186    }
 187    }
 188    }
 189   
 190    /**
 191    * Retrieves the current value of the "Content-Transfer-Encoding"
 192    * header. Returns null if the header does not exist.
 193    *
 194    * @return The current header value or null.
 195    * @exception MessagingException
 196    */
 197  18 public String getEncoding() throws MessagingException {
 198    // this might require some parsing to sort out.
 199  18 String encoding = getSingleHeader("Content-Transfer-Encoding");
 200  18 if (encoding != null) {
 201    // we need to parse this into ATOMs and other constituent parts. We want the first
 202    // ATOM token on the string.
 203  16 HeaderTokenizer tokenizer = new HeaderTokenizer(encoding, HeaderTokenizer.MIME);
 204   
 205  16 Token token = tokenizer.next();
 206  16 while (token.getType() != Token.EOF) {
 207    // if this is an ATOM type, return it.
 208  16 if (token.getType() == Token.ATOM) {
 209  16 return token.getValue();
 210    }
 211    }
 212    // not ATOMs found, just return the entire header value....somebody might be able to make sense of
 213    // this.
 214  0 return encoding;
 215    }
 216    // no header, nothing to return.
 217  2 return null;
 218    }
 219   
 220   
 221    /**
 222    * Retrieve the value of the "Content-ID" header. Returns null
 223    * if the header does not exist.
 224    *
 225    * @return The current header value or null.
 226    * @exception MessagingException
 227    */
 228  4 public String getContentID() throws MessagingException {
 229  4 return getSingleHeader("Content-ID");
 230    }
 231   
 232  2 public void setContentID(String cid) throws MessagingException {
 233  2 setOrRemoveHeader("Content-ID", cid);
 234    }
 235   
 236  0 public String getContentMD5() throws MessagingException {
 237  0 return getSingleHeader("Content-MD5");
 238    }
 239   
 240  0 public void setContentMD5(String md5) throws MessagingException {
 241  0 setHeader("Content-MD5", md5);
 242    }
 243   
 244  0 public String[] getContentLanguage() throws MessagingException {
 245  0 return getHeader("Content-Language");
 246    }
 247   
 248  0 public void setContentLanguage(String[] languages) throws MessagingException {
 249  0 if (languages == null) {
 250  0 removeHeader("Content-Language");
 251  0 } else if (languages.length == 1) {
 252  0 setHeader("Content-Language", languages[0]);
 253    } else {
 254  0 StringBuffer buf = new StringBuffer(languages.length * 20);
 255  0 buf.append(languages[0]);
 256  0 for (int i = 1; i < languages.length; i++) {
 257  0 buf.append(',').append(languages[i]);
 258    }
 259  0 setHeader("Content-Language", buf.toString());
 260    }
 261    }
 262   
 263  3 public String getDescription() throws MessagingException {
 264  3 String description = getSingleHeader("Content-Description");
 265  3 if (description != null) {
 266  2 try {
 267    // this could be both folded and encoded. Return this to usable form.
 268  2 return MimeUtility.decodeText(MimeUtility.unfold(description));
 269    } catch (UnsupportedEncodingException e) {
 270    // ignore
 271    }
 272    }
 273    // return the raw version for any errors.
 274  1 return description;
 275    }
 276   
 277  2 public void setDescription(String description) throws MessagingException {
 278  2 setDescription(description, null);
 279    }
 280   
 281  3 public void setDescription(String description, String charset) throws MessagingException {
 282  3 if (description == null) {
 283  1 removeHeader("Content-Description");
 284    }
 285    else {
 286  2 try {
 287  2 setHeader("Content-Description", MimeUtility.fold(21, MimeUtility.encodeText(description, charset, null)));
 288    } catch (UnsupportedEncodingException e) {
 289  0 throw new MessagingException(e.getMessage(), e);
 290    }
 291    }
 292    }
 293   
 294  5 public String getFileName() throws MessagingException {
 295    // see if there is a disposition. If there is, parse off the filename parameter.
 296  5 String disposition = getSingleHeader("Content-Disposition");
 297  5 String filename = null;
 298   
 299  5 if (disposition != null) {
 300  4 filename = new ContentDisposition(disposition).getParameter("filename");
 301    }
 302   
 303    // if there's no filename on the disposition, there might be a name parameter on a
 304    // Content-Type header.
 305  5 if (filename == null) {
 306  1 String type = getSingleHeader("Content-Type");
 307  1 if (type != null) {
 308  1 try {
 309  1 filename = new ContentType(type).getParameter("name");
 310    } catch (ParseException e) {
 311    }
 312    }
 313    }
 314    // if we have a name, we might need to decode this if an additional property is set.
 315  5 if (filename != null && SessionUtil.getBooleanProperty(MIME_DECODEFILENAME, false)) {
 316  0 try {
 317  0 filename = MimeUtility.decodeText(filename);
 318    } catch (UnsupportedEncodingException e) {
 319  0 throw new MessagingException("Unable to decode filename", e);
 320    }
 321    }
 322   
 323  5 return filename;
 324    }
 325   
 326   
 327  3 public void setFileName(String name) throws MessagingException {
 328  3 System.out.println("Setting file name to " + name);
 329    // there's an optional session property that requests file name encoding...we need to process this before
 330    // setting the value.
 331  3 if (name != null && SessionUtil.getBooleanProperty(MIME_ENCODEFILENAME, false)) {
 332  0 try {
 333  0 name = MimeUtility.encodeText(name);
 334    } catch (UnsupportedEncodingException e) {
 335  0 throw new MessagingException("Unable to encode filename", e);
 336    }
 337    }
 338   
 339    // get the disposition string.
 340  3 String disposition = getDisposition();
 341    // if not there, then this is an attachment.
 342  3 if (disposition == null) {
 343  3 disposition = Part.ATTACHMENT;
 344    }
 345   
 346    // now create a disposition object and set the parameter.
 347  3 ContentDisposition contentDisposition = new ContentDisposition(disposition);
 348  3 contentDisposition.setParameter("filename", name);
 349   
 350    // serialize this back out and reset.
 351  3 setHeader("Content-Disposition", contentDisposition.toString());
 352   
 353    // The Sun implementation appears to update the Content-type name parameter too, based on
 354    // another system property
 355  3 if (SessionUtil.getBooleanProperty(MIME_SETCONTENTTYPEFILENAME, true)) {
 356  3 ContentType type = new ContentType(getContentType());
 357  3 type.setParameter("name", name);
 358  3 setHeader("Content-Type", type.toString());
 359    }
 360    }
 361   
 362  4 public InputStream getInputStream() throws MessagingException, IOException {
 363  4 return getDataHandler().getInputStream();
 364    }
 365   
 366  5 protected InputStream getContentStream() throws MessagingException {
 367  5 if (contentStream != null) {
 368  0 return contentStream;
 369    }
 370   
 371  5 if (content != null) {
 372  4 return new ByteArrayInputStream(content);
 373    } else {
 374  1 throw new MessagingException("No content");
 375    }
 376    }
 377   
 378  0 public InputStream getRawInputStream() throws MessagingException {
 379  0 return getContentStream();
 380    }
 381   
 382  28 public synchronized DataHandler getDataHandler() throws MessagingException {
 383  28 if (dh == null) {
 384  5 dh = new DataHandler(new MimePartDataSource(this));
 385    }
 386  28 return dh;
 387    }
 388   
 389  0 public Object getContent() throws MessagingException, IOException {
 390  0 return getDataHandler().getContent();
 391    }
 392   
 393  11 public void setDataHandler(DataHandler handler) throws MessagingException {
 394  11 dh = handler;
 395    // if we have a handler override, then we need to invalidate any content
 396    // headers that define the types. This information will be derived from the
 397    // data heander unless subsequently overridden.
 398  11 removeHeader("Content-Type");
 399  11 removeHeader("Content-Transfer-Encoding");
 400   
 401    }
 402   
 403  7 public void setContent(Object content, String type) throws MessagingException {
 404    // Multipart content needs to be handled separately.
 405  7 if (content instanceof Multipart) {
 406  0 setContent((Multipart)content);
 407    }
 408    else {
 409  7 setDataHandler(new DataHandler(content, type));
 410    }
 411   
 412    }
 413   
 414  2 public void setText(String text) throws MessagingException {
 415  2 setText(text, null);
 416    }
 417   
 418  2 public void setText(String text, String charset) throws MessagingException {
 419    // the default subtype is plain text.
 420  2 setText(text, charset, "plain");
 421    }
 422   
 423   
 424  2 public void setText(String text, String charset, String subtype) throws MessagingException {
 425    // we need to sort out the character set if one is not provided.
 426  2 if (charset == null) {
 427    // if we have non us-ascii characters here, we need to adjust this.
 428  2 if (!ASCIIUtil.isAscii(text)) {
 429  0 charset = MimeUtility.getDefaultMIMECharset();
 430    }
 431    else {
 432  2 charset = "us-ascii";
 433    }
 434    }
 435  2 setContent(text, "text/plain; charset=" + MimeUtility.quote(charset, HeaderTokenizer.MIME));
 436    }
 437   
 438  0 public void setContent(Multipart part) throws MessagingException {
 439  0 setDataHandler(new DataHandler(part, part.getContentType()));
 440  0 part.setParent(this);
 441    }
 442   
 443  11 public void writeTo(OutputStream out) throws IOException, MessagingException {
 444  11 headers.writeTo(out, null);
 445    // add the separater between the headers and the data portion.
 446  11 out.write('\r');
 447  11 out.write('\n');
 448    // we need to process this using the transfer encoding type
 449  11 OutputStream encodingStream = MimeUtility.encode(out, getEncoding());
 450  11 getDataHandler().writeTo(encodingStream);
 451  11 encodingStream.flush();
 452    }
 453   
 454  78 public String[] getHeader(String name) throws MessagingException {
 455  78 return headers.getHeader(name);
 456    }
 457   
 458  10 public String getHeader(String name, String delimiter) throws MessagingException {
 459  10 return headers.getHeader(name, delimiter);
 460    }
 461   
 462  33 public void setHeader(String name, String value) throws MessagingException {
 463  33 headers.setHeader(name, value);
 464    }
 465   
 466    /**
 467    * Conditionally set or remove a named header. If the new value
 468    * is null, the header is removed.
 469    *
 470    * @param name The header name.
 471    * @param value The new header value. A null value causes the header to be
 472    * removed.
 473    *
 474    * @exception MessagingException
 475    */
 476  2 private void setOrRemoveHeader(String name, String value) throws MessagingException {
 477  2 if (value == null) {
 478  0 headers.removeHeader(name);
 479    }
 480    else {
 481  2 headers.setHeader(name, value);
 482    }
 483    }
 484   
 485  0 public void addHeader(String name, String value) throws MessagingException {
 486  0 headers.addHeader(name, value);
 487    }
 488   
 489  23 public void removeHeader(String name) throws MessagingException {
 490  23 headers.removeHeader(name);
 491    }
 492   
 493  0 public Enumeration getAllHeaders() throws MessagingException {
 494  0 return headers.getAllHeaders();
 495    }
 496   
 497  0 public Enumeration getMatchingHeaders(String[] name) throws MessagingException {
 498  0 return headers.getMatchingHeaders(name);
 499    }
 500   
 501  0 public Enumeration getNonMatchingHeaders(String[] name) throws MessagingException {
 502  0 return headers.getNonMatchingHeaders(name);
 503    }
 504   
 505  0 public void addHeaderLine(String line) throws MessagingException {
 506  0 headers.addHeaderLine(line);
 507    }
 508   
 509  0 public Enumeration getAllHeaderLines() throws MessagingException {
 510  0 return headers.getAllHeaderLines();
 511    }
 512   
 513  0 public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
 514  0 return headers.getMatchingHeaderLines(names);
 515    }
 516   
 517  0 public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
 518  0 return headers.getNonMatchingHeaderLines(names);
 519    }
 520   
 521  8 protected void updateHeaders() throws MessagingException {
 522  8 DataHandler handler = getDataHandler();
 523   
 524  8 try {
 525    // figure out the content type. If not set, we'll need to figure this out.
 526  8 String type = dh.getContentType();
 527    // parse this content type out so we can do matches/compares.
 528  8 ContentType content = new ContentType(type);
 529    // is this a multipart content?
 530  8 if (content.match("multipart/*")) {
 531    // the content is suppose to be a MimeMultipart. Ping it to update it's headers as well.
 532  0 try {
 533  0 MimeMultipart part = (MimeMultipart)handler.getContent();
 534  0 part.updateHeaders();
 535    } catch (ClassCastException e) {
 536  0 throw new MessagingException("Message content is not MimeMultipart", e);
 537    }
 538    }
 539  8 else if (!content.match("message/rfc822")) {
 540    // simple part, we need to update the header type information
 541    // if no encoding is set yet, figure this out from the data handler.
 542  8 if (getSingleHeader("Content-Transfer-Encoding") == null) {
 543  8 setHeader("Content-Transfer-Encoding", MimeUtility.getEncoding(handler));
 544    }
 545   
 546    // is a content type header set? Check the property to see if we need to set this.
 547  8 if (getHeader("Content-Type") == null) {
 548  6 if (SessionUtil.getBooleanProperty(MIME_SETDEFAULTTEXTCHARSET, true)) {
 549    // is this a text type? Figure out the encoding and make sure it is set.
 550  6 if (content.match("text/*")) {
 551    // the charset should be specified as a parameter on the MIME type. If not there,
 552    // try to figure one out.
 553  5 if (content.getParameter("charset") == null) {
 554   
 555  4 String encoding = getEncoding();
 556    // if we're sending this as 7-bit ASCII, our character set need to be
 557    // compatible.
 558  4 if (encoding != null && encoding.equalsIgnoreCase("7bit")) {
 559  3 content.setParameter("charset", "us-ascii");
 560    }
 561    else {
 562    // get the global default.
 563  1 content.setParameter("charset", MimeUtility.getDefaultMIMECharset());
 564    }
 565    }
 566    }
 567    }
 568    }
 569    }
 570   
 571    // if we don't have a content type header, then create one.
 572  8 if (getSingleHeader("Content-Type") == null) {
 573    // get the disposition header, and if it is there, copy the filename parameter into the
 574    // name parameter of the type.
 575  6 String disp = getHeader("Content-Disposition", null);
 576  6 if (disp != null) {
 577    // parse up the string value of the disposition
 578  0 ContentDisposition disposition = new ContentDisposition(disp);
 579    // now check for a filename value
 580  0 String filename = disposition.getParameter("filename");
 581    // copy and rename the parameter, if it exists.
 582  0 if (filename != null) {
 583  0 content.setParameter("name", filename);
 584    }
 585    }
 586    // set the header with the updated content type information.
 587  6 setHeader("Content-Type", content.toString());
 588    }
 589   
 590    } catch (IOException e) {
 591  0 throw new MessagingException("Error updating message headers", e);
 592    }
 593    }
 594   
 595  70 private String getSingleHeader(String name) throws MessagingException {
 596  70 String[] values = getHeader(name);
 597  70 if (values == null || values.length == 0) {
 598  33 return null;
 599    } else {
 600  37 return values[0];
 601    }
 602    }
 603   
 604   
 605    /**
 606    * Attach a file to this body part from a File object.
 607    *
 608    * @param file The source File object.
 609    *
 610    * @exception IOException
 611    * @exception MessagingException
 612    */
 613  2 public void attachFile(File file) throws IOException, MessagingException {
 614  2 FileDataSource dataSource = new FileDataSource(file);
 615  2 setDataHandler(new DataHandler(dataSource));
 616  2 setFileName(dataSource.getName());
 617    }
 618   
 619   
 620    /**
 621    * Attach a file to this body part from a file name.
 622    *
 623    * @param file The source file name.
 624    *
 625    * @exception IOException
 626    * @exception MessagingException
 627    */
 628  1 public void attachFile(String file) throws IOException, MessagingException {
 629    // just create a File object and attach.
 630  1 attachFile(new File(file));
 631    }
 632   
 633   
 634    /**
 635    * Save the body part content to a given target file.
 636    *
 637    * @param file The File object used to store the information.
 638    *
 639    * @exception IOException
 640    * @exception MessagingException
 641    */
 642  4 public void saveFile(File file) throws IOException, MessagingException {
 643  4 OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
 644    // we need to read the data in to write it out (sigh).
 645  4 InputStream in = getInputStream();
 646  4 try {
 647  4 byte[] buffer = new byte[8192];
 648  4 int length;
 649  ? while ((length = in.read(buffer)) > 0) {
 650  4 out.write(buffer, 0, length);
 651    }
 652    }
 653    finally {
 654    // make sure all of the streams are closed before we return
 655  4 if (in != null) {
 656  4 in.close();
 657    }
 658  4 if (out != null) {
 659  4 out.close();
 660    }
 661    }
 662    }
 663   
 664   
 665    /**
 666    * Save the body part content to a given target file.
 667    *
 668    * @param file The file name used to store the information.
 669    *
 670    * @exception IOException
 671    * @exception MessagingException
 672    */
 673  2 public void saveFile(String file) throws IOException, MessagingException {
 674  2 saveFile(new File(file));
 675    }
 676    }