Clover coverage report - Maven Clover report
Coverage timestamp: Sun Aug 20 2006 04:01:44 PDT
file stats: LOC: 374   Methods: 20
NCLOC: 242   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
MimeMultipart.java 74% 80.3% 70% 77.7%
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.BufferedInputStream;
 21    import java.io.ByteArrayInputStream;
 22    import java.io.ByteArrayOutputStream;
 23    import java.io.IOException;
 24    import java.io.InputStream;
 25    import java.io.OutputStream;
 26    import java.io.PushbackInputStream;
 27   
 28    import javax.activation.DataSource;
 29    import javax.mail.BodyPart;
 30    import javax.mail.MessagingException;
 31    import javax.mail.Multipart;
 32    import javax.mail.MultipartDataSource;
 33   
 34    import org.apache.geronimo.mail.util.SessionUtil;
 35   
 36    /**
 37    * @version $Rev: 421852 $ $Date: 2006-07-14 03:02:19 -0700 (Fri, 14 Jul 2006) $
 38    */
 39    public class MimeMultipart extends Multipart {
 40    private static final String MIME_IGNORE_MISSING_BOUNDARY = "mail.mime.multipart.ignoremissingendboundary";
 41   
 42    /**
 43    * DataSource that provides our InputStream.
 44    */
 45    protected DataSource ds;
 46    /**
 47    * Indicates if the data has been parsed.
 48    */
 49    protected boolean parsed = true;
 50   
 51    // the content type information
 52    private transient ContentType type;
 53   
 54    // indicates if we've seen the final boundary line when parsing.
 55    private boolean complete = true;
 56   
 57    // MIME multipart preable text that can appear before the first boundary line.
 58    private String preamble = null;
 59   
 60    /**
 61    * Create an empty MimeMultipart with content type "multipart/mixed"
 62    */
 63  3 public MimeMultipart() {
 64  3 this("mixed");
 65    }
 66   
 67    /**
 68    * Create an empty MimeMultipart with the subtype supplied.
 69    *
 70    * @param subtype the subtype
 71    */
 72  4 public MimeMultipart(String subtype) {
 73  4 type = new ContentType("multipart", subtype, null);
 74  4 type.setParameter("boundary", getBoundary());
 75  4 contentType = type.toString();
 76    }
 77   
 78    /**
 79    * Create a MimeMultipart from the supplied DataSource.
 80    *
 81    * @param dataSource the DataSource to use
 82    * @throws MessagingException
 83    */
 84  3 public MimeMultipart(DataSource dataSource) throws MessagingException {
 85  3 ds = dataSource;
 86  3 if (dataSource instanceof MultipartDataSource) {
 87  0 super.setMultipartDataSource((MultipartDataSource) dataSource);
 88  0 parsed = true;
 89    } else {
 90  3 type = new ContentType(ds.getContentType());
 91  3 contentType = type.toString();
 92  3 parsed = false;
 93    }
 94    }
 95   
 96  0 public void setSubType(String subtype) throws MessagingException {
 97  0 type.setSubType(subtype);
 98  0 contentType = type.toString();
 99    }
 100   
 101  2 public int getCount() throws MessagingException {
 102  2 parse();
 103  2 return super.getCount();
 104    }
 105   
 106  4 public synchronized BodyPart getBodyPart(int part) throws MessagingException {
 107  4 parse();
 108  4 return super.getBodyPart(part);
 109    }
 110   
 111  0 public BodyPart getBodyPart(String cid) throws MessagingException {
 112  0 parse();
 113  0 for (int i = 0; i < parts.size(); i++) {
 114  0 MimeBodyPart bodyPart = (MimeBodyPart) parts.get(i);
 115  0 if (cid.equals(bodyPart.getContentID())) {
 116  0 return bodyPart;
 117    }
 118    }
 119  0 return null;
 120    }
 121   
 122  3 protected void updateHeaders() throws MessagingException {
 123  3 parse();
 124  3 for (int i = 0; i < parts.size(); i++) {
 125  5 MimeBodyPart bodyPart = (MimeBodyPart) parts.get(i);
 126  5 bodyPart.updateHeaders();
 127    }
 128    }
 129   
 130    private static byte[] dash = { '-', '-' };
 131    private static byte[] crlf = { 13, 10 };
 132   
 133  5 public void writeTo(OutputStream out) throws IOException, MessagingException {
 134  5 parse();
 135  5 String boundary = type.getParameter("boundary");
 136  5 byte[] bytes = boundary.getBytes();
 137   
 138  5 if (preamble != null) {
 139  1 byte[] preambleBytes = preamble.getBytes();
 140    // write this out, followed by a line break.
 141  1 out.write(preambleBytes);
 142  1 out.write(crlf);
 143    }
 144   
 145  5 for (int i = 0; i < parts.size(); i++) {
 146  9 BodyPart bodyPart = (BodyPart) parts.get(i);
 147  9 out.write(dash);
 148  9 out.write(bytes);
 149  9 out.write(crlf);
 150  9 bodyPart.writeTo(out);
 151  9 out.write(crlf);
 152    }
 153  5 out.write(dash);
 154  5 out.write(bytes);
 155  5 out.write(dash);
 156  5 out.write(crlf);
 157  5 out.flush();
 158    }
 159   
 160  16 protected void parse() throws MessagingException {
 161  16 if (parsed) {
 162  14 return;
 163    }
 164  2 try {
 165  2 ContentType cType = new ContentType(contentType);
 166  2 byte[] boundary = ("--" + cType.getParameter("boundary")).getBytes();
 167  2 InputStream is = new BufferedInputStream(ds.getInputStream());
 168  2 PushbackInputStream pushbackInStream = new PushbackInputStream(is,
 169    (boundary.length + 2));
 170  2 readTillFirstBoundary(pushbackInStream, boundary);
 171  2 while (pushbackInStream.available()>0){
 172  3 MimeBodyPartInputStream partStream;
 173  3 partStream = new MimeBodyPartInputStream(pushbackInStream,
 174    boundary);
 175  3 addBodyPart(new MimeBodyPart(partStream));
 176   
 177    // terminated by an EOF rather than a proper boundary?
 178  3 if (!partStream.boundaryFound) {
 179  0 if (!SessionUtil.getBooleanProperty(MIME_IGNORE_MISSING_BOUNDARY, true)) {
 180  0 throw new MessagingException("Missing Multi-part end boundary");
 181    }
 182  0 complete = false;
 183    }
 184    }
 185    } catch (Exception e){
 186  0 throw new MessagingException(e.toString(),e);
 187    }
 188  2 parsed = true;
 189    }
 190   
 191    /**
 192    * Move the read pointer to the begining of the first part
 193    * read till the end of first boundary. Any data read before this point are
 194    * saved as the preamble.
 195    *
 196    * @param pushbackInStream
 197    * @param boundary
 198    * @throws MessagingException
 199    */
 200  2 private boolean readTillFirstBoundary(PushbackInputStream pushbackInStream, byte[] boundary) throws MessagingException {
 201  2 ByteArrayOutputStream preambleStream = new ByteArrayOutputStream();
 202   
 203  2 try {
 204  22 while (pushbackInStream.available() > 0) {
 205  22 int value = pushbackInStream.read();
 206  22 if ((byte) value == boundary[0]) {
 207  2 int boundaryIndex = 0;
 208  2 while (pushbackInStream.available() > 0 && (boundaryIndex < boundary.length)
 209    && ((byte) value == boundary[boundaryIndex])) {
 210  72 value = pushbackInStream.read();
 211  72 if (value == -1)
 212  0 throw new MessagingException(
 213    "Unexpected End of Stream while searching for first Mime Boundary");
 214  72 boundaryIndex++;
 215    }
 216  2 if (boundaryIndex == boundary.length) { // boundary found
 217  2 pushbackInStream.read();
 218   
 219    // save the preamble, if there is one.
 220  2 byte[] preambleBytes = preambleStream.toByteArray();
 221  2 if (preambleBytes.length > 0) {
 222  1 preamble = new String(preambleBytes);
 223    }
 224  2 return true;
 225    }
 226    else {
 227    // we need to add this to the preamble. We write the part of the boundary that
 228    // actually matched, followed by the character that was the mismatch.
 229  0 preambleStream.write(boundary, 0, boundaryIndex);
 230  0 preambleStream.write((byte)value);
 231    }
 232    }
 233    else {
 234    // this is part of the preamble.
 235  20 preambleStream.write((byte)value);
 236    }
 237    }
 238    } catch (IOException ioe) {
 239  0 throw new MessagingException(ioe.toString(), ioe);
 240    }
 241  0 return false;
 242    }
 243   
 244  0 protected InternetHeaders createInternetHeaders(InputStream in) throws MessagingException {
 245  0 return new InternetHeaders(in);
 246    }
 247   
 248  0 protected MimeBodyPart createMimeBodyPart(InternetHeaders headers, byte[] data) throws MessagingException {
 249  0 return new MimeBodyPart(headers, data);
 250    }
 251   
 252  0 protected MimeBodyPart createMimeBodyPart(InputStream in) throws MessagingException {
 253  0 return new MimeBodyPart(in);
 254    }
 255   
 256    // static used to track boudary value allocations to help ensure uniqueness.
 257    private static int part;
 258   
 259  4 private synchronized static String getBoundary() {
 260  4 int i;
 261  4 synchronized(MimeMultipart.class) {
 262  4 i = part++;
 263    }
 264  4 StringBuffer buf = new StringBuffer(64);
 265  4 buf.append("----=_Part_").append(i).append('_').append((new Object()).hashCode()).append('.').append(System.currentTimeMillis());
 266  4 return buf.toString();
 267    }
 268   
 269    private class MimeBodyPartInputStream extends InputStream {
 270    PushbackInputStream inStream;
 271    public boolean boundaryFound = false;
 272    byte[] boundary;
 273   
 274  3 public MimeBodyPartInputStream(PushbackInputStream inStream,
 275    byte[] boundary) {
 276  3 super();
 277  3 this.inStream = inStream;
 278  3 this.boundary = boundary;
 279    }
 280   
 281  294 public int read() throws IOException {
 282  294 if (boundaryFound) {
 283  3 return -1;
 284    }
 285    // read the next value from stream
 286  291 int value = inStream.read();
 287    // A problem occured because all the mime parts tends to have a /r/n at the end. Making it hard to transform them to correct DataSources.
 288    // This logic introduced to handle it
 289    //TODO look more in to this && for a better way to do this
 290  291 if (value == 13) {
 291  14 value = inStream.read();
 292  14 if (value != 10) {
 293  0 inStream.unread(value);
 294  0 return 13;
 295    } else {
 296  14 value = inStream.read();
 297  14 if ((byte) value != boundary[0]) {
 298  11 inStream.unread(value);
 299  11 inStream.unread(10);
 300  11 return 13;
 301    }
 302    }
 303  277 } else if ((byte) value != boundary[0]) {
 304  264 return value;
 305    }
 306    // read value is the first byte of the boundary. Start matching the
 307    // next characters to find a boundary
 308  16 int boundaryIndex = 0;
 309  16 while ((boundaryIndex < boundary.length)
 310    && ((byte) value == boundary[boundaryIndex])) {
 311  121 value = inStream.read();
 312  121 boundaryIndex++;
 313    }
 314  16 if (boundaryIndex == boundary.length) { // boundary found
 315  3 boundaryFound = true;
 316    // read the end of line character
 317  3 if (inStream.read() == '-' && value == '-') {
 318    //Last mime boundary should have a succeeding "--"
 319    //as we are on it, read the terminating CRLF
 320  2 inStream.read();
 321  2 inStream.read();
 322    }
 323  3 return -1;
 324    }
 325    // Boundary not found. Restoring bytes skipped.
 326    // write first skipped byte, push back the rest
 327  13 if (value != -1) { // Stream might have ended
 328  13 inStream.unread(value);
 329    }
 330  13 inStream.unread(boundary, 1, boundaryIndex - 1);
 331  13 return boundary[0];
 332    }
 333    }
 334   
 335    /**
 336    * Return true if the final boundary line for this multipart was
 337    * seen when parsing the data.
 338    *
 339    * @return
 340    * @exception MessagingException
 341    */
 342  0 public boolean isComplete() throws MessagingException {
 343    // make sure we've parsed this
 344  0 parse();
 345  0 return complete;
 346    }
 347   
 348   
 349    /**
 350    * Returns the preamble text that appears before the first bady
 351    * part of a MIME multi part. The preamble is optional, so this
 352    * might be null.
 353    *
 354    * @return The preamble text string.
 355    * @exception MessagingException
 356    */
 357  2 public String getPreamble() throws MessagingException {
 358  2 parse();
 359  2 return preamble;
 360    }
 361   
 362    /**
 363    * Set the message preamble text. This will be written before
 364    * the first boundary of a multi-part message.
 365    *
 366    * @param preamble The new boundary text. This is complete lines of text, including
 367    * new lines.
 368    *
 369    * @exception MessagingException
 370    */
 371  1 public void setPreamble(String preamble) throws MessagingException {
 372  1 this.preamble = preamble;
 373    }
 374    }