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