This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 1.x in repository https://gitbox.apache.org/repos/asf/commons-fileupload.git
commit 2108495a4775910b8559f18ed5a779d60542ee96 Author: Mark Thomas <ma...@apache.org> AuthorDate: Thu Jun 5 11:21:25 2025 +0100 Add new limit partHeaderSizeMax that defaults to 512 bytes Implements a TODO to allow users to control the maximum permitted size of the multipart headers for a single part. --- src/changes/changes.xml | 1 + .../apache/commons/fileupload/FileUploadBase.java | 40 +++++++++++++++++++-- .../apache/commons/fileupload/MultipartStream.java | 41 ++++++++++++++++++---- .../commons/fileupload/MultipartStreamTest.java | 5 +++ .../org/apache/commons/fileupload/SizesTest.java | 37 ++++++++++++++++++- 5 files changed, 115 insertions(+), 9 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 9912296a..20d50423 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -46,6 +46,7 @@ The <action> type attribute can be add,update,fix,remove. <!-- ADD --> <action type="add" dev="ggregory" due-to="mufasa1976, Jochen Wiedmann, Gary Gregory">[1.x] Enable multipart/related on FileUpload #314.</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add JApiCmp to the default Maven goal.</action> + <action type="add" dev="markt" 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> <!-- FIX --> <action type="fix" dev="ggregory" due-to="Gary Gregory">Replace use of Locale.ENGLISH with Locale.ROOT.</action> <action type="fix" dev="ggregory" due-to="Gary Gregory">Remove unused exception from FileUploadBase.createItem(Map, boolean).</action> diff --git a/src/main/java/org/apache/commons/fileupload/FileUploadBase.java b/src/main/java/org/apache/commons/fileupload/FileUploadBase.java index 40b8af4e..6646d0bc 100644 --- a/src/main/java/org/apache/commons/fileupload/FileUploadBase.java +++ b/src/main/java/org/apache/commons/fileupload/FileUploadBase.java @@ -358,6 +358,7 @@ public abstract class FileUploadBase { throw new InvalidContentTypeException(format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae); } multi.setHeaderEncoding(charEncoding); + multi.setPartHeaderSizeMax(getPartHeaderSizeMax()); skipPreamble = true; findNextItem(); } @@ -843,12 +844,20 @@ public abstract class FileUploadBase { * The maximum length of a single header line that will be parsed * (1024 bytes). * @deprecated This constant is no longer used. As of commons-fileupload - * 1.2, the only applicable limit is the total size of a parts headers, - * {@link MultipartStream#HEADER_PART_SIZE_MAX}. + * 1.6, the applicable limit is the total size of a single part's headers, + * {@link #getPartHeaderSizeMax()} in bytes. */ @Deprecated public static final int MAX_HEADER_SIZE = 1024; + /** + * Default per part header size limit in bytes. + * + * @since 1.6 + */ + public static final int DEFAULT_PART_HEADER_SIZE_MAX = 512; + + /** * Utility method that determines whether the request contains multipart * content. @@ -903,6 +912,11 @@ public abstract class FileUploadBase { */ private long fileCountMax = -1; + /** + * The maximum permitted size of the headers provided with a single part in bytes. + */ + private int partHeaderSizeMax = DEFAULT_PART_HEADER_SIZE_MAX; + /** * The content encoding to use when reading part headers. */ @@ -1136,6 +1150,17 @@ public abstract class FileUploadBase { } } + /** + * Obtain the per part size limit for headers. + * + * @return The maximum size of the headers for a single part in bytes. + * + * @since 1.6 + */ + public int getPartHeaderSizeMax() { + return partHeaderSizeMax; + } + /** * <p> Parses the {@code header-part} and returns as key/value * pairs. @@ -1422,6 +1447,17 @@ public abstract class FileUploadBase { headerEncoding = encoding; } + /** + * Sets the per part size limit for headers. + * + * @param partHeaderSizeMax The maximum size of the headers in bytes. + * + * @since 1.6 + */ + public void setPartHeaderSizeMax(final int partHeaderSizeMax) { + this.partHeaderSizeMax = partHeaderSizeMax; + } + /** * Sets the progress listener. * diff --git a/src/main/java/org/apache/commons/fileupload/MultipartStream.java b/src/main/java/org/apache/commons/fileupload/MultipartStream.java index e551eedf..39ef5bab 100644 --- a/src/main/java/org/apache/commons/fileupload/MultipartStream.java +++ b/src/main/java/org/apache/commons/fileupload/MultipartStream.java @@ -23,6 +23,7 @@ import java.io.OutputStream; import java.io.UnsupportedEncodingException; import org.apache.commons.fileupload.FileUploadBase.FileUploadIOException; +import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException; import org.apache.commons.fileupload.util.Closeable; import org.apache.commons.fileupload.util.Streams; @@ -481,7 +482,10 @@ public class MultipartStream { /** * The maximum length of {@code header-part} that will be * processed (10 kilobytes = 10240 bytes.). + * + * @deprecated Unused. Replaced by {@link #getPartHeaderSizeMax()}. */ + @Deprecated public static final int HEADER_PART_SIZE_MAX = 10240; /** @@ -591,6 +595,11 @@ public class MultipartStream { */ private final ProgressNotifier notifier; + /** + * The maximum permitted size of the headers provided with a single part in bytes. + */ + private int partHeaderSizeMax = FileUploadBase.DEFAULT_PART_HEADER_SIZE_MAX; + /** * Creates a new instance. * @@ -787,6 +796,17 @@ public class MultipartStream { return headerEncoding; } + /** + * Obtain the per part size limit for headers. + * + * @return The maximum size of the headers for a single part in bytes. + * + * @since 1.6 + */ + public int getPartHeaderSizeMax() { + return partHeaderSizeMax; + } + /** * Creates a new {@link ItemInputStream}. * @return A new instance of {@link ItemInputStream}. @@ -890,9 +910,6 @@ public class MultipartStream { * <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 FileUploadIOException if the bytes read from the stream exceeded the size limits. @@ -914,9 +931,10 @@ public class MultipartStream { throw new MalformedStreamException("Stream ended unexpectedly"); } size++; - if (size > HEADER_PART_SIZE_MAX) { - throw new MalformedStreamException( - String.format("Header section has more than %s bytes (maybe it is not properly terminated)", Integer.valueOf(HEADER_PART_SIZE_MAX))); + if (getPartHeaderSizeMax() != -1 && size > getPartHeaderSizeMax()) { + throw new FileUploadIOException(new SizeLimitExceededException( + String.format("Header section has more than %s bytes (maybe it is not properly terminated)", Integer.valueOf(getPartHeaderSizeMax())), + size, getPartHeaderSizeMax())); } if (b == HEADER_SEPARATOR[i]) { i++; @@ -977,6 +995,17 @@ public class MultipartStream { headerEncoding = encoding; } + /** + * Sets the per part size limit for headers. + * + * @param partHeaderSizeMax The maximum size of the headers in bytes. + * + * @since 1.6 + */ + public void setPartHeaderSizeMax(final int partHeaderSizeMax) { + this.partHeaderSizeMax = partHeaderSizeMax; + } + /** * Finds the beginning of the first {@code encapsulation}. * diff --git a/src/test/java/org/apache/commons/fileupload/MultipartStreamTest.java b/src/test/java/org/apache/commons/fileupload/MultipartStreamTest.java index ebdc334d..7fc80504 100644 --- a/src/test/java/org/apache/commons/fileupload/MultipartStreamTest.java +++ b/src/test/java/org/apache/commons/fileupload/MultipartStreamTest.java @@ -20,12 +20,15 @@ package org.apache.commons.fileupload; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; +import org.junit.Assert; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import org.junit.Test; +import org.apache.commons.fileupload.MultipartStream.MalformedStreamException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; @@ -115,6 +118,7 @@ public class MultipartStreamTest { final ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory()); upload.setFileSizeMax(-1); upload.setSizeMax(-1); + upload.setPartHeaderSizeMax(-1); final MockHttpServletRequest req = new MockHttpServletRequest( request.toString().getBytes("US-ASCII"), Constants.CONTENT_TYPE); @@ -123,6 +127,7 @@ public class MultipartStreamTest { fail("Expected exception."); } catch (final FileUploadException e) { // Expected + Assert.assertTrue(e.getCause() instanceof MalformedStreamException); } } } diff --git a/src/test/java/org/apache/commons/fileupload/SizesTest.java b/src/test/java/org/apache/commons/fileupload/SizesTest.java index 50b31c33..73e1b0dc 100644 --- a/src/test/java/org/apache/commons/fileupload/SizesTest.java +++ b/src/test/java/org/apache/commons/fileupload/SizesTest.java @@ -217,6 +217,42 @@ public class SizesTest { } } + /** Checks, whether the maxSize works. + */ + @Test + public void testPartHeaderSizeMaxLimit() + throws IOException, FileUploadException { + 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 ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory()); + upload.setFileSizeMax(-1); + upload.setSizeMax(-1); + upload.setPartHeaderSizeMax(100); + + final MockHttpServletRequest req = new MockHttpServletRequest( + request.getBytes("US-ASCII"), Constants.CONTENT_TYPE); + try { + upload.parseRequest(req); + fail("Expected exception."); + } catch (final FileUploadBase.SizeLimitExceededException e) { + assertEquals(100, e.getPermittedSize()); + } + } + @Test public void testMaxSizeLimitUnknownContentLength() throws IOException, FileUploadException { @@ -280,5 +316,4 @@ public class SizesTest { // expected } } - }