2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common Development
8
* and Distribution License("CDDL") (collectively, the "License"). You
9
* may not use this file except in compliance with the License. You can obtain
10
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
11
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
12
* language governing permissions and limitations under the License.
14
* When distributing the software, include this License Header Notice in each
15
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
16
* Sun designates this particular file as subject to the "Classpath" exception
17
* as provided by Sun in the GPL Version 2 section of the License file that
18
* accompanied this code. If applicable, add the following below the License
19
* Header, with the fields enclosed by brackets [] replaced by your own
20
* identifying information: "Portions Copyrighted [year]
21
* [name of copyright owner]"
25
* If you wish your version of this file to be governed by only the CDDL or
26
* only the GPL Version 2, indicate your decision by adding "[Contributor]
27
* elects to include this software in this distribution under the [CDDL or GPL
28
* Version 2] license." If you don't indicate a single choice of license, a
29
* recipient has the option to distribute your version of this file under
30
* either the CDDL, the GPL Version 2 or to extend the choice of license to
31
* its licensees as provided above. However, if you add GPL Version 2 code
32
* and therefore, elected the GPL Version 2 license, then the option applies
33
* only if the new code is made subject to such option by the copyright
37
package javax.mail.internet;
40
import javax.activation.*;
43
import com.sun.mail.util.LineOutputStream;
44
import com.sun.mail.util.LineInputStream;
45
import com.sun.mail.util.ASCIIUtility;
46
import com.sun.mail.util.PropUtil;
49
* The MimeMultipart class is an implementation of the abstract Multipart
50
* class that uses MIME conventions for the multipart data. <p>
52
* A MimeMultipart is obtained from a MimePart whose primary type
53
* is "multipart" (by invoking the part's <code>getContent()</code> method)
54
* or it can be created by a client as part of creating a new MimeMessage. <p>
56
* The default multipart subtype is "mixed". The other multipart
57
* subtypes, such as "alternative", "related", and so on, can be
58
* implemented as subclasses of MimeMultipart with additional methods
59
* to implement the additional semantics of that type of multipart
60
* content. The intent is that service providers, mail JavaBean writers
61
* and mail clients will write many such subclasses and their Command
62
* Beans, and will install them into the JavaBeans Activation
63
* Framework, so that any JavaMail implementation and its clients can
64
* transparently find and use these classes. Thus, a MIME multipart
65
* handler is treated just like any other type handler, thereby
66
* decoupling the process of providing multipart handlers from the
67
* JavaMail API. Lacking these additional MimeMultipart subclasses,
68
* all subtypes of MIME multipart data appear as MimeMultipart objects. <p>
70
* An application can directly construct a MIME multipart object of any
71
* subtype by using the <code>MimeMultipart(String subtype)</code>
72
* constructor. For example, to create a "multipart/alternative" object,
73
* use <code>new MimeMultipart("alternative")</code>. <p>
75
* The <code>mail.mime.multipart.ignoremissingendboundary</code>
76
* property may be set to <code>false</code> to cause a
77
* <code>MessagingException</code> to be thrown if the multipart
78
* data does not end with the required end boundary line. If this
79
* property is set to <code>true</code> or not set, missing end
80
* boundaries are not considered an error and the final body part
81
* ends at the end of the data. <p>
83
* The <code>mail.mime.multipart.ignoremissingboundaryparameter</code>
84
* System property may be set to <code>false</code> to cause a
85
* <code>MessagingException</code> to be thrown if the Content-Type
86
* of the MimeMultipart does not include a <code>boundary</code> parameter.
87
* If this property is set to <code>true</code> or not set, the multipart
88
* parsing code will look for a line that looks like a bounary line and
89
* use that as the boundary separating the parts. <p>
91
* The current implementation also supports the following properties: <p>
93
* The <code>mail.mime.multipart.ignoreexistingboundaryparameter</code>
94
* System property may be set to <code>true</code> to cause any boundary
95
* to be ignored and instead search for a boundary line in the message
96
* as with <code>mail.mime.multipart.ignoremissingboundaryparameter</code>. <p>
98
* Normally, when writing out a MimeMultipart that contains no body
99
* parts, or when trying to parse a multipart message with no body parts,
100
* a <code>MessagingException</code> is thrown. The MIME spec does not allow
101
* multipart content with no body parts. The
102
* <code>mail.mime.multipart.allowempty</code> System property may be set to
103
* <code>true</code> to override this behavior.
104
* When writing out such a MimeMultipart, a single empty part will be
105
* included. When reading such a multipart, a MimeMultipart will be created
106
* with no body parts.
109
* @author Bill Shannon
113
public class MimeMultipart extends Multipart {
116
* The DataSource supplying our InputStream.
118
protected DataSource ds = null;
121
* Have we parsed the data from our InputStream yet?
122
* Defaults to true; set to false when our constructor is
123
* given a DataSource with an InputStream that we need to
126
protected boolean parsed = true;
129
* Have we seen the final bounary line?
131
private boolean complete = true;
134
* The MIME multipart preamble text, the text that
135
* occurs before the first boundary line.
137
private String preamble = null;
140
* Flags to control parsing, initialized from System properties
141
* in the parse() method.
143
private boolean ignoreMissingEndBoundary = true;
144
private boolean ignoreMissingBoundaryParameter = true;
145
private boolean ignoreExistingBoundaryParameter = false;
146
private boolean allowEmpty = false;
147
private boolean bmparse = true;
150
* Default constructor. An empty MimeMultipart object
151
* is created. Its content type is set to "multipart/mixed".
152
* A unique boundary string is generated and this string is
153
* setup as the "boundary" parameter for the
154
* <code>contentType</code> field. <p>
156
* MimeBodyParts may be added later.
158
public MimeMultipart() {
163
* Construct a MimeMultipart object of the given subtype.
164
* A unique boundary string is generated and this string is
165
* setup as the "boundary" parameter for the
166
* <code>contentType</code> field. <p>
168
* MimeBodyParts may be added later.
170
public MimeMultipart(String subtype) {
173
* Compute a boundary string.
175
String boundary = UniqueValue.getUniqueBoundaryValue();
176
ContentType cType = new ContentType("multipart", subtype, null);
177
cType.setParameter("boundary", boundary);
178
contentType = cType.toString();
182
* Constructs a MimeMultipart object and its bodyparts from the
183
* given DataSource. <p>
185
* This constructor handles as a special case the situation where the
186
* given DataSource is a MultipartDataSource object. In this case, this
187
* method just invokes the superclass (i.e., Multipart) constructor
188
* that takes a MultipartDataSource object. <p>
190
* Otherwise, the DataSource is assumed to provide a MIME multipart
191
* byte stream. The <code>parsed</code> flag is set to false. When
192
* the data for the body parts are needed, the parser extracts the
193
* "boundary" parameter from the content type of this DataSource,
194
* skips the 'preamble' and reads bytes till the terminating
195
* boundary and creates MimeBodyParts for each part of the stream.
197
* @param ds DataSource, can be a MultipartDataSource
199
public MimeMultipart(DataSource ds) throws MessagingException {
202
if (ds instanceof MessageAware) {
203
MessageContext mc = ((MessageAware)ds).getMessageContext();
204
setParent(mc.getPart());
207
if (ds instanceof MultipartDataSource) {
208
// ask super to do this for us.
209
setMultipartDataSource((MultipartDataSource)ds);
213
// 'ds' was not a MultipartDataSource, we have
214
// to parse this ourself.
217
contentType = ds.getContentType();
221
* Set the subtype. This method should be invoked only on a new
222
* MimeMultipart object created by the client. The default subtype
223
* of such a multipart object is "mixed". <p>
225
* @param subtype Subtype
227
public synchronized void setSubType(String subtype)
228
throws MessagingException {
229
ContentType cType = new ContentType(contentType);
230
cType.setSubType(subtype);
231
contentType = cType.toString();
235
* Return the number of enclosed BodyPart objects.
237
* @return number of parts
239
public synchronized int getCount() throws MessagingException {
241
return super.getCount();
245
* Get the specified BodyPart. BodyParts are numbered starting at 0.
247
* @param index the index of the desired BodyPart
249
* @exception MessagingException if no such BodyPart exists
251
public synchronized BodyPart getBodyPart(int index)
252
throws MessagingException {
254
return super.getBodyPart(index);
258
* Get the MimeBodyPart referred to by the given ContentID (CID).
259
* Returns null if the part is not found.
261
* @param CID the ContentID of the desired part
264
public synchronized BodyPart getBodyPart(String CID)
265
throws MessagingException {
268
int count = getCount();
269
for (int i = 0; i < count; i++) {
270
MimeBodyPart part = (MimeBodyPart)getBodyPart(i);
271
String s = part.getContentID();
272
if (s != null && s.equals(CID))
279
* Remove the specified part from the multipart message.
280
* Shifts all the parts after the removed part down one.
282
* @param part The part to remove
283
* @return true if part removed, false otherwise
284
* @exception MessagingException if no such Part exists
285
* @exception IllegalWriteException if the underlying
286
* implementation does not support modification
289
public boolean removeBodyPart(BodyPart part) throws MessagingException {
291
return super.removeBodyPart(part);
295
* Remove the part at specified location (starting from 0).
296
* Shifts all the parts after the removed part down one.
298
* @param index Index of the part to remove
299
* @exception MessagingException
300
* @exception IndexOutOfBoundsException if the given index
302
* @exception IllegalWriteException if the underlying
303
* implementation does not support modification
306
public void removeBodyPart(int index) throws MessagingException {
308
super.removeBodyPart(index);
312
* Adds a Part to the multipart. The BodyPart is appended to
313
* the list of existing Parts.
315
* @param part The Part to be appended
316
* @exception MessagingException
317
* @exception IllegalWriteException if the underlying
318
* implementation does not support modification
321
public synchronized void addBodyPart(BodyPart part)
322
throws MessagingException {
324
super.addBodyPart(part);
328
* Adds a BodyPart at position <code>index</code>.
329
* If <code>index</code> is not the last one in the list,
330
* the subsequent parts are shifted up. If <code>index</code>
331
* is larger than the number of parts present, the
332
* BodyPart is appended to the end.
334
* @param part The BodyPart to be inserted
335
* @param index Location where to insert the part
336
* @exception MessagingException
337
* @exception IllegalWriteException if the underlying
338
* implementation does not support modification
341
public synchronized void addBodyPart(BodyPart part, int index)
342
throws MessagingException {
344
super.addBodyPart(part, index);
348
* Return true if the final boundary line for this
349
* multipart was seen. When parsing multipart content,
350
* this class will (by default) terminate parsing with
351
* no error if the end of input is reached before seeing
352
* the final multipart boundary line. In such a case,
353
* this method will return false. (If the System property
354
* "mail.mime.multipart.ignoremissingendboundary" is set to
355
* false, parsing such a message will instead throw a
356
* MessagingException.)
358
* @return true if the final boundary line was seen
359
* @since JavaMail 1.4
361
public synchronized boolean isComplete() throws MessagingException {
367
* Get the preamble text, if any, that appears before the
368
* first body part of this multipart. Some protocols,
369
* such as IMAP, will not allow access to the preamble text.
371
* @return the preamble text, or null if no preamble
372
* @since JavaMail 1.4
374
public synchronized String getPreamble() throws MessagingException {
380
* Set the preamble text to be included before the first
381
* body part. Applications should generally not include
382
* any preamble text. In some cases it may be helpful to
383
* include preamble text with instructions for users of
384
* pre-MIME software. The preamble text should be complete
385
* lines, including newlines.
387
* @param preamble the preamble text
388
* @since JavaMail 1.4
390
public synchronized void setPreamble(String preamble)
391
throws MessagingException {
392
this.preamble = preamble;
396
* Update headers. The default implementation here just
397
* calls the <code>updateHeaders</code> method on each of its
398
* children BodyParts. <p>
400
* Note that the boundary parameter is already set up when
401
* a new and empty MimeMultipart object is created. <p>
403
* This method is called when the <code>saveChanges</code>
404
* method is invoked on the Message object containing this
405
* Multipart. This is typically done as part of the Message
406
* send process, however note that a client is free to call
407
* it any number of times. So if the header updating process is
408
* expensive for a specific MimeMultipart subclass, then it
409
* might itself want to track whether its internal state actually
410
* did change, and do the header updating only if necessary.
412
protected synchronized void updateHeaders() throws MessagingException {
414
for (int i = 0; i < parts.size(); i++)
415
((MimeBodyPart)parts.elementAt(i)).updateHeaders();
419
* Iterates through all the parts and outputs each MIME part
420
* separated by a boundary.
422
public synchronized void writeTo(OutputStream os)
423
throws IOException, MessagingException {
426
String boundary = "--" +
427
(new ContentType(contentType)).getParameter("boundary");
428
LineOutputStream los = new LineOutputStream(os);
430
// if there's a preamble, write it out
431
if (preamble != null) {
432
byte[] pb = ASCIIUtility.getBytes(preamble);
434
// make sure it ends with a newline
436
!(pb[pb.length-1] == '\r' || pb[pb.length-1] == '\n')) {
439
// XXX - could force a blank line before start boundary
442
if (parts.size() == 0) {
444
// write out a single empty body part
445
los.writeln(boundary); // put out boundary
446
los.writeln(); // put out empty line
448
throw new MessagingException("Empty multipart: " + contentType);
451
for (int i = 0; i < parts.size(); i++) {
452
los.writeln(boundary); // put out boundary
453
((MimeBodyPart)parts.elementAt(i)).writeTo(os);
454
los.writeln(); // put out empty line
458
// put out last boundary
459
los.writeln(boundary + "--");
463
* Parse the InputStream from our DataSource, constructing the
464
* appropriate MimeBodyParts. The <code>parsed</code> flag is
465
* set to true, and if true on entry nothing is done. This
466
* method is called by all other methods that need data for
467
* the body parts, to make sure the data has been parsed.
469
* @since JavaMail 1.2
471
protected synchronized void parse() throws MessagingException {
475
// read properties that control parsing
478
ignoreMissingEndBoundary = PropUtil.getBooleanSystemProperty(
479
"mail.mime.multipart.ignoremissingendboundary", true);
481
ignoreMissingBoundaryParameter = PropUtil.getBooleanSystemProperty(
482
"mail.mime.multipart.ignoremissingboundaryparameter", true);
484
ignoreExistingBoundaryParameter = PropUtil.getBooleanSystemProperty(
485
"mail.mime.multipart.ignoreexistingboundaryparameter", false);
487
allowEmpty = PropUtil.getBooleanSystemProperty(
488
"mail.mime.multipart.allowempty", false);
490
bmparse = PropUtil.getBooleanSystemProperty(
491
"mail.mime.multipart.bmparse", true);
498
InputStream in = null;
499
SharedInputStream sin = null;
500
long start = 0, end = 0;
503
in = ds.getInputStream();
504
if (!(in instanceof ByteArrayInputStream) &&
505
!(in instanceof BufferedInputStream) &&
506
!(in instanceof SharedInputStream))
507
in = new BufferedInputStream(in);
508
} catch (Exception ex) {
509
throw new MessagingException("No inputstream from datasource", ex);
511
if (in instanceof SharedInputStream)
512
sin = (SharedInputStream)in;
514
ContentType cType = new ContentType(contentType);
515
String boundary = null;
516
if (!ignoreExistingBoundaryParameter) {
517
String bp = cType.getParameter("boundary");
519
boundary = "--" + bp;
521
if (boundary == null && !ignoreMissingBoundaryParameter &&
522
!ignoreExistingBoundaryParameter)
523
throw new MessagingException("Missing boundary parameter");
526
// Skip and save the preamble
527
LineInputStream lin = new LineInputStream(in);
528
StringBuffer preamblesb = null;
530
String lineSeparator = null;
531
while ((line = lin.readLine()) != null) {
533
* Strip trailing whitespace. Can't use trim method
534
* because it's too aggressive. Some bogus MIME
535
* messages will include control characters in the
539
for (i = line.length() - 1; i >= 0; i--) {
540
char c = line.charAt(i);
541
if (!(c == ' ' || c == '\t'))
544
line = line.substring(0, i + 1);
545
if (boundary != null) {
546
if (line.equals(boundary))
548
if (line.length() == boundary.length() + 2 &&
549
line.startsWith(boundary) && line.endsWith("--")) {
550
line = null; // signal end of multipart
555
* Boundary hasn't been defined, does this line
556
* look like a boundary? If so, assume it is
557
* the boundary and save it.
559
if (line.startsWith("--")) {
560
if (line.endsWith("--")) {
562
* The first boundary-like line we find is
563
* probably *not* the end-of-multipart boundary
564
* line. More likely it's a line full of dashes
565
* in the preamble text. Just keep reading.
574
// save the preamble after skipping blank lines
575
if (line.length() > 0) {
576
// if we haven't figured out what the line separator
578
if (lineSeparator == null) {
581
System.getProperty("line.separator", "\n");
582
} catch (SecurityException ex) {
583
lineSeparator = "\n";
586
// accumulate the preamble
587
if (preamblesb == null)
588
preamblesb = new StringBuffer(line.length() + 2);
589
preamblesb.append(line).append(lineSeparator);
593
if (preamblesb != null)
594
preamble = preamblesb.toString();
600
throw new MessagingException("Missing start boundary");
603
// save individual boundary bytes for easy comparison later
604
byte[] bndbytes = ASCIIUtility.getBytes(boundary);
605
int bl = bndbytes.length;
608
* Read and process body parts until we see the
609
* terminating boundary line (or EOF).
611
boolean done = false;
614
InternetHeaders headers = null;
616
start = sin.getPosition();
618
while ((line = lin.readLine()) != null && line.length() > 0)
621
if (!ignoreMissingEndBoundary)
622
throw new MessagingException(
623
"missing multipart end boundary");
624
// assume there's just a missing end boundary
629
// collect the headers for this body part
630
headers = createInternetHeaders(in);
633
if (!in.markSupported())
634
throw new MessagingException("Stream doesn't support mark");
636
ByteArrayOutputStream buf = null;
637
// if we don't have a shared input stream, we copy the data
639
buf = new ByteArrayOutputStream();
641
end = sin.getPosition();
643
boolean bol = true; // beginning of line flag
644
// the two possible end of line characters
645
int eol1 = -1, eol2 = -1;
648
* Read and save the content bytes in buf.
653
* At the beginning of a line, check whether the
654
* next line is a boundary.
657
in.mark(bl + 4 + 1000); // bnd + "--\r\n" + lots of LWSP
658
// read bytes, matching against the boundary
659
for (i = 0; i < bl; i++)
660
if (in.read() != (bndbytes[i] & 0xff))
663
// matched the boundary, check for last boundary
666
if (in.read() == '-') {
669
break; // ignore trailing text
672
// skip linear whitespace
673
while (b2 == ' ' || b2 == '\t')
675
// check for end of line
677
break; // got it! break out of the loop
680
if (in.read() != '\n')
682
break; // got it! break out of the loop
685
// failed to match, reset and proceed normally
688
// if this is not the first line, write out the
689
// end of line characters from the previous line
690
if (buf != null && eol1 != -1) {
698
// read the next byte
699
if ((b = in.read()) < 0) {
700
if (!ignoreMissingEndBoundary)
701
throw new MessagingException(
702
"missing multipart end boundary");
709
* If we're at the end of the line, save the eol characters
710
* to be written out before the beginning of the next line.
712
if (b == '\r' || b == '\n') {
715
end = sin.getPosition() - 1;
719
if ((b = in.read()) == '\n')
732
* Create a MimeBody element to represent this body part.
736
part = createMimeBodyPart(sin.newStream(start, end));
738
part = createMimeBodyPart(headers, buf.toByteArray());
739
super.addBodyPart(part);
741
} catch (IOException ioex) {
742
throw new MessagingException("IO Error", ioex);
746
} catch (IOException cex) {
755
* Parse the InputStream from our DataSource, constructing the
756
* appropriate MimeBodyParts. The <code>parsed</code> flag is
757
* set to true, and if true on entry nothing is done. This
758
* method is called by all other methods that need data for
759
* the body parts, to make sure the data has been parsed.
761
* @since JavaMail 1.2
764
* Boyer-Moore version of parser. Keep both versions around
765
* until we're sure this new one works.
767
private synchronized void parsebm() throws MessagingException {
771
InputStream in = null;
772
SharedInputStream sin = null;
773
long start = 0, end = 0;
776
in = ds.getInputStream();
777
if (!(in instanceof ByteArrayInputStream) &&
778
!(in instanceof BufferedInputStream) &&
779
!(in instanceof SharedInputStream))
780
in = new BufferedInputStream(in);
781
} catch (Exception ex) {
782
throw new MessagingException("No inputstream from datasource", ex);
784
if (in instanceof SharedInputStream)
785
sin = (SharedInputStream)in;
787
ContentType cType = new ContentType(contentType);
788
String boundary = null;
789
if (!ignoreExistingBoundaryParameter) {
790
String bp = cType.getParameter("boundary");
792
boundary = "--" + bp;
794
if (boundary == null && !ignoreMissingBoundaryParameter &&
795
!ignoreExistingBoundaryParameter)
796
throw new MessagingException("Missing boundary parameter");
799
// Skip and save the preamble
800
LineInputStream lin = new LineInputStream(in);
801
StringBuffer preamblesb = null;
803
String lineSeparator = null;
804
while ((line = lin.readLine()) != null) {
806
* Strip trailing whitespace. Can't use trim method
807
* because it's too aggressive. Some bogus MIME
808
* messages will include control characters in the
812
for (i = line.length() - 1; i >= 0; i--) {
813
char c = line.charAt(i);
814
if (!(c == ' ' || c == '\t'))
817
line = line.substring(0, i + 1);
818
if (boundary != null) {
819
if (line.equals(boundary))
821
if (line.length() == boundary.length() + 2 &&
822
line.startsWith(boundary) && line.endsWith("--")) {
823
line = null; // signal end of multipart
828
* Boundary hasn't been defined, does this line
829
* look like a boundary? If so, assume it is
830
* the boundary and save it.
832
if (line.startsWith("--")) {
833
if (line.endsWith("--")) {
835
* The first boundary-like line we find is
836
* probably *not* the end-of-multipart boundary
837
* line. More likely it's a line full of dashes
838
* in the preamble text. Just keep reading.
847
// save the preamble after skipping blank lines
848
if (line.length() > 0) {
849
// if we haven't figured out what the line separator
851
if (lineSeparator == null) {
854
System.getProperty("line.separator", "\n");
855
} catch (SecurityException ex) {
856
lineSeparator = "\n";
859
// accumulate the preamble
860
if (preamblesb == null)
861
preamblesb = new StringBuffer(line.length() + 2);
862
preamblesb.append(line).append(lineSeparator);
866
if (preamblesb != null)
867
preamble = preamblesb.toString();
873
throw new MessagingException("Missing start boundary");
876
// save individual boundary bytes for comparison later
877
byte[] bndbytes = ASCIIUtility.getBytes(boundary);
878
int bl = bndbytes.length;
881
* Compile Boyer-Moore parsing tables.
884
// initialize Bad Character Shift table
885
int[] bcs = new int[256];
886
for (int i = 0; i < bl; i++)
887
bcs[bndbytes[i]] = i + 1;
889
// initialize Good Suffix Shift table
890
int[] gss = new int[bl];
892
for (int i = bl; i > 0; i--) {
893
int j; // the beginning index of the suffix being considered
894
for (j = bl - 1; j >= i; j--) {
895
// Testing for good suffix
896
if (bndbytes[j] == bndbytes[j - i]) {
897
// bndbytes[j..len] is a good suffix
900
// No match. The array has already been
901
// filled up with correct values before.
911
* Read and process body parts until we see the
912
* terminating boundary line (or EOF).
914
boolean done = false;
917
InternetHeaders headers = null;
919
start = sin.getPosition();
921
while ((line = lin.readLine()) != null && line.length() > 0)
924
if (!ignoreMissingEndBoundary)
925
throw new MessagingException(
926
"missing multipart end boundary");
927
// assume there's just a missing end boundary
932
// collect the headers for this body part
933
headers = createInternetHeaders(in);
936
if (!in.markSupported())
937
throw new MessagingException("Stream doesn't support mark");
939
ByteArrayOutputStream buf = null;
940
// if we don't have a shared input stream, we copy the data
942
buf = new ByteArrayOutputStream();
944
end = sin.getPosition();
948
* These buffers contain the bytes we're checking
949
* for a match. inbuf is the current buffer and
950
* previnbuf is the previous buffer. We need the
951
* previous buffer to check that we're preceeded
954
// XXX - a smarter algorithm would use a sliding window
955
// over a larger buffer
956
byte[] inbuf = new byte[bl];
957
byte[] previnbuf = new byte[bl];
958
int inSize = 0; // number of valid bytes in inbuf
959
int prevSize = 0; // number of valid bytes in previnbuf
961
boolean first = true;
964
* Read and save the content bytes in buf.
967
in.mark(bl + 4 + 1000); // bnd + "--\r\n" + lots of LWSP
969
inSize = readFully(in, inbuf, 0, bl);
972
if (!ignoreMissingEndBoundary)
973
throw new MessagingException(
974
"missing multipart end boundary");
976
end = sin.getPosition();
981
// check whether inbuf contains a boundary string
983
for (i = bl - 1; i >= 0; i--) {
984
if (inbuf[i] != bndbytes[i])
987
if (i < 0) { // matched all bytes
990
// working backwards, find out if we were preceeded
991
// by an EOL, and if so find its length
992
b = previnbuf[prevSize - 1];
993
if (b == '\r' || b == '\n') {
995
if (b == '\n' && prevSize >= 2) {
996
b = previnbuf[prevSize - 2];
1002
if (first || eolLen > 0) { // yes, preceed by EOL
1004
// update "end", in case this really is
1006
end = sin.getPosition() - bl - eolLen;
1008
// matched the boundary, check for last boundary
1011
if (in.read() == '-') {
1014
break; // ignore trailing text
1017
// skip linear whitespace
1018
while (b2 == ' ' || b2 == '\t')
1020
// check for end of line
1022
break; // got it! break out of the loop
1025
if (in.read() != '\n')
1027
break; // got it! break out of the loop
1034
* Get here if boundary didn't match,
1035
* wasn't preceeded by EOL, or wasn't
1036
* followed by whitespace or EOL.
1039
// compute how many bytes we can skip
1040
int skip = Math.max(i + 1 - bcs[inbuf[i] & 0x7f], gss[i]);
1041
// want to keep at least two characters
1043
// only skipping one byte, save one byte
1044
// from previous buffer as well
1045
// first, write out bytes we're done with
1046
if (sin == null && prevSize > 1)
1047
buf.write(previnbuf, 0, prevSize - 1);
1050
if (prevSize >= 1) { // is there a byte to save?
1051
// yes, save one from previous and one from current
1052
previnbuf[0] = previnbuf[prevSize - 1];
1053
previnbuf[1] = inbuf[0];
1056
// no previous bytes to save, can only save current
1057
previnbuf[0] = inbuf[0];
1061
// first, write out data from previous buffer before
1063
if (prevSize > 0 && sin == null)
1064
buf.write(previnbuf, 0, prevSize);
1065
// all the bytes we're skipping are saved in previnbuf
1068
skipFully(in, prevSize);
1078
* Create a MimeBody element to represent this body part.
1082
part = createMimeBodyPart(sin.newStream(start, end));
1084
// write out data from previous buffer, not including EOL
1085
if (prevSize - eolLen > 0)
1086
buf.write(previnbuf, 0, prevSize - eolLen);
1087
// if we didn't find a trailing boundary,
1088
// the current buffer has data we need too
1089
if (!complete && inSize > 0)
1090
buf.write(inbuf, 0, inSize);
1091
part = createMimeBodyPart(headers, buf.toByteArray());
1093
super.addBodyPart(part);
1095
} catch (IOException ioex) {
1096
throw new MessagingException("IO Error", ioex);
1100
} catch (IOException cex) {
1109
* Read data from the input stream to fill the buffer starting
1110
* at the specified offset with the specified number of bytes.
1111
* If len is zero, return zero. If at EOF, return -1. Otherwise,
1112
* return the number of bytes read. Call the read method on the
1113
* input stream as many times as necessary to read len bytes.
1115
* @param in InputStream to read from
1116
* @param buf buffer to read into
1117
* @param off offset in the buffer for first byte
1118
* @param len number of bytes to read
1119
* @return -1 on EOF, otherwise number of bytes read
1120
* @exception IOException on I/O errors
1122
private static int readFully(InputStream in, byte[] buf, int off, int len)
1123
throws IOException {
1128
int bsize = in.read(buf, off, len);
1129
if (bsize <= 0) // should never be zero
1135
return total > 0 ? total : -1;
1139
* Skip the specified number of bytes, repeatedly calling
1140
* the skip method as necessary.
1142
private void skipFully(InputStream in, long offset) throws IOException {
1143
while (offset > 0) {
1144
long cur = in.skip(offset);
1146
throw new EOFException("can't skip");
1152
* Create and return an InternetHeaders object that loads the
1153
* headers from the given InputStream. Subclasses can override
1154
* this method to return a subclass of InternetHeaders, if
1155
* necessary. This implementation simply constructs and returns
1156
* an InternetHeaders object.
1158
* @param is the InputStream to read the headers from
1159
* @exception MessagingException
1160
* @since JavaMail 1.2
1162
protected InternetHeaders createInternetHeaders(InputStream is)
1163
throws MessagingException {
1164
return new InternetHeaders(is);
1168
* Create and return a MimeBodyPart object to represent a
1169
* body part parsed from the InputStream. Subclasses can override
1170
* this method to return a subclass of MimeBodyPart, if
1171
* necessary. This implementation simply constructs and returns
1172
* a MimeBodyPart object.
1174
* @param headers the headers for the body part
1175
* @param content the content of the body part
1176
* @exception MessagingException
1177
* @since JavaMail 1.2
1179
protected MimeBodyPart createMimeBodyPart(InternetHeaders headers,
1180
byte[] content) throws MessagingException {
1181
return new MimeBodyPart(headers, content);
1185
* Create and return a MimeBodyPart object to represent a
1186
* body part parsed from the InputStream. Subclasses can override
1187
* this method to return a subclass of MimeBodyPart, if
1188
* necessary. This implementation simply constructs and returns
1189
* a MimeBodyPart object.
1191
* @param is InputStream containing the body part
1192
* @exception MessagingException
1193
* @since JavaMail 1.2
1195
protected MimeBodyPart createMimeBodyPart(InputStream is)
1196
throws MessagingException {
1197
return new MimeBodyPart(is);