~ubuntu-branches/ubuntu/saucy/apache-mime4j/saucy

« back to all changes in this revision

Viewing changes to src/main/java/org/apache/james/mime4j/io/MimeBoundaryInputStream.java

  • Committer: Bazaar Package Importer
  • Author(s): David Paleino
  • Date: 2010-07-13 09:28:28 UTC
  • Revision ID: james.westby@ubuntu.com-20100713092828-g6wafdtidgmtx7su
Tags: upstream-0.6
ImportĀ upstreamĀ versionĀ 0.6

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
20
package org.apache.james.mime4j.io;
 
21
 
 
22
import org.apache.james.mime4j.util.ByteArrayBuffer;
 
23
 
 
24
import java.io.IOException;
 
25
 
 
26
/**
 
27
 * Stream that constrains itself to a single MIME body part.
 
28
 * After the stream ends (i.e. read() returns -1) {@link #isLastPart()}
 
29
 * can be used to determine if a final boundary has been seen or not.
 
30
 */
 
31
public class MimeBoundaryInputStream extends LineReaderInputStream {
 
32
 
 
33
    private final byte[] boundary;
 
34
    
 
35
    private boolean eof;
 
36
    private int limit;
 
37
    private boolean atBoundary;
 
38
    private int boundaryLen;
 
39
    private boolean lastPart;
 
40
    private boolean completed;
 
41
 
 
42
    private BufferedLineReaderInputStream buffer;
 
43
 
 
44
    /**
 
45
     * Creates a new MimeBoundaryInputStream.
 
46
     * 
 
47
     * @param inbuffer The underlying stream.
 
48
     * @param boundary Boundary string (not including leading hyphens).
 
49
     * @throws IllegalArgumentException when boundary is too long
 
50
     */
 
51
    public MimeBoundaryInputStream(BufferedLineReaderInputStream inbuffer, String boundary) 
 
52
            throws IOException {
 
53
        super(inbuffer);
 
54
        if (inbuffer.capacity() <= boundary.length()) {
 
55
            throw new IllegalArgumentException("Boundary is too long");
 
56
        }
 
57
        this.buffer = inbuffer;
 
58
        this.eof = false;
 
59
        this.limit = -1;
 
60
        this.atBoundary = false;
 
61
        this.boundaryLen = 0;
 
62
        this.lastPart = false;
 
63
        this.completed = false;
 
64
        
 
65
        this.boundary = new byte[boundary.length() + 2];
 
66
        this.boundary[0] = (byte) '-';
 
67
        this.boundary[1] = (byte) '-';
 
68
        for (int i = 0; i < boundary.length(); i++) {
 
69
            byte ch = (byte) boundary.charAt(i);
 
70
            if (ch == '\r' || ch == '\n') {
 
71
                throw new IllegalArgumentException("Boundary may not contain CR or LF");
 
72
            }
 
73
            this.boundary[i + 2] = ch;
 
74
        }
 
75
        fillBuffer();
 
76
    }
 
77
 
 
78
    /**
 
79
     * Closes the underlying stream.
 
80
     * 
 
81
     * @throws IOException on I/O errors.
 
82
     */
 
83
    @Override
 
84
    public void close() throws IOException {
 
85
    }
 
86
 
 
87
    /**
 
88
     * @see java.io.InputStream#markSupported()
 
89
     */
 
90
    @Override
 
91
    public boolean markSupported() {
 
92
        return false;
 
93
    }
 
94
 
 
95
    /**
 
96
     * @see java.io.InputStream#read()
 
97
     */
 
98
    @Override
 
99
    public int read() throws IOException {
 
100
        if (completed) {
 
101
            return -1;
 
102
        }
 
103
        if (endOfStream() && !hasData()) {
 
104
            skipBoundary();            
 
105
            return -1;
 
106
        }
 
107
        for (;;) {
 
108
            if (hasData()) {
 
109
                return buffer.read();
 
110
            } else if (endOfStream()) {
 
111
                skipBoundary();            
 
112
                return -1;
 
113
            }
 
114
            fillBuffer();
 
115
        }
 
116
    }
 
117
    
 
118
    @Override
 
119
    public int read(byte[] b, int off, int len) throws IOException {
 
120
        if (completed) {
 
121
            return -1;
 
122
        }
 
123
        if (endOfStream() && !hasData()) {
 
124
            skipBoundary();            
 
125
            return -1;
 
126
        }
 
127
        fillBuffer();
 
128
        if (!hasData()) {
 
129
            return read(b, off, len);
 
130
        }
 
131
        int chunk = Math.min(len, limit - buffer.pos());
 
132
        return buffer.read(b, off, chunk);
 
133
    }
 
134
 
 
135
    @Override
 
136
    public int readLine(final ByteArrayBuffer dst) throws IOException {
 
137
        if (dst == null) {
 
138
            throw new IllegalArgumentException("Destination buffer may not be null");
 
139
        }
 
140
        if (completed) {
 
141
            return -1;
 
142
        }
 
143
        if (endOfStream() && !hasData()) {
 
144
            skipBoundary();            
 
145
            return -1;
 
146
        }
 
147
 
 
148
        int total = 0;
 
149
        boolean found = false;
 
150
        int bytesRead = 0;
 
151
        while (!found) {
 
152
            if (!hasData()) {
 
153
                bytesRead = fillBuffer();
 
154
                if (!hasData() && endOfStream()) {
 
155
                    skipBoundary();
 
156
                    bytesRead = -1;
 
157
                    break;
 
158
                }
 
159
            }
 
160
            int len = this.limit - this.buffer.pos();
 
161
            int i = this.buffer.indexOf((byte)'\n', this.buffer.pos(), len);
 
162
            int chunk;
 
163
            if (i != -1) {
 
164
                found = true;
 
165
                chunk = i + 1 - this.buffer.pos();
 
166
            } else {
 
167
                chunk = len;
 
168
            }
 
169
            if (chunk > 0) {
 
170
                dst.append(this.buffer.buf(), this.buffer.pos(), chunk);
 
171
                this.buffer.skip(chunk);
 
172
                total += chunk;
 
173
            }
 
174
        }
 
175
        if (total == 0 && bytesRead == -1) {
 
176
            return -1;
 
177
        } else {
 
178
            return total;
 
179
        }
 
180
    }
 
181
    
 
182
    private boolean endOfStream() {
 
183
        return eof || atBoundary;
 
184
    }
 
185
    
 
186
    private boolean hasData() {
 
187
        return limit > buffer.pos() && limit <= buffer.limit();
 
188
    }
 
189
    
 
190
    private int fillBuffer() throws IOException {
 
191
        if (eof) {
 
192
            return -1;
 
193
        }
 
194
        int bytesRead;
 
195
        if (!hasData()) {
 
196
            bytesRead = buffer.fillBuffer();
 
197
        } else {
 
198
            bytesRead = 0;
 
199
        }
 
200
        eof = bytesRead == -1;
 
201
        
 
202
        
 
203
        int i = buffer.indexOf(boundary);
 
204
        // NOTE this currently check only for LF. It doesn't check for canonical CRLF
 
205
        // and neither for isolated CR. This will require updates according to MIME4J-60
 
206
        while (i > 0 && buffer.charAt(i-1) != '\n') {
 
207
            // skip the "fake" boundary (it does not contain LF or CR so we cannot have
 
208
            // another boundary starting before this is complete.
 
209
            i = i + boundary.length;
 
210
            i = buffer.indexOf(boundary, i, buffer.limit() - i);
 
211
        }
 
212
        if (i != -1) {
 
213
            limit = i;
 
214
            atBoundary = true;
 
215
            calculateBoundaryLen();
 
216
        } else {
 
217
            if (eof) {
 
218
                limit = buffer.limit();
 
219
            } else {
 
220
                limit = buffer.limit() - (boundary.length + 1); 
 
221
                                          // \r\n + (boundary - one char)
 
222
            }
 
223
        }
 
224
        return bytesRead;
 
225
    }
 
226
    
 
227
    private void calculateBoundaryLen() throws IOException {
 
228
        boundaryLen = boundary.length;
 
229
        int len = limit - buffer.pos();
 
230
        if (len > 0) {
 
231
            if (buffer.charAt(limit - 1) == '\n') {
 
232
                boundaryLen++;
 
233
                limit--;
 
234
            }
 
235
        }
 
236
        if (len > 1) {
 
237
            if (buffer.charAt(limit - 1) == '\r') {
 
238
                boundaryLen++;
 
239
                limit--;
 
240
            }
 
241
        }
 
242
    }
 
243
    
 
244
    private void skipBoundary() throws IOException {
 
245
        if (!completed) {
 
246
            completed = true;
 
247
            buffer.skip(boundaryLen);
 
248
            boolean checkForLastPart = true;
 
249
            for (;;) {
 
250
                if (buffer.length() > 1) {
 
251
                    int ch1 = buffer.charAt(buffer.pos());
 
252
                    int ch2 = buffer.charAt(buffer.pos() + 1);
 
253
                    
 
254
                    if (checkForLastPart) if (ch1 == '-' && ch2 == '-') {
 
255
                        this.lastPart = true;
 
256
                        buffer.skip(2);
 
257
                        checkForLastPart = false;
 
258
                        continue;
 
259
                    }
 
260
                    
 
261
                    if (ch1 == '\r' && ch2 == '\n') {
 
262
                        buffer.skip(2);
 
263
                        break;
 
264
                    } else if (ch1 == '\n') {
 
265
                        buffer.skip(1);
 
266
                        break;
 
267
                    } else {
 
268
                        // ignoring everything in a line starting with a boundary.
 
269
                        buffer.skip(1);
 
270
                    }
 
271
                    
 
272
                } else {
 
273
                    if (eof) {
 
274
                        break;
 
275
                    }
 
276
                    fillBuffer();
 
277
                }
 
278
            }
 
279
        }
 
280
    }
 
281
    
 
282
    public boolean isLastPart() {
 
283
        return lastPart;        
 
284
    }
 
285
    
 
286
    public boolean eof() {
 
287
        return eof && !buffer.hasBufferedData();
 
288
    }
 
289
 
 
290
    @Override
 
291
    public String toString() {
 
292
        final StringBuilder buffer = new StringBuilder("MimeBoundaryInputStream, boundary ");
 
293
        for (byte b : boundary) {
 
294
            buffer.append((char) b);
 
295
        }
 
296
        return buffer.toString();
 
297
    }
 
298
}