From 285182be270235934617c9816f5e9ac6bd89aa2b Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Wed, 5 Nov 2025 18:32:44 +0100 Subject: [PATCH] Relax multiple segment matching constraints in PathPattern Prior to this commit, gh-35213 allowed wildcard path elments at the start of path patterns. This came with an additional constraint that rejected such patterns if the pattern segment following the wildcard one was not a literal: * `/**/{name}` was rejected * `/**/something/{name}` was accepted The motivation here was to make the performance impact of wildard patterns as small as possible at runtime. This commit relaxes this constraint because `/**/*.js` patterns are very popular in the security space for request matchers. Closes gh-35686 --- .../ROOT/partials/web/uri-patterns.adoc | 6 ------ .../pattern/InternalPathPatternParser.java | 19 ------------------- .../util/pattern/PatternParseException.java | 1 - .../util/pattern/PathPatternParserTests.java | 1 - .../web/util/pattern/PathPatternTests.java | 10 ++++++++++ 5 files changed, 10 insertions(+), 27 deletions(-) diff --git a/framework-docs/modules/ROOT/partials/web/uri-patterns.adoc b/framework-docs/modules/ROOT/partials/web/uri-patterns.adoc index f0ff6177a8d..494c50a6c0a 100644 --- a/framework-docs/modules/ROOT/partials/web/uri-patterns.adoc +++ b/framework-docs/modules/ROOT/partials/web/uri-patterns.adoc @@ -26,9 +26,6 @@ `+"/resources/**/file.png"+` is invalid as `+**+` is not allowed in the middle of the path. -`+"/**/{name}/resources"+` is invalid as only a literal pattern is allowed right after `+**+`. -`+"/**/project/{project}/resources"+` is allowed. - `+"/**/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern. | `+{name}+` @@ -49,9 +46,6 @@ `+"/resources/{*path}/file.png"+` is invalid as `{*path}` is not allowed in the middle of the path. -`+"/{*path}/{name}/resources"+` is invalid as only a literal pattern is allowed right after `{*path}`. -`+"/{*path}/project/{project}/resources"+` is allowed. - `+"/{*path}/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern. |=== \ No newline at end of file diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/InternalPathPatternParser.java b/spring-web/src/main/java/org/springframework/web/util/pattern/InternalPathPatternParser.java index 2a17dab8abf..c6d32f032b8 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/InternalPathPatternParser.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/InternalPathPatternParser.java @@ -185,7 +185,6 @@ class InternalPathPatternParser { if (this.pathElementStart != -1) { pushPathElement(createPathElement()); } - verifyPatternElements(this.headPE); return new PathPattern(pathPattern, this.parser, this.headPE); } @@ -441,22 +440,4 @@ class InternalPathPatternParser { this.capturedVariableNames.add(variableName); } - private void verifyPatternElements(@Nullable PathElement headPE) { - PathElement currentElement = headPE; - while (currentElement != null) { - if (currentElement instanceof CaptureSegmentsPathElement || - currentElement instanceof WildcardSegmentsPathElement) { - PathElement nextElement = currentElement.next; - while (nextElement instanceof SeparatorPathElement) { - nextElement = nextElement.next; - } - if (nextElement != null && !(nextElement instanceof LiteralPathElement)) { - throw new PatternParseException(nextElement.pos, this.pathPatternData, - PatternMessage.MULTISEGMENT_PATHELEMENT_NOT_FOLLOWED_BY_LITERAL); - } - } - currentElement = currentElement.next; - } - } - } diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/PatternParseException.java b/spring-web/src/main/java/org/springframework/web/util/pattern/PatternParseException.java index d9dee2780d4..4fd4833a0a9 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/PatternParseException.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/PatternParseException.java @@ -101,7 +101,6 @@ public class PatternParseException extends IllegalArgumentException { ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR("Char ''{0}'' is not allowed in a captured variable name"), CANNOT_HAVE_MANY_MULTISEGMENT_PATHELEMENTS("Multiple '{*...}' or '**' pattern elements are not allowed"), INVALID_LOCATION_FOR_MULTISEGMENT_PATHELEMENT("'{*...}' or '**' pattern elements should be placed at the start or end of the pattern"), - MULTISEGMENT_PATHELEMENT_NOT_FOLLOWED_BY_LITERAL("'{*...}' or '**' pattern elements should be followed by a literal path element"), BADLY_FORMED_CAPTURE_THE_REST("Expected form when capturing the rest of the path is simply '{*...}'"), MISSING_REGEX_CONSTRAINT("Missing regex constraint on capture"), ILLEGAL_DOUBLE_CAPTURE("Not allowed to capture ''{0}'' twice in the same pattern"), diff --git a/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternParserTests.java b/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternParserTests.java index e70fe5e8328..1a5f8fd27bc 100644 --- a/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternParserTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternParserTests.java @@ -232,7 +232,6 @@ class PathPatternParserTests { checkError("/{abc}{*foobar}", 1, PatternMessage.CAPTURE_ALL_IS_STANDALONE_CONSTRUCT); checkError("/{abc}{*foobar}{foo}", 1, PatternMessage.CAPTURE_ALL_IS_STANDALONE_CONSTRUCT); checkError("/{*foo}/foo/{*bar}", 18, PatternMessage.CANNOT_HAVE_MANY_MULTISEGMENT_PATHELEMENTS); - checkError("/{*foo}/{bar}", 8, PatternMessage.MULTISEGMENT_PATHELEMENT_NOT_FOLLOWED_BY_LITERAL); checkError("{foo:}", 5, PatternMessage.MISSING_REGEX_CONSTRAINT); checkError("{foo}_{foo}", 0, PatternMessage.ILLEGAL_DOUBLE_CAPTURE, "foo"); checkError("/{bar}/{bar}", 7, PatternMessage.ILLEGAL_DOUBLE_CAPTURE, "bar"); diff --git a/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java b/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java index f0f215daaa9..a192d0799e0 100644 --- a/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java @@ -185,6 +185,16 @@ class PathPatternTests { checkMatches("/resource/**", "/resource/foobar"); } + @Test + void wildcardSegmentsThenNonLiteral() { + checkMatches("/**/*.js", "/script.js"); + checkMatches("/**/*.js", "/js/script.js"); + checkMatches("/**/*.js", "/files/js/script.js"); + checkMatches("/**/{type}/*.js", "/files/js/script.js"); + + checkNoMatch("/**/*.js", "/files/style.css"); + } + @Test void antPathMatcherTests() { // test exact matching