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

coheigea pushed a commit to branch 4.1.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/4.1.x-fixes by this push:
     new 731dd55b45f Avoid polynomial backtracking for adjacent JAX-RS template 
variables (#3705)
731dd55b45f is described below

commit 731dd55b45f5ad90ae3aa3b396cb6d9a112441a4
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Wed Nov 11 14:06:30 2026 +0000

    Avoid polynomial backtracking for adjacent JAX-RS template variables (#3705)
    
    (cherry picked from commit 024afda3e96a690acebfbeabf1716115ddd99063)
---
 .../org/apache/cxf/jaxrs/model/URITemplate.java    | 20 ++++++++--
 .../apache/cxf/jaxrs/model/URITemplateTest.java    | 43 +++++++++++++++++++++-
 2 files changed, 59 insertions(+), 4 deletions(-)

diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/model/URITemplate.java 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/model/URITemplate.java
index 285f012648a..47e941b9e24 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/model/URITemplate.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/model/URITemplate.java
@@ -43,6 +43,9 @@ public final class URITemplate {
     public static final String LIMITED_REGEX_SUFFIX = "(/.*)?";
     public static final String FINAL_MATCH_GROUP = "FINAL_MATCH_GROUP";
     private static final String DEFAULT_PATH_VARIABLE_REGEX = "([^/]+?)";
+    // For an unconstrained variable directly followed by another, e.g. {a} in 
{a}{b}: the lazy
+    // default only ever binds one character there, and matching exactly one 
avoids backtracking.
+    private static final String ADJACENT_PATH_VARIABLE_REGEX = "([^/])";
     private static final String CHARACTERS_TO_ESCAPE = ".*+$()";
     private static final String SLASH = "/";
     private static final String SLASH_QUOTE = "/;";
@@ -64,9 +67,10 @@ public final class URITemplate {
         CurlyBraceTokenizer tok = new CurlyBraceTokenizer(template);
         uriChunks = new ArrayList<>();
         while (tok.hasNext()) {
-            String templatePart = tok.next();
-            UriChunk chunk = UriChunk.createUriChunk(templatePart);
-            uriChunks.add(chunk);
+            uriChunks.add(UriChunk.createUriChunk(tok.next()));
+        }
+        for (int c = 0; c < uriChunks.size(); c++) {
+            UriChunk chunk = uriChunks.get(c);
             if (chunk instanceof Literal) {
                 String encodedValue = 
HttpUtils.encodePartiallyEncoded(chunk.getValue(), false);
                 String substr = escapeCharacters(encodedValue);
@@ -89,6 +93,8 @@ public final class URITemplate {
                         patternBuilder.append(pattern);
                         patternBuilder.append(')');
                     }
+                } else if (isUnconstrainedVariable(c + 1)) {
+                    patternBuilder.append(ADJACENT_PATH_VARIABLE_REGEX);
                 } else {
                     patternBuilder.append(DEFAULT_PATH_VARIABLE_REGEX);
                 }
@@ -106,6 +112,14 @@ public final class URITemplate {
         templateRegexPattern = Pattern.compile(patternBuilder.toString());
     }
 
+    private boolean isUnconstrainedVariable(int index) {
+        if (index >= uriChunks.size()) {
+            return false;
+        }
+        UriChunk chunk = uriChunks.get(index);
+        return chunk instanceof Variable && ((Variable)chunk).getPattern() == 
null;
+    }
+
     public String getLiteralChars() {
         return literals;
     }
diff --git 
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/model/URITemplateTest.java
 
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/model/URITemplateTest.java
index 7e7f968df16..086744f6e43 100644
--- 
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/model/URITemplateTest.java
+++ 
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/model/URITemplateTest.java
@@ -799,4 +799,45 @@ public class URITemplateTest {
         assertTrue("should match '/cxf' url", t2.match("/cxf", values));
         Assert.assertEquals("cxf", values.get("project").get(0));
     }
-}
\ No newline at end of file
+
+    @Test
+    public void testAdjacentVariables() {
+        URITemplate t = new URITemplate("/{a}{b}{c}.json");
+        MultivaluedMap<String, String> values = new MetadataMap<>();
+        assertTrue(t.match("/xyz123.json", values));
+        assertEquals("x", values.getFirst("a"));
+        assertEquals("y", values.getFirst("b"));
+        assertEquals("z123", values.getFirst("c"));
+        assertEquals("/", values.getFirst(URITemplate.FINAL_MATCH_GROUP));
+
+        values.clear();
+        assertFalse(t.match("/xy.json", values));
+
+        values.clear();
+        t = new URITemplate("/{a}{b}/rest");
+        assertTrue(t.match("/abc/rest/more", values));
+        assertEquals("a", values.getFirst("a"));
+        assertEquals("bc", values.getFirst("b"));
+        assertEquals("/more", values.getFirst(URITemplate.FINAL_MATCH_GROUP));
+    }
+
+    @Test
+    public void testAdjacentVariablesWithCustomRegex() {
+        // Only an unconstrained variable followed by an unconstrained 
variable is limited to one char
+        URITemplate t = new URITemplate("/{a}{b:\\d+}");
+        MultivaluedMap<String, String> values = new MetadataMap<>();
+        assertTrue(t.match("/abc123", values));
+        assertEquals("abc", values.getFirst("a"));
+        assertEquals("123", values.getFirst("b"));
+    }
+
+    @Test(timeout = 5000)
+    public void testAdjacentVariablesNoBacktracking() {
+        URITemplate t = new URITemplate("/{a}{b}{c}.json");
+        StringBuilder sb = new StringBuilder("/");
+        for (int i = 0; i < 4096; i++) {
+            sb.append('a');
+        }
+        assertFalse(t.match(sb.toString(), new MetadataMap<>()));
+    }
+}

Reply via email to