    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 * <code>
    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 * </code>
    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 = new MultipartInput(input, boundary);
    069 *     boolean nextPart = multipartStream.skipPreamble();
    070 *     OutputStream output;
    071 *     while (nextPart) {
    072 *         String header = multipartStream.readHeaders();
    073 *         // process headers
    074 *         // create some output stream
    075 *         multipartStream.readBodyData(output);
    076 *         nextPart = multipartStream.readBoundary();
    077 *     }
    078 * } catch (MultipartInput.MalformedStreamException e) {
    079 *     // the stream failed to follow required syntax
    080 * } catch (IOException e) {
    081 *     // a read or write error occurred
    082 * }
    083 * </pre>
    084 */
    085public final class MultipartInput {
    086
    087    /**
    088     * Builds a new {@link MultipartInput} instance.
    089     * <p>
    090     * For example:
    091     * </p>
    092     *
    093     * <pre>{@code
    094     * MultipartInput factory = MultipartInput.builder().setPath(path).setBufferSize(DEFAULT_THRESHOLD).get();
    095     * }
    096     * </pre>
    097     */
    098    public static class Builder extends AbstractStreamBuilder<MultipartInput, Builder> {
    099
    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}