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 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 FileItemStream.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 FileItemStream.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 FileItemStream.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 }