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 *
10
* http://www.apache.org/licenses/LICENSE-2.0 *
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
****************************************************************/
20
package org.apache.james.mime4j.io;
22
import org.apache.james.mime4j.util.ByteArrayBuffer;
24
import java.io.IOException;
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.
31
public class MimeBoundaryInputStream extends LineReaderInputStream {
33
private final byte[] boundary;
37
private boolean atBoundary;
38
private int boundaryLen;
39
private boolean lastPart;
40
private boolean completed;
42
private BufferedLineReaderInputStream buffer;
45
* Creates a new MimeBoundaryInputStream.
47
* @param inbuffer The underlying stream.
48
* @param boundary Boundary string (not including leading hyphens).
49
* @throws IllegalArgumentException when boundary is too long
51
public MimeBoundaryInputStream(BufferedLineReaderInputStream inbuffer, String boundary)
54
if (inbuffer.capacity() <= boundary.length()) {
55
throw new IllegalArgumentException("Boundary is too long");
57
this.buffer = inbuffer;
60
this.atBoundary = false;
62
this.lastPart = false;
63
this.completed = false;
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");
73
this.boundary[i + 2] = ch;
79
* Closes the underlying stream.
81
* @throws IOException on I/O errors.
84
public void close() throws IOException {
88
* @see java.io.InputStream#markSupported()
91
public boolean markSupported() {
96
* @see java.io.InputStream#read()
99
public int read() throws IOException {
103
if (endOfStream() && !hasData()) {
109
return buffer.read();
110
} else if (endOfStream()) {
119
public int read(byte[] b, int off, int len) throws IOException {
123
if (endOfStream() && !hasData()) {
129
return read(b, off, len);
131
int chunk = Math.min(len, limit - buffer.pos());
132
return buffer.read(b, off, chunk);
136
public int readLine(final ByteArrayBuffer dst) throws IOException {
138
throw new IllegalArgumentException("Destination buffer may not be null");
143
if (endOfStream() && !hasData()) {
149
boolean found = false;
153
bytesRead = fillBuffer();
154
if (!hasData() && endOfStream()) {
160
int len = this.limit - this.buffer.pos();
161
int i = this.buffer.indexOf((byte)'\n', this.buffer.pos(), len);
165
chunk = i + 1 - this.buffer.pos();
170
dst.append(this.buffer.buf(), this.buffer.pos(), chunk);
171
this.buffer.skip(chunk);
175
if (total == 0 && bytesRead == -1) {
182
private boolean endOfStream() {
183
return eof || atBoundary;
186
private boolean hasData() {
187
return limit > buffer.pos() && limit <= buffer.limit();
190
private int fillBuffer() throws IOException {
196
bytesRead = buffer.fillBuffer();
200
eof = bytesRead == -1;
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);
215
calculateBoundaryLen();
218
limit = buffer.limit();
220
limit = buffer.limit() - (boundary.length + 1);
221
// \r\n + (boundary - one char)
227
private void calculateBoundaryLen() throws IOException {
228
boundaryLen = boundary.length;
229
int len = limit - buffer.pos();
231
if (buffer.charAt(limit - 1) == '\n') {
237
if (buffer.charAt(limit - 1) == '\r') {
244
private void skipBoundary() throws IOException {
247
buffer.skip(boundaryLen);
248
boolean checkForLastPart = true;
250
if (buffer.length() > 1) {
251
int ch1 = buffer.charAt(buffer.pos());
252
int ch2 = buffer.charAt(buffer.pos() + 1);
254
if (checkForLastPart) if (ch1 == '-' && ch2 == '-') {
255
this.lastPart = true;
257
checkForLastPart = false;
261
if (ch1 == '\r' && ch2 == '\n') {
264
} else if (ch1 == '\n') {
268
// ignoring everything in a line starting with a boundary.
282
public boolean isLastPart() {
286
public boolean eof() {
287
return eof && !buffer.hasBufferedData();
291
public String toString() {
292
final StringBuilder buffer = new StringBuilder("MimeBoundaryInputStream, boundary ");
293
for (byte b : boundary) {
294
buffer.append((char) b);
296
return buffer.toString();