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