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