Thu, 31 Aug 2017 15:18:52 +0800
merge
1 /*
2 * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 /*
27 * @(#)MimeMultipart.java 1.31 03/01/29
28 */
32 package com.sun.xml.internal.messaging.saaj.packaging.mime.internet;
34 import java.io.*;
35 import java.util.BitSet;
37 import javax.activation.DataSource;
39 import com.sun.xml.internal.messaging.saaj.packaging.mime.*;
40 import com.sun.xml.internal.messaging.saaj.packaging.mime.util.*;
42 import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
43 import com.sun.xml.internal.messaging.saaj.util.FinalArrayList;
45 /**
46 * The MimeMultipart class is an implementation of the abstract Multipart
47 * class that uses MIME conventions for the multipart data. <p>
48 *
49 * A MimeMultipart is obtained from a MimePart whose primary type
50 * is "multipart" (by invoking the part's <code>getContent()</code> method)
51 * or it can be created by a client as part of creating a new MimeMessage. <p>
52 *
53 * The default multipart subtype is "mixed". The other multipart
54 * subtypes, such as "alternative", "related", and so on, can be
55 * implemented as subclasses of MimeMultipart with additional methods
56 * to implement the additional semantics of that type of multipart
57 * content. The intent is that service providers, mail JavaBean writers
58 * and mail clients will write many such subclasses and their Command
59 * Beans, and will install them into the JavaBeans Activation
60 * Framework, so that any JavaMail implementation and its clients can
61 * transparently find and use these classes. Thus, a MIME multipart
62 * handler is treated just like any other type handler, thereby
63 * decoupling the process of providing multipart handlers from the
64 * JavaMail API. Lacking these additional MimeMultipart subclasses,
65 * all subtypes of MIME multipart data appear as MimeMultipart objects. <p>
66 *
67 * An application can directly construct a MIME multipart object of any
68 * subtype by using the <code>MimeMultipart(String subtype)</code>
69 * constructor. For example, to create a "multipart/alternative" object,
70 * use <code>new MimeMultipart("alternative")</code>.
71 *
72 */
74 //TODO: cleanup the SharedInputStream handling
75 public class BMMimeMultipart extends MimeMultipart {
77 /*
78 * When true it indicates parsing hasnt been done at all
79 */
80 private boolean begining = true;
82 int[] bcs = new int[256];
83 int[] gss = null;
84 private static final int BUFFER_SIZE = 4096;
85 private byte[] buffer = new byte[BUFFER_SIZE];
86 private byte[] prevBuffer = new byte[BUFFER_SIZE];
87 private BitSet lastPartFound = new BitSet(1);
89 // cached inputstream which is possibly partially consumed
90 private InputStream in = null;
91 private String boundary = null;
92 // current stream position, set to -1 on EOF
93 int b = 0;
95 // property to indicate if lazyAttachments is ON
96 private boolean lazyAttachments = false;
98 /**
99 * Default constructor. An empty MimeMultipart object
100 * is created. Its content type is set to "multipart/mixed".
101 * A unique boundary string is generated and this string is
102 * setup as the "boundary" parameter for the
103 * <code>contentType</code> field. <p>
104 *
105 * MimeBodyParts may be added later.
106 */
107 public BMMimeMultipart() {
108 super();
109 //this("mixed");
110 }
112 /**
113 * Construct a MimeMultipart object of the given subtype.
114 * A unique boundary string is generated and this string is
115 * setup as the "boundary" parameter for the
116 * <code>contentType</code> field. <p>
117 *
118 * MimeBodyParts may be added later.
119 */
120 public BMMimeMultipart(String subtype) {
121 super(subtype);
122 /*
123 * Compute a boundary string.
124 String boundary = UniqueValue.getUniqueBoundaryValue();
125 ContentType cType = new ContentType("multipart", subtype, null);
126 contentType.setParameter("boundary", boundary);
127 */
128 }
130 /**
131 * Constructs a MimeMultipart object and its bodyparts from the
132 * given DataSource. <p>
133 *
134 * This constructor handles as a special case the situation where the
135 * given DataSource is a MultipartDataSource object. In this case, this
136 * method just invokes the superclass (i.e., Multipart) constructor
137 * that takes a MultipartDataSource object. <p>
138 *
139 * Otherwise, the DataSource is assumed to provide a MIME multipart
140 * byte stream. The <code>parsed</code> flag is set to false. When
141 * the data for the body parts are needed, the parser extracts the
142 * "boundary" parameter from the content type of this DataSource,
143 * skips the 'preamble' and reads bytes till the terminating
144 * boundary and creates MimeBodyParts for each part of the stream.
145 *
146 * @param ds DataSource, can be a MultipartDataSource
147 */
148 public BMMimeMultipart(DataSource ds, ContentType ct)
149 throws MessagingException {
150 super(ds,ct);
151 boundary = ct.getParameter("boundary");
152 /*
153 if (ds instanceof MultipartDataSource) {
154 // ask super to do this for us.
155 setMultipartDataSource((MultipartDataSource)ds);
156 return;
157 }
159 // 'ds' was not a MultipartDataSource, we have
160 // to parse this ourself.
161 parsed = false;
162 this.ds = ds;
163 if (ct==null)
164 contentType = new ContentType(ds.getContentType());
165 else
166 contentType = ct;
167 */
169 }
171 public InputStream initStream() throws MessagingException {
173 if (in == null) {
174 try {
175 in = ds.getInputStream();
176 if (!(in instanceof ByteArrayInputStream) &&
177 !(in instanceof BufferedInputStream) &&
178 !(in instanceof SharedInputStream))
179 in = new BufferedInputStream(in);
180 } catch (Exception ex) {
181 throw new MessagingException("No inputstream from datasource");
182 }
184 if (!in.markSupported()) {
185 throw new MessagingException(
186 "InputStream does not support Marking");
187 }
188 }
189 return in;
190 }
192 /**
193 * Parse the InputStream from our DataSource, constructing the
194 * appropriate MimeBodyParts. The <code>parsed</code> flag is
195 * set to true, and if true on entry nothing is done. This
196 * method is called by all other methods that need data for
197 * the body parts, to make sure the data has been parsed.
198 *
199 * @since JavaMail 1.2
200 */
201 protected void parse() throws MessagingException {
202 if (parsed)
203 return;
205 initStream();
207 SharedInputStream sin = null;
208 if (in instanceof SharedInputStream) {
209 sin = (SharedInputStream)in;
210 }
212 String bnd = "--" + boundary;
213 byte[] bndbytes = ASCIIUtility.getBytes(bnd);
214 try {
215 parse(in, bndbytes, sin);
216 } catch (IOException ioex) {
217 throw new MessagingException("IO Error", ioex);
218 } catch (Exception ex) {
219 throw new MessagingException("Error", ex);
220 }
222 parsed = true;
223 }
225 public boolean lastBodyPartFound() {
226 return lastPartFound.get(0);
227 }
229 public MimeBodyPart getNextPart(
230 InputStream stream, byte[] pattern, SharedInputStream sin)
231 throws Exception {
233 if (!stream.markSupported()) {
234 throw new Exception("InputStream does not support Marking");
235 }
237 if (begining) {
238 compile(pattern);
239 if (!skipPreamble(stream, pattern, sin)) {
240 throw new Exception(
241 "Missing Start Boundary, or boundary does not start on a new line");
242 }
243 begining = false;
244 }
246 if (lastBodyPartFound()) {
247 throw new Exception("No parts found in Multipart InputStream");
248 }
250 if (sin != null) {
251 long start = sin.getPosition();
252 b = readHeaders(stream);
253 if (b == -1) {
254 throw new Exception(
255 "End of Stream encountered while reading part headers");
256 }
257 long[] v = new long[1];
258 v[0] = -1; // just to ensure the code later sets it correctly
259 b = readBody(stream, pattern, v, null, sin);
260 // looks like this check has to be disabled
261 // it is allowed to have Mime Package without closing boundary
262 if (!ignoreMissingEndBoundary) {
263 if ((b == -1) && !lastBodyPartFound()) {
264 throw new MessagingException("Missing End Boundary for Mime Package : EOF while skipping headers");
265 }
266 }
267 long end = v[0];
268 MimeBodyPart mbp = createMimeBodyPart(sin.newStream(start, end));
269 addBodyPart(mbp);
270 return mbp;
272 } else {
273 InternetHeaders headers = createInternetHeaders(stream);
274 ByteOutputStream baos = new ByteOutputStream();
275 b = readBody(stream, pattern, null,baos, null);
276 // looks like this check has to be disabled
277 // in the old impl it is allowed to have Mime Package
278 // without closing boundary
279 if (!ignoreMissingEndBoundary) {
280 if ((b == -1) && !lastBodyPartFound()) {
281 throw new MessagingException("Missing End Boundary for Mime Package : EOF while skipping headers");
282 }
283 }
284 MimeBodyPart mbp = createMimeBodyPart(
285 headers, baos.getBytes(), baos.getCount());
286 addBodyPart(mbp);
287 return mbp;
288 }
290 }
292 public boolean parse(
293 InputStream stream, byte[] pattern, SharedInputStream sin)
294 throws Exception {
296 while (!lastPartFound.get(0) && (b != -1)) {
297 getNextPart(stream, pattern, sin);
298 }
299 return true;
300 }
302 private int readHeaders(InputStream is) throws Exception {
303 // if the headers are to end properly then there has to be CRLF
304 // actually we just need to mark the start and end positions
305 int b = is.read();
306 while(b != -1) {
307 // when it is a shared input stream no need to copy
308 if (b == '\r') {
309 b = is.read();
310 if (b == '\n') {
311 b = is.read();
312 if (b == '\r') {
313 b = is.read();
314 if (b == '\n') {
315 return b;
316 } else {
317 continue;
318 }
319 } else {
320 continue;
321 }
322 } else {
323 continue;
324 }
325 }
326 b = is.read();
327 }
328 if (b == -1) {
329 throw new Exception(
330 "End of inputstream while reading Mime-Part Headers");
331 }
332 return b;
333 }
335 private int readBody(
336 InputStream is, byte[] pattern, long[] posVector,
337 ByteOutputStream baos, SharedInputStream sin)
338 throws Exception {
339 if (!find(is, pattern, posVector, baos, sin)) {
340 throw new Exception(
341 "Missing boundary delimitier while reading Body Part");
342 }
343 return b;
344 }
346 private boolean skipPreamble(
347 InputStream is, byte[] pattern, SharedInputStream sin)
348 throws Exception {
349 if (!find(is, pattern, sin)) {
350 return false;
351 }
352 if (lastPartFound.get(0)) {
353 throw new Exception(
354 "Found closing boundary delimiter while trying to skip preamble");
355 }
356 return true;
357 }
360 public int readNext(InputStream is, byte[] buff, int patternLength,
361 BitSet eof, long[] posVector, SharedInputStream sin)
362 throws Exception {
364 int bufferLength = is.read(buffer, 0, patternLength);
365 if (bufferLength == -1) {
366 eof.flip(0);
367 } else if (bufferLength < patternLength) {
368 //repeatedly read patternLength - bufferLength
369 int temp = 0;
370 long pos = 0;
371 int i = bufferLength;
372 for (; i < patternLength; i++) {
373 if (sin != null) {
374 pos = sin.getPosition();
375 }
376 temp = is.read();
377 if (temp == -1) {
378 eof.flip(0);
379 if (sin != null) {
380 posVector[0] = pos;
381 }
382 break;
383 }
384 buffer[i] = (byte)temp;
385 }
386 bufferLength=i;
387 }
388 return bufferLength;
389 }
391 public boolean find(InputStream is, byte[] pattern, SharedInputStream sin)
392 throws Exception {
393 int i;
394 int l = pattern.length;
395 int lx = l -1;
396 int bufferLength = 0;
397 BitSet eof = new BitSet(1);
398 long[] posVector = new long[1];
400 while (true) {
401 is.mark(l);
402 bufferLength = readNext(is, buffer, l, eof, posVector, sin);
403 if (eof.get(0)) {
404 // End of stream
405 return false;
406 }
408 /*
409 if (bufferLength < l) {
410 //is.reset();
411 return false;
412 }*/
414 for(i = lx; i >= 0; i--) {
415 if (buffer[i] != pattern[i]) {
416 break;
417 }
418 }
420 if (i < 0) {
421 // found the boundary, skip *LWSP-char and CRLF
422 if (!skipLWSPAndCRLF(is)) {
423 throw new Exception("Boundary does not terminate with CRLF");
424 }
425 return true;
426 }
428 int s = Math.max(i + 1 - bcs[buffer[i] & 0x7f], gss[i]);
429 is.reset();
430 is.skip(s);
431 }
432 }
434 public boolean find(
435 InputStream is, byte[] pattern, long[] posVector,
436 ByteOutputStream out, SharedInputStream sin) throws Exception {
437 int i;
438 int l = pattern.length;
439 int lx = l -1;
440 int bufferLength = 0;
441 int s = 0;
442 long endPos = -1;
443 byte[] tmp = null;
445 boolean first = true;
446 BitSet eof = new BitSet(1);
448 while (true) {
449 is.mark(l);
450 if (!first) {
451 tmp = prevBuffer;
452 prevBuffer = buffer;
453 buffer = tmp;
454 }
455 if (sin != null) {
456 endPos = sin.getPosition();
457 }
459 bufferLength = readNext(is, buffer, l, eof, posVector, sin);
461 if (bufferLength == -1) {
462 // End of stream
463 // looks like it is allowed to not have a closing boundary
464 //return false;
465 //if (sin != null) {
466 // posVector[0] = endPos;
467 //}
468 b = -1;
469 if ((s == l) && (sin == null)) {
470 out.write(prevBuffer, 0, s);
471 }
472 return true;
473 }
475 if (bufferLength < l) {
476 if (sin != null) {
477 //endPos = sin.getPosition();
478 //posVector[0] = endPos;
479 } else {
480 // looks like it is allowed to not have a closing boundary
481 // in the old implementation
482 out.write(buffer, 0, bufferLength);
483 }
484 // looks like it is allowed to not have a closing boundary
485 // in the old implementation
486 //return false;
487 b = -1;
488 return true;
489 }
491 for(i = lx; i >= 0; i--) {
492 if (buffer[i] != pattern[i]) {
493 break;
494 }
495 }
497 if (i < 0) {
498 if (s > 0) {
499 //looks like the earlier impl allowed just an LF
500 // so if s == 1 : it must be an LF
501 // if s == 2 : it must be a CR LF
502 if (s <= 2) {
503 //it could be "some-char\n" so write some-char
504 if (s == 2) {
505 if (prevBuffer[1] == '\n') {
506 if (prevBuffer[0] != '\r' && prevBuffer[0] != '\n') {
507 out.write(prevBuffer,0,1);
508 }
509 if (sin != null) {
510 posVector[0] = endPos;
511 }
513 } else {
514 throw new Exception(
515 "Boundary characters encountered in part Body " +
516 "without a preceeding CRLF");
517 }
519 } else if (s==1) {
520 if (prevBuffer[0] != '\n') {
521 throw new Exception(
522 "Boundary characters encountered in part Body " +
523 "without a preceeding CRLF");
524 }else {
525 if (sin != null) {
526 posVector[0] = endPos;
527 }
528 }
529 }
531 } else if (s > 2) {
532 if ((prevBuffer[s-2] == '\r') && (prevBuffer[s-1] == '\n')) {
533 if (sin != null) {
534 posVector[0] = endPos - 2;
535 } else {
536 out.write(prevBuffer, 0, s - 2);
537 }
538 } else if (prevBuffer[s-1] == '\n') {
539 //old impl allowed just a \n
540 if (sin != null) {
541 posVector[0] = endPos - 1;
542 } else {
543 out.write(prevBuffer, 0, s - 1);
544 }
545 } else {
546 throw new Exception(
547 "Boundary characters encountered in part Body " +
548 "without a preceeding CRLF");
549 }
550 }
551 }
552 // found the boundary, skip *LWSP-char and CRLF
553 if (!skipLWSPAndCRLF(is)) {
554 //throw new Exception(
555 // "Boundary does not terminate with CRLF");
556 }
557 return true;
558 }
560 if ((s > 0) && (sin == null)) {
561 if (prevBuffer[s-1] == (byte)13) {
562 // if buffer[0] == (byte)10
563 if (buffer[0] == (byte)10) {
564 int j=lx-1;
565 for(j = lx-1; j > 0; j--) {
566 if (buffer[j+1] != pattern[j]) {
567 break;
568 }
569 }
570 if (j == 0) {
571 // matched the pattern excluding the last char of the pattern
572 // so dont write the CR into stream
573 out.write(prevBuffer,0,s-1);
574 } else {
575 out.write(prevBuffer,0,s);
576 }
577 } else {
578 out.write(prevBuffer, 0, s);
579 }
580 } else {
581 out.write(prevBuffer, 0, s);
582 }
583 }
585 s = Math.max(i + 1 - bcs[buffer[i] & 0x7f], gss[i]);
586 is.reset();
587 is.skip(s);
588 if (first) {
589 first = false;
590 }
591 }
592 }
594 private boolean skipLWSPAndCRLF(InputStream is) throws Exception {
596 b = is.read();
597 //looks like old impl allowed just a \n as well
598 if (b == '\n') {
599 return true;
600 }
602 if (b == '\r') {
603 b = is.read();
604 //skip any multiple '\r' "\r\n" --> "\r\r\n" on Win2k
605 if (b == '\r') {
606 b = is.read();
607 }
608 if (b == '\n') {
609 return true;
610 } else {
611 throw new Exception(
612 "transport padding after a Mime Boundary should end in a CRLF, found CR only");
613 }
614 }
616 if (b == '-') {
617 b = is.read();
618 if (b != '-') {
619 throw new Exception(
620 "Unexpected singular '-' character after Mime Boundary");
621 } else {
622 //System.out.println("Last Part Found");
623 lastPartFound.flip(0);
624 // read the next char
625 b = is.read();
626 }
627 }
629 while ((b != -1) && ((b == ' ') || (b == '\t'))) {
630 b = is.read();
631 if (b == '\n') {
632 return true;
633 }
634 if (b == '\r') {
635 b = is.read();
636 //skip any multiple '\r': "\r\n" --> "\r\r\n" on Win2k
637 if (b == '\r') {
638 b = is.read();
639 }
640 if (b == '\n') {
641 return true;
642 }
643 }
644 }
646 if (b == -1) {
647 // the last boundary need not have CRLF
648 if (!lastPartFound.get(0)) {
649 throw new Exception(
650 "End of Multipart Stream before encountering closing boundary delimiter");
651 }
652 return true;
653 }
654 return false;
655 }
657 private void compile(byte[] pattern) {
658 int l = pattern.length;
660 int i;
661 int j;
663 // Copied from J2SE 1.4 regex code
664 // java.util.regex.Pattern.java
666 // Initialise Bad Character Shift table
667 for (i = 0; i < l; i++) {
668 bcs[pattern[i]] = i + 1;
669 }
671 // Initialise Good Suffix Shift table
672 gss = new int[l];
673 NEXT: for (i = l; i > 0; i--) {
674 // j is the beginning index of suffix being considered
675 for (j = l - 1; j >= i; j--) {
676 // Testing for good suffix
677 if (pattern[j] == pattern[j - i]) {
678 // pattern[j..len] is a good suffix
679 gss[j - 1] = i;
680 } else {
681 // No match. The array has already been
682 // filled up with correct values before.
683 continue NEXT;
684 }
685 }
686 while (j > 0) {
687 gss[--j] = i;
688 }
689 }
690 gss[l - 1] = 1;
691 }
694 /**
695 * Iterates through all the parts and outputs each Mime part
696 * separated by a boundary.
697 */
698 byte[] buf = new byte[1024];
700 public void writeTo(OutputStream os)
701 throws IOException, MessagingException {
703 // inputStream was not null
704 if (in != null) {
705 contentType.setParameter("boundary", this.boundary);
706 }
708 String bnd = "--" + contentType.getParameter("boundary");
709 for (int i = 0; i < parts.size(); i++) {
710 OutputUtil.writeln(bnd, os); // put out boundary
711 ((MimeBodyPart)parts.get(i)).writeTo(os);
712 OutputUtil.writeln(os); // put out empty line
713 }
715 if (in != null) {
716 OutputUtil.writeln(bnd, os); // put out boundary
717 if ((os instanceof ByteOutputStream) && lazyAttachments) {
718 ((ByteOutputStream)os).write(in);
719 } else {
720 ByteOutputStream baos = new ByteOutputStream(in.available());
721 baos.write(in);
722 baos.writeTo(os);
723 // reset the inputstream so that we can support a
724 //getAttachment later
725 in = baos.newInputStream();
726 }
728 // this will endup writing the end boundary
729 } else {
730 // put out last boundary
731 OutputUtil.writeAsAscii(bnd, os);
732 OutputUtil.writeAsAscii("--", os);
733 }
734 }
736 public void setInputStream(InputStream is) {
737 this.in = is;
738 }
740 public InputStream getInputStream() {
741 return this.in;
742 }
744 public void setBoundary(String bnd) {
745 this.boundary = bnd;
746 if (this.contentType != null) {
747 this.contentType.setParameter("boundary", bnd);
748 }
749 }
750 public String getBoundary() {
751 return this.boundary;
752 }
754 public boolean isEndOfStream() {
755 return (b == -1);
756 }
758 public void setLazyAttachments(boolean flag) {
759 lazyAttachments = flag;
760 }
762 }