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<>()));
+ }
+}