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

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


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

commit 7972903b11613f9c6f09f1cc527215cf2eb6378a
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 3970ee484be..1d85bbffbfb 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
@@ -44,6 +44,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 = "/;";
@@ -65,9 +68,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);
@@ -90,6 +94,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);
                 }
@@ -107,6 +113,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 fb8c2e5b839..5ea31e78996 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
@@ -800,4 +800,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