Modified: websites/production/commons/content/proper/commons-fileupload/commons-fileupload2-core/jacoco/org.apache.commons.fileupload2.core/MultipartInput.java.html ============================================================================== --- websites/production/commons/content/proper/commons-fileupload/commons-fileupload2-core/jacoco/org.apache.commons.fileupload2.core/MultipartInput.java.html (original) +++ websites/production/commons/content/proper/commons-fileupload/commons-fileupload2-core/jacoco/org.apache.commons.fileupload2.core/MultipartInput.java.html Mon Jun 16 14:44:13 2025 @@ -110,12 +110,16 @@ public final class MultipartInput { */ private ProgressNotifier progressNotifier; + /** The per part size limit for headers. + */ +<span class="fc" id="L115"> private int partHeaderSizeMax = DEFAULT_PART_HEADER_SIZE_MAX;</span> + /** * Constructs a new instance. */ -<span class="fc" id="L116"> public Builder() {</span> -<span class="fc" id="L117"> setBufferSizeDefault(DEFAULT_BUFSIZE);</span> -<span class="fc" id="L118"> }</span> +<span class="fc" id="L120"> public Builder() {</span> +<span class="fc" id="L121"> setBufferSizeDefault(DEFAULT_BUFSIZE);</span> +<span class="fc" id="L122"> }</span> /** * Constructs a new instance. @@ -134,7 +138,15 @@ public final class MultipartInput { */ @Override public MultipartInput get() throws IOException { -<span class="fc" id="L137"> return new MultipartInput(getInputStream(), boundary, getBufferSize(), progressNotifier);</span> +<span class="fc" id="L141"> return new MultipartInput(getInputStream(), boundary, getBufferSize(), getPartHeaderSizeMax(), progressNotifier);</span> + } + + /** Returns the per part size limit for headers. + * @return The maximum size of the headers in bytes. + * @since 2.0.0-M4 + */ + public int getPartHeaderSizeMax() { +<span class="fc" id="L149"> return partHeaderSizeMax;</span> } /** @@ -144,20 +156,30 @@ public final class MultipartInput { * @return {@code this} instance. */ public Builder setBoundary(final byte[] boundary) { -<span class="fc" id="L147"> this.boundary = boundary;</span> -<span class="fc" id="L148"> return this;</span> +<span class="fc" id="L159"> this.boundary = boundary;</span> +<span class="fc" id="L160"> return this;</span> } + /** Sets the per part size limit for headers. + * @param partHeaderSizeMax The maximum size of the headers in bytes. + * @return This builder. + * @since 2.0.0-M4 + */ + public Builder setPartHeaderSizeMax(final int partHeaderSizeMax) { +<span class="fc" id="L169"> this.partHeaderSizeMax = partHeaderSizeMax;</span> +<span class="fc" id="L170"> return this;</span> + } + /** - * Sets the progress notifier. - * - * @param progressNotifier progress notifier. - * @return {@code this} instance. - */ - public Builder setProgressNotifier(final ProgressNotifier progressNotifier) { -<span class="fc" id="L158"> this.progressNotifier = progressNotifier;</span> -<span class="fc" id="L159"> return this;</span> - } + * Sets the progress notifier. + * + * @param progressNotifier progress notifier. + * @return {@code this} instance. + */ + public Builder setProgressNotifier(final ProgressNotifier progressNotifier) { +<span class="fc" id="L180"> this.progressNotifier = progressNotifier;</span> +<span class="fc" id="L181"> return this;</span> + } } @@ -177,8 +199,8 @@ public final class MultipartInput { * @param message The detail message (which is saved for later retrieval by the {@link #getMessage()} method) */ public FileUploadBoundaryException(final String message) { -<span class="nc" id="L180"> super(message);</span> -<span class="nc" id="L181"> }</span> +<span class="nc" id="L202"> super(message);</span> +<span class="nc" id="L203"> }</span> } @@ -215,9 +237,9 @@ public final class MultipartInput { /** * Creates a new instance. */ -<span class="nc" id="L218"> ItemInputStream() {</span> -<span class="nc" id="L219"> findSeparator();</span> -<span class="nc" id="L220"> }</span> +<span class="fc" id="L240"> ItemInputStream() {</span> +<span class="fc" id="L241"> findSeparator();</span> +<span class="fc" id="L242"> }</span> /** * Returns the number of bytes, which are currently available, without blocking. @@ -227,17 +249,17 @@ public final class MultipartInput { */ @Override public int available() throws IOException { -<span class="nc bnc" id="L230" title="All 2 branches missed."> if (pos == -1) {</span> -<span class="nc" id="L231"> return tail - head - pad;</span> +<span class="fc bfc" id="L252" title="All 2 branches covered."> if (pos == -1) {</span> +<span class="fc" id="L253"> return tail - head - pad;</span> } -<span class="nc" id="L233"> return pos - head;</span> +<span class="fc" id="L255"> return pos - head;</span> } private void checkOpen() throws ItemSkippedException { -<span class="nc bnc" id="L237" title="All 2 branches missed."> if (closed) {</span> -<span class="nc" id="L238"> throw new FileItemInput.ItemSkippedException("checkOpen()");</span> +<span class="pc bpc" id="L259" title="1 of 2 branches missed."> if (closed) {</span> +<span class="nc" id="L260"> throw new FileItemInput.ItemSkippedException("checkOpen()");</span> } -<span class="nc" id="L240"> }</span> +<span class="fc" id="L262"> }</span> /** * Closes the input stream. @@ -246,8 +268,8 @@ public final class MultipartInput { */ @Override public void close() throws IOException { -<span class="nc" id="L249"> close(false);</span> -<span class="nc" id="L250"> }</span> +<span class="fc" id="L271"> close(false);</span> +<span class="fc" id="L272"> }</span> /** * Closes the input stream. @@ -256,42 +278,42 @@ public final class MultipartInput { * @throws IOException An I/O error occurred. */ public void close(final boolean closeUnderlying) throws IOException { -<span class="nc bnc" id="L259" title="All 2 branches missed."> if (closed) {</span> -<span class="nc" id="L260"> return;</span> +<span class="pc bpc" id="L281" title="1 of 2 branches missed."> if (closed) {</span> +<span class="nc" id="L282"> return;</span> } -<span class="nc bnc" id="L262" title="All 2 branches missed."> if (closeUnderlying) {</span> -<span class="nc" id="L263"> closed = true;</span> -<span class="nc" id="L264"> input.close();</span> +<span class="pc bpc" id="L284" title="1 of 2 branches missed."> if (closeUnderlying) {</span> +<span class="nc" id="L285"> closed = true;</span> +<span class="nc" id="L286"> input.close();</span> } else { for (;;) { -<span class="nc" id="L267"> var avail = available();</span> -<span class="nc bnc" id="L268" title="All 2 branches missed."> if (avail == 0) {</span> -<span class="nc" id="L269"> avail = makeAvailable();</span> -<span class="nc bnc" id="L270" title="All 2 branches missed."> if (avail == 0) {</span> -<span class="nc" id="L271"> break;</span> +<span class="fc" id="L289"> var avail = available();</span> +<span class="pc bpc" id="L290" title="1 of 2 branches missed."> if (avail == 0) {</span> +<span class="fc" id="L291"> avail = makeAvailable();</span> +<span class="pc bpc" id="L292" title="1 of 2 branches missed."> if (avail == 0) {</span> +<span class="fc" id="L293"> break;</span> } } -<span class="nc bnc" id="L274" title="All 2 branches missed."> if (skip(avail) != avail) {</span> +<span class="nc bnc" id="L296" title="All 2 branches missed."> if (skip(avail) != avail) {</span> // TODO What to do? } -<span class="nc" id="L277"> }</span> +<span class="nc" id="L299"> }</span> } -<span class="nc" id="L279"> closed = true;</span> -<span class="nc" id="L280"> }</span> +<span class="fc" id="L301"> closed = true;</span> +<span class="fc" id="L302"> }</span> /** * Called for finding the separator. */ private void findSeparator() { -<span class="nc" id="L286"> pos = MultipartInput.this.findSeparator();</span> -<span class="nc bnc" id="L287" title="All 2 branches missed."> if (pos == -1) {</span> -<span class="nc bnc" id="L288" title="All 2 branches missed."> if (tail - head > keepRegion) {</span> -<span class="nc" id="L289"> pad = keepRegion;</span> +<span class="fc" id="L308"> pos = MultipartInput.this.findSeparator();</span> +<span class="fc bfc" id="L309" title="All 2 branches covered."> if (pos == -1) {</span> +<span class="pc bpc" id="L310" title="1 of 2 branches missed."> if (tail - head > keepRegion) {</span> +<span class="nc" id="L311"> pad = keepRegion;</span> } else { -<span class="nc" id="L291"> pad = tail - head;</span> +<span class="fc" id="L313"> pad = tail - head;</span> } } -<span class="nc" id="L294"> }</span> +<span class="fc" id="L316"> }</span> /** * Gets the number of bytes, which have been read by the stream. @@ -299,7 +321,7 @@ public final class MultipartInput { * @return Number of bytes, which have been read so far. */ public long getBytesRead() { -<span class="nc" id="L302"> return total;</span> +<span class="nc" id="L324"> return total;</span> } /** @@ -308,7 +330,7 @@ public final class MultipartInput { * @return whether this instance is closed. */ public boolean isClosed() { -<span class="nc" id="L311"> return closed;</span> +<span class="nc" id="L333"> return closed;</span> } /** @@ -318,39 +340,39 @@ public final class MultipartInput { * @throws IOException An I/O error occurred. */ private int makeAvailable() throws IOException { -<span class="nc bnc" id="L321" title="All 2 branches missed."> if (pos != -1) {</span> -<span class="nc" id="L322"> return 0;</span> +<span class="fc bfc" id="L343" title="All 2 branches covered."> if (pos != -1) {</span> +<span class="fc" id="L344"> return 0;</span> } // Move the data to the beginning of the buffer. -<span class="nc" id="L326"> total += tail - head - pad;</span> -<span class="nc" id="L327"> System.arraycopy(buffer, tail - pad, buffer, 0, pad);</span> +<span class="fc" id="L348"> total += tail - head - pad;</span> +<span class="fc" id="L349"> System.arraycopy(buffer, tail - pad, buffer, 0, pad);</span> // Refill buffer with new data. -<span class="nc" id="L330"> head = 0;</span> -<span class="nc" id="L331"> tail = pad;</span> +<span class="fc" id="L352"> head = 0;</span> +<span class="fc" id="L353"> tail = pad;</span> for (;;) { -<span class="nc" id="L334"> final var bytesRead = input.read(buffer, tail, bufSize - tail);</span> -<span class="nc bnc" id="L335" title="All 2 branches missed."> if (bytesRead == -1) {</span> +<span class="fc" id="L356"> final var bytesRead = input.read(buffer, tail, bufSize - tail);</span> +<span class="pc bpc" id="L357" title="1 of 2 branches missed."> if (bytesRead == -1) {</span> // The last pad amount is left in the buffer. // Boundary can't be in there so signal an error // condition. -<span class="nc" id="L339"> final var msg = "Stream ended unexpectedly";</span> -<span class="nc" id="L340"> throw new MalformedStreamException(msg);</span> +<span class="nc" id="L361"> final var msg = "Stream ended unexpectedly";</span> +<span class="nc" id="L362"> throw new MalformedStreamException(msg);</span> } -<span class="nc bnc" id="L342" title="All 2 branches missed."> if (notifier != null) {</span> -<span class="nc" id="L343"> notifier.noteBytesRead(bytesRead);</span> +<span class="pc bpc" id="L364" title="1 of 2 branches missed."> if (notifier != null) {</span> +<span class="nc" id="L365"> notifier.noteBytesRead(bytesRead);</span> } -<span class="nc" id="L345"> tail += bytesRead;</span> +<span class="fc" id="L367"> tail += bytesRead;</span> -<span class="nc" id="L347"> findSeparator();</span> -<span class="nc" id="L348"> final var av = available();</span> +<span class="fc" id="L369"> findSeparator();</span> +<span class="fc" id="L370"> final var av = available();</span> -<span class="nc bnc" id="L350" title="All 4 branches missed."> if (av > 0 || pos != -1) {</span> -<span class="nc" id="L351"> return av;</span> +<span class="pc bpc" id="L372" title="2 of 4 branches missed."> if (av > 0 || pos != -1) {</span> +<span class="fc" id="L373"> return av;</span> } -<span class="nc" id="L353"> }</span> +<span class="nc" id="L375"> }</span> } /** @@ -361,16 +383,16 @@ public final class MultipartInput { */ @Override public int read() throws IOException { -<span class="nc" id="L364"> checkOpen();</span> -<span class="nc bnc" id="L365" title="All 4 branches missed."> if (available() == 0 && makeAvailable() == 0) {</span> -<span class="nc" id="L366"> return -1;</span> - } -<span class="nc" id="L368"> ++total;</span> -<span class="nc" id="L369"> final int b = buffer[head++];</span> -<span class="nc bnc" id="L370" title="All 2 branches missed."> if (b >= 0) {</span> -<span class="nc" id="L371"> return b;</span> +<span class="nc" id="L386"> checkOpen();</span> +<span class="nc bnc" id="L387" title="All 4 branches missed."> if (available() == 0 && makeAvailable() == 0) {</span> +<span class="nc" id="L388"> return -1;</span> + } +<span class="nc" id="L390"> ++total;</span> +<span class="nc" id="L391"> final int b = buffer[head++];</span> +<span class="nc bnc" id="L392" title="All 2 branches missed."> if (b >= 0) {</span> +<span class="nc" id="L393"> return b;</span> } -<span class="nc" id="L373"> return b + BYTE_POSITIVE_OFFSET;</span> +<span class="nc" id="L395"> return b + BYTE_POSITIVE_OFFSET;</span> } /** @@ -384,22 +406,22 @@ public final class MultipartInput { */ @Override public int read(final byte[] b, final int off, final int len) throws IOException { -<span class="nc" id="L387"> checkOpen();</span> -<span class="nc bnc" id="L388" title="All 2 branches missed."> if (len == 0) {</span> -<span class="nc" id="L389"> return 0;</span> - } -<span class="nc" id="L391"> var res = available();</span> -<span class="nc bnc" id="L392" title="All 2 branches missed."> if (res == 0) {</span> -<span class="nc" id="L393"> res = makeAvailable();</span> -<span class="nc bnc" id="L394" title="All 2 branches missed."> if (res == 0) {</span> -<span class="nc" id="L395"> return -1;</span> +<span class="fc" id="L409"> checkOpen();</span> +<span class="pc bpc" id="L410" title="1 of 2 branches missed."> if (len == 0) {</span> +<span class="nc" id="L411"> return 0;</span> + } +<span class="fc" id="L413"> var res = available();</span> +<span class="pc bpc" id="L414" title="1 of 2 branches missed."> if (res == 0) {</span> +<span class="fc" id="L415"> res = makeAvailable();</span> +<span class="pc bpc" id="L416" title="1 of 2 branches missed."> if (res == 0) {</span> +<span class="fc" id="L417"> return -1;</span> } } -<span class="nc" id="L398"> res = Math.min(res, len);</span> -<span class="nc" id="L399"> System.arraycopy(buffer, head, b, off, res);</span> -<span class="nc" id="L400"> head += res;</span> -<span class="nc" id="L401"> total += res;</span> -<span class="nc" id="L402"> return res;</span> +<span class="nc" id="L420"> res = Math.min(res, len);</span> +<span class="nc" id="L421"> System.arraycopy(buffer, head, b, off, res);</span> +<span class="nc" id="L422"> head += res;</span> +<span class="nc" id="L423"> total += res;</span> +<span class="nc" id="L424"> return res;</span> } /** @@ -411,20 +433,20 @@ public final class MultipartInput { */ @Override public long skip(final long bytes) throws IOException { -<span class="nc" id="L414"> checkOpen();</span> -<span class="nc" id="L415"> var available = available();</span> -<span class="nc bnc" id="L416" title="All 2 branches missed."> if (available == 0) {</span> -<span class="nc" id="L417"> available = makeAvailable();</span> -<span class="nc bnc" id="L418" title="All 2 branches missed."> if (available == 0) {</span> -<span class="nc" id="L419"> return 0;</span> +<span class="nc" id="L436"> checkOpen();</span> +<span class="nc" id="L437"> var available = available();</span> +<span class="nc bnc" id="L438" title="All 2 branches missed."> if (available == 0) {</span> +<span class="nc" id="L439"> available = makeAvailable();</span> +<span class="nc bnc" id="L440" title="All 2 branches missed."> if (available == 0) {</span> +<span class="nc" id="L441"> return 0;</span> } } // Fix "Implicit narrowing conversion in compound assignment" // https://github.com/apache/commons-fileupload/security/code-scanning/118 // Math.min always returns an int because available is an int. -<span class="nc" id="L425"> final var res = Math.toIntExact(Math.min(available, bytes));</span> -<span class="nc" id="L426"> head += res;</span> -<span class="nc" id="L427"> return res;</span> +<span class="nc" id="L447"> final var res = Math.toIntExact(Math.min(available, bytes));</span> +<span class="nc" id="L448"> head += res;</span> +<span class="nc" id="L449"> return res;</span> } } @@ -445,8 +467,8 @@ public final class MultipartInput { * @param message The detail message. */ public MalformedStreamException(final String message) { -<span class="nc" id="L448"> super(message);</span> -<span class="nc" id="L449"> }</span> +<span class="nc" id="L470"> super(message);</span> +<span class="nc" id="L471"> }</span> /** * Constructs an {@code MalformedStreamException} with the specified detail message. @@ -456,8 +478,8 @@ public final class MultipartInput { * cause is nonexistent or unknown.) */ public MalformedStreamException(final String message, final Throwable cause) { -<span class="nc" id="L459"> super(message, cause);</span> -<span class="nc" id="L460"> }</span> +<span class="nc" id="L481"> super(message, cause);</span> +<span class="nc" id="L482"> }</span> } @@ -492,10 +514,10 @@ public final class MultipartInput { * @param progressListener The listener to invoke. * @param contentLength The expected content length. */ -<span class="fc" id="L495"> public ProgressNotifier(final ProgressListener progressListener, final long contentLength) {</span> -<span class="pc bpc" id="L496" title="1 of 2 branches missed."> this.progressListener = progressListener != null ? progressListener : ProgressListener.NOP;</span> -<span class="fc" id="L497"> this.contentLength = contentLength;</span> -<span class="fc" id="L498"> }</span> +<span class="fc" id="L517"> public ProgressNotifier(final ProgressListener progressListener, final long contentLength) {</span> +<span class="pc bpc" id="L518" title="1 of 2 branches missed."> this.progressListener = progressListener != null ? progressListener : ProgressListener.NOP;</span> +<span class="fc" id="L519"> this.contentLength = contentLength;</span> +<span class="fc" id="L520"> }</span> /** * Called to indicate that bytes have been read. @@ -506,24 +528,24 @@ public final class MultipartInput { // // Indicates, that the given number of bytes have been read from the input stream. // -<span class="nc" id="L509"> bytesRead += byteCount;</span> -<span class="nc" id="L510"> notifyListener();</span> -<span class="nc" id="L511"> }</span> +<span class="nc" id="L531"> bytesRead += byteCount;</span> +<span class="nc" id="L532"> notifyListener();</span> +<span class="nc" id="L533"> }</span> /** * Called to indicate, that a new file item has been detected. */ public void noteItem() { -<span class="nc" id="L517"> ++items;</span> -<span class="nc" id="L518"> notifyListener();</span> -<span class="nc" id="L519"> }</span> +<span class="nc" id="L539"> ++items;</span> +<span class="nc" id="L540"> notifyListener();</span> +<span class="nc" id="L541"> }</span> /** * Called for notifying the listener. */ private void notifyListener() { -<span class="nc" id="L525"> progressListener.update(bytesRead, contentLength, items);</span> -<span class="nc" id="L526"> }</span> +<span class="nc" id="L547"> progressListener.update(bytesRead, contentLength, items);</span> +<span class="nc" id="L548"> }</span> } @@ -543,34 +565,35 @@ public final class MultipartInput { public static final byte DASH = 0x2D; /** - * The maximum length of {@code header-part} that will be processed (10 kilobytes = 10240 bytes.). + * The default length of the buffer used for processing a request. */ - public static final int HEADER_PART_SIZE_MAX = 10_240; + static final int DEFAULT_BUFSIZE = 4096; /** - * The default length of the buffer used for processing a request. + * Default per part header size limit in bytes. + * @since 2.0.0-M4 */ - static final int DEFAULT_BUFSIZE = 4096; + public static final int DEFAULT_PART_HEADER_SIZE_MAX = 512; /** * A byte sequence that marks the end of {@code header-part} ({@code CRLFCRLF}). */ -<span class="fc" id="L558"> static final byte[] HEADER_SEPARATOR = { CR, LF, CR, LF };</span> +<span class="fc" id="L581"> static final byte[] HEADER_SEPARATOR = { CR, LF, CR, LF };</span> /** * A byte sequence that that follows a delimiter that will be followed by an encapsulation ({@code CRLF}). */ -<span class="fc" id="L563"> static final byte[] FIELD_SEPARATOR = { CR, LF };</span> +<span class="fc" id="L586"> static final byte[] FIELD_SEPARATOR = { CR, LF };</span> /** * A byte sequence that that follows a delimiter of the last encapsulation in the stream ({@code --}). */ -<span class="fc" id="L568"> static final byte[] STREAM_TERMINATOR = { DASH, DASH };</span> +<span class="fc" id="L591"> static final byte[] STREAM_TERMINATOR = { DASH, DASH };</span> /** * A byte sequence that precedes a boundary ({@code CRLF--}). */ -<span class="fc" id="L573"> static final byte[] BOUNDARY_PREFIX = { CR, LF, DASH, DASH };</span> +<span class="fc" id="L596"> static final byte[] BOUNDARY_PREFIX = { CR, LF, DASH, DASH };</span> /** * Compares {@code count} first bytes in the arrays {@code a} and {@code b}. @@ -581,12 +604,12 @@ public final class MultipartInput { * @return {@code true} if {@code count} first bytes in arrays {@code a} and {@code b} are equal. */ static boolean arrayEquals(final byte[] a, final byte[] b, final int count) { -<span class="nc bnc" id="L584" title="All 2 branches missed."> for (var i = 0; i < count; i++) {</span> -<span class="nc bnc" id="L585" title="All 2 branches missed."> if (a[i] != b[i]) {</span> -<span class="nc" id="L586"> return false;</span> +<span class="fc bfc" id="L607" title="All 2 branches covered."> for (var i = 0; i < count; i++) {</span> +<span class="fc bfc" id="L608" title="All 2 branches covered."> if (a[i] != b[i]) {</span> +<span class="fc" id="L609"> return false;</span> } } -<span class="nc" id="L589"> return true;</span> +<span class="fc" id="L612"> return true;</span> } /** @@ -595,7 +618,7 @@ public final class MultipartInput { * @return a new {@link Builder}. */ public static Builder builder() { -<span class="fc" id="L598"> return new Builder();</span> +<span class="fc" id="L621"> return new Builder();</span> } /** @@ -656,6 +679,11 @@ public final class MultipartInput { private final ProgressNotifier notifier; /** + * The maximum size of the headers in bytes. + */ + private final int partHeaderSizeMax; + + /** * Constructs a {@code MultipartInput} with a custom size buffer. * <p> * 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 @@ -668,57 +696,58 @@ public final class MultipartInput { * @param notifier The notifier, which is used for calling the progress listener, if any. * @throws IllegalArgumentException If the buffer size is too small. */ -<span class="fc" id="L671"> private MultipartInput(final InputStream input, final byte[] boundary, final int bufferSize, final ProgressNotifier notifier) {</span> -<span class="pc bpc" id="L672" title="1 of 2 branches missed."> if (boundary == null) {</span> -<span class="nc" id="L673"> throw new IllegalArgumentException("boundary may not be null");</span> +<span class="fc" id="L699"> private MultipartInput(final InputStream input, final byte[] boundary, final int bufferSize, final int partHeaderSizeMax, final ProgressNotifier notifier) {</span> +<span class="pc bpc" id="L700" title="1 of 2 branches missed."> if (boundary == null) {</span> +<span class="nc" id="L701"> throw new IllegalArgumentException("boundary may not be null");</span> } // We prepend CR/LF to the boundary to chop trailing CR/LF from // body-data tokens. -<span class="fc" id="L677"> this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length;</span> -<span class="fc bfc" id="L678" title="All 2 branches covered."> if (bufferSize < this.boundaryLength + 1) {</span> -<span class="fc" id="L679"> throw new IllegalArgumentException("The buffer size specified for the MultipartInput is too small");</span> - } - -<span class="fc" id="L682"> this.input = input;</span> -<span class="fc" id="L683"> this.bufSize = Math.max(bufferSize, boundaryLength * 2);</span> -<span class="fc" id="L684"> this.buffer = new byte[this.bufSize];</span> -<span class="fc" id="L685"> this.notifier = notifier;</span> - -<span class="fc" id="L687"> this.boundary = new byte[this.boundaryLength];</span> -<span class="fc" id="L688"> this.boundaryTable = new int[this.boundaryLength + 1];</span> -<span class="fc" id="L689"> this.keepRegion = this.boundary.length;</span> - -<span class="fc" id="L691"> System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0, BOUNDARY_PREFIX.length);</span> -<span class="fc" id="L692"> System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length);</span> -<span class="fc" id="L693"> computeBoundaryTable();</span> - -<span class="fc" id="L695"> head = 0;</span> -<span class="fc" id="L696"> tail = 0;</span> -<span class="fc" id="L697"> }</span> +<span class="fc" id="L705"> this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length;</span> +<span class="fc bfc" id="L706" title="All 2 branches covered."> if (bufferSize < this.boundaryLength + 1) {</span> +<span class="fc" id="L707"> throw new IllegalArgumentException("The buffer size specified for the MultipartInput is too small");</span> + } + +<span class="fc" id="L710"> this.input = input;</span> +<span class="fc" id="L711"> this.bufSize = Math.max(bufferSize, boundaryLength * 2);</span> +<span class="fc" id="L712"> this.buffer = new byte[this.bufSize];</span> +<span class="fc" id="L713"> this.notifier = notifier;</span> +<span class="fc" id="L714"> this.partHeaderSizeMax = partHeaderSizeMax;</span> + +<span class="fc" id="L716"> this.boundary = new byte[this.boundaryLength];</span> +<span class="fc" id="L717"> this.boundaryTable = new int[this.boundaryLength + 1];</span> +<span class="fc" id="L718"> this.keepRegion = this.boundary.length;</span> + +<span class="fc" id="L720"> System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0, BOUNDARY_PREFIX.length);</span> +<span class="fc" id="L721"> System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length);</span> +<span class="fc" id="L722"> computeBoundaryTable();</span> + +<span class="fc" id="L724"> head = 0;</span> +<span class="fc" id="L725"> tail = 0;</span> +<span class="fc" id="L726"> }</span> /** * Computes the table used for Knuth-Morris-Pratt search algorithm. */ private void computeBoundaryTable() { -<span class="fc" id="L703"> var position = 2;</span> -<span class="fc" id="L704"> var candidate = 0;</span> +<span class="fc" id="L732"> var position = 2;</span> +<span class="fc" id="L733"> var candidate = 0;</span> -<span class="fc" id="L706"> boundaryTable[0] = -1;</span> -<span class="fc" id="L707"> boundaryTable[1] = 0;</span> +<span class="fc" id="L735"> boundaryTable[0] = -1;</span> +<span class="fc" id="L736"> boundaryTable[1] = 0;</span> -<span class="fc bfc" id="L709" title="All 2 branches covered."> while (position <= boundaryLength) {</span> -<span class="pc bpc" id="L710" title="1 of 2 branches missed."> if (boundary[position - 1] == boundary[candidate]) {</span> -<span class="nc" id="L711"> boundaryTable[position] = candidate + 1;</span> -<span class="nc" id="L712"> candidate++;</span> -<span class="nc" id="L713"> position++;</span> -<span class="pc bpc" id="L714" title="1 of 2 branches missed."> } else if (candidate > 0) {</span> -<span class="nc" id="L715"> candidate = boundaryTable[candidate];</span> +<span class="fc bfc" id="L738" title="All 2 branches covered."> while (position <= boundaryLength) {</span> +<span class="fc bfc" id="L739" title="All 2 branches covered."> if (boundary[position - 1] == boundary[candidate]) {</span> +<span class="fc" id="L740"> boundaryTable[position] = candidate + 1;</span> +<span class="fc" id="L741"> candidate++;</span> +<span class="fc" id="L742"> position++;</span> +<span class="fc bfc" id="L743" title="All 2 branches covered."> } else if (candidate > 0) {</span> +<span class="fc" id="L744"> candidate = boundaryTable[candidate];</span> } else { -<span class="fc" id="L717"> boundaryTable[position] = 0;</span> -<span class="fc" id="L718"> position++;</span> +<span class="fc" id="L746"> boundaryTable[position] = 0;</span> +<span class="fc" id="L747"> position++;</span> } } -<span class="fc" id="L721"> }</span> +<span class="fc" id="L750"> }</span> /** * Reads {@code body-data} from the current {@code encapsulation} and discards it. @@ -731,7 +760,7 @@ public final class MultipartInput { * @throws IOException if an i/o error occurs. */ public long discardBodyData() throws MalformedStreamException, IOException { -<span class="nc" id="L734"> return readBodyData(NullOutputStream.INSTANCE);</span> +<span class="fc" id="L763"> return readBodyData(NullOutputStream.INSTANCE);</span> } /** @@ -742,13 +771,13 @@ public final class MultipartInput { * @return The position of byte found, counting from beginning of the {@code buffer}, or {@code -1} if not found. */ protected int findByte(final byte value, final int pos) { -<span class="nc bnc" id="L745" title="All 2 branches missed."> for (var i = pos; i < tail; i++) {</span> -<span class="nc bnc" id="L746" title="All 2 branches missed."> if (buffer[i] == value) {</span> -<span class="nc" id="L747"> return i;</span> +<span class="nc bnc" id="L774" title="All 2 branches missed."> for (var i = pos; i < tail; i++) {</span> +<span class="nc bnc" id="L775" title="All 2 branches missed."> if (buffer[i] == value) {</span> +<span class="nc" id="L776"> return i;</span> } } -<span class="nc" id="L751"> return -1;</span> +<span class="nc" id="L780"> return -1;</span> } /** @@ -757,19 +786,19 @@ public final class MultipartInput { * @return The position of the boundary found, counting from the beginning of the {@code buffer}, or {@code -1} if not found. */ protected int findSeparator() { -<span class="nc" id="L760"> var bufferPos = this.head;</span> -<span class="nc" id="L761"> var tablePos = 0;</span> -<span class="nc bnc" id="L762" title="All 2 branches missed."> while (bufferPos < this.tail) {</span> -<span class="nc bnc" id="L763" title="All 4 branches missed."> while (tablePos >= 0 && buffer[bufferPos] != boundary[tablePos]) {</span> -<span class="nc" id="L764"> tablePos = boundaryTable[tablePos];</span> - } -<span class="nc" id="L766"> bufferPos++;</span> -<span class="nc" id="L767"> tablePos++;</span> -<span class="nc bnc" id="L768" title="All 2 branches missed."> if (tablePos == boundaryLength) {</span> -<span class="nc" id="L769"> return bufferPos - boundaryLength;</span> +<span class="fc" id="L789"> var bufferPos = this.head;</span> +<span class="fc" id="L790"> var tablePos = 0;</span> +<span class="fc bfc" id="L791" title="All 2 branches covered."> while (bufferPos < this.tail) {</span> +<span class="pc bpc" id="L792" title="2 of 4 branches missed."> while (tablePos >= 0 && buffer[bufferPos] != boundary[tablePos]) {</span> +<span class="nc" id="L793"> tablePos = boundaryTable[tablePos];</span> + } +<span class="fc" id="L795"> bufferPos++;</span> +<span class="fc" id="L796"> tablePos++;</span> +<span class="fc bfc" id="L797" title="All 2 branches covered."> if (tablePos == boundaryLength) {</span> +<span class="fc" id="L798"> return bufferPos - boundaryLength;</span> } } -<span class="nc" id="L772"> return -1;</span> +<span class="fc" id="L801"> return -1;</span> } /** @@ -779,7 +808,16 @@ public final class MultipartInput { * @return The encoding used to read part headers. */ public Charset getHeaderCharset() { -<span class="nc" id="L782"> return headerCharset;</span> +<span class="nc" id="L811"> return headerCharset;</span> + } + + /** Returns the per part size limit for headers. + * + * @return The maximum size of the headers in bytes. + * @since 2.0.0-M4 + */ + public int getPartHeaderSizeMax() { +<span class="fc" id="L820"> return partHeaderSizeMax;</span> } /** @@ -788,7 +826,7 @@ public final class MultipartInput { * @return A new instance of {@link ItemInputStream}. */ public ItemInputStream newInputStream() { -<span class="nc" id="L791"> return new ItemInputStream();</span> +<span class="fc" id="L829"> return new ItemInputStream();</span> } /** @@ -803,8 +841,8 @@ public final class MultipartInput { * @throws IOException if an i/o error occurs. */ public long readBodyData(final OutputStream output) throws MalformedStreamException, IOException { -<span class="nc" id="L806"> try (var inputStream = newInputStream()) {</span> -<span class="nc" id="L807"> return IOUtils.copyLarge(inputStream, output);</span> +<span class="fc" id="L844"> try (var inputStream = newInputStream()) {</span> +<span class="fc" id="L845"> return IOUtils.copyLarge(inputStream, output);</span> } } @@ -816,35 +854,35 @@ public final class MultipartInput { * @throws MalformedStreamException if the stream ends unexpectedly or fails to follow required syntax. */ public boolean readBoundary() throws FileUploadSizeException, MalformedStreamException { -<span class="nc" id="L819"> final var marker = new byte[2];</span> +<span class="fc" id="L857"> final var marker = new byte[2];</span> final boolean nextChunk; -<span class="nc" id="L821"> head += boundaryLength;</span> +<span class="fc" id="L859"> head += boundaryLength;</span> try { -<span class="nc" id="L823"> marker[0] = readByte();</span> -<span class="nc bnc" id="L824" title="All 2 branches missed."> if (marker[0] == LF) {</span> +<span class="fc" id="L861"> marker[0] = readByte();</span> +<span class="pc bpc" id="L862" title="1 of 2 branches missed."> if (marker[0] == LF) {</span> // Work around IE5 Mac bug with input type=image. // Because the boundary delimiter, not including the trailing // CRLF, must not appear within any file (RFC 2046, section // 5.1.1), we know the missing CR is due to a buggy browser // rather than a file containing something similar to a // boundary. -<span class="nc" id="L831"> return true;</span> +<span class="nc" id="L869"> return true;</span> } -<span class="nc" id="L834"> marker[1] = readByte();</span> -<span class="nc bnc" id="L835" title="All 2 branches missed."> if (arrayEquals(marker, STREAM_TERMINATOR, 2)) {</span> -<span class="nc" id="L836"> nextChunk = false;</span> -<span class="nc bnc" id="L837" title="All 2 branches missed."> } else if (arrayEquals(marker, FIELD_SEPARATOR, 2)) {</span> -<span class="nc" id="L838"> nextChunk = true;</span> +<span class="fc" id="L872"> marker[1] = readByte();</span> +<span class="pc bpc" id="L873" title="1 of 2 branches missed."> if (arrayEquals(marker, STREAM_TERMINATOR, 2)) {</span> +<span class="nc" id="L874"> nextChunk = false;</span> +<span class="pc bpc" id="L875" title="1 of 2 branches missed."> } else if (arrayEquals(marker, FIELD_SEPARATOR, 2)) {</span> +<span class="fc" id="L876"> nextChunk = true;</span> } else { -<span class="nc" id="L840"> throw new MalformedStreamException("Unexpected characters follow a boundary");</span> +<span class="nc" id="L878"> throw new MalformedStreamException("Unexpected characters follow a boundary");</span> } -<span class="nc" id="L842"> } catch (final FileUploadSizeException e) {</span> -<span class="nc" id="L843"> throw e;</span> -<span class="nc" id="L844"> } catch (final IOException e) {</span> -<span class="nc" id="L845"> throw new MalformedStreamException("Stream ended unexpectedly", e);</span> -<span class="nc" id="L846"> }</span> -<span class="nc" id="L847"> return nextChunk;</span> +<span class="nc" id="L880"> } catch (final FileUploadSizeException e) {</span> +<span class="nc" id="L881"> throw e;</span> +<span class="nc" id="L882"> } catch (final IOException e) {</span> +<span class="nc" id="L883"> throw new MalformedStreamException("Stream ended unexpectedly", e);</span> +<span class="fc" id="L884"> }</span> +<span class="fc" id="L885"> return nextChunk;</span> } /** @@ -855,19 +893,19 @@ public final class MultipartInput { */ public byte readByte() throws IOException { // Buffer depleted ? -<span class="nc bnc" id="L858" title="All 2 branches missed."> if (head == tail) {</span> -<span class="nc" id="L859"> head = 0;</span> +<span class="pc bpc" id="L896" title="1 of 2 branches missed."> if (head == tail) {</span> +<span class="nc" id="L897"> head = 0;</span> // Refill. -<span class="nc" id="L861"> tail = input.read(buffer, head, bufSize);</span> -<span class="nc bnc" id="L862" title="All 2 branches missed."> if (tail == -1) {</span> +<span class="nc" id="L899"> tail = input.read(buffer, head, bufSize);</span> +<span class="nc bnc" id="L900" title="All 2 branches missed."> if (tail == -1) {</span> // No more data available. -<span class="nc" id="L864"> throw new IOException("No more data is available");</span> +<span class="nc" id="L902"> throw new IOException("No more data is available");</span> } -<span class="nc bnc" id="L866" title="All 2 branches missed."> if (notifier != null) {</span> -<span class="nc" id="L867"> notifier.noteBytesRead(tail);</span> +<span class="nc bnc" id="L904" title="All 2 branches missed."> if (notifier != null) {</span> +<span class="nc" id="L905"> notifier.noteBytesRead(tail);</span> } } -<span class="nc" id="L870"> return buffer[head++];</span> +<span class="fc" id="L908"> return buffer[head++];</span> } /** @@ -875,43 +913,46 @@ public final class MultipartInput { * <p> * Headers are returned verbatim to the input stream, including the trailing {@code CRLF} marker. Parsing is left to the application. * </p> + * <p> + * <strong>TODO</strong> allow limiting maximum header size to protect against abuse. + * </p> * * @return The {@code header-part} of the current encapsulation. * @throws FileUploadSizeException if the bytes read from the stream exceeded the size limits. * @throws MalformedStreamException if the stream ends unexpectedly. */ public String readHeaders() throws FileUploadSizeException, MalformedStreamException { -<span class="nc" id="L884"> var i = 0;</span> +<span class="fc" id="L925"> var i = 0;</span> byte b; // to support multi-byte characters -<span class="nc" id="L887"> final var baos = new ByteArrayOutputStream();</span> -<span class="nc" id="L888"> var size = 0;</span> -<span class="nc bnc" id="L889" title="All 2 branches missed."> while (i < HEADER_SEPARATOR.length) {</span> +<span class="fc" id="L928"> final var baos = new ByteArrayOutputStream();</span> +<span class="fc" id="L929"> var size = 0;</span> +<span class="pc bpc" id="L930" title="1 of 2 branches missed."> while (i < HEADER_SEPARATOR.length) {</span> try { -<span class="nc" id="L891"> b = readByte();</span> -<span class="nc" id="L892"> } catch (final FileUploadSizeException e) {</span> +<span class="fc" id="L932"> b = readByte();</span> +<span class="nc" id="L933"> } catch (final FileUploadSizeException e) {</span> // wraps a FileUploadSizeException, re-throw as it will be unwrapped later -<span class="nc" id="L894"> throw e;</span> -<span class="nc" id="L895"> } catch (final IOException e) {</span> -<span class="nc" id="L896"> throw new MalformedStreamException("Stream ended unexpectedly", e);</span> -<span class="nc" id="L897"> }</span> -<span class="nc bnc" id="L898" title="All 2 branches missed."> if (++size > HEADER_PART_SIZE_MAX) {</span> -<span class="nc" id="L899"> throw new MalformedStreamException(</span> -<span class="nc" id="L900"> String.format("Header section has more than %s bytes (maybe it is not properly terminated)", HEADER_PART_SIZE_MAX));</span> +<span class="nc" id="L935"> throw e;</span> +<span class="nc" id="L936"> } catch (final IOException e) {</span> +<span class="nc" id="L937"> throw new MalformedStreamException("Stream ended unexpectedly", e);</span> +<span class="fc" id="L938"> }</span> +<span class="fc" id="L939"> final int phsm = getPartHeaderSizeMax();</span> +<span class="pc bpc" id="L940" title="1 of 4 branches missed."> if (phsm != -1 && ++size > phsm) {</span> +<span class="fc" id="L941"> throw new FileUploadSizeException(</span> +<span class="fc" id="L942"> String.format("Header section has more than %s bytes (maybe it is not properly terminated)", Integer.valueOf(phsm)), phsm, size);</span> } -<span class="nc bnc" id="L902" title="All 2 branches missed."> if (b == HEADER_SEPARATOR[i]) {</span> -<span class="nc" id="L903"> i++;</span> +<span class="fc bfc" id="L944" title="All 2 branches covered."> if (b == HEADER_SEPARATOR[i]) {</span> +<span class="fc" id="L945"> i++;</span> } else { -<span class="nc" id="L905"> i = 0;</span> +<span class="fc" id="L947"> i = 0;</span> } -<span class="nc" id="L907"> baos.write(b);</span> - } - +<span class="fc" id="L949"> baos.write(b);</span> +<span class="fc" id="L950"> }</span> try { -<span class="nc" id="L911"> return baos.toString(Charsets.toCharset(headerCharset, Charset.defaultCharset()).name());</span> -<span class="nc" id="L912"> } catch (final UnsupportedEncodingException e) {</span> +<span class="nc" id="L952"> return baos.toString(Charsets.toCharset(headerCharset, Charset.defaultCharset()).name());</span> +<span class="nc" id="L953"> } catch (final UnsupportedEncodingException e) {</span> // not possible -<span class="nc" id="L914"> throw new IllegalStateException(e);</span> +<span class="nc" id="L955"> throw new IllegalStateException(e);</span> } } @@ -931,12 +972,12 @@ public final class MultipartInput { * @throws FileUploadBoundaryException if the {@code boundary} has a different length than the one being currently parsed. */ public void setBoundary(final byte[] boundary) throws FileUploadBoundaryException { -<span class="nc bnc" id="L934" title="All 2 branches missed."> if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) {</span> -<span class="nc" id="L935"> throw new FileUploadBoundaryException("The length of a boundary token cannot be changed");</span> +<span class="nc bnc" id="L975" title="All 2 branches missed."> if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) {</span> +<span class="nc" id="L976"> throw new FileUploadBoundaryException("The length of a boundary token cannot be changed");</span> } -<span class="nc" id="L937"> System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length);</span> -<span class="nc" id="L938"> computeBoundaryTable();</span> -<span class="nc" id="L939"> }</span> +<span class="nc" id="L978"> System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length);</span> +<span class="nc" id="L979"> computeBoundaryTable();</span> +<span class="nc" id="L980"> }</span> /** * Sets the character encoding to be used when reading the headers of individual parts. When not specified, or {@code null}, the platform default encoding @@ -945,8 +986,8 @@ public final class MultipartInput { * @param headerCharset The encoding used to read part headers. */ public void setHeaderCharset(final Charset headerCharset) { -<span class="nc" id="L948"> this.headerCharset = headerCharset;</span> -<span class="nc" id="L949"> }</span> +<span class="nc" id="L989"> this.headerCharset = headerCharset;</span> +<span class="nc" id="L990"> }</span> /** * Finds the beginning of the first {@code encapsulation}. @@ -956,25 +997,25 @@ public final class MultipartInput { */ public boolean skipPreamble() throws IOException { // First delimiter may be not preceded with a CRLF. -<span class="nc" id="L959"> System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2);</span> -<span class="nc" id="L960"> boundaryLength = boundary.length - 2;</span> -<span class="nc" id="L961"> computeBoundaryTable();</span> +<span class="fc" id="L1000"> System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2);</span> +<span class="fc" id="L1001"> boundaryLength = boundary.length - 2;</span> +<span class="fc" id="L1002"> computeBoundaryTable();</span> try { // Discard all data up to the delimiter. -<span class="nc" id="L964"> discardBodyData();</span> +<span class="fc" id="L1005"> discardBodyData();</span> // Read boundary - if succeeded, the stream contains an // encapsulation. -<span class="nc" id="L968"> return readBoundary();</span> -<span class="nc" id="L969"> } catch (final MalformedStreamException e) {</span> -<span class="nc" id="L970"> return false;</span> +<span class="fc" id="L1009"> return readBoundary();</span> +<span class="nc" id="L1010"> } catch (final MalformedStreamException e) {</span> +<span class="nc" id="L1011"> return false;</span> } finally { // Restore delimiter. -<span class="nc" id="L973"> System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2);</span> -<span class="nc" id="L974"> boundaryLength = boundary.length;</span> -<span class="nc" id="L975"> boundary[0] = CR;</span> -<span class="nc" id="L976"> boundary[1] = LF;</span> -<span class="nc" id="L977"> computeBoundaryTable();</span> +<span class="fc" id="L1014"> System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2);</span> +<span class="fc" id="L1015"> boundaryLength = boundary.length;</span> +<span class="fc" id="L1016"> boundary[0] = CR;</span> +<span class="fc" id="L1017"> boundary[1] = LF;</span> +<span class="fc" id="L1018"> computeBoundaryTable();</span> } }