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