Skip to content

Commit dc4cda1

Browse files
committed
WARN against invalid patterns with PathPatternParser
As of gh-24952, `PathPatternParser` will strictly reject patterns with `"**"` in the middle of them. `"**"` is only allowed at the end of the pattern for matching multiple path segments until the end of the path. Currently, if `"**"` is used in the middle of a pattern it will be considered as a single `"*"` instead. Rejecting such cases should clarify the situation. This commit prepares for that upcoming change and: * logs a warning message if such a case is used by an application * expands the MVC and WebFlux documentation about URI matching in general Closes gh-24958
1 parent 5eba1ae commit dc4cda1

File tree

5 files changed

+94
-14
lines changed

5 files changed

+94
-14
lines changed

spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
* and captures it as a variable named "spring"</li>
4848
* </ul>
4949
*
50+
* Notable behavior difference with {@code AntPathMatcher}:<br>
51+
* {@code **} and its capturing variant <code>{*spring}</code> cannot be used in the middle of a pattern
52+
* string, only at the end: {@code /pages/{**}} is valid, but {@code /pages/{**}/details} is not.
53+
*
5054
* <h3>Examples</h3>
5155
* <ul>
5256
* <li>{@code /pages/t?st.html} &mdash; matches {@code /pages/test.html} as well as

spring-web/src/main/java/org/springframework/web/util/pattern/PathPatternParser.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.web.util.pattern;
1818

19+
import org.apache.commons.logging.Log;
20+
import org.apache.commons.logging.LogFactory;
21+
1922
import org.springframework.http.server.PathContainer;
2023

2124
/**
@@ -34,6 +37,8 @@
3437
*/
3538
public class PathPatternParser {
3639

40+
private static final Log logger = LogFactory.getLog(PathPatternParser.class);
41+
3742
private boolean matchOptionalTrailingSeparator = true;
3843

3944
private boolean caseSensitive = true;
@@ -104,11 +109,16 @@ public PathContainer.Options getPathOptions() {
104109
* stage. Produces a PathPattern object that can be used for fast matching
105110
* against paths. Each invocation of this method delegates to a new instance of
106111
* the {@link InternalPathPatternParser} because that class is not thread-safe.
107-
* @param pathPattern the input path pattern, e.g. /foo/{bar}
112+
* @param pathPattern the input path pattern, e.g. /project/{name}
108113
* @return a PathPattern for quickly matching paths against request paths
109114
* @throws PatternParseException in case of parse errors
110115
*/
111116
public PathPattern parse(String pathPattern) throws PatternParseException {
117+
int wildcardIndex = pathPattern.indexOf("**" + this.pathOptions.separator());
118+
if (wildcardIndex != -1 && wildcardIndex != pathPattern.length() - 3) {
119+
logger.warn("'**' patterns are not supported in the middle of patterns and will be rejected in the future. " +
120+
"Consider using '*' instead for matching a single path segment.");
121+
}
112122
return new InternalPathPatternParser(this).parse(pathPattern);
113123
}
114124

spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternParserTests.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -414,6 +414,14 @@ public void compareTests() {
414414
assertThat(patterns.get(1)).isEqualTo(p2);
415415
}
416416

417+
@Test // Should be updated with gh-24952
418+
public void doubleWildcardWithinPatternNotSupported() {
419+
PathPatternParser parser = new PathPatternParser();
420+
PathPattern pattern = parser.parse("/resources/**/details");
421+
assertThat(pattern.matches(PathContainer.parsePath("/resources/test/details"))).isTrue();
422+
assertThat(pattern.matches(PathContainer.parsePath("/resources/projects/spring/details"))).isFalse();
423+
}
424+
417425
@Test
418426
public void separatorTests() {
419427
PathPatternParser parser = new PathPatternParser();

src/docs/asciidoc/web/webflux.adoc

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1402,12 +1402,44 @@ The following example uses type and method level mappings:
14021402

14031403
You can map requests by using glob patterns and wildcards:
14041404

1405-
* `?` matches one character
1406-
* `*` matches zero or more characters within a path segment
1407-
* `**` match zero or more path segments
1405+
[cols="2,3,5"]
1406+
|===
1407+
|Pattern |Description |Example
14081408

1409-
You can also declare URI variables and access their values with `@PathVariable`,
1410-
as the following example shows:
1409+
| `+?+`
1410+
| Matches one character
1411+
| `+"/pages/t?st.html"+`
1412+
1413+
matches `+"/pages/test.html"+`
1414+
and `+"/pages/t3st.html"+`
1415+
1416+
| `+*+`
1417+
| Matches zero or more characters within a path segment
1418+
| `+"/resources/*.png"+` matches `+"/resources/file.png"+`
1419+
1420+
`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+`
1421+
1422+
| `+**+`
1423+
| Matches zero or more path segments until the end of the path
1424+
| `+"/resources/**"+` matches `+"/resources/file.png"+` and `+"/resources/images/file.png"+`
1425+
1426+
`+"/resources/**/file.png"+` is invalid as `+**+` is only allowed at the end of the path.
1427+
1428+
| `+{name}+`
1429+
| Matches a path segment and captures it as a variable named "name"
1430+
| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+`
1431+
1432+
| `+{name:[a-z]+}+`
1433+
| Matches the regexp `+"[a-z]+"+` as a path variable named "name"
1434+
| `+"/projects/{project:[a-z]+}/versions"+` matches `+"/projects/spring/versions"+` but not `+"/projects/spring1/versions"+`
1435+
1436+
| `+{*path}+`
1437+
| Matches zero or more path segments until the end of the path and captures it as a variable named "path"
1438+
| `+"/resources/{*file}"+` matches `+"/resources/images/file.png"+` and captures `+file=resources/file.png+`
1439+
1440+
|===
1441+
1442+
Captured URI variables can be accessed with `@PathVariable`, as the following example shows:
14111443

14121444
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
14131445
.Java

src/docs/asciidoc/web/webmvc.adoc

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,14 +1535,40 @@ The following example has type and method level mappings:
15351535
==== URI patterns
15361536
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-uri-templates, WebFlux>>#
15371537

1538-
You can map requests by using the following global patterns and wildcards:
1538+
You can map requests by using glob patterns and wildcards:
15391539

1540-
* `?` matches one character
1541-
* `*` matches zero or more characters within a path segment
1542-
* `**` match zero or more path segments
1540+
[cols="2,3,5"]
1541+
|===
1542+
|Pattern |Description |Example
15431543

1544-
You can also declare URI variables and access their values with `@PathVariable`,
1545-
as the following example shows:
1544+
| `+?+`
1545+
| Matches one character
1546+
| `+"/pages/t?st.html"+`
1547+
1548+
matches `+"/pages/test.html"+`
1549+
and `+"/pages/t3st.html"+`
1550+
1551+
| `+*+`
1552+
| Matches zero or more characters within a path segment
1553+
| `+"/resources/*.png"+` matches `+"/resources/file.png"+`
1554+
1555+
`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+`
1556+
1557+
| `+**+`
1558+
| Matches zero or more path segments until the end of the path
1559+
| `+"/resources/**"+` matches `+"/resources/file.png"+` and `+"/resources/images/file.png"+`
1560+
1561+
| `+{name}+`
1562+
| Matches a path segment and captures it as a variable named "name"
1563+
| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+`
1564+
1565+
| `+{name:[a-z]+}+`
1566+
| Matches the regexp `+"[a-z]+"+` as a path variable named "name"
1567+
| `+"/projects/{project:[a-z]+}/versions"+` matches `+"/projects/spring/versions"+` but not `+"/projects/spring1/versions"+`
1568+
1569+
|===
1570+
1571+
Captured URI variables can be accessed with `@PathVariable`, as the following example shows:
15461572

15471573
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
15481574
.Java

0 commit comments

Comments
 (0)