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