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

markt-asf 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 7f4aa3753d Review "Apply the first matching URL pattern in 
configuration order"
7f4aa3753d is described below

commit 7f4aa3753daafb7aed53961129395097bef5bae3
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 827a6dac60..e6c02f79f5 100644
--- a/java/org/apache/catalina/valves/ParameterLimitValve.java
+++ b/java/org/apache/catalina/valves/ParameterLimitValve.java
@@ -91,12 +91,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
@@ -200,18 +201,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:
      *
@@ -226,7 +228,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);
     }
@@ -240,11 +242,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) {
@@ -263,16 +265,17 @@ public class ParameterLimitValve extends ValveBase {
                 String patternString = 
JreCompat.getInstance().strip(line.substring(0, lastEqualsIndex));
                 String limitsString = 
JreCompat.getInstance().strip(line.substring(lastEqualsIndex + 1));
 
-                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);
@@ -297,13 +300,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);
@@ -315,9 +318,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) {
@@ -331,4 +334,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 e260d4a4bf..54b27f9213 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.Arrays;
 import java.util.HashMap;
@@ -648,6 +649,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&param2=value2&param3=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]

Reply via email to