1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
28
29
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
46
47
48
49
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
80
81
82
83 @Override
84 public void close() throws IOException {
85 }
86
87
88
89
90 @Override
91 public boolean markSupported() {
92 return false;
93 }
94
95
96
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
205
206 while (i > 0 && buffer.charAt(i-1) != '\n') {
207
208
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
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
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 }