KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > mail > internet > MimeMultipart


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the "License"). You may not use this file except
5  * in compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * glassfish/bootstrap/legal/CDDLv1.0.txt or
9  * https://glassfish.dev.java.net/public/CDDLv1.0.html.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * HEADER in each file and include the License file at
15  * glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable,
16  * add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your
18  * own identifying information: Portions Copyright [yyyy]
19  * [name of copyright owner]
20  */

21
22 /*
23  * @(#)MimeMultipart.java 1.41 05/12/09
24  *
25  * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
26  */

27
28 package javax.mail.internet;
29
30 import javax.mail.*;
31 import javax.activation.*;
32 import java.util.*;
33 import java.io.*;
34 import com.sun.mail.util.LineOutputStream;
35 import com.sun.mail.util.LineInputStream;
36 import com.sun.mail.util.ASCIIUtility;
37
38 /**
39  * The MimeMultipart class is an implementation of the abstract Multipart
40  * class that uses MIME conventions for the multipart data. <p>
41  *
42  * A MimeMultipart is obtained from a MimePart whose primary type
43  * is "multipart" (by invoking the part's <code>getContent()</code> method)
44  * or it can be created by a client as part of creating a new MimeMessage. <p>
45  *
46  * The default multipart subtype is "mixed". The other multipart
47  * subtypes, such as "alternative", "related", and so on, can be
48  * implemented as subclasses of MimeMultipart with additional methods
49  * to implement the additional semantics of that type of multipart
50  * content. The intent is that service providers, mail JavaBean writers
51  * and mail clients will write many such subclasses and their Command
52  * Beans, and will install them into the JavaBeans Activation
53  * Framework, so that any JavaMail implementation and its clients can
54  * transparently find and use these classes. Thus, a MIME multipart
55  * handler is treated just like any other type handler, thereby
56  * decoupling the process of providing multipart handlers from the
57  * JavaMail API. Lacking these additional MimeMultipart subclasses,
58  * all subtypes of MIME multipart data appear as MimeMultipart objects. <p>
59  *
60  * An application can directly construct a MIME multipart object of any
61  * subtype by using the <code>MimeMultipart(String subtype)</code>
62  * constructor. For example, to create a "multipart/alternative" object,
63  * use <code>new MimeMultipart("alternative")</code>. <p>
64  *
65  * The <code>mail.mime.multipart.ignoremissingendboundary</code>
66  * property may be set to <code>false</code> to cause a
67  * <code>MessagingException</code> to be thrown if the multipart
68  * data does not end with the required end boundary line. If this
69  * property is set to <code>true</code> or not set, missing end
70  * boundaries are not considered an error and the final body part
71  * ends at the end of the data. <p>
72  *
73  * The <code>mail.mime.multipart.ignoremissingboundaryparameter</code>
74  * System property may be set to <code>false</code> to cause a
75  * <code>MessagingException</code> to be thrown if the Content-Type
76  * of the MimeMultipart does not include a <code>boundary</code> parameter.
77  * If this property is set to <code>true</code> or not set, the multipart
78  * parsing code will look for a line that looks like a bounary line and
79  * use that as the boundary separating the parts.
80  *
81  * @version 1.41, 05/12/09
82  * @author John Mani
83  * @author Bill Shannon
84  * @author Max Spivak
85  */

86
87 public class MimeMultipart extends Multipart {
88
89     private static boolean ignoreMissingEndBoundary = true;
90     private static boolean ignoreMissingBoundaryParameter = true;
91     private static boolean bmparse = true;
92
93     static {
94     try {
95         String JavaDoc s = System.getProperty(
96             "mail.mime.multipart.ignoremissingendboundary");
97         // default to true
98
ignoreMissingEndBoundary =
99             s == null || !s.equalsIgnoreCase("false");
100         s = System.getProperty(
101             "mail.mime.multipart.ignoremissingboundaryparameter");
102         // default to true
103
ignoreMissingBoundaryParameter =
104             s == null || !s.equalsIgnoreCase("false");
105         s = System.getProperty(
106             "mail.mime.multipart.bmparse");
107         // default to true
108
bmparse = s == null || !s.equalsIgnoreCase("false");
109     } catch (SecurityException JavaDoc sex) {
110         // ignore it
111
}
112     }
113
114     /**
115      * The DataSource supplying our InputStream.
116      */

117     protected DataSource ds = null;
118
119     /**
120      * Have we parsed the data from our InputStream yet?
121      * Defaults to true; set to false when our constructor is
122      * given a DataSource with an InputStream that we need to
123      * parse.
124      */

125     protected boolean parsed = true;
126
127     /**
128      * Have we seen the final bounary line?
129      */

130     private boolean complete = true;
131
132     /**
133      * The MIME multipart preamble text, the text that
134      * occurs before the first boundary line.
135      */

136     private String JavaDoc preamble = null;
137
138     /**
139      * Default constructor. An empty MimeMultipart object
140      * is created. Its content type is set to "multipart/mixed".
141      * A unique boundary string is generated and this string is
142      * setup as the "boundary" parameter for the
143      * <code>contentType</code> field. <p>
144      *
145      * MimeBodyParts may be added later.
146      */

147     public MimeMultipart() {
148     this("mixed");
149     }
150
151     /**
152      * Construct a MimeMultipart object of the given subtype.
153      * A unique boundary string is generated and this string is
154      * setup as the "boundary" parameter for the
155      * <code>contentType</code> field. <p>
156      *
157      * MimeBodyParts may be added later.
158      */

159     public MimeMultipart(String JavaDoc subtype) {
160     super();
161     /*
162      * Compute a boundary string.
163      */

164     String JavaDoc boundary = UniqueValue.getUniqueBoundaryValue();
165     ContentType JavaDoc cType = new ContentType JavaDoc("multipart", subtype, null);
166     cType.setParameter("boundary", boundary);
167     contentType = cType.toString();
168     }
169
170     /**
171      * Constructs a MimeMultipart object and its bodyparts from the
172      * given DataSource. <p>
173      *
174      * This constructor handles as a special case the situation where the
175      * given DataSource is a MultipartDataSource object. In this case, this
176      * method just invokes the superclass (i.e., Multipart) constructor
177      * that takes a MultipartDataSource object. <p>
178      *
179      * Otherwise, the DataSource is assumed to provide a MIME multipart
180      * byte stream. The <code>parsed</code> flag is set to false. When
181      * the data for the body parts are needed, the parser extracts the
182      * "boundary" parameter from the content type of this DataSource,
183      * skips the 'preamble' and reads bytes till the terminating
184      * boundary and creates MimeBodyParts for each part of the stream.
185      *
186      * @param ds DataSource, can be a MultipartDataSource
187      */

188     public MimeMultipart(DataSource ds) throws MessagingException {
189     super();
190
191     if (ds instanceof MessageAware) {
192         MessageContext mc = ((MessageAware)ds).getMessageContext();
193         setParent(mc.getPart());
194     }
195
196     if (ds instanceof MultipartDataSource) {
197         // ask super to do this for us.
198
setMultipartDataSource((MultipartDataSource)ds);
199         return;
200     }
201
202     // 'ds' was not a MultipartDataSource, we have
203
// to parse this ourself.
204
parsed = false;
205     this.ds = ds;
206     contentType = ds.getContentType();
207     }
208
209     /**
210      * Set the subtype. This method should be invoked only on a new
211      * MimeMultipart object created by the client. The default subtype
212      * of such a multipart object is "mixed". <p>
213      *
214      * @param subtype Subtype
215      */

216     public synchronized void setSubType(String JavaDoc subtype)
217             throws MessagingException {
218     ContentType JavaDoc cType = new ContentType JavaDoc(contentType);
219     cType.setSubType(subtype);
220     contentType = cType.toString();
221     }
222
223     /**
224      * Return the number of enclosed BodyPart objects.
225      *
226      * @return number of parts
227      */

228     public synchronized int getCount() throws MessagingException {
229     parse();
230     return super.getCount();
231     }
232
233     /**
234      * Get the specified BodyPart. BodyParts are numbered starting at 0.
235      *
236      * @param index the index of the desired BodyPart
237      * @return the Part
238      * @exception MessagingException if no such BodyPart exists
239      */

240     public synchronized BodyPart getBodyPart(int index)
241             throws MessagingException {
242     parse();
243     return super.getBodyPart(index);
244     }
245
246     /**
247      * Get the MimeBodyPart referred to by the given ContentID (CID).
248      * Returns null if the part is not found.
249      *
250      * @param CID the ContentID of the desired part
251      * @return the Part
252      */

253     public synchronized BodyPart getBodyPart(String JavaDoc CID)
254             throws MessagingException {
255     parse();
256
257     int count = getCount();
258     for (int i = 0; i < count; i++) {
259        MimeBodyPart JavaDoc part = (MimeBodyPart JavaDoc)getBodyPart(i);
260        String JavaDoc s = part.getContentID();
261        if (s != null && s.equals(CID))
262         return part;
263     }
264     return null;
265     }
266
267     /**
268      * Return true if the final boundary line for this
269      * multipart was seen. When parsing multipart content,
270      * this class will (by default) terminate parsing with
271      * no error if the end of input is reached before seeing
272      * the final multipart boundary line. In such a case,
273      * this method will return false. (If the System property
274      * "mail.mime.multipart.ignoremissingendboundary" is set to
275      * false, parsing such a message will instead throw a
276      * MessagingException.)
277      *
278      * @return true if the final boundary line was seen
279      * @since JavaMail 1.4
280      */

281     public boolean isComplete() throws MessagingException {
282     parse();
283     return complete;
284     }
285
286     /**
287      * Get the preamble text, if any, that appears before the
288      * first body part of this multipart. Some protocols,
289      * such as IMAP, will not allow access to the preamble text.
290      *
291      * @return the preamble text, or null if no preamble
292      * @since JavaMail 1.4
293      */

294     public String JavaDoc getPreamble() throws MessagingException {
295     parse();
296     return preamble;
297     }
298
299     /**
300      * Set the preamble text to be included before the first
301      * body part. Applications should generally not include
302      * any preamble text. In some cases it may be helpful to
303      * include preamble text with instructions for users of
304      * pre-MIME software. The preamble text should be complete
305      * lines, including newlines.
306      *
307      * @param preamble the preamble text
308      * @since JavaMail 1.4
309      */

310     public void setPreamble(String JavaDoc preamble) throws MessagingException {
311     this.preamble = preamble;
312     }
313
314     /**
315      * Update headers. The default implementation here just
316      * calls the <code>updateHeaders</code> method on each of its
317      * children BodyParts. <p>
318      *
319      * Note that the boundary parameter is already set up when
320      * a new and empty MimeMultipart object is created. <p>
321      *
322      * This method is called when the <code>saveChanges</code>
323      * method is invoked on the Message object containing this
324      * Multipart. This is typically done as part of the Message
325      * send process, however note that a client is free to call
326      * it any number of times. So if the header updating process is
327      * expensive for a specific MimeMultipart subclass, then it
328      * might itself want to track whether its internal state actually
329      * did change, and do the header updating only if necessary.
330      */

331     protected void updateHeaders() throws MessagingException {
332     for (int i = 0; i < parts.size(); i++)
333         ((MimeBodyPart JavaDoc)parts.elementAt(i)).updateHeaders();
334     }
335
336     /**
337      * Iterates through all the parts and outputs each Mime part
338      * separated by a boundary.
339      */

340     public void writeTo(OutputStream os)
341                 throws IOException, MessagingException {
342     parse();
343     
344     String JavaDoc boundary = "--" +
345         (new ContentType JavaDoc(contentType)).getParameter("boundary");
346     LineOutputStream los = new LineOutputStream(os);
347
348     // if there's a preamble, write it out
349
if (preamble != null) {
350         byte[] pb = ASCIIUtility.getBytes(preamble);
351         los.write(pb);
352         // make sure it ends with a newline
353
if (pb.length > 0 &&
354             !(pb[pb.length-1] == '\r' || pb[pb.length-1] == '\n')) {
355         los.writeln();
356         }
357         // XXX - could force a blank line before start boundary
358
}
359     for (int i = 0; i < parts.size(); i++) {
360         los.writeln(boundary); // put out boundary
361
((MimeBodyPart JavaDoc)parts.elementAt(i)).writeTo(os);
362         los.writeln(); // put out empty line
363
}
364
365     // put out last boundary
366
los.writeln(boundary + "--");
367     }
368
369     /**
370      * Parse the InputStream from our DataSource, constructing the
371      * appropriate MimeBodyParts. The <code>parsed</code> flag is
372      * set to true, and if true on entry nothing is done. This
373      * method is called by all other methods that need data for
374      * the body parts, to make sure the data has been parsed.
375      *
376      * @since JavaMail 1.2
377      */

378     protected synchronized void parse() throws MessagingException {
379     if (parsed)
380         return;
381
382     if (bmparse) {
383         parsebm();
384         return;
385     }
386
387     InputStream in = null;
388     SharedInputStream JavaDoc sin = null;
389     long start = 0, end = 0;
390
391     try {
392         in = ds.getInputStream();
393         if (!(in instanceof ByteArrayInputStream) &&
394         !(in instanceof BufferedInputStream) &&
395         !(in instanceof SharedInputStream JavaDoc))
396         in = new BufferedInputStream(in);
397     } catch (Exception JavaDoc ex) {
398         throw new MessagingException("No inputstream from datasource");
399     }
400     if (in instanceof SharedInputStream JavaDoc)
401         sin = (SharedInputStream JavaDoc)in;
402
403     ContentType JavaDoc cType = new ContentType JavaDoc(contentType);
404     String JavaDoc boundary = null;
405     String JavaDoc bp = cType.getParameter("boundary");
406     if (bp != null)
407         boundary = "--" + bp;
408     else if (!ignoreMissingBoundaryParameter)
409         throw new MessagingException("Missing boundary parameter");
410
411     try {
412         // Skip and save the preamble
413
LineInputStream lin = new LineInputStream(in);
414         String JavaDoc line;
415         String JavaDoc lineSeparator = null;
416         while ((line = lin.readLine()) != null) {
417         /*
418          * Strip trailing whitespace. Can't use trim method
419          * because it's too aggressive. Some bogus MIME
420          * messages will include control characters in the
421          * boundary string.
422          */

423         int i;
424         for (i = line.length() - 1; i >= 0; i--) {
425             char c = line.charAt(i);
426             if (!(c == ' ' || c == '\t'))
427             break;
428         }
429         line = line.substring(0, i + 1);
430         if (boundary != null) {
431             if (line.equals(boundary))
432             break;
433         } else {
434             /*
435              * Boundary hasn't been defined, does this line
436              * look like a boundary? If so, assume it is
437              * the boundary and save it.
438              */

439             if (line.startsWith("--")) {
440             boundary = line;
441             break;
442             }
443         }
444
445         // save the preamble after skipping blank lines
446
if (line.length() > 0) {
447             // if we haven't figured out what the line seprator
448
// is, do it now
449
if (lineSeparator == null) {
450             try {
451                 lineSeparator =
452                 System.getProperty("line.separator", "\n");
453             } catch (SecurityException JavaDoc ex) {
454                 lineSeparator = "\n";
455             }
456             }
457             // accumulate the preamble
458
if (preamble == null)
459             preamble = line + lineSeparator;
460             else
461             preamble += line + lineSeparator;
462         }
463         }
464         if (line == null)
465         throw new MessagingException("Missing start boundary");
466
467         // save individual boundary bytes for easy comparison later
468
byte[] bndbytes = ASCIIUtility.getBytes(boundary);
469         int bl = bndbytes.length;
470         
471         /*
472          * Read and process body parts until we see the
473          * terminating boundary line (or EOF).
474          */

475         boolean done = false;
476     getparts:
477         while (!done) {
478         InternetHeaders JavaDoc headers = null;
479         if (sin != null) {
480             start = sin.getPosition();
481             // skip headers
482
while ((line = lin.readLine()) != null && line.length() > 0)
483             ;
484             if (line == null) {
485             if (!ignoreMissingEndBoundary)
486                 throw new MessagingException(
487                     "missing multipart end boundary");
488             // assume there's just a missing end boundary
489
complete = false;
490             break getparts;
491             }
492         } else {
493             // collect the headers for this body part
494
headers = createInternetHeaders(in);
495         }
496
497         if (!in.markSupported())
498             throw new MessagingException("Stream doesn't support mark");
499
500         ByteArrayOutputStream buf = null;
501         // if we don't have a shared input stream, we copy the data
502
if (sin == null)
503             buf = new ByteArrayOutputStream();
504         else
505             end = sin.getPosition();
506         int b;
507         boolean bol = true; // beginning of line flag
508
// the two possible end of line characters
509
int eol1 = -1, eol2 = -1;
510
511         /*
512          * Read and save the content bytes in buf.
513          */

514         for (;;) {
515             if (bol) {
516             /*
517              * At the beginning of a line, check whether the
518              * next line is a boundary.
519              */

520             int i;
521             in.mark(bl + 4 + 1000); // bnd + "--\r\n" + lots of LWSP
522
// read bytes, matching against the boundary
523
for (i = 0; i < bl; i++)
524                 if (in.read() != (bndbytes[i] & 0xff))
525                 break;
526             if (i == bl) {
527                 // matched the boundary, check for last boundary
528
int b2 = in.read();
529                 if (b2 == '-') {
530                 if (in.read() == '-') {
531                     complete = true;
532                     done = true;
533                     break; // ignore trailing text
534
}
535                 }
536                 // skip linear whitespace
537
while (b2 == ' ' || b2 == '\t')
538                 b2 = in.read();
539                 // check for end of line
540
if (b2 == '\n')
541                 break; // got it! break out of the loop
542
if (b2 == '\r') {
543                 in.mark(1);
544                 if (in.read() != '\n')
545                     in.reset();
546                 break; // got it! break out of the loop
547
}
548             }
549             // failed to match, reset and proceed normally
550
in.reset();
551
552             // if this is not the first line, write out the
553
// end of line characters from the previous line
554
if (buf != null && eol1 != -1) {
555                 buf.write(eol1);
556                 if (eol2 != -1)
557                 buf.write(eol2);
558                 eol1 = eol2 = -1;
559             }
560             }
561
562             // read the next byte
563
if ((b = in.read()) < 0) {
564             if (!ignoreMissingEndBoundary)
565                 throw new MessagingException(
566                     "missing multipart end boundary");
567             complete = false;
568             done = true;
569             break;
570             }
571
572             /*
573              * If we're at the end of the line, save the eol characters
574              * to be written out before the beginning of the next line.
575              */

576             if (b == '\r' || b == '\n') {
577             bol = true;
578             if (sin != null)
579                 end = sin.getPosition() - 1;
580             eol1 = b;
581             if (b == '\r') {
582                 in.mark(1);
583                 if ((b = in.read()) == '\n')
584                 eol2 = b;
585                 else
586                 in.reset();
587             }
588             } else {
589             bol = false;
590             if (buf != null)
591                 buf.write(b);
592             }
593         }
594
595         /*
596          * Create a MimeBody element to represent this body part.
597          */

598         MimeBodyPart JavaDoc part;
599         if (sin != null)
600             part = createMimeBodyPart(sin.newStream(start, end));
601         else
602             part = createMimeBodyPart(headers, buf.toByteArray());
603         addBodyPart(part);
604         }
605     } catch (IOException ioex) {
606         throw new MessagingException("IO Error", ioex);
607     } finally {
608         try {
609         in.close();
610         } catch (IOException cex) {
611         // ignore
612
}
613     }
614
615     parsed = true;
616     }
617
618     /**
619      * Parse the InputStream from our DataSource, constructing the
620      * appropriate MimeBodyParts. The <code>parsed</code> flag is
621      * set to true, and if true on entry nothing is done. This
622      * method is called by all other methods that need data for
623      * the body parts, to make sure the data has been parsed.
624      *
625      * @since JavaMail 1.2
626      */

627     /*
628      * Boyer-Moore version of parser. Keep both versions around
629      * until we're sure this new one works.
630      */

631     private synchronized void parsebm() throws MessagingException {
632     if (parsed)
633         return;
634     
635     InputStream in = null;
636     SharedInputStream JavaDoc sin = null;
637     long start = 0, end = 0;
638
639     try {
640         in = ds.getInputStream();
641         if (!(in instanceof ByteArrayInputStream) &&
642         !(in instanceof BufferedInputStream) &&
643         !(in instanceof SharedInputStream JavaDoc))
644         in = new BufferedInputStream(in);
645     } catch (Exception JavaDoc ex) {
646         throw new MessagingException("No inputstream from datasource");
647     }
648     if (in instanceof SharedInputStream JavaDoc)
649         sin = (SharedInputStream JavaDoc)in;
650
651     ContentType JavaDoc cType = new ContentType JavaDoc(contentType);
652     String JavaDoc boundary = null;
653     String JavaDoc bp = cType.getParameter("boundary");
654     if (bp != null)
655         boundary = "--" + bp;
656     else if (!ignoreMissingBoundaryParameter)
657         throw new MessagingException("Missing boundary parameter");
658
659     try {
660         // Skip and save the preamble
661
LineInputStream lin = new LineInputStream(in);
662         String JavaDoc line;
663         String JavaDoc lineSeparator = null;
664         while ((line = lin.readLine()) != null) {
665         /*
666          * Strip trailing whitespace. Can't use trim method
667          * because it's too aggressive. Some bogus MIME
668          * messages will include control characters in the
669          * boundary string.
670          */

671         int i;
672         for (i = line.length() - 1; i >= 0; i--) {
673             char c = line.charAt(i);
674             if (!(c == ' ' || c == '\t'))
675             break;
676         }
677         line = line.substring(0, i + 1);
678         if (boundary != null) {
679             if (line.equals(boundary))
680             break;
681         } else {
682             /*
683              * Boundary hasn't been defined, does this line
684              * look like a boundary? If so, assume it is
685              * the boundary and save it.
686              */

687             if (line.startsWith("--")) {
688             boundary = line;
689             break;
690             }
691         }
692
693         // save the preamble after skipping blank lines
694
if (line.length() > 0) {
695             // if we haven't figured out what the line seprator
696
// is, do it now
697
if (lineSeparator == null) {
698             try {
699                 lineSeparator =
700                 System.getProperty("line.separator", "\n");
701             } catch (SecurityException JavaDoc ex) {
702                 lineSeparator = "\n";
703             }
704             }
705             // accumulate the preamble
706
if (preamble == null)
707             preamble = line + lineSeparator;
708             else
709             preamble += line + lineSeparator;
710         }
711         }
712         if (line == null)
713         throw new MessagingException("Missing start boundary");
714
715         // save individual boundary bytes for comparison later
716
byte[] bndbytes = ASCIIUtility.getBytes(boundary);
717         int bl = bndbytes.length;
718
719         /*
720          * Compile Boyer-Moore parsing tables.
721          */

722
723         // initialize Bad Character Shift table
724
int[] bcs = new int[256];
725         for (int i = 0; i < bl; i++)
726         bcs[bndbytes[i]] = i + 1;
727
728         // initialize Good Suffix Shift table
729
int[] gss = new int[bl];
730     NEXT:
731         for (int i = bl; i > 0; i--) {
732         int j; // the beginning index of the suffix being considered
733
for (j = bl - 1; j >= i; j--) {
734             // Testing for good suffix
735
if (bndbytes[j] == bndbytes[j - i]) {
736             // bndbytes[j..len] is a good suffix
737
gss[j - 1] = i;
738             } else {
739                // No match. The array has already been
740
// filled up with correct values before.
741
continue NEXT;
742             }
743         }
744         while (j > 0)
745             gss[--j] = i;
746         }
747         gss[bl - 1] = 1;
748  
749         /*
750          * Read and process body parts until we see the
751          * terminating boundary line (or EOF).
752          */

753         boolean done = false;
754     getparts:
755         while (!done) {
756         InternetHeaders JavaDoc headers = null;
757         if (sin != null) {
758             start = sin.getPosition();
759             // skip headers
760
while ((line = lin.readLine()) != null && line.length() > 0)
761             ;
762             if (line == null) {
763             if (!ignoreMissingEndBoundary)
764                 throw new MessagingException(
765                     "missing multipart end boundary");
766             // assume there's just a missing end boundary
767
complete = false;
768             break getparts;
769             }
770         } else {
771             // collect the headers for this body part
772
headers = createInternetHeaders(in);
773         }
774
775         if (!in.markSupported())
776             throw new MessagingException("Stream doesn't support mark");
777
778         ByteArrayOutputStream buf = null;
779         // if we don't have a shared input stream, we copy the data
780
if (sin == null)
781             buf = new ByteArrayOutputStream();
782         else
783             end = sin.getPosition();
784         int b;
785
786         /*
787          * These buffers contain the bytes we're checking
788          * for a match. inbuf is the current buffer and
789          * previnbuf is the previous buffer. We need the
790          * previous buffer to check that we're preceeded
791          * by an EOL.
792          */

793         // XXX - a smarter algorithm would use a sliding window
794
// over a larger buffer
795
byte[] inbuf = new byte[bl];
796         byte[] previnbuf = new byte[bl];
797         int inSize = 0; // number of valid bytes in inbuf
798
int prevSize = 0; // number of valid bytes in previnbuf
799
int eolLen;
800         boolean first = true;
801
802         /*
803          * Read and save the content bytes in buf.
804          */

805         for (;;) {
806             in.mark(bl + 4 + 1000); // bnd + "--\r\n" + lots of LWSP
807
eolLen = 0;
808             inSize = in.read(inbuf, 0, bl);
809             if (inSize < bl) {
810             // hit EOF
811
if (!ignoreMissingEndBoundary)
812                 throw new MessagingException(
813                     "missing multipart end boundary");
814             if (sin != null)
815                 end = sin.getPosition();
816             complete = false;
817             done = true;
818             break;
819             }
820             // check whether inbuf contains a boundary string
821
int i;
822             for (i = bl - 1; i >= 0; i--) {
823             if (inbuf[i] != bndbytes[i])
824                 break;
825             }
826             if (i < 0) { // matched all bytes
827
eolLen = 0;
828             if (!first) {
829                 // working backwards, find out if we were preceeded
830
// by an EOL, and if so find its length
831
b = previnbuf[prevSize - 1];
832                 if (b == '\r' || b == '\n') {
833                 eolLen = 1;
834                 if (b == '\n' && prevSize >= 2) {
835                     b = previnbuf[prevSize - 2];
836                     if (b == '\r')
837                     eolLen = 2;
838                 }
839                 }
840             }
841             if (first || eolLen > 0) { // yes, preceed by EOL
842
if (sin != null) {
843                 // update "end", in case this really is
844
// a valid boundary
845
end = sin.getPosition() - bl - eolLen;
846                 }
847                 // matched the boundary, check for last boundary
848
int b2 = in.read();
849                 if (b2 == '-') {
850                 if (in.read() == '-') {
851                     complete = true;
852                     done = true;
853                     break; // ignore trailing text
854
}
855                 }
856                 // skip linear whitespace
857
while (b2 == ' ' || b2 == '\t')
858                 b2 = in.read();
859                 // check for end of line
860
if (b2 == '\n')
861                 break; // got it! break out of the loop
862
if (b2 == '\r') {
863                 in.mark(1);
864                 if (in.read() != '\n')
865                     in.reset();
866                 break; // got it! break out of the loop
867
}
868             }
869             i = 0;
870             }
871
872             /*
873              * Get here if boundary didn't match,
874              * wasn't preceeded by EOL, or wasn't
875              * followed by whitespace or EOL.
876              */

877
878             // compute how many bytes we can skip
879
int skip = Math.max(i + 1 - bcs[inbuf[i] & 0x7f], gss[i]);
880             // want to keep at least two characters
881
if (skip < 2) {
882             // only skipping one byte, save one byte
883
// from previous buffer as well
884
// first, write out bytes we're done with
885
if (sin == null && prevSize > 1)
886                 buf.write(previnbuf, 0, prevSize - 1);
887             in.reset();
888             in.skip(1);
889             if (prevSize >= 1) { // is there a byte to save?
890
// yes, save one from previous and one from current
891
previnbuf[0] = previnbuf[prevSize - 1];
892                 previnbuf[1] = inbuf[0];
893                 prevSize = 2;
894             } else {
895                 // no previous bytes to save, can only save current
896
previnbuf[0] = inbuf[0];
897                 prevSize = 1;
898             }
899             } else {
900             // first, write out data from previous buffer before
901
// we dump it
902
if (prevSize > 0 && sin == null)
903                 buf.write(previnbuf, 0, prevSize);
904             // all the bytes we're skipping are saved in previnbuf
905
prevSize = skip;
906             in.reset();
907             in.skip(prevSize);
908             // swap buffers
909
byte[] tmp = inbuf;
910             inbuf = previnbuf;
911             previnbuf = tmp;
912             }
913             first = false;
914         }
915
916         /*
917          * Create a MimeBody element to represent this body part.
918          */

919         MimeBodyPart JavaDoc part;
920         if (sin != null) {
921             part = createMimeBodyPart(sin.newStream(start, end));
922         } else {
923             // write out data from previous buffer, not including EOL
924
if (prevSize - eolLen > 0)
925             buf.write(previnbuf, 0, prevSize - eolLen);
926             // if we didn't find a trailing boundary,
927
// the current buffer has data we need too
928
if (!complete && inSize > 0)
929             buf.write(inbuf, 0, inSize);
930             part = createMimeBodyPart(headers, buf.toByteArray());
931         }
932         addBodyPart(part);
933         }
934     } catch (IOException ioex) {
935         throw new MessagingException("IO Error", ioex);
936     } finally {
937         try {
938         in.close();
939         } catch (IOException cex) {
940         // ignore
941
}
942     }
943
944     parsed = true;
945     }
946
947     /**
948      * Create and return an InternetHeaders object that loads the
949      * headers from the given InputStream. Subclasses can override
950      * this method to return a subclass of InternetHeaders, if
951      * necessary. This implementation simply constructs and returns
952      * an InternetHeaders object.
953      *
954      * @param is the InputStream to read the headers from
955      * @exception MessagingException
956      * @since JavaMail 1.2
957      */

958     protected InternetHeaders JavaDoc createInternetHeaders(InputStream is)
959                 throws MessagingException {
960     return new InternetHeaders JavaDoc(is);
961     }
962
963     /**
964      * Create and return a MimeBodyPart object to represent a
965      * body part parsed from the InputStream. Subclasses can override
966      * this method to return a subclass of MimeBodyPart, if
967      * necessary. This implementation simply constructs and returns
968      * a MimeBodyPart object.
969      *
970      * @param headers the headers for the body part
971      * @param content the content of the body part
972      * @exception MessagingException
973      * @since JavaMail 1.2
974      */

975     protected MimeBodyPart JavaDoc createMimeBodyPart(InternetHeaders JavaDoc headers,
976                 byte[] content) throws MessagingException {
977     return new MimeBodyPart JavaDoc(headers, content);
978     }
979
980     /**
981      * Create and return a MimeBodyPart object to represent a
982      * body part parsed from the InputStream. Subclasses can override
983      * this method to return a subclass of MimeBodyPart, if
984      * necessary. This implementation simply constructs and returns
985      * a MimeBodyPart object.
986      *
987      * @param is InputStream containing the body part
988      * @exception MessagingException
989      * @since JavaMail 1.2
990      */

991     protected MimeBodyPart JavaDoc createMimeBodyPart(InputStream is)
992                 throws MessagingException {
993     return new MimeBodyPart JavaDoc(is);
994     }
995 }
996
Popular Tags