This is an automated email from the ASF dual-hosted git repository. jochen pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-fileupload.git
The following commit(s) were added to refs/heads/master by this push: new e5b5543b Adding support for the partHeaderSizeMax limit by cherry-picking 2108495a4775910b8559f18ed5a779d60542ee96 e5b5543b is described below commit e5b5543b3a40ac9dde3c33ef1858901b3ca6656a Author: Jochen Wiedmann <jochen.wiedm...@gmail.com> AuthorDate: Sun Jun 8 16:20:20 2025 +0200 Adding support for the partHeaderSizeMax limit by cherry-picking 2108495a4775910b8559f18ed5a779d60542ee96 --- .../commons/fileupload2/core/MultipartInput.java | 62 ++++++++++++++++++---- .../fileupload2/core/MultipartStreamTest.java | 55 +++++++++++++++++++ src/changes/changes.xml | 1 + 3 files changed, 109 insertions(+), 9 deletions(-) diff --git a/commons-fileupload2-core/src/main/java/org/apache/commons/fileupload2/core/MultipartInput.java b/commons-fileupload2-core/src/main/java/org/apache/commons/fileupload2/core/MultipartInput.java index 68a63deb..b7ffc9e3 100644 --- a/commons-fileupload2-core/src/main/java/org/apache/commons/fileupload2/core/MultipartInput.java +++ b/commons-fileupload2-core/src/main/java/org/apache/commons/fileupload2/core/MultipartInput.java @@ -110,6 +110,10 @@ public final class MultipartInput { */ private ProgressNotifier progressNotifier; + /** The per part size limit for headers. + */ + private int partHeaderSizeMax = DEFAULT_PART_HEADER_SIZE_MAX; + /** * Constructs a new instance. */ @@ -134,7 +138,7 @@ public final class MultipartInput { */ @Override public MultipartInput get() throws IOException { - return new MultipartInput(getInputStream(), boundary, getBufferSize(), progressNotifier); + return new MultipartInput(getInputStream(), boundary, getBufferSize(), getPartHeaderSizeMax(), progressNotifier); } /** @@ -148,6 +152,15 @@ public final class MultipartInput { return this; } + /** Sets the per part size limit for headers. + * @param partHeaderSizeMax The maximum size of the headers in bytes. + * @since 2.0.0-M4 + */ + public Builder setPartHeaderSizeMax(int partHeaderSizeMax) { + this.partHeaderSizeMax = partHeaderSizeMax; + return this; + } + /** * Sets the progress notifier. * @@ -159,6 +172,14 @@ public final class MultipartInput { return this; } + /** 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() { + return partHeaderSizeMax; + } + } /** @@ -543,14 +564,15 @@ 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}). @@ -655,6 +677,14 @@ public final class MultipartInput { */ private final ProgressNotifier notifier; + /** The per part size limit for headers. + * + * @param partHeaderSizeMax The maximum size of the headers in bytes. + *Add commentMore actions + * @since 2.0.0-M4 + */ + private int partHeaderSizeMax; + /** * Constructs a {@code MultipartInput} with a custom size buffer. * <p> @@ -668,7 +698,7 @@ 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. */ - private MultipartInput(final InputStream input, final byte[] boundary, final int bufferSize, final ProgressNotifier notifier) { + private MultipartInput(final InputStream input, final byte[] boundary, final int bufferSize, final int partHeaderSizeMax, final ProgressNotifier notifier) { if (boundary == null) { throw new IllegalArgumentException("boundary may not be null"); } @@ -683,6 +713,7 @@ public final class MultipartInput { this.bufSize = Math.max(bufferSize, boundaryLength * 2); this.buffer = new byte[this.bufSize]; this.notifier = notifier; + this.partHeaderSizeMax = partHeaderSizeMax; this.boundary = new byte[this.boundaryLength]; this.boundaryTable = new int[this.boundaryLength + 1]; @@ -782,6 +813,15 @@ public final class MultipartInput { return headerCharset; } + /** Returns the per part size limit for headers. + * + * @param partHeaderSizeMax The maximum size of the headers in bytes. + * @since 2.0.0-M4 + */ + public int getPartHeaderSizeMax() { + return partHeaderSizeMax; + } + /** * Creates a new {@link ItemInputStream}. * @@ -875,6 +915,9 @@ 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. @@ -895,9 +938,10 @@ public final class MultipartInput { } catch (final IOException e) { throw new MalformedStreamException("Stream ended unexpectedly", e); } - if (++size > HEADER_PART_SIZE_MAX) { - throw new MalformedStreamException( - String.format("Header section has more than %s bytes (maybe it is not properly terminated)", HEADER_PART_SIZE_MAX)); + int phsm = getPartHeaderSizeMax(); + if (phsm != -1 && ++size > phsm) { + throw new FileUploadSizeException(String.format("Header section has more than %s bytes (maybe it is not properly terminated)", Integer.valueOf(phsm)), + (long) phsm, (long) size); } if (b == HEADER_SEPARATOR[i]) { i++; diff --git a/commons-fileupload2-core/src/test/java/org/apache/commons/fileupload2/core/MultipartStreamTest.java b/commons-fileupload2-core/src/test/java/org/apache/commons/fileupload2/core/MultipartStreamTest.java index 61d26afa..7a70ecec 100644 --- a/commons-fileupload2-core/src/test/java/org/apache/commons/fileupload2/core/MultipartStreamTest.java +++ b/commons-fileupload2-core/src/test/java/org/apache/commons/fileupload2/core/MultipartStreamTest.java @@ -16,11 +16,16 @@ */ package org.apache.commons.fileupload2.core; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; @@ -65,4 +70,54 @@ public class MultipartStreamTest { assertNotNull(ms); } + /** Checks, whether the maxSize works. + */ + @Test + public void testPartHeaderSizeMaxLimit() + throws Exception { + final String request = + "-----1234\r\n" + + "Content-Disposition: form-data; name=\"file1\"; filename=\"foo1.tab\"\r\n" + + "Content-Type: text/whatever\r\n" + + "Content-Length: 10\r\n" + + "\r\n" + + "This is the content of the file\n" + + "\r\n" + + "-----1234\r\n" + + "Content-Disposition: form-data; name=\"file2\"; filename=\"foo2.tab\"\r\n" + + "Content-Type: text/whatever\r\n" + + "\r\n" + + "This is the content of the file\n" + + "\r\n" + + "-----1234--\r\n"; + + final String strContents = request; + final byte[] byteContents = request.getBytes(StandardCharsets.UTF_8); + final InputStream input = new ByteArrayInputStream(byteContents); + final byte[] boundary = "---1234".getBytes(); + final MultipartInput mi = MultipartInput.builder().setInputStream(input).setBoundary(boundary) + .setPartHeaderSizeMax(100).get(); + assertNotNull(mi); + try { + boolean nextPart = mi.skipPreamble(); + OutputStream nullOutput = new OutputStream() { + @Override + public void write(int pB) throws IOException { + // Do nothing. (Null output) + } + }; + while (nextPart) { + String headers = mi.readHeaders(); + System.out.print("Headers=" + headers.length() + ", " + headers); + assertNotNull(headers); + // process headers + // create some output stream + mi.readBodyData(nullOutput); + nextPart = mi.readBoundary(); + } + fail("Expected Exception"); + } catch (FileUploadSizeException fuse) { + assertEquals(100, fuse.getPermitted()); + } + } } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index e6799568..895bfefa 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -45,6 +45,7 @@ The <action> type attribute can be add,update,fix,remove. <!-- FIX --> <action type="fix" dev="ppkarwasz" due-to="Basil Crow">Simplify exception handling in FileItem API #309.</action> <!-- ADD --> + <action type="add" dev="jochen" due-to="Mark Thomas">Add partHeaderSizeMax, a new limit that sets a maximum number of bytes for each individual multipart header. The default is 512 bytes.</action> <!-- UPDATE --> </release> <release version="2.0.0-M3" date="2025-05-07" description="This release requires Java 11.">