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