Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
MultipartStream |
|
| 3.210526315789474;3.211 | ||||
MultipartStream$IllegalBoundaryException |
|
| 3.210526315789474;3.211 | ||||
MultipartStream$ItemInputStream |
|
| 3.210526315789474;3.211 | ||||
MultipartStream$MalformedStreamException |
|
| 3.210526315789474;3.211 | ||||
MultipartStream$ProgressNotifier |
|
| 3.210526315789474;3.211 |
1 | /* | |
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 org.apache.commons.fileupload; | |
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 | import org.apache.commons.fileupload.FileUploadBase.FileUploadIOException; | |
28 | import org.apache.commons.fileupload.util.Closeable; | |
29 | import org.apache.commons.fileupload.util.Streams; | |
30 | ||
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 | 66303156 | 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 | 39 | ProgressNotifier(ProgressListener pListener, long pContentLength) { |
120 | 39 | listener = pListener; |
121 | 39 | contentLength = pContentLength; |
122 | 39 | } |
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 | 8507 | bytesRead += pBytes; |
134 | 8507 | notifyListener(); |
135 | 8507 | } |
136 | ||
137 | /** | |
138 | * Called to indicate, that a new file item has been detected. | |
139 | */ | |
140 | void noteItem() { | |
141 | 3190 | ++items; |
142 | 3190 | notifyListener(); |
143 | 3190 | } |
144 | ||
145 | /** | |
146 | * Called for notifying the listener. | |
147 | */ | |
148 | private void notifyListener() { | |
149 | 11697 | if (listener != null) { |
150 | 5212 | listener.update(bytesRead, contentLength, items); |
151 | } | |
152 | 11697 | } |
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 | 1 | 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 | 1 | 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 | 1 | protected static final byte[] STREAM_TERMINATOR = {DASH, DASH}; |
201 | ||
202 | /** | |
203 | * A byte sequence that precedes a boundary (<code>CRLF--</code>). | |
204 | */ | |
205 | 1 | 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 | 0 | this(null, null, null); |
280 | 0 | } |
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 | 0 | this(input, boundary, bufSize, null); |
302 | 0 | } |
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 | 39 | ProgressNotifier pNotifier) { |
327 | ||
328 | 39 | if (boundary == null) { |
329 | 0 | 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 | 39 | this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length; |
334 | 39 | if (bufSize < this.boundaryLength + 1) { |
335 | 1 | throw new IllegalArgumentException( |
336 | "The buffer size specified for the MultipartStream is too small"); | |
337 | } | |
338 | ||
339 | 38 | this.input = input; |
340 | 38 | this.bufSize = Math.max(bufSize, boundaryLength * 2); |
341 | 38 | this.buffer = new byte[this.bufSize]; |
342 | 38 | this.notifier = pNotifier; |
343 | ||
344 | 38 | this.boundary = new byte[this.boundaryLength]; |
345 | 38 | this.boundaryTable = new int[this.boundaryLength + 1]; |
346 | 38 | this.keepRegion = this.boundary.length; |
347 | ||
348 | 38 | System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0, |
349 | BOUNDARY_PREFIX.length); | |
350 | 38 | System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, |
351 | boundary.length); | |
352 | 38 | computeBoundaryTable(); |
353 | ||
354 | 38 | head = 0; |
355 | 38 | tail = 0; |
356 | 38 | } |
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 | 37 | this(input, boundary, DEFAULT_BUFSIZE, pNotifier); |
373 | 37 | } |
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 | 0 | this(input, boundary, DEFAULT_BUFSIZE, null); |
389 | 0 | } |
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 | 0 | 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 | 36 | headerEncoding = encoding; |
413 | 36 | } |
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 | 170498 | if (head == tail) { |
426 | 71 | head = 0; |
427 | // Refill. | |
428 | 71 | tail = input.read(buffer, head, bufSize); |
429 | 71 | if (tail == -1) { |
430 | // No more data available. | |
431 | 0 | throw new IOException("No more data is available"); |
432 | } | |
433 | 71 | if (notifier != null) { |
434 | 71 | notifier.noteBytesRead(tail); |
435 | } | |
436 | } | |
437 | 170498 | 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 | 3228 | byte[] marker = new byte[2]; |
454 | 3228 | boolean nextChunk = false; |
455 | ||
456 | 3228 | head += boundaryLength; |
457 | try { | |
458 | 3228 | marker[0] = readByte(); |
459 | 3228 | 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 | 4 | return true; |
467 | } | |
468 | ||
469 | 3224 | marker[1] = readByte(); |
470 | 3224 | if (arrayequals(marker, STREAM_TERMINATOR, 2)) { |
471 | 33 | nextChunk = false; |
472 | 3191 | } else if (arrayequals(marker, FIELD_SEPARATOR, 2)) { |
473 | 3191 | nextChunk = true; |
474 | } else { | |
475 | 0 | throw new MalformedStreamException( |
476 | "Unexpected characters follow a boundary"); | |
477 | } | |
478 | 0 | } catch (FileUploadIOException e) { |
479 | // wraps a SizeException, re-throw as it will be unwrapped later | |
480 | 0 | throw e; |
481 | 0 | } catch (IOException e) { |
482 | 0 | throw new MalformedStreamException("Stream ended unexpectedly"); |
483 | 3224 | } |
484 | 3224 | 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 | 8 | if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) { |
509 | 0 | throw new IllegalBoundaryException( |
510 | "The length of a boundary token cannot be changed"); | |
511 | } | |
512 | 8 | System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, |
513 | boundary.length); | |
514 | 8 | computeBoundaryTable(); |
515 | 8 | } |
516 | ||
517 | /** | |
518 | * Compute the table used for Knuth-Morris-Pratt search algorithm. | |
519 | */ | |
520 | private void computeBoundaryTable() { | |
521 | 6504 | int position = 2; |
522 | 6504 | int candidate = 0; |
523 | ||
524 | 6504 | boundaryTable[0] = -1; |
525 | 6504 | boundaryTable[1] = 0; |
526 | ||
527 | 77942 | while (position <= boundaryLength) { |
528 | 71438 | if (boundary[position - 1] == boundary[candidate]) { |
529 | 12880 | boundaryTable[position] = candidate + 1; |
530 | 12880 | candidate++; |
531 | 12880 | position++; |
532 | 58558 | } else if (candidate > 0) { |
533 | 12880 | candidate = boundaryTable[candidate]; |
534 | } else { | |
535 | 45678 | boundaryTable[position] = 0; |
536 | 45678 | position++; |
537 | } | |
538 | } | |
539 | 6504 | } |
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 | 3195 | int i = 0; |
559 | byte b; | |
560 | // to support multi-byte characters | |
561 | 3195 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
562 | 3195 | int size = 0; |
563 | 167241 | while (i < HEADER_SEPARATOR.length) { |
564 | try { | |
565 | 164046 | b = readByte(); |
566 | 0 | } catch (FileUploadIOException e) { |
567 | // wraps a SizeException, re-throw as it will be unwrapped later | |
568 | 0 | throw e; |
569 | 0 | } catch (IOException e) { |
570 | 0 | throw new MalformedStreamException("Stream ended unexpectedly"); |
571 | 164046 | } |
572 | 164046 | if (++size > HEADER_PART_SIZE_MAX) { |
573 | 0 | throw new MalformedStreamException( |
574 | 0 | format("Header section has more than %s bytes (maybe it is not properly terminated)", |
575 | 0 | Integer.valueOf(HEADER_PART_SIZE_MAX))); |
576 | } | |
577 | 164046 | if (b == HEADER_SEPARATOR[i]) { |
578 | 12888 | i++; |
579 | } else { | |
580 | 151158 | i = 0; |
581 | } | |
582 | 164046 | baos.write(b); |
583 | } | |
584 | ||
585 | 3195 | String headers = null; |
586 | 3195 | if (headerEncoding != null) { |
587 | try { | |
588 | 0 | headers = baos.toString(headerEncoding); |
589 | 0 | } catch (UnsupportedEncodingException e) { |
590 | // Fall back to platform default if specified encoding is not | |
591 | // supported. | |
592 | 0 | headers = baos.toString(); |
593 | 0 | } |
594 | } else { | |
595 | 3195 | headers = baos.toString(); |
596 | } | |
597 | ||
598 | 3195 | 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 | 3229 | 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 | 6419 | 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 | 3229 | 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 | 3229 | System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2); |
660 | 3229 | boundaryLength = boundary.length - 2; |
661 | 3229 | computeBoundaryTable(); |
662 | try { | |
663 | // Discard all data up to the delimiter. | |
664 | 3229 | discardBodyData(); |
665 | ||
666 | // Read boundary - if succeeded, the stream contains an | |
667 | // encapsulation. | |
668 | 6456 | return readBoundary(); |
669 | 0 | } catch (MalformedStreamException e) { |
670 | 0 | return false; |
671 | } finally { | |
672 | // Restore delimiter. | |
673 | 3229 | System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2); |
674 | 3229 | boundaryLength = boundary.length; |
675 | 3229 | boundary[0] = CR; |
676 | 3229 | boundary[1] = LF; |
677 | 3229 | computeBoundaryTable(); |
678 | 1 | } |
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 | 12863 | for (int i = 0; i < count; i++) { |
696 | 9639 | if (a[i] != b[i]) { |
697 | 3191 | return false; |
698 | } | |
699 | } | |
700 | 3224 | 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 | 0 | for (int i = pos; i < tail; i++) { |
716 | 0 | if (buffer[i] == value) { |
717 | 0 | return i; |
718 | } | |
719 | } | |
720 | ||
721 | 0 | 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 | 14855 | int bufferPos = this.head; |
735 | 14855 | int tablePos = 0; |
736 | ||
737 | 34380763 | while (bufferPos < this.tail) { |
738 | 68680474 | while (tablePos >= 0 && buffer[bufferPos] != boundary[tablePos]) { |
739 | 34308150 | tablePos = boundaryTable[tablePos]; |
740 | } | |
741 | 34372324 | bufferPos++; |
742 | 34372324 | tablePos++; |
743 | 34372324 | if (tablePos == boundaryLength) { |
744 | 6416 | return bufferPos - boundaryLength; |
745 | } | |
746 | } | |
747 | 8439 | 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 | 0 | super(); |
767 | 0 | } |
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 | 2 | super(message); |
777 | 2 | } |
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 | 0 | super(); |
797 | 0 | } |
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 | 0 | super(message); |
807 | 0 | } |
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 | 6419 | ItemInputStream() { |
841 | 6419 | findSeparator(); |
842 | 6419 | } |
843 | ||
844 | /** | |
845 | * Called for finding the separator. | |
846 | */ | |
847 | private void findSeparator() { | |
848 | 14855 | pos = MultipartStream.this.findSeparator(); |
849 | 14855 | if (pos == -1) { |
850 | 8439 | if (tail - head > keepRegion) { |
851 | 8374 | pad = keepRegion; |
852 | } else { | |
853 | 65 | pad = tail - head; |
854 | } | |
855 | } | |
856 | 14855 | } |
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 | 0 | 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 | 17069673 | if (pos == -1) { |
878 | 14948730 | return tail - head - pad; |
879 | } | |
880 | 2120943 | 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 | 17039872 | if (closed) { |
898 | 0 | throw new FileItemStream.ItemSkippedException(); |
899 | } | |
900 | 17039872 | if (available() == 0 && makeAvailable() == 0) { |
901 | 1024 | return -1; |
902 | } | |
903 | 17038848 | ++total; |
904 | 17038848 | int b = buffer[head++]; |
905 | 17038848 | if (b >= 0) { |
906 | 8552192 | return b; |
907 | } | |
908 | 8486656 | 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 | 14949 | if (closed) { |
924 | 0 | throw new FileItemStream.ItemSkippedException(); |
925 | } | |
926 | 14949 | if (len == 0) { |
927 | 0 | return 0; |
928 | } | |
929 | 14949 | int res = available(); |
930 | 14949 | if (res == 0) { |
931 | 9615 | res = makeAvailable(); |
932 | 9612 | if (res == 0) { |
933 | 5389 | return -1; |
934 | } | |
935 | } | |
936 | 9557 | res = Math.min(res, len); |
937 | 9557 | System.arraycopy(buffer, head, b, off, res); |
938 | 9557 | head += res; |
939 | 9557 | total += res; |
940 | 9557 | 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 | 9603 | close(false); |
951 | 9600 | } |
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 | 9605 | if (closed) { |
962 | 3187 | return; |
963 | } | |
964 | 6418 | if (pCloseUnderlying) { |
965 | 2 | closed = true; |
966 | 2 | input.close(); |
967 | } else { | |
968 | for (;;) { | |
969 | 6416 | int av = available(); |
970 | 6416 | if (av == 0) { |
971 | 6416 | av = makeAvailable(); |
972 | 6413 | if (av == 0) { |
973 | 6413 | break; |
974 | } | |
975 | } | |
976 | 0 | skip(av); |
977 | 0 | } |
978 | } | |
979 | 6415 | closed = true; |
980 | 6415 | } |
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 | 0 | if (closed) { |
993 | 0 | throw new FileItemStream.ItemSkippedException(); |
994 | } | |
995 | 0 | int av = available(); |
996 | 0 | if (av == 0) { |
997 | 0 | av = makeAvailable(); |
998 | 0 | if (av == 0) { |
999 | 0 | return 0; |
1000 | } | |
1001 | } | |
1002 | 0 | long res = Math.min(av, bytes); |
1003 | 0 | head += res; |
1004 | 0 | 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 | 21227 | if (pos != -1) { |
1015 | 12791 | return 0; |
1016 | } | |
1017 | ||
1018 | // Move the data to the beginning of the buffer. | |
1019 | 8436 | total += tail - head - pad; |
1020 | 8436 | System.arraycopy(buffer, tail - pad, buffer, 0, pad); |
1021 | ||
1022 | // Refill buffer with new data. | |
1023 | 8436 | head = 0; |
1024 | 8436 | tail = pad; |
1025 | ||
1026 | for (;;) { | |
1027 | 8442 | int bytesRead = input.read(buffer, tail, bufSize - tail); |
1028 | 8438 | 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 | 2 | final String msg = "Stream ended unexpectedly"; |
1033 | 2 | throw new MalformedStreamException(msg); |
1034 | } | |
1035 | 8436 | if (notifier != null) { |
1036 | 8436 | notifier.noteBytesRead(bytesRead); |
1037 | } | |
1038 | 8436 | tail += bytesRead; |
1039 | ||
1040 | 8436 | findSeparator(); |
1041 | 8436 | int av = available(); |
1042 | ||
1043 | 8436 | if (av > 0 || pos != -1) { |
1044 | 8430 | return av; |
1045 | } | |
1046 | 6 | } |
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 | 3185 | return closed; |
1057 | } | |
1058 | ||
1059 | } | |
1060 | ||
1061 | } |