commit | author | age
|
85c480
|
1 |
/* |
U |
2 |
* Licensed to the Apache Software Foundation (ASF) under one or more |
|
3 |
* contributor license agreements. See the NOTICE file distributed with |
|
4 |
* this work for additional information regarding copyright ownership. |
|
5 |
* The ASF licenses this file to You under the Apache License, Version 2.0 |
|
6 |
* (the "License"); you may not use this file except in compliance with |
|
7 |
* the License. You may obtain a copy of the License at |
|
8 |
* |
|
9 |
* http://www.apache.org/licenses/LICENSE-2.0 |
|
10 |
* |
|
11 |
* Unless required by applicable law or agreed to in writing, software |
|
12 |
* distributed under the License is distributed on an "AS IS" BASIS, |
|
13 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14 |
* See the License for the specific language governing permissions and |
|
15 |
* limitations under the License. |
|
16 |
*/ |
|
17 |
package de.uhilger.neon.up; |
|
18 |
|
|
19 |
import static java.lang.String.format; |
|
20 |
|
|
21 |
import java.io.ByteArrayOutputStream; |
|
22 |
import java.io.IOException; |
|
23 |
import java.io.InputStream; |
|
24 |
import java.io.OutputStream; |
|
25 |
import java.io.UnsupportedEncodingException; |
|
26 |
|
|
27 |
|
|
28 |
/** |
|
29 |
* Der Lowlevel MultipartParser aus dem Projekt Apache Commons Fileupload |
|
30 |
* ohne Abhaengigkeiten zum Rest von Commons Fileupload und Commons IO. |
|
31 |
* |
|
32 |
* <p> Low level API for processing file uploads. |
|
33 |
* |
|
34 |
* <p> This class can be used to process data streams conforming to MIME |
|
35 |
* 'multipart' format as defined in |
|
36 |
* <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Arbitrarily |
|
37 |
* large amounts of data in the stream can be processed under constant |
|
38 |
* memory usage. |
|
39 |
* |
|
40 |
* <p> The format of the stream is defined in the following way:<br> |
|
41 |
* |
|
42 |
* <code> |
|
43 |
* multipart-body := preamble 1*encapsulation close-delimiter epilogue<br> |
|
44 |
* encapsulation := delimiter body CRLF<br> |
|
45 |
* delimiter := "--" boundary CRLF<br> |
|
46 |
* close-delimiter := "--" boundary "--"<br> |
|
47 |
* preamble := <ignore><br> |
|
48 |
* epilogue := <ignore><br> |
|
49 |
* body := header-part CRLF body-part<br> |
|
50 |
* header-part := 1*header CRLF<br> |
|
51 |
* header := header-name ":" header-value<br> |
|
52 |
* header-name := <printable ascii characters except ":"><br> |
|
53 |
* header-value := <any ascii characters except CR & LF><br> |
|
54 |
* body-data := <arbitrary data><br> |
|
55 |
* </code> |
|
56 |
* |
|
57 |
* <p>Note that body-data can contain another mulipart entity. There |
|
58 |
* is limited support for single pass processing of such nested |
|
59 |
* streams. The nested stream is <strong>required</strong> to have a |
|
60 |
* boundary token of the same length as the parent stream (see {@link |
|
61 |
* #setBoundary(byte[])}). |
|
62 |
* |
|
63 |
* <p>Here is an example of usage of this class.<br> |
|
64 |
* |
|
65 |
* <pre> |
|
66 |
* try { |
|
67 |
* MultipartStream multipartStream = new MultipartStream(input, boundary); |
|
68 |
* boolean nextPart = multipartStream.skipPreamble(); |
|
69 |
* OutputStream output; |
|
70 |
* while(nextPart) { |
|
71 |
* String header = multipartStream.readHeaders(); |
|
72 |
* // process headers |
|
73 |
* // create some output stream |
|
74 |
* multipartStream.readBodyData(output); |
|
75 |
* nextPart = multipartStream.readBoundary(); |
|
76 |
* } |
|
77 |
* } catch(MultipartStream.MalformedStreamException e) { |
|
78 |
* // the stream failed to follow required syntax |
|
79 |
* } catch(IOException e) { |
|
80 |
* // a read or write error occurred |
|
81 |
* } |
|
82 |
* </pre> |
|
83 |
*/ |
|
84 |
public class MultipartStream { |
|
85 |
|
|
86 |
/** |
|
87 |
* Internal class, which is used to invoke the |
|
88 |
* {@link ProgressListener}. |
|
89 |
*/ |
|
90 |
public static class ProgressNotifier { |
|
91 |
|
|
92 |
/** |
|
93 |
* The listener to invoke. |
|
94 |
*/ |
|
95 |
private final ProgressListener listener; |
|
96 |
|
|
97 |
/** |
|
98 |
* Number of expected bytes, if known, or -1. |
|
99 |
*/ |
|
100 |
private final long contentLength; |
|
101 |
|
|
102 |
/** |
|
103 |
* Number of bytes, which have been read so far. |
|
104 |
*/ |
|
105 |
private long bytesRead; |
|
106 |
|
|
107 |
/** |
|
108 |
* Number of items, which have been read so far. |
|
109 |
*/ |
|
110 |
private int items; |
|
111 |
|
|
112 |
/** |
|
113 |
* Creates a new instance with the given listener |
|
114 |
* and content length. |
|
115 |
* |
|
116 |
* @param pListener The listener to invoke. |
|
117 |
* @param pContentLength The expected content length. |
|
118 |
*/ |
|
119 |
ProgressNotifier(ProgressListener pListener, long pContentLength) { |
|
120 |
listener = pListener; |
|
121 |
contentLength = pContentLength; |
|
122 |
} |
|
123 |
|
|
124 |
/** |
|
125 |
* Called to indicate that bytes have been read. |
|
126 |
* |
|
127 |
* @param pBytes Number of bytes, which have been read. |
|
128 |
*/ |
|
129 |
void noteBytesRead(int pBytes) { |
|
130 |
/* Indicates, that the given number of bytes have been read from |
|
131 |
* the input stream. |
|
132 |
*/ |
|
133 |
bytesRead += pBytes; |
|
134 |
notifyListener(); |
|
135 |
} |
|
136 |
|
|
137 |
/** |
|
138 |
* Called to indicate, that a new file item has been detected. |
|
139 |
*/ |
|
140 |
void noteItem() { |
|
141 |
++items; |
|
142 |
notifyListener(); |
|
143 |
} |
|
144 |
|
|
145 |
/** |
|
146 |
* Called for notifying the listener. |
|
147 |
*/ |
|
148 |
private void notifyListener() { |
|
149 |
if (listener != null) { |
|
150 |
listener.update(bytesRead, contentLength, items); |
|
151 |
} |
|
152 |
} |
|
153 |
|
|
154 |
} |
|
155 |
|
|
156 |
// ----------------------------------------------------- Manifest constants |
|
157 |
|
|
158 |
/** |
|
159 |
* The Carriage Return ASCII character value. |
|
160 |
*/ |
|
161 |
public static final byte CR = 0x0D; |
|
162 |
|
|
163 |
/** |
|
164 |
* The Line Feed ASCII character value. |
|
165 |
*/ |
|
166 |
public static final byte LF = 0x0A; |
|
167 |
|
|
168 |
/** |
|
169 |
* The dash (-) ASCII character value. |
|
170 |
*/ |
|
171 |
public static final byte DASH = 0x2D; |
|
172 |
|
|
173 |
/** |
|
174 |
* The maximum length of <code>header-part</code> that will be |
|
175 |
* processed (10 kilobytes = 10240 bytes.). |
|
176 |
*/ |
|
177 |
public static final int HEADER_PART_SIZE_MAX = 10240; |
|
178 |
|
|
179 |
/** |
|
180 |
* The default length of the buffer used for processing a request. |
|
181 |
*/ |
|
182 |
protected static final int DEFAULT_BUFSIZE = 4096; |
|
183 |
|
|
184 |
/** |
|
185 |
* A byte sequence that marks the end of <code>header-part</code> |
|
186 |
* (<code>CRLFCRLF</code>). |
|
187 |
*/ |
|
188 |
protected static final byte[] HEADER_SEPARATOR = {CR, LF, CR, LF}; |
|
189 |
|
|
190 |
/** |
|
191 |
* A byte sequence that that follows a delimiter that will be |
|
192 |
* followed by an encapsulation (<code>CRLF</code>). |
|
193 |
*/ |
|
194 |
protected static final byte[] FIELD_SEPARATOR = {CR, LF}; |
|
195 |
|
|
196 |
/** |
|
197 |
* A byte sequence that that follows a delimiter of the last |
|
198 |
* encapsulation in the stream (<code>--</code>). |
|
199 |
*/ |
|
200 |
protected static final byte[] STREAM_TERMINATOR = {DASH, DASH}; |
|
201 |
|
|
202 |
/** |
|
203 |
* A byte sequence that precedes a boundary (<code>CRLF--</code>). |
|
204 |
*/ |
|
205 |
protected static final byte[] BOUNDARY_PREFIX = {CR, LF, DASH, DASH}; |
|
206 |
|
|
207 |
// ----------------------------------------------------------- Data members |
|
208 |
|
|
209 |
/** |
|
210 |
* The input stream from which data is read. |
|
211 |
*/ |
|
212 |
private final InputStream input; |
|
213 |
|
|
214 |
/** |
|
215 |
* The length of the boundary token plus the leading <code>CRLF--</code>. |
|
216 |
*/ |
|
217 |
private int boundaryLength; |
|
218 |
|
|
219 |
/** |
|
220 |
* The amount of data, in bytes, that must be kept in the buffer in order |
|
221 |
* to detect delimiters reliably. |
|
222 |
*/ |
|
223 |
private final int keepRegion; |
|
224 |
|
|
225 |
/** |
|
226 |
* The byte sequence that partitions the stream. |
|
227 |
*/ |
|
228 |
private final byte[] boundary; |
|
229 |
|
|
230 |
/** |
|
231 |
* The table for Knuth-Morris-Pratt search algorithm. |
|
232 |
*/ |
|
233 |
private final int[] boundaryTable; |
|
234 |
|
|
235 |
/** |
|
236 |
* The length of the buffer used for processing the request. |
|
237 |
*/ |
|
238 |
private final int bufSize; |
|
239 |
|
|
240 |
/** |
|
241 |
* The buffer used for processing the request. |
|
242 |
*/ |
|
243 |
private final byte[] buffer; |
|
244 |
|
|
245 |
/** |
|
246 |
* The index of first valid character in the buffer. |
|
247 |
* <br> |
|
248 |
* 0 <= head < bufSize |
|
249 |
*/ |
|
250 |
private int head; |
|
251 |
|
|
252 |
/** |
|
253 |
* The index of last valid character in the buffer + 1. |
|
254 |
* <br> |
|
255 |
* 0 <= tail <= bufSize |
|
256 |
*/ |
|
257 |
private int tail; |
|
258 |
|
|
259 |
/** |
|
260 |
* The content encoding to use when reading headers. |
|
261 |
*/ |
|
262 |
private String headerEncoding; |
|
263 |
|
|
264 |
/** |
|
265 |
* The progress notifier, if any, or null. |
|
266 |
*/ |
|
267 |
private final ProgressNotifier notifier; |
|
268 |
|
|
269 |
// ----------------------------------------------------------- Constructors |
|
270 |
|
|
271 |
/** |
|
272 |
* Creates a new instance. |
|
273 |
* |
|
274 |
* @deprecated 1.2.1 Use {@link #MultipartStream(InputStream, byte[], int, |
|
275 |
* ProgressNotifier)} |
|
276 |
*/ |
|
277 |
@Deprecated |
|
278 |
public MultipartStream() { |
|
279 |
this(null, null, null); |
|
280 |
} |
|
281 |
|
|
282 |
/** |
|
283 |
* <p> Constructs a <code>MultipartStream</code> with a custom size buffer |
|
284 |
* and no progress notifier. |
|
285 |
* |
|
286 |
* <p> Note that the buffer must be at least big enough to contain the |
|
287 |
* boundary string, plus 4 characters for CR/LF and double dash, plus at |
|
288 |
* least one byte of data. Too small a buffer size setting will degrade |
|
289 |
* performance. |
|
290 |
* |
|
291 |
* @param input The <code>InputStream</code> to serve as a data source. |
|
292 |
* @param boundary The token used for dividing the stream into |
|
293 |
* <code>encapsulations</code>. |
|
294 |
* @param bufSize The size of the buffer to be used, in bytes. |
|
295 |
* |
|
296 |
* @deprecated 1.2.1 Use {@link #MultipartStream(InputStream, byte[], int, |
|
297 |
* ProgressNotifier)}. |
|
298 |
*/ |
|
299 |
@Deprecated |
|
300 |
public MultipartStream(InputStream input, byte[] boundary, int bufSize) { |
|
301 |
this(input, boundary, bufSize, null); |
|
302 |
} |
|
303 |
|
|
304 |
/** |
|
305 |
* <p> Constructs a <code>MultipartStream</code> with a custom size buffer. |
|
306 |
* |
|
307 |
* <p> Note that the buffer must be at least big enough to contain the |
|
308 |
* boundary string, plus 4 characters for CR/LF and double dash, plus at |
|
309 |
* least one byte of data. Too small a buffer size setting will degrade |
|
310 |
* performance. |
|
311 |
* |
|
312 |
* @param input The <code>InputStream</code> to serve as a data source. |
|
313 |
* @param boundary The token used for dividing the stream into |
|
314 |
* <code>encapsulations</code>. |
|
315 |
* @param bufSize The size of the buffer to be used, in bytes. |
|
316 |
* @param pNotifier The notifier, which is used for calling the |
|
317 |
* progress listener, if any. |
|
318 |
* |
|
319 |
* @throws IllegalArgumentException If the buffer size is too small |
|
320 |
* |
|
321 |
* @since 1.3.1 |
|
322 |
*/ |
|
323 |
public MultipartStream(InputStream input, |
|
324 |
byte[] boundary, |
|
325 |
int bufSize, |
|
326 |
ProgressNotifier pNotifier) { |
|
327 |
|
|
328 |
if (boundary == null) { |
|
329 |
throw new IllegalArgumentException("boundary may not be null"); |
|
330 |
} |
|
331 |
// We prepend CR/LF to the boundary to chop trailing CR/LF from |
|
332 |
// body-data tokens. |
|
333 |
this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length; |
|
334 |
if (bufSize < this.boundaryLength + 1) { |
|
335 |
throw new IllegalArgumentException( |
|
336 |
"The buffer size specified for the MultipartStream is too small"); |
|
337 |
} |
|
338 |
|
|
339 |
this.input = input; |
|
340 |
this.bufSize = Math.max(bufSize, boundaryLength * 2); |
|
341 |
this.buffer = new byte[this.bufSize]; |
|
342 |
this.notifier = pNotifier; |
|
343 |
|
|
344 |
this.boundary = new byte[this.boundaryLength]; |
|
345 |
this.boundaryTable = new int[this.boundaryLength + 1]; |
|
346 |
this.keepRegion = this.boundary.length; |
|
347 |
|
|
348 |
System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0, |
|
349 |
BOUNDARY_PREFIX.length); |
|
350 |
System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, |
|
351 |
boundary.length); |
|
352 |
computeBoundaryTable(); |
|
353 |
|
|
354 |
head = 0; |
|
355 |
tail = 0; |
|
356 |
} |
|
357 |
|
|
358 |
/** |
|
359 |
* <p> Constructs a <code>MultipartStream</code> with a default size buffer. |
|
360 |
* |
|
361 |
* @param input The <code>InputStream</code> to serve as a data source. |
|
362 |
* @param boundary The token used for dividing the stream into |
|
363 |
* <code>encapsulations</code>. |
|
364 |
* @param pNotifier An object for calling the progress listener, if any. |
|
365 |
* |
|
366 |
* |
|
367 |
* @see #MultipartStream(InputStream, byte[], int, ProgressNotifier) |
|
368 |
*/ |
|
369 |
MultipartStream(InputStream input, |
|
370 |
byte[] boundary, |
|
371 |
ProgressNotifier pNotifier) { |
|
372 |
this(input, boundary, DEFAULT_BUFSIZE, pNotifier); |
|
373 |
} |
|
374 |
|
|
375 |
/** |
|
376 |
* <p> Constructs a <code>MultipartStream</code> with a default size buffer. |
|
377 |
* |
|
378 |
* @param input The <code>InputStream</code> to serve as a data source. |
|
379 |
* @param boundary The token used for dividing the stream into |
|
380 |
* <code>encapsulations</code>. |
|
381 |
* |
|
382 |
* @deprecated 1.2.1 Use {@link #MultipartStream(InputStream, byte[], int, |
|
383 |
* ProgressNotifier)}. |
|
384 |
*/ |
|
385 |
@Deprecated |
|
386 |
public MultipartStream(InputStream input, |
|
387 |
byte[] boundary) { |
|
388 |
this(input, boundary, DEFAULT_BUFSIZE, null); |
|
389 |
} |
|
390 |
|
|
391 |
// --------------------------------------------------------- Public methods |
|
392 |
|
|
393 |
/** |
|
394 |
* Retrieves the character encoding used when reading the headers of an |
|
395 |
* individual part. When not specified, or <code>null</code>, the platform |
|
396 |
* default encoding is used. |
|
397 |
* |
|
398 |
* @return The encoding used to read part headers. |
|
399 |
*/ |
|
400 |
public String getHeaderEncoding() { |
|
401 |
return headerEncoding; |
|
402 |
} |
|
403 |
|
|
404 |
/** |
|
405 |
* Specifies the character encoding to be used when reading the headers of |
|
406 |
* individual parts. When not specified, or <code>null</code>, the platform |
|
407 |
* default encoding is used. |
|
408 |
* |
|
409 |
* @param encoding The encoding used to read part headers. |
|
410 |
*/ |
|
411 |
public void setHeaderEncoding(String encoding) { |
|
412 |
headerEncoding = encoding; |
|
413 |
} |
|
414 |
|
|
415 |
/** |
|
416 |
* Reads a byte from the <code>buffer</code>, and refills it as |
|
417 |
* necessary. |
|
418 |
* |
|
419 |
* @return The next byte from the input stream. |
|
420 |
* |
|
421 |
* @throws IOException if there is no more data available. |
|
422 |
*/ |
|
423 |
public byte readByte() throws IOException { |
|
424 |
// Buffer depleted ? |
|
425 |
if (head == tail) { |
|
426 |
head = 0; |
|
427 |
// Refill. |
|
428 |
tail = input.read(buffer, head, bufSize); |
|
429 |
if (tail == -1) { |
|
430 |
// No more data available. |
|
431 |
throw new IOException("No more data is available"); |
|
432 |
} |
|
433 |
if (notifier != null) { |
|
434 |
notifier.noteBytesRead(tail); |
|
435 |
} |
|
436 |
} |
|
437 |
return buffer[head++]; |
|
438 |
} |
|
439 |
|
|
440 |
/** |
|
441 |
* Skips a <code>boundary</code> token, and checks whether more |
|
442 |
* <code>encapsulations</code> are contained in the stream. |
|
443 |
* |
|
444 |
* @return <code>true</code> if there are more encapsulations in |
|
445 |
* this stream; <code>false</code> otherwise. |
|
446 |
* |
|
447 |
* @throws FileUploadIOException if the bytes read from the stream exceeded the size limits |
|
448 |
* @throws MalformedStreamException if the stream ends unexpectedly or |
|
449 |
* fails to follow required syntax. |
|
450 |
*/ |
|
451 |
public boolean readBoundary() |
|
452 |
throws FileUploadIOException, MalformedStreamException { |
|
453 |
byte[] marker = new byte[2]; |
|
454 |
boolean nextChunk = false; |
|
455 |
|
|
456 |
head += boundaryLength; |
|
457 |
try { |
|
458 |
marker[0] = readByte(); |
|
459 |
if (marker[0] == LF) { |
|
460 |
// Work around IE5 Mac bug with input type=image. |
|
461 |
// Because the boundary delimiter, not including the trailing |
|
462 |
// CRLF, must not appear within any file (RFC 2046, section |
|
463 |
// 5.1.1), we know the missing CR is due to a buggy browser |
|
464 |
// rather than a file containing something similar to a |
|
465 |
// boundary. |
|
466 |
return true; |
|
467 |
} |
|
468 |
|
|
469 |
marker[1] = readByte(); |
|
470 |
if (arrayequals(marker, STREAM_TERMINATOR, 2)) { |
|
471 |
nextChunk = false; |
|
472 |
} else if (arrayequals(marker, FIELD_SEPARATOR, 2)) { |
|
473 |
nextChunk = true; |
|
474 |
} else { |
|
475 |
throw new MalformedStreamException( |
|
476 |
"Unexpected characters follow a boundary"); |
|
477 |
} |
|
478 |
} catch (FileUploadIOException e) { |
|
479 |
// wraps a SizeException, re-throw as it will be unwrapped later |
|
480 |
throw e; |
|
481 |
} catch (IOException e) { |
|
482 |
throw new MalformedStreamException("Stream ended unexpectedly"); |
|
483 |
} |
|
484 |
return nextChunk; |
|
485 |
} |
|
486 |
|
|
487 |
/** |
|
488 |
* <p>Changes the boundary token used for partitioning the stream. |
|
489 |
* |
|
490 |
* <p>This method allows single pass processing of nested multipart |
|
491 |
* streams. |
|
492 |
* |
|
493 |
* <p>The boundary token of the nested stream is <code>required</code> |
|
494 |
* to be of the same length as the boundary token in parent stream. |
|
495 |
* |
|
496 |
* <p>Restoring the parent stream boundary token after processing of a |
|
497 |
* nested stream is left to the application. |
|
498 |
* |
|
499 |
* @param boundary The boundary to be used for parsing of the nested |
|
500 |
* stream. |
|
501 |
* |
|
502 |
* @throws IllegalBoundaryException if the <code>boundary</code> |
|
503 |
* has a different length than the one |
|
504 |
* being currently parsed. |
|
505 |
*/ |
|
506 |
public void setBoundary(byte[] boundary) |
|
507 |
throws IllegalBoundaryException { |
|
508 |
if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) { |
|
509 |
throw new IllegalBoundaryException( |
|
510 |
"The length of a boundary token cannot be changed"); |
|
511 |
} |
|
512 |
System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, |
|
513 |
boundary.length); |
|
514 |
computeBoundaryTable(); |
|
515 |
} |
|
516 |
|
|
517 |
/** |
|
518 |
* Compute the table used for Knuth-Morris-Pratt search algorithm. |
|
519 |
*/ |
|
520 |
private void computeBoundaryTable() { |
|
521 |
int position = 2; |
|
522 |
int candidate = 0; |
|
523 |
|
|
524 |
boundaryTable[0] = -1; |
|
525 |
boundaryTable[1] = 0; |
|
526 |
|
|
527 |
while (position <= boundaryLength) { |
|
528 |
if (boundary[position - 1] == boundary[candidate]) { |
|
529 |
boundaryTable[position] = candidate + 1; |
|
530 |
candidate++; |
|
531 |
position++; |
|
532 |
} else if (candidate > 0) { |
|
533 |
candidate = boundaryTable[candidate]; |
|
534 |
} else { |
|
535 |
boundaryTable[position] = 0; |
|
536 |
position++; |
|
537 |
} |
|
538 |
} |
|
539 |
} |
|
540 |
|
|
541 |
/** |
|
542 |
* <p>Reads the <code>header-part</code> of the current |
|
543 |
* <code>encapsulation</code>. |
|
544 |
* |
|
545 |
* <p>Headers are returned verbatim to the input stream, including the |
|
546 |
* trailing <code>CRLF</code> marker. Parsing is left to the |
|
547 |
* application. |
|
548 |
* |
|
549 |
* <p><strong>TODO</strong> allow limiting maximum header size to |
|
550 |
* protect against abuse. |
|
551 |
* |
|
552 |
* @return The <code>header-part</code> of the current encapsulation. |
|
553 |
* |
|
554 |
* @throws FileUploadIOException if the bytes read from the stream exceeded the size limits. |
|
555 |
* @throws MalformedStreamException if the stream ends unexpectedly. |
|
556 |
*/ |
|
557 |
public String readHeaders() throws FileUploadIOException, MalformedStreamException { |
|
558 |
int i = 0; |
|
559 |
byte b; |
|
560 |
// to support multi-byte characters |
|
561 |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|
562 |
int size = 0; |
|
563 |
while (i < HEADER_SEPARATOR.length) { |
|
564 |
try { |
|
565 |
b = readByte(); |
|
566 |
} catch (FileUploadIOException e) { |
|
567 |
// wraps a SizeException, re-throw as it will be unwrapped later |
|
568 |
throw e; |
|
569 |
} catch (IOException e) { |
|
570 |
throw new MalformedStreamException("Stream ended unexpectedly"); |
|
571 |
} |
|
572 |
if (++size > HEADER_PART_SIZE_MAX) { |
|
573 |
throw new MalformedStreamException( |
|
574 |
format("Header section has more than %s bytes (maybe it is not properly terminated)", |
|
575 |
Integer.valueOf(HEADER_PART_SIZE_MAX))); |
|
576 |
} |
|
577 |
if (b == HEADER_SEPARATOR[i]) { |
|
578 |
i++; |
|
579 |
} else { |
|
580 |
i = 0; |
|
581 |
} |
|
582 |
baos.write(b); |
|
583 |
} |
|
584 |
|
|
585 |
String headers = null; |
|
586 |
if (headerEncoding != null) { |
|
587 |
try { |
|
588 |
headers = baos.toString(headerEncoding); |
|
589 |
} catch (UnsupportedEncodingException e) { |
|
590 |
// Fall back to platform default if specified encoding is not |
|
591 |
// supported. |
|
592 |
headers = baos.toString(); |
|
593 |
} |
|
594 |
} else { |
|
595 |
headers = baos.toString(); |
|
596 |
} |
|
597 |
|
|
598 |
return headers; |
|
599 |
} |
|
600 |
|
|
601 |
/** |
|
602 |
* <p>Reads <code>body-data</code> from the current |
|
603 |
* <code>encapsulation</code> and writes its contents into the |
|
604 |
* output <code>Stream</code>. |
|
605 |
* |
|
606 |
* <p>Arbitrary large amounts of data can be processed by this |
|
607 |
* method using a constant size buffer. (see {@link |
|
608 |
* #MultipartStream(InputStream,byte[],int, |
|
609 |
* MultipartStream.ProgressNotifier) constructor}). |
|
610 |
* |
|
611 |
* @param output The <code>Stream</code> to write data into. May |
|
612 |
* be null, in which case this method is equivalent |
|
613 |
* to {@link #discardBodyData()}. |
|
614 |
* |
|
615 |
* @return the amount of data written. |
|
616 |
* |
|
617 |
* @throws MalformedStreamException if the stream ends unexpectedly. |
|
618 |
* @throws IOException if an i/o error occurs. |
|
619 |
*/ |
|
620 |
public int readBodyData(OutputStream output) |
|
621 |
throws MalformedStreamException, IOException { |
|
622 |
return (int) Streams.copy(newInputStream(), output, false); // N.B. Streams.copy closes the input stream |
|
623 |
} |
|
624 |
|
|
625 |
/** |
|
626 |
* Creates a new {@link ItemInputStream}. |
|
627 |
* @return A new instance of {@link ItemInputStream}. |
|
628 |
*/ |
|
629 |
ItemInputStream newInputStream() { |
|
630 |
return new ItemInputStream(); |
|
631 |
} |
|
632 |
|
|
633 |
/** |
|
634 |
* <p> Reads <code>body-data</code> from the current |
|
635 |
* <code>encapsulation</code> and discards it. |
|
636 |
* |
|
637 |
* <p>Use this method to skip encapsulations you don't need or don't |
|
638 |
* understand. |
|
639 |
* |
|
640 |
* @return The amount of data discarded. |
|
641 |
* |
|
642 |
* @throws MalformedStreamException if the stream ends unexpectedly. |
|
643 |
* @throws IOException if an i/o error occurs. |
|
644 |
*/ |
|
645 |
public int discardBodyData() throws MalformedStreamException, IOException { |
|
646 |
return readBodyData(null); |
|
647 |
} |
|
648 |
|
|
649 |
/** |
|
650 |
* Finds the beginning of the first <code>encapsulation</code>. |
|
651 |
* |
|
652 |
* @return <code>true</code> if an <code>encapsulation</code> was found in |
|
653 |
* the stream. |
|
654 |
* |
|
655 |
* @throws IOException if an i/o error occurs. |
|
656 |
*/ |
|
657 |
public boolean skipPreamble() throws IOException { |
|
658 |
// First delimiter may be not preceeded with a CRLF. |
|
659 |
System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2); |
|
660 |
boundaryLength = boundary.length - 2; |
|
661 |
computeBoundaryTable(); |
|
662 |
try { |
|
663 |
// Discard all data up to the delimiter. |
|
664 |
discardBodyData(); |
|
665 |
|
|
666 |
// Read boundary - if succeeded, the stream contains an |
|
667 |
// encapsulation. |
|
668 |
return readBoundary(); |
|
669 |
} catch (MalformedStreamException e) { |
|
670 |
return false; |
|
671 |
} finally { |
|
672 |
// Restore delimiter. |
|
673 |
System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2); |
|
674 |
boundaryLength = boundary.length; |
|
675 |
boundary[0] = CR; |
|
676 |
boundary[1] = LF; |
|
677 |
computeBoundaryTable(); |
|
678 |
} |
|
679 |
} |
|
680 |
|
|
681 |
/** |
|
682 |
* Compares <code>count</code> first bytes in the arrays |
|
683 |
* <code>a</code> and <code>b</code>. |
|
684 |
* |
|
685 |
* @param a The first array to compare. |
|
686 |
* @param b The second array to compare. |
|
687 |
* @param count How many bytes should be compared. |
|
688 |
* |
|
689 |
* @return <code>true</code> if <code>count</code> first bytes in arrays |
|
690 |
* <code>a</code> and <code>b</code> are equal. |
|
691 |
*/ |
|
692 |
public static boolean arrayequals(byte[] a, |
|
693 |
byte[] b, |
|
694 |
int count) { |
|
695 |
for (int i = 0; i < count; i++) { |
|
696 |
if (a[i] != b[i]) { |
|
697 |
return false; |
|
698 |
} |
|
699 |
} |
|
700 |
return true; |
|
701 |
} |
|
702 |
|
|
703 |
/** |
|
704 |
* Searches for a byte of specified value in the <code>buffer</code>, |
|
705 |
* starting at the specified <code>position</code>. |
|
706 |
* |
|
707 |
* @param value The value to find. |
|
708 |
* @param pos The starting position for searching. |
|
709 |
* |
|
710 |
* @return The position of byte found, counting from beginning of the |
|
711 |
* <code>buffer</code>, or <code>-1</code> if not found. |
|
712 |
*/ |
|
713 |
protected int findByte(byte value, |
|
714 |
int pos) { |
|
715 |
for (int i = pos; i < tail; i++) { |
|
716 |
if (buffer[i] == value) { |
|
717 |
return i; |
|
718 |
} |
|
719 |
} |
|
720 |
|
|
721 |
return -1; |
|
722 |
} |
|
723 |
|
|
724 |
/** |
|
725 |
* Searches for the <code>boundary</code> in the <code>buffer</code> |
|
726 |
* region delimited by <code>head</code> and <code>tail</code>. |
|
727 |
* |
|
728 |
* @return The position of the boundary found, counting from the |
|
729 |
* beginning of the <code>buffer</code>, or <code>-1</code> if |
|
730 |
* not found. |
|
731 |
*/ |
|
732 |
protected int findSeparator() { |
|
733 |
|
|
734 |
int bufferPos = this.head; |
|
735 |
int tablePos = 0; |
|
736 |
|
|
737 |
while (bufferPos < this.tail) { |
|
738 |
while (tablePos >= 0 && buffer[bufferPos] != boundary[tablePos]) { |
|
739 |
tablePos = boundaryTable[tablePos]; |
|
740 |
} |
|
741 |
bufferPos++; |
|
742 |
tablePos++; |
|
743 |
if (tablePos == boundaryLength) { |
|
744 |
return bufferPos - boundaryLength; |
|
745 |
} |
|
746 |
} |
|
747 |
return -1; |
|
748 |
} |
|
749 |
|
|
750 |
/** |
|
751 |
* Thrown to indicate that the input stream fails to follow the |
|
752 |
* required syntax. |
|
753 |
*/ |
|
754 |
public static class MalformedStreamException extends IOException { |
|
755 |
|
|
756 |
/** |
|
757 |
* The UID to use when serializing this instance. |
|
758 |
*/ |
|
759 |
private static final long serialVersionUID = 6466926458059796677L; |
|
760 |
|
|
761 |
/** |
|
762 |
* Constructs a <code>MalformedStreamException</code> with no |
|
763 |
* detail message. |
|
764 |
*/ |
|
765 |
public MalformedStreamException() { |
|
766 |
super(); |
|
767 |
} |
|
768 |
|
|
769 |
/** |
|
770 |
* Constructs an <code>MalformedStreamException</code> with |
|
771 |
* the specified detail message. |
|
772 |
* |
|
773 |
* @param message The detail message. |
|
774 |
*/ |
|
775 |
public MalformedStreamException(String message) { |
|
776 |
super(message); |
|
777 |
} |
|
778 |
|
|
779 |
} |
|
780 |
|
|
781 |
/** |
|
782 |
* Thrown upon attempt of setting an invalid boundary token. |
|
783 |
*/ |
|
784 |
public static class IllegalBoundaryException extends IOException { |
|
785 |
|
|
786 |
/** |
|
787 |
* The UID to use when serializing this instance. |
|
788 |
*/ |
|
789 |
private static final long serialVersionUID = -161533165102632918L; |
|
790 |
|
|
791 |
/** |
|
792 |
* Constructs an <code>IllegalBoundaryException</code> with no |
|
793 |
* detail message. |
|
794 |
*/ |
|
795 |
public IllegalBoundaryException() { |
|
796 |
super(); |
|
797 |
} |
|
798 |
|
|
799 |
/** |
|
800 |
* Constructs an <code>IllegalBoundaryException</code> with |
|
801 |
* the specified detail message. |
|
802 |
* |
|
803 |
* @param message The detail message. |
|
804 |
*/ |
|
805 |
public IllegalBoundaryException(String message) { |
|
806 |
super(message); |
|
807 |
} |
|
808 |
|
|
809 |
} |
|
810 |
|
|
811 |
/** |
|
812 |
* An {@link InputStream} for reading an items contents. |
|
813 |
*/ |
|
814 |
public class ItemInputStream extends InputStream implements Closeable { |
|
815 |
|
|
816 |
/** |
|
817 |
* The number of bytes, which have been read so far. |
|
818 |
*/ |
|
819 |
private long total; |
|
820 |
|
|
821 |
/** |
|
822 |
* The number of bytes, which must be hold, because |
|
823 |
* they might be a part of the boundary. |
|
824 |
*/ |
|
825 |
private int pad; |
|
826 |
|
|
827 |
/** |
|
828 |
* The current offset in the buffer. |
|
829 |
*/ |
|
830 |
private int pos; |
|
831 |
|
|
832 |
/** |
|
833 |
* Whether the stream is already closed. |
|
834 |
*/ |
|
835 |
private boolean closed; |
|
836 |
|
|
837 |
/** |
|
838 |
* Creates a new instance. |
|
839 |
*/ |
|
840 |
ItemInputStream() { |
|
841 |
findSeparator(); |
|
842 |
} |
|
843 |
|
|
844 |
/** |
|
845 |
* Called for finding the separator. |
|
846 |
*/ |
|
847 |
private void findSeparator() { |
|
848 |
pos = MultipartStream.this.findSeparator(); |
|
849 |
if (pos == -1) { |
|
850 |
if (tail - head > keepRegion) { |
|
851 |
pad = keepRegion; |
|
852 |
} else { |
|
853 |
pad = tail - head; |
|
854 |
} |
|
855 |
} |
|
856 |
} |
|
857 |
|
|
858 |
/** |
|
859 |
* Returns the number of bytes, which have been read |
|
860 |
* by the stream. |
|
861 |
* |
|
862 |
* @return Number of bytes, which have been read so far. |
|
863 |
*/ |
|
864 |
public long getBytesRead() { |
|
865 |
return total; |
|
866 |
} |
|
867 |
|
|
868 |
/** |
|
869 |
* Returns the number of bytes, which are currently |
|
870 |
* available, without blocking. |
|
871 |
* |
|
872 |
* @throws IOException An I/O error occurs. |
|
873 |
* @return Number of bytes in the buffer. |
|
874 |
*/ |
|
875 |
@Override |
|
876 |
public int available() throws IOException { |
|
877 |
if (pos == -1) { |
|
878 |
return tail - head - pad; |
|
879 |
} |
|
880 |
return pos - head; |
|
881 |
} |
|
882 |
|
|
883 |
/** |
|
884 |
* Offset when converting negative bytes to integers. |
|
885 |
*/ |
|
886 |
private static final int BYTE_POSITIVE_OFFSET = 256; |
|
887 |
|
|
888 |
/** |
|
889 |
* Returns the next byte in the stream. |
|
890 |
* |
|
891 |
* @return The next byte in the stream, as a non-negative |
|
892 |
* integer, or -1 for EOF. |
|
893 |
* @throws IOException An I/O error occurred. |
|
894 |
*/ |
|
895 |
@Override |
|
896 |
public int read() throws IOException { |
|
897 |
if (closed) { |
|
898 |
throw new ItemSkippedException(); |
|
899 |
} |
|
900 |
if (available() == 0 && makeAvailable() == 0) { |
|
901 |
return -1; |
|
902 |
} |
|
903 |
++total; |
|
904 |
int b = buffer[head++]; |
|
905 |
if (b >= 0) { |
|
906 |
return b; |
|
907 |
} |
|
908 |
return b + BYTE_POSITIVE_OFFSET; |
|
909 |
} |
|
910 |
|
|
911 |
/** |
|
912 |
* Reads bytes into the given buffer. |
|
913 |
* |
|
914 |
* @param b The destination buffer, where to write to. |
|
915 |
* @param off Offset of the first byte in the buffer. |
|
916 |
* @param len Maximum number of bytes to read. |
|
917 |
* @return Number of bytes, which have been actually read, |
|
918 |
* or -1 for EOF. |
|
919 |
* @throws IOException An I/O error occurred. |
|
920 |
*/ |
|
921 |
@Override |
|
922 |
public int read(byte[] b, int off, int len) throws IOException { |
|
923 |
if (closed) { |
|
924 |
throw new ItemSkippedException(); |
|
925 |
} |
|
926 |
if (len == 0) { |
|
927 |
return 0; |
|
928 |
} |
|
929 |
int res = available(); |
|
930 |
if (res == 0) { |
|
931 |
res = makeAvailable(); |
|
932 |
if (res == 0) { |
|
933 |
return -1; |
|
934 |
} |
|
935 |
} |
|
936 |
res = Math.min(res, len); |
|
937 |
System.arraycopy(buffer, head, b, off, res); |
|
938 |
head += res; |
|
939 |
total += res; |
|
940 |
return res; |
|
941 |
} |
|
942 |
|
|
943 |
/** |
|
944 |
* Closes the input stream. |
|
945 |
* |
|
946 |
* @throws IOException An I/O error occurred. |
|
947 |
*/ |
|
948 |
@Override |
|
949 |
public void close() throws IOException { |
|
950 |
close(false); |
|
951 |
} |
|
952 |
|
|
953 |
/** |
|
954 |
* Closes the input stream. |
|
955 |
* |
|
956 |
* @param pCloseUnderlying Whether to close the underlying stream |
|
957 |
* (hard close) |
|
958 |
* @throws IOException An I/O error occurred. |
|
959 |
*/ |
|
960 |
public void close(boolean pCloseUnderlying) throws IOException { |
|
961 |
if (closed) { |
|
962 |
return; |
|
963 |
} |
|
964 |
if (pCloseUnderlying) { |
|
965 |
closed = true; |
|
966 |
input.close(); |
|
967 |
} else { |
|
968 |
for (;;) { |
|
969 |
int av = available(); |
|
970 |
if (av == 0) { |
|
971 |
av = makeAvailable(); |
|
972 |
if (av == 0) { |
|
973 |
break; |
|
974 |
} |
|
975 |
} |
|
976 |
skip(av); |
|
977 |
} |
|
978 |
} |
|
979 |
closed = true; |
|
980 |
} |
|
981 |
|
|
982 |
/** |
|
983 |
* Skips the given number of bytes. |
|
984 |
* |
|
985 |
* @param bytes Number of bytes to skip. |
|
986 |
* @return The number of bytes, which have actually been |
|
987 |
* skipped. |
|
988 |
* @throws IOException An I/O error occurred. |
|
989 |
*/ |
|
990 |
@Override |
|
991 |
public long skip(long bytes) throws IOException { |
|
992 |
if (closed) { |
|
993 |
throw new ItemSkippedException(); |
|
994 |
} |
|
995 |
int av = available(); |
|
996 |
if (av == 0) { |
|
997 |
av = makeAvailable(); |
|
998 |
if (av == 0) { |
|
999 |
return 0; |
|
1000 |
} |
|
1001 |
} |
|
1002 |
long res = Math.min(av, bytes); |
|
1003 |
head += res; |
|
1004 |
return res; |
|
1005 |
} |
|
1006 |
|
|
1007 |
/** |
|
1008 |
* Attempts to read more data. |
|
1009 |
* |
|
1010 |
* @return Number of available bytes |
|
1011 |
* @throws IOException An I/O error occurred. |
|
1012 |
*/ |
|
1013 |
private int makeAvailable() throws IOException { |
|
1014 |
if (pos != -1) { |
|
1015 |
return 0; |
|
1016 |
} |
|
1017 |
|
|
1018 |
// Move the data to the beginning of the buffer. |
|
1019 |
total += tail - head - pad; |
|
1020 |
System.arraycopy(buffer, tail - pad, buffer, 0, pad); |
|
1021 |
|
|
1022 |
// Refill buffer with new data. |
|
1023 |
head = 0; |
|
1024 |
tail = pad; |
|
1025 |
|
|
1026 |
for (;;) { |
|
1027 |
int bytesRead = input.read(buffer, tail, bufSize - tail); |
|
1028 |
if (bytesRead == -1) { |
|
1029 |
// The last pad amount is left in the buffer. |
|
1030 |
// Boundary can't be in there so signal an error |
|
1031 |
// condition. |
|
1032 |
final String msg = "Stream ended unexpectedly"; |
|
1033 |
throw new MalformedStreamException(msg); |
|
1034 |
} |
|
1035 |
if (notifier != null) { |
|
1036 |
notifier.noteBytesRead(bytesRead); |
|
1037 |
} |
|
1038 |
tail += bytesRead; |
|
1039 |
|
|
1040 |
findSeparator(); |
|
1041 |
int av = available(); |
|
1042 |
|
|
1043 |
if (av > 0 || pos != -1) { |
|
1044 |
return av; |
|
1045 |
} |
|
1046 |
} |
|
1047 |
} |
|
1048 |
|
|
1049 |
/** |
|
1050 |
* Returns, whether the stream is closed. |
|
1051 |
* |
|
1052 |
* @return True, if the stream is closed, otherwise false. |
|
1053 |
*/ |
|
1054 |
@Override |
|
1055 |
public boolean isClosed() { |
|
1056 |
return closed; |
|
1057 |
} |
|
1058 |
|
|
1059 |
} |
|
1060 |
|
|
1061 |
} |