1/****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one   *
3 * or more contributor license agreements.  See the NOTICE file *
4 * distributed with this work for additional information        *
5 * regarding copyright ownership.  The ASF licenses this file   *
6 * to you under the Apache License, Version 2.0 (the            *
7 * "License"); you may not use this file except in compliance   *
8 * with the License.  You may obtain a copy of the License at   *
9 *                                                              *
10 *   http://www.apache.org/licenses/LICENSE-2.0                 *
11 *                                                              *
12 * Unless required by applicable law or agreed to in writing,   *
13 * software distributed under the License is distributed on an  *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15 * KIND, either express or implied.  See the License for the    *
16 * specific language governing permissions and limitations      *
17 * under the License.                                           *
18 ****************************************************************/
19
20package org.apache.james.mime4j;
21
22import java.io.IOException;
23import java.io.InputStream;
24import java.io.PushbackInputStream;
25
26/**
27 * Stream that constrains itself to a single MIME body part.
28 * After the stream ends (i.e. read() returns -1) {@link #hasMoreParts()}
29 * can be used to determine if a final boundary has been seen or not.
30 * If {@link #parentEOF()} is <code>true</code> an unexpected end of stream
31 * has been detected in the parent stream.
32 *
33 *
34 *
35 * @version $Id: MimeBoundaryInputStream.java,v 1.2 2004/11/29 13:15:42 ntherning Exp $
36 */
37public class MimeBoundaryInputStream extends InputStream {
38
39    private PushbackInputStream s = null;
40    private byte[] boundary = null;
41    private boolean first = true;
42    private boolean eof = false;
43    private boolean parenteof = false;
44    private boolean moreParts = true;
45
46    /**
47     * Creates a new MimeBoundaryInputStream.
48     * @param s The underlying stream.
49     * @param boundary Boundary string (not including leading hyphens).
50     */
51    public MimeBoundaryInputStream(InputStream s, String boundary)
52            throws IOException {
53
54        this.s = new PushbackInputStream(s, boundary.length() + 4);
55
56        boundary = "--" + boundary;
57        this.boundary = new byte[boundary.length()];
58        for (int i = 0; i < this.boundary.length; i++) {
59            this.boundary[i] = (byte) boundary.charAt(i);
60        }
61
62        /*
63         * By reading one byte we will update moreParts to be as expected
64         * before any bytes have been read.
65         */
66        int b = read();
67        if (b != -1) {
68            this.s.unread(b);
69        }
70    }
71
72    /**
73     * Closes the underlying stream.
74     *
75     * @throws IOException on I/O errors.
76     */
77    public void close() throws IOException {
78        s.close();
79    }
80
81    /**
82     * Determines if the underlying stream has more parts (this stream has
83     * not seen an end boundary).
84     *
85     * @return <code>true</code> if there are more parts in the underlying
86     *         stream, <code>false</code> otherwise.
87     */
88    public boolean hasMoreParts() {
89        return moreParts;
90    }
91
92    /**
93     * Determines if the parent stream has reached EOF
94     *
95     * @return <code>true</code>  if EOF has been reached for the parent stream,
96     *         <code>false</code> otherwise.
97     */
98    public boolean parentEOF() {
99        return parenteof;
100    }
101
102    /**
103     * Consumes all unread bytes of this stream. After a call to this method
104     * this stream will have reached EOF.
105     *
106     * @throws IOException on I/O errors.
107     */
108    public void consume() throws IOException {
109        while (read() != -1) {
110        }
111    }
112
113    /**
114     * @see java.io.InputStream#read()
115     */
116    public int read() throws IOException {
117        if (eof) {
118            return -1;
119        }
120
121        if (first) {
122            first = false;
123            if (matchBoundary()) {
124                return -1;
125            }
126        }
127
128        int b1 = s.read();
129        int b2 = s.read();
130
131        if (b1 == '\r' && b2 == '\n') {
132            if (matchBoundary()) {
133                return -1;
134            }
135        }
136
137        if (b2 != -1) {
138            s.unread(b2);
139        }
140
141        parenteof = b1 == -1;
142        eof = parenteof;
143
144        return b1;
145    }
146
147    private boolean matchBoundary() throws IOException {
148
149        for (int i = 0; i < boundary.length; i++) {
150            int b = s.read();
151            if (b != boundary[i]) {
152                if (b != -1) {
153                    s.unread(b);
154                }
155                for (int j = i - 1; j >= 0; j--) {
156                    s.unread(boundary[j]);
157                }
158                return false;
159            }
160        }
161
162        /*
163         * We have a match. Is it an end boundary?
164         */
165        int prev = s.read();
166        int curr = s.read();
167        moreParts = !(prev == '-' && curr == '-');
168        do {
169            if (curr == '\n' && prev == '\r') {
170                break;
171            }
172            prev = curr;
173        } while ((curr = s.read()) != -1);
174
175        if (curr == -1) {
176            moreParts = false;
177            parenteof = true;
178        }
179
180        eof = true;
181
182        return true;
183    }
184}
185