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
         }
     }
-
 }

Reply via email to