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

rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit d455348d330d34afb5f1539aa87d40366756db9b
Author: opencode <[email protected]>
AuthorDate: Mon Oct 12 14:43:25 2026 +0200

    Accept any whitespace as key/value separator in txt and rnd rewrite maps
    
    RandomizedTextRewriteMap parsed mod_rewrite map files with a hard coded
    single space as the separator between key and value. Tab separated lines
    were rejected with an IllegalArgumentException at start up, lines with
    several whitespace characters silently kept the extra whitespace at the
    start of the value, and comment or empty lines indented with whitespace
    were mis-parsed, the former failing start up and the latter creating a
    garbage entry under an empty key. Trim each line and treat any run of
    whitespace as the separator, as mod_rewrite documents and as the httpd
    parser implements it. The value may still contain whitespace, as
    before. Add a unit test class covering the separator variants, comment
    handling, random pipe separated values and the invalid key-only line.
---
 .../valves/rewrite/RandomizedTextRewriteMap.java   |   5 +-
 .../rewrite/TestRandomizedTextRewriteMap.java      | 126 +++++++++++++++++++++
 2 files changed, 130 insertions(+), 1 deletion(-)

diff --git 
a/java/org/apache/catalina/valves/rewrite/RandomizedTextRewriteMap.java 
b/java/org/apache/catalina/valves/rewrite/RandomizedTextRewriteMap.java
index b90181625d..d809a77f9a 100644
--- a/java/org/apache/catalina/valves/rewrite/RandomizedTextRewriteMap.java
+++ b/java/org/apache/catalina/valves/rewrite/RandomizedTextRewriteMap.java
@@ -52,6 +52,7 @@ public class RandomizedTextRewriteMap implements RewriteMap {
         try (Resource txtResource = 
ConfigFileLoader.getSource().getResource(txtFilePath);
                 BufferedReader reader = new BufferedReader(new 
InputStreamReader(txtResource.getInputStream()))) {
             while ((line = reader.readLine()) != null) {
+                line = line.trim();
                 if (line.startsWith("#") || line.isEmpty()) {
                     // Ignore comment line or empty lines
                     continue;
@@ -59,7 +60,9 @@ public class RandomizedTextRewriteMap implements RewriteMap {
                     // Ignore comment characters after '#'
                     line = line.substring(0, line.indexOf('#')).trim();
                 }
-                String[] keyValuePair = line.split(" ", 2);
+                // Like mod_rewrite, any whitespace separates the key from the
+                // value. The value may itself contain whitespace.
+                String[] keyValuePair = line.split("\\s+", 2);
                 if (keyValuePair.length > 1) {
                     String key = keyValuePair[0];
                     String value = keyValuePair[1];
diff --git 
a/test/org/apache/catalina/valves/rewrite/TestRandomizedTextRewriteMap.java 
b/test/org/apache/catalina/valves/rewrite/TestRandomizedTextRewriteMap.java
new file mode 100644
index 0000000000..68aa20b1e1
--- /dev/null
+++ b/test/org/apache/catalina/valves/rewrite/TestRandomizedTextRewriteMap.java
@@ -0,0 +1,126 @@
+/*
+ * 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.rewrite;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestRandomizedTextRewriteMap {
+
+    private String createMapFile(String content) throws IOException {
+        File file = File.createTempFile("rewrite-map", ".txt");
+        file.deleteOnExit();
+        Files.writeString(file.toPath(), content, StandardCharsets.UTF_8);
+        return file.getAbsolutePath();
+    }
+
+    @Test
+    public void testSingleSpaceSeparation() throws Exception {
+        RandomizedTextRewriteMap map =
+                new RandomizedTextRewriteMap(createMapFile("key value\n"), 
false);
+        Assert.assertEquals("value", map.lookup("key"));
+    }
+
+    @Test
+    public void testTabSeparation() throws Exception {
+        // mod_rewrite separates key and value by any whitespace
+        RandomizedTextRewriteMap map =
+                new RandomizedTextRewriteMap(createMapFile("key\tvalue\n"), 
false);
+        Assert.assertEquals("value", map.lookup("key"));
+    }
+
+    @Test
+    public void testMultipleWhitespaceSeparation() throws Exception {
+        // A single space was required before, which silently added the extra
+        // whitespace to the start of the value
+        RandomizedTextRewriteMap map =
+                new RandomizedTextRewriteMap(createMapFile("key  \tvalue\n"), 
false);
+        Assert.assertEquals("value", map.lookup("key"));
+    }
+
+    @Test
+    public void testWhitespaceAroundEntry() throws Exception {
+        RandomizedTextRewriteMap map = new RandomizedTextRewriteMap(
+                createMapFile("  key value  \n"), false);
+        Assert.assertEquals("value", map.lookup("key"));
+    }
+
+    @Test
+    public void testIndentedCommentLine() throws Exception {
+        // An indentation before the comment marker used to make the line
+        // look like an entry without a value and fail the map start up
+        RandomizedTextRewriteMap map = new RandomizedTextRewriteMap(
+                createMapFile("  # comment\nkey value\n"), false);
+        Assert.assertEquals("value", map.lookup("key"));
+    }
+
+    @Test
+    public void testCommentAfterValue() throws Exception {
+        RandomizedTextRewriteMap map = new RandomizedTextRewriteMap(
+                createMapFile("key value # comment\n"), false);
+        Assert.assertEquals("value", map.lookup("key"));
+    }
+
+    @Test
+    public void testValueMayContainWhitespace() throws Exception {
+        RandomizedTextRewriteMap map = new RandomizedTextRewriteMap(
+                createMapFile("key one two\n"), false);
+        Assert.assertEquals("one two", map.lookup("key"));
+    }
+
+    @Test
+    public void testRandomValues() throws Exception {
+        RandomizedTextRewriteMap map = new RandomizedTextRewriteMap(
+                createMapFile("key one|two\n"), true);
+        Set<String> possible = new HashSet<>();
+        Set<String> seen = new HashSet<>();
+        possible.add("one");
+        possible.add("two");
+        for (int i = 0; i < 100; i++) {
+            String value = map.lookup("key");
+            Assert.assertTrue(possible.contains(value));
+            seen.add(value);
+        }
+        // Both values must show up. The probability of one of the two values
+        // never being picked in 100 draws is below 10^-30.
+        Assert.assertEquals(possible, seen);
+    }
+
+    @Test
+    public void testUnknownKeyResolvesToNull() throws Exception {
+        RandomizedTextRewriteMap map =
+                new RandomizedTextRewriteMap(createMapFile("key value\n"), 
false);
+        Assert.assertNull(map.lookup("other"));
+    }
+
+    @Test
+    public void testLineWithoutValueThrows() throws Exception {
+        try {
+            new RandomizedTextRewriteMap(createMapFile("key\n"), false);
+            Assert.fail("IAE expected.");
+        } catch (IllegalArgumentException e) {
+            // expected, an entry without a value is invalid
+        }
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to