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.">

Reply via email to