This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push:
new 60f3b06e3a Review "Apply the first matching URL pattern in
configuration order"
60f3b06e3a is described below
commit 60f3b06e3a839f97935fb9c2dc65611549d0722a
Author: Mark Thomas <[email protected]>
AuthorDate: Thu Oct 29 16:54:27 2026 +0000
Review "Apply the first matching URL pattern in configuration order"
Pattern replacement now works as documented
---
.../catalina/valves/ParameterLimitValve.java | 68 +++++++++++++---------
.../catalina/valves/TestParameterLimitValve.java | 24 ++++++++
2 files changed, 65 insertions(+), 27 deletions(-)
diff --git a/java/org/apache/catalina/valves/ParameterLimitValve.java
b/java/org/apache/catalina/valves/ParameterLimitValve.java
index 71b57a87dd..49e970750e 100644
--- a/java/org/apache/catalina/valves/ParameterLimitValve.java
+++ b/java/org/apache/catalina/valves/ParameterLimitValve.java
@@ -90,12 +90,13 @@ import org.apache.tomcat.util.file.ConfigurationSource;
public class ParameterLimitValve extends ValveBase {
/**
- * Map for URL-specific limits. Entries are kept in configuration order so
that, when several patterns match a
- * request, the first configured matching pattern wins. The map is
published by replacing the reference rather than
- * by being modified after publication, so readers work with a snapshot of
the reference and never observe a
- * partially updated map.
+ * Map for URL-specific limits, keyed by the regular expression of the URL
pattern. {@link Pattern} does not
+ * implement value based equality so it can't be used as the key if a
repeated pattern is to replace the earlier
+ * limits. Entries are kept in configuration order so that, when several
patterns match a request, the first
+ * configured matching pattern wins. The map is published by replacing the
reference rather than by being modified
+ * after publication, so readers work with a snapshot of the reference and
never observe a partially updated map.
*/
- private volatile Map<Pattern,Integer[]> urlPatternLimits =
Collections.emptyMap();
+ private volatile Map<String,UrlPatternLimit> urlPatternLimits =
Collections.emptyMap();
/**
* Relative path to the configuration file. Note: If the valve's container
is a context, this will be relative to
@@ -199,18 +200,19 @@ public class ParameterLimitValve extends ValveBase {
* @param urlPatternConfig The URL pattern to parameter limit mappings
*/
public synchronized void setUrlPatternLimits(String urlPatternConfig) {
- urlPatternLimits = Collections.unmodifiableMap(
- parseUrlPatternLimits(new BufferedReader(new
StringReader(urlPatternConfig))));
+ urlPatternLimits = Collections
+ .unmodifiableMap(parseUrlPatternLimits(new BufferedReader(new
StringReader(urlPatternConfig))));
}
/**
* Set the mapping of URL patterns to their corresponding parameter
limits. The input should be provided line by
* line, where each line contains a pattern and a limit, separated by the
last '='. The parsed limits are added
- * after any previously configured limits. If a parsed pattern matches an
already configured pattern, its limit
- * replaces the previous one at the original position, preserving the
configuration order used to select the
- * matching pattern. The parsed limits are only applied if the complete
input is parsed successfully. If parsing
- * fails, the exception is propagated and any previously configured limits
are left unchanged. If a read error
- * occurs, it is logged and the input read so far is discarded without
modifying the configured limits.
+ * after any previously configured limits. If a parsed pattern has the
same regular expression as an already
+ * configured pattern, its limit replaces the previous one at the original
position, preserving the configuration
+ * order used to select the matching pattern. The same applies to a
pattern repeated within the input. The parsed
+ * limits are only applied if the complete input is parsed successfully.
If parsing fails, the exception is
+ * propagated and any previously configured limits are left unchanged. If
a read error occurs, it is logged and the
+ * input read so far is discarded without modifying the configured limits.
* <p>
* Example:
*
@@ -225,7 +227,7 @@ public class ParameterLimitValve extends ValveBase {
* line.
*/
public synchronized void setUrlPatternLimits(BufferedReader reader) {
- Map<Pattern,Integer[]> newLimits = new
LinkedHashMap<>(urlPatternLimits);
+ Map<String,UrlPatternLimit> newLimits = new
LinkedHashMap<>(urlPatternLimits);
newLimits.putAll(parseUrlPatternLimits(reader));
urlPatternLimits = Collections.unmodifiableMap(newLimits);
}
@@ -239,11 +241,11 @@ public class ParameterLimitValve extends ValveBase {
*
* @return The parsed mappings
*/
- private Map<Pattern,Integer[]> parseUrlPatternLimits(BufferedReader
reader) {
+ private Map<String,UrlPatternLimit> parseUrlPatternLimits(BufferedReader
reader) {
if (containerLog == null && getContainer() != null) {
containerLog = LogFactory.getLog(getContainer().getLogName() +
".parameterLimit");
}
- Map<Pattern,Integer[]> newLimits = new LinkedHashMap<>();
+ Map<String,UrlPatternLimit> newLimits = new LinkedHashMap<>();
try {
String line;
while ((line = reader.readLine()) != null) {
@@ -262,16 +264,17 @@ public class ParameterLimitValve extends ValveBase {
String patternString = line.substring(0,
lastEqualsIndex).strip();
String limitsString = line.substring(lastEqualsIndex +
1).strip();
- Pattern pattern =
Pattern.compile(UDecoder.URLDecode(patternString, StandardCharsets.UTF_8));
+ String regex = UDecoder.URLDecode(patternString,
StandardCharsets.UTF_8);
+ Pattern pattern = Pattern.compile(regex);
String[] limits = limitsString.split(",");
if (limits.length == 1) {
- newLimits.put(pattern, new Integer[] {
Integer.valueOf(limits[0]), null, null });
+ newLimits.put(regex,
+ new UrlPatternLimit(pattern, new Integer[] {
Integer.valueOf(limits[0]), null, null }));
} else if (limits.length == 3) {
- newLimits.put(pattern, new Integer[] {
Integer.valueOf(limits[0]),
- Integer.valueOf(limits[1]),
Integer.valueOf(limits[2]) });
+ newLimits.put(regex, new UrlPatternLimit(pattern, new
Integer[] { Integer.valueOf(limits[0]),
+ Integer.valueOf(limits[1]),
Integer.valueOf(limits[2]) }));
} else {
- throw new IllegalArgumentException(
- sm.getString("parameterLimitValve.invalidLimits",
limitsString));
+ throw new
IllegalArgumentException(sm.getString("parameterLimitValve.invalidLimits",
limitsString));
}
if (containerLog != null && containerLog.isTraceEnabled()) {
containerLog.trace("Add pattern " + pattern + " and
limit(s) " + limitsString);
@@ -296,13 +299,13 @@ public class ParameterLimitValve extends ValveBase {
/**
* {@inheritDoc}
* <p>
- * Checks if any of the defined patterns matches the URI of the request
and if it does, enforces the parameter
- * limit of the first matching pattern in configuration order. Then invoke
the next Valve in the sequence.
+ * Checks if any of the defined patterns matches the URI of the request
and if it does, enforces the parameter limit
+ * of the first matching pattern in configuration order. Then invoke the
next Valve in the sequence.
*/
@Override
public void invoke(Request request, Response response) throws IOException,
ServletException {
- Map<Pattern,Integer[]> urlPatternLimitsSnapshot = urlPatternLimits;
+ Map<String,UrlPatternLimit> urlPatternLimitsSnapshot =
urlPatternLimits;
if (urlPatternLimitsSnapshot.isEmpty()) {
getNext().invoke(request, response);
@@ -314,9 +317,9 @@ public class ParameterLimitValve extends ValveBase {
// Iterate over the URL patterns and apply the limits of the first
// matching pattern. The map is in configuration order, so when several
// patterns match, the first configured matching pattern wins.
- for (Map.Entry<Pattern,Integer[]> entry :
urlPatternLimitsSnapshot.entrySet()) {
- if (entry.getKey().matcher(requestURI).matches()) {
- Integer[] limits = entry.getValue();
+ for (UrlPatternLimit urlPatternLimit :
urlPatternLimitsSnapshot.values()) {
+ if (urlPatternLimit.pattern().matcher(requestURI).matches()) {
+ Integer[] limits = urlPatternLimit.limits();
// maxParameterCount should always be present
request.setMaxParameterCount(limits[0].intValue());
if (limits[1] != null) {
@@ -330,4 +333,15 @@ public class ParameterLimitValve extends ValveBase {
// Invoke the next valve to continue processing the request
getNext().invoke(request, response);
}
+
+
+ /**
+ * The compiled URL pattern and the limits to apply to the requests that
match it.
+ *
+ * @param pattern The compiled URL pattern
+ * @param limits The maxParameterCount, maxPartCount and
maxPartHeaderSize limits, the latter two being
+ * {@code null} if not configured
+ */
+ private record UrlPatternLimit(Pattern pattern, Integer[] limits) {
+ }
}
diff --git a/test/org/apache/catalina/valves/TestParameterLimitValve.java
b/test/org/apache/catalina/valves/TestParameterLimitValve.java
index 022cc9e151..8072944ce6 100644
--- a/test/org/apache/catalina/valves/TestParameterLimitValve.java
+++ b/test/org/apache/catalina/valves/TestParameterLimitValve.java
@@ -22,6 +22,7 @@ import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
+import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
@@ -636,6 +637,29 @@ public class TestParameterLimitValve extends
TomcatBaseTest {
Assert.assertEquals(200, rc);
}
+ @Test
+ public void testRepeatedPatternReplacesLimitAtOriginalPosition() throws
Exception {
+ Tomcat tomcat = getTomcatInstance();
+
+ StandardContext ctx = (StandardContext) getProgrammaticRootContext();
+
+ ParameterLimitValve parameterLimitValve = new ParameterLimitValve();
+ ctx.getPipeline().addValve(parameterLimitValve);
+ parameterLimitValve.setUrlPatternLimits("/special/.*=100" + CRLF +
"/.*=100");
+ // Appending the same pattern must replace its limit, not add a
shadowed entry
+ parameterLimitValve.setUrlPatternLimits(new BufferedReader(new
StringReader("/special/.*=2")));
+
+ Tomcat.addServlet(ctx, "snoop", new SnoopServlet());
+ ctx.addServletMapping("/special/endpoint", "snoop");
+
+ tomcat.start();
+
+ int rc = getUrl("http://localhost:" + getPort() +
+ "/special/endpoint?param1=value1¶m2=value2¶m3=value3",
+ new ByteChunk(), null);
+ Assert.assertEquals(400, rc);
+ }
+
@Test
public void testInvalidConfigLeavesLimitsUnchanged() throws Exception {
Tomcat tomcat = getTomcatInstance();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]