This is an automated email from the ASF dual-hosted git repository.
markt pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new 0488123180 Add support for new attributes to ParameterLimitValve
0488123180 is described below
commit 04881231804f6896a17c6dbf3e159d723949af0e
Author: Mark Thomas <[email protected]>
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 | 107 ++++++++++++++++++++-
.../valves/TestParameterLimitValveConfig.java | 44 +++++++++
webapps/docs/changelog.xml | 3 +-
webapps/docs/config/valve.xml | 5 +
7 files changed, 233 insertions(+), 32 deletions(-)
diff --git a/java/org/apache/catalina/connector/Request.java
b/java/org/apache/catalina/connector/Request.java
index ccadd497af..b34e7e148c 100644
--- a/java/org/apache/catalina/connector/Request.java
+++ b/java/org/apache/catalina/connector/Request.java
@@ -145,7 +145,9 @@ public class Request implements HttpServletRequest {
this.connector = connector;
if (connector != null) {
- this.maxParameterCount = connector.getMaxParameterCount();
+ maxParameterCount = connector.getMaxParameterCount();
+ maxPartCount = connector.getMaxPartCount();
+ maxPartHeaderSize = connector.getMaxPartHeaderSize();
}
formats = new SimpleDateFormat[formatsTemplate.length];
@@ -449,6 +451,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) {
@@ -480,8 +486,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) {
@@ -870,8 +880,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
*/
@@ -879,6 +890,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
@@ -2630,7 +2662,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.
*
@@ -2646,7 +2678,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 0b511ffc1b..4c75bd4d68 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 a2e5bb5afc..04cb44a41c 100644
--- a/test/org/apache/catalina/valves/TestParameterLimitValve.java
+++ b/test/org/apache/catalina/valves/TestParameterLimitValve.java
@@ -20,26 +20,37 @@ 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.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
-import javax.servlet.ServletRequestParametersBaseTest;
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.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 +247,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 +436,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", Arrays.asList(new String[] {
"multipart/form-data; boundary=" + boundary }));
+ reqHeaders.put("Content-Length", Arrays.asList(new String[] {
Integer.toString(content.length())}));
+
+ int rc = postUrl(content.getBytes(), "http://localhost:" + getPort() +
"/upload/endpoint?" +
+ "param1=value1¶m2=value2¶m3=value3¶m4=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 cd3e7743c2..6261e5c4db 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 ad49c588c9..ff020161f8 100644
--- a/webapps/docs/config/valve.xml
+++ b/webapps/docs/config/valve.xml
@@ -2709,10 +2709,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: [email protected]
For additional commands, e-mail: [email protected]