Skip to content

Package: MimeMultipart

MimeMultipart

nameinstructionbranchcomplexitylinemethod
MimeMultipart()
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
MimeMultipart(BodyPart[])
M: 22 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
MimeMultipart(DataSource)
M: 56 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 19 C: 0
0%
M: 1 C: 0
0%
MimeMultipart(String)
M: 46 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 15 C: 0
0%
M: 1 C: 0
0%
MimeMultipart(String, BodyPart[])
M: 23 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
addBodyPart(BodyPart)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
addBodyPart(BodyPart, int)
M: 7 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
allDashes(String)
M: 17 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
createInternetHeaders(InputStream)
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
createMimeBodyPart(InputStream)
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
createMimeBodyPart(InternetHeaders, byte[])
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
createMimeBodyPartIs(InputStream)
M: 10 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
getBodyPart(String)
M: 30 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 8 C: 0
0%
M: 1 C: 0
0%
getBodyPart(int)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
getCount()
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
getPreamble()
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
initializeProperties()
M: 21 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%
isComplete()
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
parse()
M: 648 C: 0
0%
M: 146 C: 0
0%
M: 74 C: 0
0%
M: 169 C: 0
0%
M: 1 C: 0
0%
readFully(InputStream, byte[], int, int)
M: 36 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 12 C: 0
0%
M: 1 C: 0
0%
removeBodyPart(BodyPart)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
removeBodyPart(int)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
setPreamble(String)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
setSubType(String)
M: 14 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
skipFully(InputStream, long)
M: 23 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 7 C: 0
0%
M: 1 C: 0
0%
updateHeaders()
M: 18 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
writeTo(OutputStream)
M: 112 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 20 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package jakarta.mail.internet;
18:
19: import java.io.BufferedInputStream;
20: import java.io.ByteArrayInputStream;
21: import java.io.ByteArrayOutputStream;
22: import java.io.EOFException;
23: import java.io.IOException;
24: import java.io.InputStream;
25: import java.io.OutputStream;
26:
27: import jakarta.activation.DataSource;
28: import jakarta.mail.BodyPart;
29: import jakarta.mail.IllegalWriteException;
30: import jakarta.mail.MessageAware;
31: import jakarta.mail.MessageContext;
32: import jakarta.mail.MessagingException;
33: import jakarta.mail.Multipart;
34: import jakarta.mail.MultipartDataSource;
35: import jakarta.mail.util.LineInputStream;
36: import jakarta.mail.util.LineOutputStream;
37:
38:
39:
40: /**
41: * The MimeMultipart class is an implementation of the abstract Multipart
42: * class that uses MIME conventions for the multipart data. <p>
43: *
44: * A MimeMultipart is obtained from a MimePart whose primary type
45: * is "multipart" (by invoking the part's <code>getContent()</code> method)
46: * or it can be created by a client as part of creating a new MimeMessage. <p>
47: *
48: * The default multipart subtype is "mixed". The other multipart
49: * subtypes, such as "alternative", "related", and so on, can be
50: * implemented as subclasses of MimeMultipart with additional methods
51: * to implement the additional semantics of that type of multipart
52: * content. The intent is that service providers, mail JavaBean writers
53: * and mail clients will write many such subclasses and their Command
54: * Beans, and will install them into the JavaBeans Activation
55: * Framework, so that any Jakarta Mail implementation and its clients can
56: * transparently find and use these classes. Thus, a MIME multipart
57: * handler is treated just like any other type handler, thereby
58: * decoupling the process of providing multipart handlers from the
59: * Jakarta Mail API. Lacking these additional MimeMultipart subclasses,
60: * all subtypes of MIME multipart data appear as MimeMultipart objects. <p>
61: *
62: * An application can directly construct a MIME multipart object of any
63: * subtype by using the <code>MimeMultipart(String subtype)</code>
64: * constructor. For example, to create a "multipart/alternative" object,
65: * use <code>new MimeMultipart("alternative")</code>. <p>
66: *
67: * The <code>mail.mime.multipart.ignoremissingendboundary</code>
68: * property may be set to <code>false</code> to cause a
69: * <code>MessagingException</code> to be thrown if the multipart
70: * data does not end with the required end boundary line. If this
71: * property is set to <code>true</code> or not set, missing end
72: * boundaries are not considered an error and the final body part
73: * ends at the end of the data. <p>
74: *
75: * The <code>mail.mime.multipart.ignoremissingboundaryparameter</code>
76: * System property may be set to <code>false</code> to cause a
77: * <code>MessagingException</code> to be thrown if the Content-Type
78: * of the MimeMultipart does not include a <code>boundary</code> parameter.
79: * If this property is set to <code>true</code> or not set, the multipart
80: * parsing code will look for a line that looks like a bounary line and
81: * use that as the boundary separating the parts. <p>
82: *
83: * The <code>mail.mime.multipart.ignoreexistingboundaryparameter</code>
84: * System property may be set to <code>true</code> to cause any boundary
85: * to be ignored and instead search for a boundary line in the message
86: * as with <code>mail.mime.multipart.ignoremissingboundaryparameter</code>. <p>
87: *
88: * Normally, when writing out a MimeMultipart that contains no body
89: * parts, or when trying to parse a multipart message with no body parts,
90: * a <code>MessagingException</code> is thrown. The MIME spec does not allow
91: * multipart content with no body parts. The
92: * <code>mail.mime.multipart.allowempty</code> System property may be set to
93: * <code>true</code> to override this behavior.
94: * When writing out such a MimeMultipart, a single empty part will be
95: * included. When reading such a multipart, a MimeMultipart will be created
96: * with no body parts.
97: *
98: * @author John Mani
99: * @author Bill Shannon
100: * @author Max Spivak
101: */
102:
103: public class MimeMultipart extends Multipart {
104:
105: /**
106: * The DataSource supplying our InputStream.
107: */
108: protected DataSource ds = null;
109:
110: /**
111: * Have we parsed the data from our InputStream yet?
112: * Defaults to true; set to false when our constructor is
113: * given a DataSource with an InputStream that we need to
114: * parse.
115: */
116: protected boolean parsed = true;
117:
118: /**
119: * Have we seen the final bounary line?
120: *
121: * @since        JavaMail 1.5
122: */
123: protected boolean complete = true;
124:
125: /**
126: * The MIME multipart preamble text, the text that
127: * occurs before the first boundary line.
128: *
129: * @since        JavaMail 1.5
130: */
131: protected String preamble = null;
132:
133: /**
134: * Flag corresponding to the "mail.mime.multipart.ignoremissingendboundary"
135: * property, set in the {@link #initializeProperties} method called from
136: * constructors and the parse method.
137: *
138: * @since        JavaMail 1.5
139: */
140: protected boolean ignoreMissingEndBoundary = true;
141:
142: /**
143: * Flag corresponding to the
144: * "mail.mime.multipart.ignoremissingboundaryparameter"
145: * property, set in the {@link #initializeProperties} method called from
146: * constructors and the parse method.
147: *
148: * @since        JavaMail 1.5
149: */
150: protected boolean ignoreMissingBoundaryParameter = true;
151:
152: /**
153: * Flag corresponding to the
154: * "mail.mime.multipart.ignoreexistingboundaryparameter"
155: * property, set in the {@link #initializeProperties} method called from
156: * constructors and the parse method.
157: *
158: * @since        JavaMail 1.5
159: */
160: protected boolean ignoreExistingBoundaryParameter = false;
161:
162: /**
163: * Flag corresponding to the "mail.mime.multipart.allowempty"
164: * property, set in the {@link #initializeProperties} method called from
165: * constructors and the parse method.
166: *
167: * @since        JavaMail 1.5
168: */
169: protected boolean allowEmpty = false;
170:
171: /**
172: * Default constructor. An empty MimeMultipart object
173: * is created. Its content type is set to "multipart/mixed".
174: * A unique boundary string is generated and this string is
175: * setup as the "boundary" parameter for the
176: * <code>contentType</code> field. <p>
177: *
178: * MimeBodyParts may be added later.
179: */
180: public MimeMultipart() {
181:         this("mixed");
182: }
183:
184: /**
185: * Construct a MimeMultipart object of the given subtype.
186: * A unique boundary string is generated and this string is
187: * setup as the "boundary" parameter for the
188: * <code>contentType</code> field.
189: * Calls the {@link #initializeProperties} method.<p>
190: *
191: * MimeBodyParts may be added later.
192: *
193: * @param        subtype        the MIME content subtype
194: */
195: public MimeMultipart(String subtype) {
196:         super();
197:         /*
198:          * Compute a boundary string.
199:          */
200:         String boundary = UniqueValue.getUniqueBoundaryValue();
201:         ContentType cType = new ContentType("multipart", subtype, null);
202:         cType.setParameter("boundary", boundary);
203:         contentType = cType.toString();
204:         initializeProperties();
205: }
206:
207: /**
208: * Construct a MimeMultipart object of the default "mixed" subtype,
209: * and with the given body parts. More body parts may be added later.
210: *
211: * @param        parts        the body parts
212: * @exception        MessagingException for failures
213: * @since        JavaMail 1.5
214: */
215: public MimeMultipart(BodyPart... parts) throws MessagingException {
216:         this();
217:•        for (BodyPart bp : parts)
218:          super.addBodyPart(bp);
219: }
220:
221: /**
222: * Construct a MimeMultipart object of the given subtype
223: * and with the given body parts. More body parts may be added later.
224: *
225: * @param        subtype        the MIME content subtype
226: * @param        parts        the body parts
227: * @exception        MessagingException for failures
228: * @since        JavaMail 1.5
229: */
230: public MimeMultipart(String subtype, BodyPart... parts)
231:                                 throws MessagingException {
232:         this(subtype);
233:•        for (BodyPart bp : parts)
234:          super.addBodyPart(bp);
235: }
236:
237: /**
238: * Constructs a MimeMultipart object and its bodyparts from the
239: * given DataSource. <p>
240: *
241: * This constructor handles as a special case the situation where the
242: * given DataSource is a MultipartDataSource object. In this case, this
243: * method just invokes the superclass (i.e., Multipart) constructor
244: * that takes a MultipartDataSource object. <p>
245: *
246: * Otherwise, the DataSource is assumed to provide a MIME multipart
247: * byte stream. The <code>parsed</code> flag is set to false. When
248: * the data for the body parts are needed, the parser extracts the
249: * "boundary" parameter from the content type of this DataSource,
250: * skips the 'preamble' and reads bytes till the terminating
251: * boundary and creates MimeBodyParts for each part of the stream.
252: *
253: * @param        ds        DataSource, can be a MultipartDataSource
254: * @exception        ParseException for failures parsing the message
255: * @exception        MessagingException for other failures
256: */
257: public MimeMultipart(DataSource ds) throws MessagingException {
258:         super();
259:
260:•        if (ds instanceof MessageAware) {
261:          MessageContext mc = ((MessageAware)ds).getMessageContext();
262:          setParent(mc.getPart());
263:         }
264:
265:•        if (ds instanceof MultipartDataSource) {
266:          // ask super to do this for us.
267:          setMultipartDataSource((MultipartDataSource)ds);
268:          return;
269:         }
270:
271:         // 'ds' was not a MultipartDataSource, we have
272:         // to parse this ourself.
273:         parsed = false;
274:         this.ds = ds;
275:         contentType = ds.getContentType();
276: }
277:
278: /**
279: * Initialize flags that control parsing behavior,
280: * based on System properties described above in
281: * the class documentation.
282: *
283: * @since        JavaMail 1.5
284: */
285: protected void initializeProperties() {
286:         // read properties that control parsing
287:
288:         // default to true
289:         ignoreMissingEndBoundary = MimeUtility.getBooleanSystemProperty(
290:          "mail.mime.multipart.ignoremissingendboundary", true);
291:         // default to true
292:         ignoreMissingBoundaryParameter = MimeUtility.getBooleanSystemProperty(
293:          "mail.mime.multipart.ignoremissingboundaryparameter", true);
294:         // default to false
295:         ignoreExistingBoundaryParameter = MimeUtility.getBooleanSystemProperty(
296:          "mail.mime.multipart.ignoreexistingboundaryparameter", false);
297:         // default to false
298:         allowEmpty = MimeUtility.getBooleanSystemProperty(
299:          "mail.mime.multipart.allowempty", false);
300: }
301:
302: /**
303: * Set the subtype. This method should be invoked only on a new
304: * MimeMultipart object created by the client. The default subtype
305: * of such a multipart object is "mixed".
306: *
307: * @param        subtype                Subtype
308: * @exception        MessagingException for failures
309: */
310: public synchronized void setSubType(String subtype)
311:                         throws MessagingException {
312:         ContentType cType = new ContentType(contentType);        
313:         cType.setSubType(subtype);
314:         contentType = cType.toString();
315: }
316:
317: /**
318: * Return the number of enclosed BodyPart objects.
319: *
320: * @return                number of parts
321: */
322: @Override
323: public synchronized int getCount() throws MessagingException {
324:         parse();
325:         return super.getCount();
326: }
327:
328: /**
329: * Get the specified BodyPart. BodyParts are numbered starting at 0.
330: *
331: * @param index        the index of the desired BodyPart
332: * @return                the Part
333: * @exception MessagingException if no such BodyPart exists
334: */
335: @Override
336: public synchronized BodyPart getBodyPart(int index)
337:                         throws MessagingException {
338:         parse();
339:         return super.getBodyPart(index);
340: }
341:
342: /**
343: * Get the MimeBodyPart referred to by the given ContentID (CID).
344: * Returns null if the part is not found.
345: *
346: * @param CID         the ContentID of the desired part
347: * @return the Part
348: * @exception        MessagingException for failures
349: */
350: public synchronized BodyPart getBodyPart(String CID)
351:                         throws MessagingException {
352:         parse();
353:
354:         int count = getCount();
355:•        for (int i = 0; i < count; i++) {
356:          MimeBodyPart part = (MimeBodyPart)getBodyPart(i);
357:          String s = part.getContentID();
358:•         if (s != null && s.equals(CID))
359:                 return part;
360:         }
361:         return null;
362: }
363:
364: /**
365: * Remove the specified part from the multipart message.
366: * Shifts all the parts after the removed part down one.
367: *
368: * @param part        The part to remove
369: * @return                true if part removed, false otherwise
370: * @exception        MessagingException if no such Part exists
371: * @exception        IllegalWriteException if the underlying
372: *                        implementation does not support modification
373: *                        of existing values
374: */
375: @Override
376: public boolean removeBodyPart(BodyPart part) throws MessagingException {
377:         parse();
378:         return super.removeBodyPart(part);
379: }
380:
381: /**
382: * Remove the part at specified location (starting from 0).
383: * Shifts all the parts after the removed part down one.
384: *
385: * @param index        Index of the part to remove
386: * @exception IndexOutOfBoundsException if the given index
387: *                        is out of range.
388: * @exception        IllegalWriteException if the underlying
389: *                        implementation does not support modification
390: *                        of existing values
391: * @exception        MessagingException for other failures
392: */
393: @Override
394: public void removeBodyPart(int index) throws MessagingException {
395:         parse();
396:         super.removeBodyPart(index);
397: }
398:
399: /**
400: * Adds a Part to the multipart. The BodyPart is appended to
401: * the list of existing Parts.
402: *
403: * @param part The Part to be appended
404: * @exception        IllegalWriteException if the underlying
405: *                        implementation does not support modification
406: *                        of existing values
407: * @exception MessagingException for other failures
408: */
409: @Override
410: public synchronized void addBodyPart(BodyPart part)
411:                 throws MessagingException {
412:         parse();
413:         super.addBodyPart(part);
414: }
415:
416: /**
417: * Adds a BodyPart at position <code>index</code>.
418: * If <code>index</code> is not the last one in the list,
419: * the subsequent parts are shifted up. If <code>index</code>
420: * is larger than the number of parts present, the
421: * BodyPart is appended to the end.
422: *
423: * @param part The BodyPart to be inserted
424: * @param index Location where to insert the part
425: * @exception        IllegalWriteException if the underlying
426: *                        implementation does not support modification
427: *                        of existing values
428: * @exception MessagingException for other failures
429: */
430: @Override
431: public synchronized void addBodyPart(BodyPart part, int index)
432:                                 throws MessagingException {
433:         parse();
434:         super.addBodyPart(part, index);
435: }
436:
437: /**
438: * Return true if the final boundary line for this
439: * multipart was seen. When parsing multipart content,
440: * this class will (by default) terminate parsing with
441: * no error if the end of input is reached before seeing
442: * the final multipart boundary line. In such a case,
443: * this method will return false. (If the System property
444: * "mail.mime.multipart.ignoremissingendboundary" is set to
445: * false, parsing such a message will instead throw a
446: * MessagingException.)
447: *
448: * @return        true if the final boundary line was seen
449: * @exception        MessagingException for failures
450: * @since                JavaMail 1.4
451: */
452: public synchronized boolean isComplete() throws MessagingException {
453:         parse();
454:         return complete;
455: }
456:
457: /**
458: * Get the preamble text, if any, that appears before the
459: * first body part of this multipart. Some protocols,
460: * such as IMAP, will not allow access to the preamble text.
461: *
462: * @return                the preamble text, or null if no preamble
463: * @exception        MessagingException for failures
464: * @since                JavaMail 1.4
465: */
466: public synchronized String getPreamble() throws MessagingException {
467:         parse();
468:         return preamble;
469: }
470:
471: /**
472: * Set the preamble text to be included before the first
473: * body part. Applications should generally not include
474: * any preamble text. In some cases it may be helpful to
475: * include preamble text with instructions for users of
476: * pre-MIME software. The preamble text should be complete
477: * lines, including newlines.
478: *
479: * @param        preamble        the preamble text
480: * @exception        MessagingException for failures
481: * @since                JavaMail 1.4
482: */
483: public synchronized void setPreamble(String preamble)
484:                                 throws MessagingException {
485:         this.preamble = preamble;
486: }
487:
488: /**
489: * Update headers. The default implementation here just
490: * calls the <code>updateHeaders</code> method on each of its
491: * children BodyParts. <p>
492: *
493: * Note that the boundary parameter is already set up when
494: * a new and empty MimeMultipart object is created. <p>
495: *
496: * This method is called when the <code>saveChanges</code>
497: * method is invoked on the Message object containing this
498: * Multipart. This is typically done as part of the Message
499: * send process, however note that a client is free to call
500: * it any number of times. So if the header updating process is
501: * expensive for a specific MimeMultipart subclass, then it
502: * might itself want to track whether its internal state actually
503: * did change, and do the header updating only if necessary.
504: *
505: * @exception        MessagingException for failures
506: */
507: protected synchronized void updateHeaders() throws MessagingException {
508:         parse();
509:•        for (int i = 0; i < parts.size(); i++)
510:          ((MimeBodyPart)parts.elementAt(i)).updateHeaders();
511: }
512:
513: /**
514: * Iterates through all the parts and outputs each MIME part
515: * separated by a boundary.
516: */
517: @Override
518: public synchronized void writeTo(OutputStream os)
519:                                 throws IOException, MessagingException {
520:         parse();
521:         
522:         String boundary = "--" +
523:                 (new ContentType(contentType)).getParameter("boundary");
524: LineOutputStream los = streamProvider.outputLineStream(os, false);
525:         // if there's a preamble, write it out
526:•        if (preamble != null) {
527:          byte[] pb = MimeUtility.getBytes(preamble);
528:          los.write(pb);
529:          // make sure it ends with a newline
530:•         if (pb.length > 0 &&
531:                  !(pb[pb.length-1] == '\r' || pb[pb.length-1] == '\n')) {
532:                 los.writeln();
533:          }
534:          // XXX - could force a blank line before start boundary
535:         }
536:
537:•        if (parts.size() == 0) {
538:•         if (allowEmpty) {
539:                 // write out a single empty body part
540:                 los.writeln(boundary); // put out boundary
541:                 los.writeln(); // put out empty line
542:          } else {
543:                 throw new MessagingException("Empty multipart: " + contentType);
544:          }
545:         } else {
546:•         for (int i = 0; i < parts.size(); i++) {
547:                 los.writeln(boundary); // put out boundary
548:                 ((MimeBodyPart)parts.elementAt(i)).writeTo(os);
549:                 los.writeln(); // put out empty line
550:          }
551:         }
552:
553:         // put out last boundary
554:         los.writeln(boundary + "--");
555: }
556:
557: /**
558: * Parse the InputStream from our DataSource, constructing the
559: * appropriate MimeBodyParts. The <code>parsed</code> flag is
560: * set to true, and if true on entry nothing is done. This
561: * method is called by all other methods that need data for
562: * the body parts, to make sure the data has been parsed.
563: * The {@link #initializeProperties} method is called before
564: * parsing the data.
565: *
566: * @exception        ParseException for failures parsing the message
567: * @exception        MessagingException for other failures
568: * @since        JavaMail 1.2
569: */
570: protected synchronized void parse() throws MessagingException {
571:•        if (parsed)
572:          return;
573:
574:         initializeProperties();
575:
576:         InputStream in = null;
577:         SharedInputStream sin = null;
578:         long start = 0, end = 0;
579:
580:         try {
581:          in = ds.getInputStream();
582:•         if (!(in instanceof ByteArrayInputStream) &&
583:                 !(in instanceof BufferedInputStream) &&
584:                 !(in instanceof SharedInputStream))
585:                 in = new BufferedInputStream(in);
586:         } catch (Exception ex) {
587:          throw new MessagingException("No inputstream from datasource", ex);
588:         }
589:•        if (in instanceof SharedInputStream)
590:          sin = (SharedInputStream)in;
591:
592:         ContentType cType = new ContentType(contentType);
593:         String boundary = null;
594:•        if (!ignoreExistingBoundaryParameter) {
595:          String bp = cType.getParameter("boundary");
596:•         if (bp != null)
597:                 boundary = "--" + bp;
598:         }
599:•        if (boundary == null && !ignoreMissingBoundaryParameter &&
600:                 !ignoreExistingBoundaryParameter)
601:          throw new ParseException("Missing boundary parameter");
602:
603:         try {
604:          // Skip and save the preamble
605:                 LineInputStream lin = streamProvider.inputLineStream(in, false);
606:          StringBuilder preamblesb = null;
607:          String line;
608:•         while ((line = lin.readLine()) != null) {
609:                 /*
610:                  * Strip trailing whitespace. Can't use trim method
611:                  * because it's too aggressive. Some bogus MIME
612:                  * messages will include control characters in the
613:                  * boundary string.
614:                  */
615:                 int i;
616:•                for (i = line.length() - 1; i >= 0; i--) {
617:                  char c = line.charAt(i);
618:•                 if (!(c == ' ' || c == '\t'))
619:                         break;
620:                 }
621:                 line = line.substring(0, i + 1);
622:•                if (boundary != null) {
623:•                 if (line.equals(boundary))
624:                         break;
625:•                 if (line.length() == boundary.length() + 2 &&
626:•                         line.startsWith(boundary) && line.endsWith("--")) {
627:                         line = null;        // signal end of multipart
628:                         break;
629:                  }
630:                 } else {
631:                  /*
632:                  * Boundary hasn't been defined, does this line
633:                  * look like a boundary? If so, assume it is
634:                  * the boundary and save it.
635:                  */
636:•                 if (line.length() > 2 && line.startsWith("--")) {
637:•                        if (line.length() > 4 && allDashes(line)) {
638:                          /*
639:                          * The first boundary-like line we find is
640:                          * probably *not* the end-of-multipart boundary
641:                          * line. More likely it's a line full of dashes
642:                          * in the preamble text. Just keep reading.
643:                          */
644:                         } else {
645:                          boundary = line;
646:                          break;
647:                         }
648:                  }
649:                 }
650:
651:                 // save the preamble after skipping blank lines
652:•                if (line.length() > 0) {
653:                  // accumulate the preamble
654:•                 if (preamblesb == null)
655:                         preamblesb = new StringBuilder(line.length() + 2);
656:                  preamblesb.append(line).append(System.lineSeparator());
657:                 }
658:          }
659:
660:•         if (preamblesb != null)
661:                 preamble = preamblesb.toString();
662:
663:•         if (line == null) {
664:•                if (allowEmpty)
665:                  return;
666:                 else
667:                  throw new ParseException("Missing start boundary");
668:          }
669:
670:          // save individual boundary bytes for comparison later
671:          byte[] bndbytes = MimeUtility.getBytes(boundary);
672:          int bl = bndbytes.length;
673:
674:          /*
675:          * Compile Boyer-Moore parsing tables.
676:          */
677:
678:          // initialize Bad Character Shift table
679:          int[] bcs = new int[256];
680:•         for (int i = 0; i < bl; i++)
681:                 bcs[bndbytes[i] & 0xff] = i + 1;
682:
683:          // initialize Good Suffix Shift table
684:          int[] gss = new int[bl];
685:         NEXT:
686:•         for (int i = bl; i > 0; i--) {
687:                 int j;        // the beginning index of the suffix being considered
688:•                for (j = bl - 1; j >= i; j--) {
689:                  // Testing for good suffix
690:•                 if (bndbytes[j] == bndbytes[j - i]) {
691:                         // bndbytes[j..len] is a good suffix
692:                         gss[j - 1] = i;
693:                  } else {
694:                  // No match. The array has already been
695:                  // filled up with correct values before.
696:                  continue NEXT;
697:                  }
698:                 }
699:•                while (j > 0)
700:                  gss[--j] = i;
701:          }
702:          gss[bl - 1] = 1;
703:
704:          /*
705:          * Read and process body parts until we see the
706:          * terminating boundary line (or EOF).
707:          */
708:          boolean done = false;
709:         getparts:
710:•         while (!done) {
711:                 InternetHeaders headers = null;
712:•                if (sin != null) {
713:                  start = sin.getPosition();
714:                  // skip headers
715:•                 while ((line = lin.readLine()) != null && line.length() > 0)
716:                         
717:•                 if (line == null) {
718:•                        if (!ignoreMissingEndBoundary)
719:                          throw new ParseException(
720:                                         "missing multipart end boundary");
721:                         // assume there's just a missing end boundary
722:                         complete = false;
723:                         break getparts;
724:                  }
725:                 } else {
726:                  // collect the headers for this body part
727:                  headers = createInternetHeaders(in);
728:                 }
729:
730:•                if (!in.markSupported())
731:                  throw new MessagingException("Stream doesn't support mark");
732:
733:                 ByteArrayOutputStream buf = null;
734:                 // if we don't have a shared input stream, we copy the data
735:•                if (sin == null)
736:                  buf = new ByteArrayOutputStream();
737:                 else
738:                  end = sin.getPosition();
739:                 int b;
740:
741:                 /*
742:                  * These buffers contain the bytes we're checking
743:                  * for a match. inbuf is the current buffer and
744:                  * previnbuf is the previous buffer. We need the
745:                  * previous buffer to check that we're preceeded
746:                  * by an EOL.
747:                  */
748:                 // XXX - a smarter algorithm would use a sliding window
749:                 //         over a larger buffer
750:                 byte[] inbuf = new byte[bl];
751:                 byte[] previnbuf = new byte[bl];
752:                 int inSize = 0;                // number of valid bytes in inbuf
753:                 int prevSize = 0;        // number of valid bytes in previnbuf
754:                 int eolLen;
755:                 boolean first = true;
756:
757:                 /*
758:                  * Read and save the content bytes in buf.
759:                  */
760:                 for (;;) {
761:                  in.mark(bl + 4 + 1000); // bnd + "--\r\n" + lots of LWSP
762:                  eolLen = 0;
763:                  inSize = readFully(in, inbuf, 0, bl);
764:•                 if (inSize < bl) {
765:                         // hit EOF
766:•                        if (!ignoreMissingEndBoundary)
767:                          throw new ParseException(
768:                                         "missing multipart end boundary");
769:•                        if (sin != null)
770:                          end = sin.getPosition();
771:                         complete = false;
772:                         done = true;
773:                         break;
774:                  }
775:                  // check whether inbuf contains a boundary string
776:                  int i;
777:•                 for (i = bl - 1; i >= 0; i--) {
778:•                        if (inbuf[i] != bndbytes[i])
779:                          break;
780:                  }
781:•                 if (i < 0) {        // matched all bytes
782:                         eolLen = 0;
783:•                        if (!first) {
784:                          // working backwards, find out if we were preceeded
785:                          // by an EOL, and if so find its length
786:                          b = previnbuf[prevSize - 1];
787:•                         if (b == '\r' || b == '\n') {
788:                                 eolLen = 1;
789:•                                if (b == '\n' && prevSize >= 2) {
790:                                  b = previnbuf[prevSize - 2];
791:•                                 if (b == '\r')
792:                                         eolLen = 2;
793:                                 }
794:                          }
795:                         }
796:•                        if (first || eolLen > 0) {        // yes, preceed by EOL
797:•                         if (sin != null) {
798:                                 // update "end", in case this really is
799:                                 // a valid boundary
800:                                 end = sin.getPosition() - bl - eolLen;
801:                          }
802:                          // matched the boundary, check for last boundary
803:                          int b2 = in.read();
804:•                         if (b2 == '-') {
805:•                                if (in.read() == '-') {
806:                                  complete = true;
807:                                  done = true;
808:                                  break;        // ignore trailing text
809:                                 }
810:                          }
811:                          // skip linear whitespace
812:•                         while (b2 == ' ' || b2 == '\t')
813:                                 b2 = in.read();
814:                          // check for end of line
815:•                         if (b2 == '\n')
816:                                 break;        // got it! break out of the loop
817:•                         if (b2 == '\r') {
818:                                 in.mark(1);
819:•                                if (in.read() != '\n')
820:                                  in.reset();
821:                                 break;        // got it! break out of the loop
822:                          }
823:                         }
824:                         i = 0;
825:                  }
826:
827:                  /*
828:                  * Get here if boundary didn't match,
829:                  * wasn't preceeded by EOL, or wasn't
830:                  * followed by whitespace or EOL.
831:                  */
832:
833:                  // compute how many bytes we can skip
834:                  int skip = Math.max(i + 1 - bcs[inbuf[i] & 0xff], gss[i]);
835:                  // want to keep at least two characters
836:•                 if (skip < 2) {
837:                         // only skipping one byte, save one byte
838:                         // from previous buffer as well
839:                         // first, write out bytes we're done with
840:•                        if (sin == null && prevSize > 1)
841:                          buf.write(previnbuf, 0, prevSize - 1);
842:                         in.reset();
843:                         skipFully(in, 1);
844:•                        if (prevSize >= 1) {        // is there a byte to save?
845:                          // yes, save one from previous and one from current
846:                          previnbuf[0] = previnbuf[prevSize - 1];
847:                          previnbuf[1] = inbuf[0];
848:                          prevSize = 2;
849:                         } else {
850:                          // no previous bytes to save, can only save current
851:                          previnbuf[0] = inbuf[0];
852:                          prevSize = 1;
853:                         }
854:                  } else {
855:                         // first, write out data from previous buffer before
856:                         // we dump it
857:•                        if (prevSize > 0 && sin == null)
858:                          buf.write(previnbuf, 0, prevSize);
859:                         // all the bytes we're skipping are saved in previnbuf
860:                         prevSize = skip;
861:                         in.reset();
862:                         skipFully(in, prevSize);
863:                         // swap buffers
864:                         byte[] tmp = inbuf;
865:                         inbuf = previnbuf;
866:                         previnbuf = tmp;
867:                  }
868:                  first = false;
869:                 }
870:
871:                 /*
872:                  * Create a MimeBody element to represent this body part.
873:                  */
874:                 MimeBodyPart part;
875:•                if (sin != null) {
876:                  part = createMimeBodyPartIs(sin.newStream(start, end));
877:                 } else {
878:                  // write out data from previous buffer, not including EOL
879:•                 if (prevSize - eolLen > 0)
880:                         buf.write(previnbuf, 0, prevSize - eolLen);
881:                  // if we didn't find a trailing boundary,
882:                  // the current buffer has data we need too
883:•                 if (!complete && inSize > 0)
884:                         buf.write(inbuf, 0, inSize);
885:                  part = createMimeBodyPart(headers, buf.toByteArray());
886:                 }
887:                 super.addBodyPart(part);
888:          }
889:         } catch (IOException ioex) {
890:          throw new MessagingException("IO Error", ioex);
891:         } finally {
892:          try {
893:                 in.close();
894:          } catch (IOException cex) {
895:                 // ignore
896:          }
897:         }
898:
899:         parsed = true;
900: }
901:
902: /**
903: * Is the string all dashes ('-')?
904: */
905: private static boolean allDashes(String s) {
906:•        for (int i = 0; i < s.length(); i++) {
907:•         if (s.charAt(i) != '-')
908:                 return false;
909:         }
910:         return true;
911: }
912:
913: /**
914: * Read data from the input stream to fill the buffer starting
915: * at the specified offset with the specified number of bytes.
916: * If len is zero, return zero. If at EOF, return -1. Otherwise,
917: * return the number of bytes read. Call the read method on the
918: * input stream as many times as necessary to read len bytes.
919: *
920: * @param        in        InputStream to read from
921: * @param        buf        buffer to read into
922: * @param        off        offset in the buffer for first byte
923: * @param        len        number of bytes to read
924: * @return                -1 on EOF, otherwise number of bytes read
925: * @exception        IOException        on I/O errors
926: */
927: private static int readFully(InputStream in, byte[] buf, int off, int len)
928:                                 throws IOException {
929:•        if (len == 0)
930:          return 0;
931:         int total = 0;
932:•        while (len > 0) {
933:          int bsize = in.read(buf, off, len);
934:•         if (bsize <= 0)        // should never be zero
935:                 break;
936:          off += bsize;
937:          total += bsize;
938:          len -= bsize;
939:         }
940:•        return total > 0 ? total : -1;
941: }
942:
943: /**
944: * Skip the specified number of bytes, repeatedly calling
945: * the skip method as necessary.
946: */
947: private void skipFully(InputStream in, long offset) throws IOException {
948:•        while (offset > 0) {
949:          long cur = in.skip(offset);
950:•         if (cur <= 0)
951:                 throw new EOFException("can't skip");
952:          offset -= cur;
953:         }
954: }
955:
956: /**
957: * Create and return an InternetHeaders object that loads the
958: * headers from the given InputStream. Subclasses can override
959: * this method to return a subclass of InternetHeaders, if
960: * necessary. This implementation simply constructs and returns
961: * an InternetHeaders object.
962: *
963: * @param        is        the InputStream to read the headers from
964: * @return        an InternetHeaders object
965: * @exception         MessagingException for failures
966: * @since                JavaMail 1.2
967: */
968: protected InternetHeaders createInternetHeaders(InputStream is)
969:                                 throws MessagingException {
970:         return new InternetHeaders(is);
971: }
972:
973: /**
974: * Create and return a MimeBodyPart object to represent a
975: * body part parsed from the InputStream. Subclasses can override
976: * this method to return a subclass of MimeBodyPart, if
977: * necessary. This implementation simply constructs and returns
978: * a MimeBodyPart object.
979: *
980: * @param        headers                the headers for the body part
981: * @param        content                the content of the body part
982: * @return        a MimeBodyPart
983: * @exception                 MessagingException for failures
984: * @since                        JavaMail 1.2
985: */
986: protected MimeBodyPart createMimeBodyPart(InternetHeaders headers,
987:                                 byte[] content) throws MessagingException {
988:         return new MimeBodyPart(headers, content);
989: }
990:
991: /**
992: * Create and return a MimeBodyPart object to represent a
993: * body part parsed from the InputStream. Subclasses can override
994: * this method to return a subclass of MimeBodyPart, if
995: * necessary. This implementation simply constructs and returns
996: * a MimeBodyPart object.
997: *
998: * @param        is                InputStream containing the body part
999: * @return        a MimeBodyPart
1000: * @exception                 MessagingException for failures
1001: * @since                        JavaMail 1.2
1002: */
1003: protected MimeBodyPart createMimeBodyPart(InputStream is)
1004:                                 throws MessagingException {
1005:         return new MimeBodyPart(is);
1006: }
1007:
1008: private MimeBodyPart createMimeBodyPartIs(InputStream is)
1009:                                 throws MessagingException {
1010:         try {
1011:          return createMimeBodyPart(is);
1012:         } finally {
1013:          try {
1014:                 is.close();
1015:          } catch (IOException ex) {
1016:                 // ignore it
1017:          }
1018:         }
1019: }
1020: }