This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/10.1.x by this push:
     new 88ad622460 Add support for new attributes to ParameterLimitValve
88ad622460 is described below

commit 88ad62246090a8492f880004eb64b99ad852b500
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Wed Jun 4 10:31:42 2025 +0100

    Add support for new attributes to ParameterLimitValve
---
 java/org/apache/catalina/connector/Request.java    |  40 +++++++-
 .../apache/catalina/valves/LocalStrings.properties |   7 ++
 .../catalina/valves/ParameterLimitValve.java       |  59 +++++++-----
 .../catalina/valves/TestParameterLimitValve.java   | 106 ++++++++++++++++++++-
 .../valves/TestParameterLimitValveConfig.java      |  44 +++++++++
 webapps/docs/changelog.xml                         |   3 +-
 webapps/docs/config/valve.xml                      |   5 +
 7 files changed, 232 insertions(+), 32 deletions(-)

diff --git a/java/org/apache/catalina/connector/Request.java 
b/java/org/apache/catalina/connector/Request.java
index 99513df345..74b45f9558 100644
--- a/java/org/apache/catalina/connector/Request.java
+++ b/java/org/apache/catalina/connector/Request.java
@@ -144,7 +144,9 @@ public class Request implements HttpServletRequest {
     public Request(Connector connector) {
         this.connector = connector;
         if (connector != null) {
-            this.maxParameterCount = connector.getMaxParameterCount();
+            maxParameterCount = connector.getMaxParameterCount();
+            maxPartCount = connector.getMaxPartCount();
+            maxPartHeaderSize = connector.getMaxPartHeaderSize();
         }
     }
 
@@ -419,6 +421,10 @@ public class Request implements HttpServletRequest {
      */
     private int maxParameterCount = -1;
 
+    private int maxPartCount = -1;
+
+    private int maxPartHeaderSize = -1;
+
     // --------------------------------------------------------- Public Methods
 
     public void addPathParameter(String name, String value) {
@@ -450,8 +456,12 @@ public class Request implements HttpServletRequest {
         parametersParsed = false;
         if (connector != null) {
             maxParameterCount = connector.getMaxParameterCount();
+            maxPartCount = connector.getMaxPartCount();
+            maxPartHeaderSize = connector.getMaxPartHeaderSize();
         } else {
             maxParameterCount = -1;
+            maxPartCount = -1;
+            maxPartHeaderSize = -1;
         }
         if (parts != null) {
             for (Part part : parts) {
@@ -840,8 +850,9 @@ public class Request implements HttpServletRequest {
         coyoteRequest.setServerPort(port);
     }
 
+
     /**
-     * Set the maximum number of request parameters (GET plus POST) for a 
single request
+     * Set the maximum number of request parameters (GET plus POST including 
multipart) for a single request.
      *
      * @param maxParameterCount The maximum number of request parameters
      */
@@ -849,6 +860,27 @@ public class Request implements HttpServletRequest {
         this.maxParameterCount = maxParameterCount;
     }
 
+
+    /**
+     * Set the maximum number of parts for a single multipart request.
+     *
+     * @param maxPartCount The maximum number of request parts
+     */
+    public void setMaxPartCount(int maxPartCount) {
+        this.maxPartCount = maxPartCount;
+    }
+
+
+    /**
+     * Set the maximum header size per part for a single multipart request.
+     *
+     * @param maxPartHeaderSize The maximum size of the headers for one part
+     */
+    public void setMaxPartHeaderSize(int maxPartHeaderSize) {
+        this.maxPartHeaderSize = maxPartHeaderSize;
+    }
+
+
     // ------------------------------------------------- ServletRequest Methods
 
     @Override
@@ -2589,7 +2621,7 @@ public class Request implements HttpServletRequest {
             upload.setFileItemFactory(factory);
             upload.setFileSizeMax(mce.getMaxFileSize());
             upload.setSizeMax(mce.getMaxRequestSize());
-            upload.setPartHeaderSizeMax(connector.getMaxPartHeaderSize());
+            upload.setPartHeaderSizeMax(maxPartHeaderSize);
             /*
              * There are two independent limits on the number of parts.
              *
@@ -2605,7 +2637,7 @@ public class Request implements HttpServletRequest {
             if (partLimit > -1) {
                 partLimit = partLimit - parameters.size();
             }
-            int maxPartCount = connector.getMaxPartCount();
+            int maxPartCount = this.maxPartCount;
             if (maxPartCount > -1) {
                 if (partLimit < 0 || partLimit > maxPartCount) {
                     partLimit = maxPartCount;
diff --git a/java/org/apache/catalina/valves/LocalStrings.properties 
b/java/org/apache/catalina/valves/LocalStrings.properties
index fb62ba75a6..6c74796089 100644
--- a/java/org/apache/catalina/valves/LocalStrings.properties
+++ b/java/org/apache/catalina/valves/LocalStrings.properties
@@ -137,6 +137,13 @@ loadBalancerDrainingValve.skip=Client is presenting a 
valid [{0}] cookie, re-bal
 
 patternTokenizer.unexpectedParenthesis=Unexpected ')' in pattern
 
+parameterLimitValve.closeError=Error closing configuration
+parameterLimitValve.invalidLimits=Each limit configuration must contain either 
a single integer or three, comma-separated integers. Invalid limit string [{0}]
+parameterLimitValve.invalidLine=Each line must contain at least one '=' 
character. Invalid line [{0}]
+parameterLimitValve.noConfiguration=No configuration resource found [{0}]
+parameterLimitValve.readConfiguration=Read configuration from [/WEB-INF/{0}]
+parameterLimitValve.readError=Error reading 
configurationpatternTokenizer.unexpectedParenthesis=Unexpected ')' in pattern
+
 persistentValve.acquireFailed=The request for [{0}] did not obtain the per 
session Semaphore as no permit was available
 persistentValve.acquireInterrupted=The request for [{0}] did not obtain the 
per session Semaphore as it was interrupted while waiting for a permit
 persistentValve.filter.failure=Unable to compile filter=[{0}]
diff --git a/java/org/apache/catalina/valves/ParameterLimitValve.java 
b/java/org/apache/catalina/valves/ParameterLimitValve.java
index 67fb456ee0..8b6ef86424 100644
--- a/java/org/apache/catalina/valves/ParameterLimitValve.java
+++ b/java/org/apache/catalina/valves/ParameterLimitValve.java
@@ -38,10 +38,10 @@ import org.apache.tomcat.util.buf.UDecoder;
 import org.apache.tomcat.util.file.ConfigFileLoader;
 import org.apache.tomcat.util.file.ConfigurationSource;
 
-
 /**
- * This is a concrete implementation of {@link ValveBase} that enforces a 
limit on the number of HTTP request
- * parameters. The features of this implementation include:
+ * This is a concrete implementation of {@link ValveBase} that allows 
alternative values for the
+ * <strong>Connector</strong> attributes {@code maxParameterCount}, {@code 
maxPartCount} and {@code maxPartHeaderSize}
+ * to be applied to a request. The features of this implementation include:
  * <ul>
  * <li>URL-specific parameter limits that can be defined using regular 
expressions</li>
  * <li>Configurable through Tomcat's <code>server.xml</code> or 
<code>context.xml</code></li>
@@ -52,11 +52,9 @@ import org.apache.tomcat.util.file.ConfigurationSource;
  * The default limit, specified by Connector's value, applies to all requests 
unless a more specific URL pattern is
  * matched. URL patterns and their corresponding limits can be configured via 
a regular expression mapping through the
  * <code>urlPatternLimits</code> attribute.
- * </p>
  * <p>
  * The Valve checks each incoming request and enforces the appropriate limit. 
If a request exceeds the allowed number of
  * parameters, a <code>400 Bad Request</code> response is returned.
- * </p>
  * <p>
  * Example, configuration in <code>context.xml</code>:
  *
@@ -73,33 +71,36 @@ import org.apache.tomcat.util.file.ConfigurationSource;
  * {@code
  * /api/.*=150
  * /admin/.*=50
+ * /upload/.*=30,5,1024
  * }
  * </pre>
  * <p>
  * The configuration allows for flexible control over different sections of 
your application, such as applying higher
  * limits for API endpoints and stricter limits for admin areas.
- * </p>
- *
- * @author Dimitris Soumis
+ * <p>
+ * If a single integer is provided, it is used for {@code maxParameterCount}.
+ * <p>
+ * If three integers are provided, they are applied to {@code 
maxParameterCount}, {@code maxPartCount} and
+ * {@code maxPartHeaderSize} respectively.
  */
 
 public class ParameterLimitValve extends ValveBase {
 
     /**
-     * Map for URL-specific limits
+     * Map for URL-specific limits.
      */
-    protected Map<Pattern,Integer> urlPatternLimits = new 
ConcurrentHashMap<>();
+    private Map<Pattern,Integer[]> urlPatternLimits = new 
ConcurrentHashMap<>();
 
     /**
      * Relative path to the configuration file. Note: If the valve's container 
is a context, this will be relative to
      * /WEB-INF/.
      */
-    protected String resourcePath = "parameter_limit.config";
+    private String resourcePath = "parameter_limit.config";
 
     /**
      * Will be set to true if the valve is associated with a context.
      */
-    protected boolean context = false;
+    private boolean context = false;
 
     public ParameterLimitValve() {
         super(true);
@@ -211,14 +212,22 @@ public class ParameterLimitValve extends ValveBase {
                 }
 
                 String patternString = line.substring(0, 
lastEqualsIndex).trim();
-                String limitString = line.substring(lastEqualsIndex + 
1).trim();
+                String limitsString = line.substring(lastEqualsIndex + 
1).trim();
 
                 Pattern pattern = 
Pattern.compile(UDecoder.URLDecode(patternString, StandardCharsets.UTF_8));
-                int limit = Integer.parseInt(limitString);
+                String[] limits = limitsString.split(",");
+                if (limits.length == 1) {
+                    urlPatternLimits.put(pattern, new Integer[] { 
Integer.valueOf(limits[0]), null, null});
+                } else if (limits.length == 3) {
+                    urlPatternLimits.put(pattern, new Integer[] {
+                            Integer.valueOf(limits[0]), 
Integer.valueOf(limits[1]), Integer.valueOf(limits[2])});
+                } else {
+                    throw new IllegalArgumentException(
+                            
sm.getString("parameterLimitValve.invalidLimitsString", limitsString));
+                }
                 if (containerLog != null && containerLog.isTraceEnabled()) {
-                    containerLog.trace("Add pattern " + pattern + " and limit 
" + limit);
+                    containerLog.trace("Add pattern " + pattern + " and 
limit(s) " + limitsString);
                 }
-                urlPatternLimits.put(pattern, Integer.valueOf(limit));
             }
         } catch (IOException e) {
             containerLog.error(sm.getString("parameterLimitValve.readError"), 
e);
@@ -244,7 +253,7 @@ public class ParameterLimitValve extends ValveBase {
     @Override
     public void invoke(Request request, Response response) throws IOException, 
ServletException {
 
-        if (urlPatternLimits == null || urlPatternLimits.isEmpty()) {
+        if (urlPatternLimits.isEmpty()) {
             getNext().invoke(request, response);
             return;
         }
@@ -252,12 +261,15 @@ public class ParameterLimitValve extends ValveBase {
         String requestURI = context ? request.getRequestPathMB().toString() : 
request.getDecodedRequestURI();
 
         // Iterate over the URL patterns and apply corresponding limits
-        for (Map.Entry<Pattern,Integer> entry : urlPatternLimits.entrySet()) {
-            Pattern pattern = entry.getKey();
-            int limit = entry.getValue().intValue();
-
-            if (pattern.matcher(requestURI).matches()) {
-                request.setMaxParameterCount(limit);
+        for (Map.Entry<Pattern,Integer[]> entry : urlPatternLimits.entrySet()) 
{
+            if (entry.getKey().matcher(requestURI).matches()) {
+                Integer[] limits = entry.getValue();
+                // maxParameterCount should always be present
+                request.setMaxParameterCount(limits[0].intValue());
+                if (limits[1] != null) {
+                    request.setMaxPartCount(limits[1].intValue());
+                    request.setMaxPartHeaderSize(limits[2].intValue());
+                }
                 break;
             }
         }
@@ -265,5 +277,4 @@ public class ParameterLimitValve extends ValveBase {
         // Invoke the next valve to continue processing the request
         getNext().invoke(request, response);
     }
-
 }
diff --git a/test/org/apache/catalina/valves/TestParameterLimitValve.java 
b/test/org/apache/catalina/valves/TestParameterLimitValve.java
index 47bccb9765..9e4e85dc11 100644
--- a/test/org/apache/catalina/valves/TestParameterLimitValve.java
+++ b/test/org/apache/catalina/valves/TestParameterLimitValve.java
@@ -20,26 +20,36 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
 import java.io.FileWriter;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
-import jakarta.servlet.ServletRequestParametersBaseTest;
+import jakarta.servlet.MultipartConfigElement;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
 
 import org.junit.Assert;
 import org.junit.Test;
 
 import static org.apache.catalina.startup.SimpleHttpClient.CRLF;
 import org.apache.catalina.Context;
+import org.apache.catalina.Wrapper;
 import org.apache.catalina.core.StandardContext;
 import org.apache.catalina.filters.FailedRequestFilter;
 import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.descriptor.web.FilterDef;
 import org.apache.tomcat.util.descriptor.web.FilterMap;
 import org.apache.tomcat.util.scan.StandardJarScanner;
 
 
-public class TestParameterLimitValve extends ServletRequestParametersBaseTest {
+public class TestParameterLimitValve extends TomcatBaseTest {
 
     @Test
     public void testSpecificUrlPatternLimit() throws Exception {
@@ -236,7 +246,8 @@ public class TestParameterLimitValve extends 
ServletRequestParametersBaseTest {
     public void testUrlPatternLimitsFromFile() throws Exception {
         File configFile = File.createTempFile("parameter_limit", ".config");
         try (PrintWriter writer = new PrintWriter(new FileWriter(configFile))) 
{
-            writer.println("# Commented line");
+            writer.println("# Commented line - empty line follows");
+            writer.println("");
             writer.println("/api/.*=2");
             writer.println("# Commented line");
         }
@@ -424,6 +435,95 @@ public class TestParameterLimitValve extends 
ServletRequestParametersBaseTest {
     }
 
 
+    @Test
+    public void testMultipart() throws Exception {
+        doTestMultipart(50,  10,  512, true);
+    }
+
+
+    @Test
+    public void testMultipartParameterLimitExceeded01() throws Exception {
+        doTestMultipart(1,  10,  512, false);
+    }
+
+
+    @Test
+    public void testMultipartParameterLimitExceeded02() throws Exception {
+        doTestMultipart(5,  10,  512, false);
+    }
+
+
+    @Test
+    public void testMultipartPartLimitExceeded() throws Exception {
+        doTestMultipart(50,  1,  512, false);
+    }
+
+
+    @Test
+    public void testMultipartPartHeaderSizeLimitExceeded() throws Exception {
+        doTestMultipart(50,  10,  1, false);
+    }
+
+
+    private void doTestMultipart(int maxParameterCount, int maxPartCount, int 
maxPartHeaderSize, boolean okExpected) throws Exception {
+
+        Tomcat tomcat = getTomcatInstance();
+        StandardContext ctx = (StandardContext) getProgrammaticRootContext();
+
+        ParameterLimitValve parameterLimitValve = new ParameterLimitValve();
+        ctx.getPipeline().addValve(parameterLimitValve);
+        parameterLimitValve.setUrlPatternLimits("/upload/.*=" + 
Integer.toString(maxParameterCount) + "," +
+                Integer.toString(maxPartCount) + "," + 
Integer.toString(maxPartHeaderSize));
+
+        Wrapper w = Tomcat.addServlet(ctx, "multipart", new 
MultipartServlet());
+        // Use defaults for Multipart
+        w.setMultipartConfigElement(new MultipartConfigElement(""));
+        ctx.addServletMappingDecoded("/upload/*", "multipart");
+
+        tomcat.start();
+
+        // Construct a simple multipart body with two parts
+        String boundary = "--simpleBoundary";
+
+        String content = "--" + boundary + CRLF +
+                "Content-Disposition: form-data; name=\"part1\"" + CRLF + CRLF 
+
+                "part value 1" + CRLF +
+                "--" + boundary + CRLF +
+                "Content-Disposition: form-data; name=\"part2\"" + CRLF + CRLF 
+
+                "part value 2" + CRLF +                "--" + boundary + "--" 
+ CRLF;
+
+        Map<String,List<String>> reqHeaders = new HashMap<>();
+        reqHeaders.put("Content-Type", List.of("multipart/form-data; 
boundary=" + boundary));
+        reqHeaders.put("Content-Length", 
List.of(Integer.toString(content.length())));
+
+        int rc = postUrl(content.getBytes(), "http://localhost:"; + getPort() + 
"/upload/endpoint?" +
+                "param1=value1&param2=value2&param3=value3&param4=value4",
+                new ByteChunk(), reqHeaders, null);
+
+        if (okExpected) {
+            Assert.assertEquals(HttpServletResponse.SC_OK, rc);
+        } else {
+            Assert.assertTrue(Integer.toString(rc),
+                    rc == HttpServletResponse.SC_BAD_REQUEST || rc == 
HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
+        }
+    }
+
+
+    private static class MultipartServlet extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        protected void doPost(HttpServletRequest req, HttpServletResponse 
resp) throws ServletException, IOException {
+            resp.setContentType("text/plain");
+            resp.setCharacterEncoding("UTF-8");
+            PrintWriter pw = resp.getWriter();
+            pw.println("Parameters: " + req.getParameterMap().size());
+            pw.println("Parts: " + req.getParts().size());
+        }
+    }
+
+
     private static void addFailedRequestFilter(Context context) {
         FilterDef failedRequestFilter = new FilterDef();
         failedRequestFilter.setFilterName("failedRequestFilter");
diff --git a/test/org/apache/catalina/valves/TestParameterLimitValveConfig.java 
b/test/org/apache/catalina/valves/TestParameterLimitValveConfig.java
new file mode 100644
index 0000000000..6bf2a4b23c
--- /dev/null
+++ b/test/org/apache/catalina/valves/TestParameterLimitValveConfig.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.valves;
+
+import org.junit.Test;
+
+public class TestParameterLimitValveConfig {
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNoEquals() {
+        ParameterLimitValve parameterLimitValve = new ParameterLimitValve();
+        parameterLimitValve.setUrlPatternLimits("/abc");
+    }
+
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidLimitCount02() {
+        ParameterLimitValve parameterLimitValve = new ParameterLimitValve();
+        parameterLimitValve.setUrlPatternLimits("/abc=1,2");
+
+    }
+
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidLimitCount04() {
+        ParameterLimitValve parameterLimitValve = new ParameterLimitValve();
+        parameterLimitValve.setUrlPatternLimits("/abc=1,2,3,4");
+
+    }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 2b0ad12daa..6b59cb5ac5 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -151,7 +151,8 @@
         new attributes on the <code>Connector</code> element.
         <code>maxPartCount</code> limits the total number of parts in a
         multi-part request and <code>maxPartHeaderSize</code> limits the size 
of
-        the headers provided with each part. (markt)
+        the headers provided with each part. Add support for these new
+        attributes to the <code>ParameterLimitValve</code>. (markt)
       </add>
     </changelog>
   </subsection>
diff --git a/webapps/docs/config/valve.xml b/webapps/docs/config/valve.xml
index 8226167214..2fc3a9b0c5 100644
--- a/webapps/docs/config/valve.xml
+++ b/webapps/docs/config/valve.xml
@@ -2714,10 +2714,15 @@
           <code>
             /api/.*=100
             /admin/.*=50
+            /upload/.*=30,5,1024
           </code>
         Default value: <code>parameter_limit.config</code>.
         It must be placed in the Host configuration folder or in the WEB-INF 
folder of the web application.
         </p>
+        <p>If a single integer is provided, it is used for 
<code>maxParameterCount</code>. If three integers are
+        provided, they are applied to <code>maxParameterCount</code>, 
<code>maxPartCount</code> and
+        <code>maxPartHeaderSize</code> respectively.
+        </p>
       </attribute>
 
     </attributes>


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to