This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-5474-multipart-maxfiles-semantics in repository https://gitbox.apache.org/repos/asf/struts.git
commit ed74a27749290f6a4e1970a1e8f9d37e499e1446 Author: Lukasz Lenart <[email protected]> AuthorDate: Wed Jul 22 15:34:01 2026 +0200 WW-5474 fix(multipart): honor -1 unlimited sentinel in total-parts backstop prepareServletFileUpload applied the total-parts backstop whenever both maxFiles and maxParameterCount were non-null, without checking for the -1 "unlimited" sentinel already honored by enforceMaxFiles/enforceMaxParameterCount. With maxFiles=-1 and maxParameterCount=256, maxParts computed to 255 and was passed to commons-fileupload2's setMaxFileCount (which counts ALL parts), wrongly rejecting large file-only uploads. Only apply the backstop when both limits are finite (non-null and >= 0). Co-Authored-By: Claude Opus 4.8 <[email protected]> --- .../multipart/AbstractMultiPartRequest.java | 2 +- .../multipart/JakartaMultiPartRequestTest.java | 25 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequest.java b/core/src/main/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequest.java index 0c5e618bd..17fadc035 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequest.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequest.java @@ -239,7 +239,7 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest { LOG.debug("Applies max size: {} to file upload request", maxSize); servletFileUpload.setMaxSize(maxSize); } - if (maxFiles != null && maxParameterCount != null) { + if (maxFiles != null && maxFiles >= 0 && maxParameterCount != null && maxParameterCount >= 0) { long maxParts = maxFiles + maxParameterCount; LOG.debug("Applies total parts backstop: {} to file upload request", maxParts); servletFileUpload.setMaxFileCount(maxParts); diff --git a/core/src/test/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequestTest.java b/core/src/test/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequestTest.java index 641388ff7..b80d45760 100644 --- a/core/src/test/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequestTest.java +++ b/core/src/test/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequestTest.java @@ -521,4 +521,29 @@ public class JakartaMultiPartRequestTest extends AbstractMultiPartRequestTest { .isEqualTo("valid file content"); } + @Test + public void unlimitedMaxFilesIsNotClampedByTotalPartsBackstop() throws IOException { + // Regression for WW-5474: maxFiles=-1 (unlimited) combined with a finite + // maxParameterCount must not compute a finite total-parts backstop + // (maxFiles + maxParameterCount) and pass it to commons-fileupload2's + // setMaxFileCount, which counts ALL parts. Before the fix, -1 + 256 = 255 + // wrongly rejected a 300-file/0-field upload at part 256. + int fileCount = 300; + StringBuilder content = new StringBuilder(); + for (int i = 0; i < fileCount; i++) { + content.append(formFile("file" + i, "test" + i + ".csv", "1,2,3,4")); + } + content.append(endline).append("--").append(boundary).append("--"); + mockRequest.setContent(content.toString().getBytes(StandardCharsets.UTF_8)); + + multiPart.setMaxSize("-1"); // isolate: don't let the size backstop interfere + multiPart.setMaxFiles("-1"); // unlimited files + multiPart.setMaxParameterCount("256"); // finite, but must not clamp file count + + multiPart.parse(mockRequest, tempDir); + + assertThat(multiPart.getErrors()).isEmpty(); + assertThat(multiPart.getFileParameterNames().asIterator()).toIterable().hasSize(fileCount); + } + }
