mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Support wildcard path elements at the start of path patterns
Prior to this commit, the `PathPattern` and `PathPatternParser` would
allow multiple-segments matching and capturing with the following:
* "/files/**" (matching 0-N segments until the end)
* "/files/{*path}" (matching 0-N segments until the end and capturing
the value as the "path" variable)
This would be only allowed as the last path element in the pattern and
the parser would reject other combinations.
This commit expands the support and allows multiple segments matching at
the beginning of the path:
* "/**/index.html" (matching 0-N segments from the start)
* "/{*path}/index.html" (matching 0-N segments until the end and capturing
the value as the "path" variable)
This does come with additional restrictions:
1. "/files/**/file.txt" and "/files/{*path}/file.txt" are invalid,
as multiple segment matching is not allowed in the middle of the
pattern.
2. "/{*path}/files/**" is not allowed, as a single "{*path}" or "/**"
element is allowed in a pattern
3. "/{*path}/{folder}/file.txt" "/**/{folder:[a-z]+}/file.txt" are
invalid because only a literal pattern is allowed right after
multiple segments path elements.
Closes gh-35213
This commit is contained in:
+301
-279
@@ -20,6 +20,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.server.PathContainer;
|
||||
@@ -36,66 +37,303 @@ import static org.assertj.core.api.Assertions.fail;
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Sam Brannen
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class PathPatternParserTests {
|
||||
|
||||
private PathPattern pathPattern;
|
||||
|
||||
@Test
|
||||
void basicPatterns() {
|
||||
checkStructure("/");
|
||||
checkStructure("/foo");
|
||||
checkStructure("foo");
|
||||
checkStructure("foo/");
|
||||
checkStructure("/foo/");
|
||||
checkStructure("");
|
||||
/**
|
||||
* Verify that the parsed pattern matches
|
||||
* the text and path elements of the original pattern.
|
||||
*/
|
||||
@Nested
|
||||
class StructureTests {
|
||||
|
||||
@Test
|
||||
void literalPatterns() {
|
||||
checkStructure("/");
|
||||
checkStructure("/foo");
|
||||
checkStructure("foo");
|
||||
checkStructure("foo/");
|
||||
checkStructure("/foo/");
|
||||
checkStructure("");
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleCharWildcardPatterns() {
|
||||
pathPattern = checkStructure("?");
|
||||
assertPathElements(pathPattern, SingleCharWildcardedPathElement.class);
|
||||
checkStructure("/?/");
|
||||
checkStructure("/?abc?/");
|
||||
}
|
||||
|
||||
@Test
|
||||
void wildcardSegmentsStartOfPathPatterns() {
|
||||
pathPattern = checkStructure("/**/foo");
|
||||
assertPathElements(pathPattern, WildcardSegmentsPathElement.class, SeparatorPathElement.class, LiteralPathElement.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void wildcardSegmentEndOfPathPatterns() {
|
||||
pathPattern = checkStructure("/**");
|
||||
assertPathElements(pathPattern, WildcardSegmentsPathElement.class);
|
||||
pathPattern = checkStructure("/foo/**");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, LiteralPathElement.class, WildcardSegmentsPathElement.class);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void regexpSegmentIsNotWildcardSegment() {
|
||||
// this is not double wildcard, it's / then **acb (an odd, unnecessary use of double *)
|
||||
pathPattern = checkStructure("/**acb");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, RegexPathElement.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialCapturingPatterns() {
|
||||
pathPattern = checkStructure("{foo}abc");
|
||||
assertPathElements(pathPattern, RegexPathElement.class);
|
||||
checkStructure("abc{foo}");
|
||||
checkStructure("/abc{foo}");
|
||||
checkStructure("{foo}def/");
|
||||
checkStructure("/abc{foo}def/");
|
||||
checkStructure("{foo}abc{bar}");
|
||||
checkStructure("{foo}abc{bar}/");
|
||||
checkStructure("/{foo}abc{bar}/");
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeCapturingPatterns() {
|
||||
pathPattern = checkStructure("{foo}");
|
||||
assertPathElements(pathPattern, CaptureVariablePathElement.class);
|
||||
checkStructure("/{foo}");
|
||||
checkStructure("/{f}/");
|
||||
checkStructure("/{foo}/{bar}/{wibble}");
|
||||
checkStructure("/{mobile-number}"); // gh-23101
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeCaptureWithConstraints() {
|
||||
pathPattern = checkStructure("{foo:...}");
|
||||
assertPathElements(pathPattern, CaptureVariablePathElement.class);
|
||||
pathPattern = checkStructure("{foo:[0-9]*}");
|
||||
assertPathElements(pathPattern, CaptureVariablePathElement.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void captureSegmentsStartOfPathPatterns() {
|
||||
pathPattern = checkStructure("/{*foobar}");
|
||||
assertPathElements(pathPattern, CaptureSegmentsPathElement.class);
|
||||
pathPattern = checkStructure("/{*foobar}/foo");
|
||||
assertPathElements(pathPattern, CaptureSegmentsPathElement.class, SeparatorPathElement.class, LiteralPathElement.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void captureSegmentsEndOfPathPatterns() {
|
||||
pathPattern = parse("{*foobar}");
|
||||
assertThat(pathPattern.computePatternString()).isEqualTo("/{*foobar}");
|
||||
assertPathElements(pathPattern, CaptureSegmentsPathElement.class);
|
||||
pathPattern = checkStructure("/{*foobar}");
|
||||
assertPathElements(pathPattern, CaptureSegmentsPathElement.class);
|
||||
pathPattern = checkStructure("/foo/{*foobar}");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, LiteralPathElement.class, CaptureSegmentsPathElement.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleSeparatorPatterns() {
|
||||
pathPattern = checkStructure("///aaa");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
SeparatorPathElement.class, LiteralPathElement.class);
|
||||
pathPattern = checkStructure("///aaa////aaa/b");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
SeparatorPathElement.class, LiteralPathElement.class, SeparatorPathElement.class,
|
||||
SeparatorPathElement.class, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
LiteralPathElement.class, SeparatorPathElement.class, LiteralPathElement.class);
|
||||
pathPattern = checkStructure("/////**");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
SeparatorPathElement.class, SeparatorPathElement.class, WildcardSegmentsPathElement.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void regexPathElementPatterns() {
|
||||
pathPattern = checkStructure("/{var:\\\\}");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, CaptureVariablePathElement.class);
|
||||
|
||||
pathPattern = checkStructure("/{var:\\/}");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, CaptureVariablePathElement.class);
|
||||
|
||||
pathPattern = checkStructure("/{var:a{1,2}}");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, CaptureVariablePathElement.class);
|
||||
|
||||
pathPattern = checkStructure("/{var:[^\\/]*}");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, CaptureVariablePathElement.class);
|
||||
|
||||
pathPattern = checkStructure("/{var:\\[*}");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, CaptureVariablePathElement.class);
|
||||
|
||||
pathPattern = checkStructure("/{var:[\\{]*}");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, CaptureVariablePathElement.class);
|
||||
|
||||
pathPattern = checkStructure("/{var:[\\}]*}");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, CaptureVariablePathElement.class);
|
||||
|
||||
pathPattern = checkStructure("*");
|
||||
assertPathElements(pathPattern, WildcardPathElement.class);
|
||||
checkStructure("/*");
|
||||
checkStructure("/*/");
|
||||
checkStructure("*/");
|
||||
checkStructure("/*/");
|
||||
pathPattern = checkStructure("/*a*/");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, RegexPathElement.class, SeparatorPathElement.class);
|
||||
pathPattern = checkStructure("*/");
|
||||
assertPathElements(pathPattern, WildcardPathElement.class, SeparatorPathElement.class);
|
||||
|
||||
pathPattern = checkStructure("{symbolicName:[\\p{L}\\.]+}-sources-{version:[\\p{N}\\.]+}.jar");
|
||||
assertPathElements(pathPattern, RegexPathElement.class);
|
||||
}
|
||||
|
||||
private PathPattern checkStructure(String pattern) {
|
||||
PathPatternParser patternParser = new PathPatternParser();
|
||||
PathPattern pp = patternParser.parse(pattern);
|
||||
assertThat(pp.computePatternString()).isEqualTo(pattern);
|
||||
return pp;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
final void assertPathElements(PathPattern p, Class<? extends PathElement>... sectionClasses) {
|
||||
PathElement head = p.getHeadSection();
|
||||
for (Class<? extends PathElement> sectionClass : sectionClasses) {
|
||||
if (head == null) {
|
||||
fail("Ran out of data in parsed pattern. Pattern is: " + p.toChainString());
|
||||
}
|
||||
assertThat(head.getClass().getSimpleName()).as("Not expected section type. Pattern is: " + p.toChainString()).isEqualTo(sectionClass.getSimpleName());
|
||||
head = head.next;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleCharWildcardPatterns() {
|
||||
pathPattern = checkStructure("?");
|
||||
assertPathElements(pathPattern, SingleCharWildcardedPathElement.class);
|
||||
checkStructure("/?/");
|
||||
checkStructure("/?abc?/");
|
||||
@Nested
|
||||
class ParsingErrorTests {
|
||||
|
||||
@Test
|
||||
void captureSegmentsIllegalSyntax() {
|
||||
checkError("/{*foobar}abc", 1, PatternMessage.CAPTURE_ALL_IS_STANDALONE_CONSTRUCT);
|
||||
checkError("/{*f%obar}", 4, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR);
|
||||
checkError("/{*foobar}abc", 1, PatternMessage.CAPTURE_ALL_IS_STANDALONE_CONSTRUCT);
|
||||
checkError("/{f*oobar}", 3, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR);
|
||||
checkError("/{*foobar:.*}/abc", 9, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR);
|
||||
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");
|
||||
checkError("/{bar}/{bar}_{foo}", 7, PatternMessage.ILLEGAL_DOUBLE_CAPTURE, "bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void regexpSegmentsIllegalSyntax() {
|
||||
checkError("/{var:[^/]*}", 8, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("/{var:abc", 8, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
// Do not check the expected position due a change in RegEx parsing in JDK 13.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/23669
|
||||
checkError("/{var:a{{1,2}}}", PatternMessage.REGEX_PATTERN_SYNTAX_EXCEPTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void illegalCapturePatterns() {
|
||||
checkError("{abc/", 4, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("{abc:}/", 5, PatternMessage.MISSING_REGEX_CONSTRAINT);
|
||||
checkError("{", 1, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("{abc", 4, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("{/}", 1, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("/{", 2, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("}", 0, PatternMessage.MISSING_OPEN_CAPTURE);
|
||||
checkError("/}", 1, PatternMessage.MISSING_OPEN_CAPTURE);
|
||||
checkError("def}", 3, PatternMessage.MISSING_OPEN_CAPTURE);
|
||||
checkError("/{/}", 2, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("/{{/}", 2, PatternMessage.ILLEGAL_NESTED_CAPTURE);
|
||||
checkError("/{abc{/}", 5, PatternMessage.ILLEGAL_NESTED_CAPTURE);
|
||||
checkError("/{0abc}/abc", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR);
|
||||
checkError("/{a?bc}/abc", 3, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR);
|
||||
checkError("/{abc}_{abc}", 1, PatternMessage.ILLEGAL_DOUBLE_CAPTURE);
|
||||
checkError("/foobar/{abc}_{abc}", 8, PatternMessage.ILLEGAL_DOUBLE_CAPTURE);
|
||||
checkError("/foobar/{abc:..}_{abc:..}", 8, PatternMessage.ILLEGAL_DOUBLE_CAPTURE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void captureGroupInRegexpNotAllowed() {
|
||||
PathPattern pp = parse("/{abc:foo(bar)}");
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
pp.matchAndExtract(PathContainer.parsePath("/foo")))
|
||||
.withMessage("No capture groups allowed in the constraint regex: foo(bar)");
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
pp.matchAndExtract(PathContainer.parsePath("/foobar")))
|
||||
.withMessage("No capture groups allowed in the constraint regex: foo(bar)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void badPatterns() {
|
||||
//checkError("/{foo}{bar}/",6,PatternMessage.CANNOT_HAVE_ADJACENT_CAPTURES);
|
||||
checkError("/{?}/", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, "?");
|
||||
checkError("/{a?b}/", 3, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR, "?");
|
||||
checkError("/{%%$}", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, "%");
|
||||
checkError("/{ }", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, " ");
|
||||
checkError("/{%:[0-9]*}", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, "%");
|
||||
}
|
||||
|
||||
@Test
|
||||
void captureTheRestWithinPatternNotSupported() {
|
||||
PathPatternParser parser = new PathPatternParser();
|
||||
assertThatThrownBy(() -> parser.parse("/resources/**/details"))
|
||||
.isInstanceOf(PatternParseException.class)
|
||||
.extracting("messageType").isEqualTo(PatternMessage.INVALID_LOCATION_FOR_MULTISEGMENT_PATHELEMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link #checkError(String, int, PatternMessage, String...)},
|
||||
* passing {@code -1} as the {@code expectedPos}.
|
||||
* @since 5.2
|
||||
*/
|
||||
private void checkError(String pattern, PatternMessage expectedMessage, String... expectedInserts) {
|
||||
checkError(pattern, -1, expectedMessage, expectedInserts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expectedPos the expected position, or {@code -1} if the position should not be checked
|
||||
*/
|
||||
private void checkError(String pattern, int expectedPos, PatternMessage expectedMessage,
|
||||
String... expectedInserts) {
|
||||
|
||||
assertThatExceptionOfType(PatternParseException.class)
|
||||
.isThrownBy(() -> pathPattern = parse(pattern))
|
||||
.satisfies(ex -> {
|
||||
if (expectedPos >= 0) {
|
||||
assertThat(ex.getPosition()).as(ex.toDetailedString()).isEqualTo(expectedPos);
|
||||
}
|
||||
assertThat(ex.getMessageType()).as(ex.toDetailedString()).isEqualTo(expectedMessage);
|
||||
if (expectedInserts.length != 0) {
|
||||
assertThat(ex.getInserts()).isEqualTo(expectedInserts);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiwildcardPattern() {
|
||||
pathPattern = checkStructure("/**");
|
||||
assertPathElements(pathPattern, WildcardTheRestPathElement.class);
|
||||
// this is not double wildcard, it's / then **acb (an odd, unnecessary use of double *)
|
||||
pathPattern = checkStructure("/**acb");
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, RegexPathElement.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringTests() {
|
||||
assertThat(checkStructure("/{*foobar}").toChainString()).isEqualTo("CaptureTheRest(/{*foobar})");
|
||||
assertThat(checkStructure("{foobar}").toChainString()).isEqualTo("CaptureVariable({foobar})");
|
||||
assertThat(checkStructure("abc").toChainString()).isEqualTo("Literal(abc)");
|
||||
assertThat(checkStructure("{a}_*_{b}").toChainString()).isEqualTo("Regex({a}_*_{b})");
|
||||
assertThat(checkStructure("/").toChainString()).isEqualTo("Separator(/)");
|
||||
assertThat(checkStructure("?a?b?c").toChainString()).isEqualTo("SingleCharWildcarded(?a?b?c)");
|
||||
assertThat(checkStructure("*").toChainString()).isEqualTo("Wildcard(*)");
|
||||
assertThat(checkStructure("/**").toChainString()).isEqualTo("WildcardTheRest(/**)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void captureTheRestPatterns() {
|
||||
pathPattern = parse("{*foobar}");
|
||||
assertThat(pathPattern.computePatternString()).isEqualTo("/{*foobar}");
|
||||
assertPathElements(pathPattern, CaptureTheRestPathElement.class);
|
||||
pathPattern = checkStructure("/{*foobar}");
|
||||
assertPathElements(pathPattern, CaptureTheRestPathElement.class);
|
||||
checkError("/{*foobar}/", 10, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST);
|
||||
checkError("/{*foobar}abc", 10, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST);
|
||||
checkError("/{*f%obar}", 4, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR);
|
||||
checkError("/{*foobar}abc", 10, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST);
|
||||
checkError("/{f*oobar}", 3, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR);
|
||||
checkError("/{*foobar}/abc", 10, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST);
|
||||
checkError("/{*foobar:.*}/abc", 9, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR);
|
||||
checkError("/{abc}{*foobar}", 1, PatternMessage.CAPTURE_ALL_IS_STANDALONE_CONSTRUCT);
|
||||
checkError("/{abc}{*foobar}{foo}", 15, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST);
|
||||
assertThat(parse("/{*foobar}").toChainString()).isEqualTo("CaptureSegments(/{*foobar})");
|
||||
assertThat(parse("{foobar}").toChainString()).isEqualTo("CaptureVariable({foobar})");
|
||||
assertThat(parse("abc").toChainString()).isEqualTo("Literal(abc)");
|
||||
assertThat(parse("{a}_*_{b}").toChainString()).isEqualTo("Regex({a}_*_{b})");
|
||||
assertThat(parse("/").toChainString()).isEqualTo("Separator(/)");
|
||||
assertThat(parse("?a?b?c").toChainString()).isEqualTo("SingleCharWildcarded(?a?b?c)");
|
||||
assertThat(parse("*").toChainString()).isEqualTo("Wildcard(*)");
|
||||
assertThat(parse("/**").toChainString()).isEqualTo("WildcardSegments(/**)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -116,82 +354,6 @@ class PathPatternParserTests {
|
||||
assertThat(pp2.hashCode()).isNotEqualTo(pp1.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void regexPathElementPatterns() {
|
||||
checkError("/{var:[^/]*}", 8, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("/{var:abc", 8, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
|
||||
// Do not check the expected position due a change in RegEx parsing in JDK 13.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/23669
|
||||
checkError("/{var:a{{1,2}}}", PatternMessage.REGEX_PATTERN_SYNTAX_EXCEPTION);
|
||||
|
||||
pathPattern = checkStructure("/{var:\\\\}");
|
||||
PathElement next = pathPattern.getHeadSection().next;
|
||||
assertThat(next.getClass().getName()).isEqualTo(CaptureVariablePathElement.class.getName());
|
||||
assertMatches(pathPattern, "/\\");
|
||||
|
||||
pathPattern = checkStructure("/{var:\\/}");
|
||||
next = pathPattern.getHeadSection().next;
|
||||
assertThat(next.getClass().getName()).isEqualTo(CaptureVariablePathElement.class.getName());
|
||||
assertNoMatch(pathPattern, "/aaa");
|
||||
|
||||
pathPattern = checkStructure("/{var:a{1,2}}");
|
||||
next = pathPattern.getHeadSection().next;
|
||||
assertThat(next.getClass().getName()).isEqualTo(CaptureVariablePathElement.class.getName());
|
||||
|
||||
pathPattern = checkStructure("/{var:[^\\/]*}");
|
||||
next = pathPattern.getHeadSection().next;
|
||||
assertThat(next.getClass().getName()).isEqualTo(CaptureVariablePathElement.class.getName());
|
||||
PathPattern.PathMatchInfo result = matchAndExtract(pathPattern, "/foo");
|
||||
assertThat(result.getUriVariables().get("var")).isEqualTo("foo");
|
||||
|
||||
pathPattern = checkStructure("/{var:\\[*}");
|
||||
next = pathPattern.getHeadSection().next;
|
||||
assertThat(next.getClass().getName()).isEqualTo(CaptureVariablePathElement.class.getName());
|
||||
result = matchAndExtract(pathPattern, "/[[[");
|
||||
assertThat(result.getUriVariables().get("var")).isEqualTo("[[[");
|
||||
|
||||
pathPattern = checkStructure("/{var:[\\{]*}");
|
||||
next = pathPattern.getHeadSection().next;
|
||||
assertThat(next.getClass().getName()).isEqualTo(CaptureVariablePathElement.class.getName());
|
||||
result = matchAndExtract(pathPattern, "/{{{");
|
||||
assertThat(result.getUriVariables().get("var")).isEqualTo("{{{");
|
||||
|
||||
pathPattern = checkStructure("/{var:[\\}]*}");
|
||||
next = pathPattern.getHeadSection().next;
|
||||
assertThat(next.getClass().getName()).isEqualTo(CaptureVariablePathElement.class.getName());
|
||||
result = matchAndExtract(pathPattern, "/}}}");
|
||||
assertThat(result.getUriVariables().get("var")).isEqualTo("}}}");
|
||||
|
||||
pathPattern = checkStructure("*");
|
||||
assertThat(pathPattern.getHeadSection().getClass().getName()).isEqualTo(WildcardPathElement.class.getName());
|
||||
checkStructure("/*");
|
||||
checkStructure("/*/");
|
||||
checkStructure("*/");
|
||||
checkStructure("/*/");
|
||||
pathPattern = checkStructure("/*a*/");
|
||||
next = pathPattern.getHeadSection().next;
|
||||
assertThat(next.getClass().getName()).isEqualTo(RegexPathElement.class.getName());
|
||||
pathPattern = checkStructure("*/");
|
||||
assertThat(pathPattern.getHeadSection().getClass().getName()).isEqualTo(WildcardPathElement.class.getName());
|
||||
checkError("{foo}_{foo}", 0, PatternMessage.ILLEGAL_DOUBLE_CAPTURE, "foo");
|
||||
checkError("/{bar}/{bar}", 7, PatternMessage.ILLEGAL_DOUBLE_CAPTURE, "bar");
|
||||
checkError("/{bar}/{bar}_{foo}", 7, PatternMessage.ILLEGAL_DOUBLE_CAPTURE, "bar");
|
||||
|
||||
pathPattern = checkStructure("{symbolicName:[\\p{L}\\.]+}-sources-{version:[\\p{N}\\.]+}.jar");
|
||||
assertThat(pathPattern.getHeadSection().getClass().getName()).isEqualTo(RegexPathElement.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeCapturingPatterns() {
|
||||
pathPattern = checkStructure("{foo}");
|
||||
assertThat(pathPattern.getHeadSection().getClass().getName()).isEqualTo(CaptureVariablePathElement.class.getName());
|
||||
checkStructure("/{foo}");
|
||||
checkStructure("/{f}/");
|
||||
checkStructure("/{foo}/{bar}/{wibble}");
|
||||
checkStructure("/{mobile-number}"); // gh-23101
|
||||
}
|
||||
|
||||
@Test
|
||||
void noEncoding() {
|
||||
// Check no encoding of expressions or constraints
|
||||
@@ -205,66 +367,6 @@ class PathPatternParserTests {
|
||||
assertThat(pp.toChainString()).isEqualTo("Regex({foo:f o}_ _{bar:b\\|o})");
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeCaptureWithConstraints() {
|
||||
pathPattern = checkStructure("{foo:...}");
|
||||
assertPathElements(pathPattern, CaptureVariablePathElement.class);
|
||||
pathPattern = checkStructure("{foo:[0-9]*}");
|
||||
assertPathElements(pathPattern, CaptureVariablePathElement.class);
|
||||
checkError("{foo:}", 5, PatternMessage.MISSING_REGEX_CONSTRAINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialCapturingPatterns() {
|
||||
pathPattern = checkStructure("{foo}abc");
|
||||
assertThat(pathPattern.getHeadSection().getClass().getName()).isEqualTo(RegexPathElement.class.getName());
|
||||
checkStructure("abc{foo}");
|
||||
checkStructure("/abc{foo}");
|
||||
checkStructure("{foo}def/");
|
||||
checkStructure("/abc{foo}def/");
|
||||
checkStructure("{foo}abc{bar}");
|
||||
checkStructure("{foo}abc{bar}/");
|
||||
checkStructure("/{foo}abc{bar}/");
|
||||
}
|
||||
|
||||
@Test
|
||||
void illegalCapturePatterns() {
|
||||
checkError("{abc/", 4, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("{abc:}/", 5, PatternMessage.MISSING_REGEX_CONSTRAINT);
|
||||
checkError("{", 1, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("{abc", 4, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("{/}", 1, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("/{", 2, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("}", 0, PatternMessage.MISSING_OPEN_CAPTURE);
|
||||
checkError("/}", 1, PatternMessage.MISSING_OPEN_CAPTURE);
|
||||
checkError("def}", 3, PatternMessage.MISSING_OPEN_CAPTURE);
|
||||
checkError("/{/}", 2, PatternMessage.MISSING_CLOSE_CAPTURE);
|
||||
checkError("/{{/}", 2, PatternMessage.ILLEGAL_NESTED_CAPTURE);
|
||||
checkError("/{abc{/}", 5, PatternMessage.ILLEGAL_NESTED_CAPTURE);
|
||||
checkError("/{0abc}/abc", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR);
|
||||
checkError("/{a?bc}/abc", 3, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR);
|
||||
checkError("/{abc}_{abc}", 1, PatternMessage.ILLEGAL_DOUBLE_CAPTURE);
|
||||
checkError("/foobar/{abc}_{abc}", 8, PatternMessage.ILLEGAL_DOUBLE_CAPTURE);
|
||||
checkError("/foobar/{abc:..}_{abc:..}", 8, PatternMessage.ILLEGAL_DOUBLE_CAPTURE);
|
||||
PathPattern pp = parse("/{abc:foo(bar)}");
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
pp.matchAndExtract(toPSC("/foo")))
|
||||
.withMessage("No capture groups allowed in the constraint regex: foo(bar)");
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
pp.matchAndExtract(toPSC("/foobar")))
|
||||
.withMessage("No capture groups allowed in the constraint regex: foo(bar)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void badPatterns() {
|
||||
// checkError("/{foo}{bar}/",6,PatternMessage.CANNOT_HAVE_ADJACENT_CAPTURES);
|
||||
checkError("/{?}/", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, "?");
|
||||
checkError("/{a?b}/", 3, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR, "?");
|
||||
checkError("/{%%$}", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, "%");
|
||||
checkError("/{ }", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, " ");
|
||||
checkError("/{%:[0-9]*}", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, "%");
|
||||
}
|
||||
|
||||
@Test
|
||||
void patternPropertyGetCaptureCountTests() {
|
||||
// Test all basic section types
|
||||
@@ -311,30 +413,23 @@ class PathPatternParserTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleSeparatorPatterns() {
|
||||
pathPattern = checkStructure("///aaa");
|
||||
void normalizedLengthWhenMultipleSeparator() {
|
||||
pathPattern = parse("///aaa");
|
||||
assertThat(pathPattern.getNormalizedLength()).isEqualTo(6);
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
SeparatorPathElement.class, LiteralPathElement.class);
|
||||
pathPattern = checkStructure("///aaa////aaa/b");
|
||||
pathPattern = parse("///aaa////aaa/b");
|
||||
assertThat(pathPattern.getNormalizedLength()).isEqualTo(15);
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
SeparatorPathElement.class, LiteralPathElement.class, SeparatorPathElement.class,
|
||||
SeparatorPathElement.class, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
LiteralPathElement.class, SeparatorPathElement.class, LiteralPathElement.class);
|
||||
pathPattern = checkStructure("/////**");
|
||||
pathPattern = parse("/////**");
|
||||
assertThat(pathPattern.getNormalizedLength()).isEqualTo(5);
|
||||
assertPathElements(pathPattern, SeparatorPathElement.class, SeparatorPathElement.class,
|
||||
SeparatorPathElement.class, SeparatorPathElement.class, WildcardTheRestPathElement.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void patternPropertyGetLengthTests() {
|
||||
void normalizedLengthWhenVariable() {
|
||||
// Test all basic section types
|
||||
assertThat(parse("{foo}").getNormalizedLength()).isEqualTo(1);
|
||||
assertThat(parse("foo").getNormalizedLength()).isEqualTo(3);
|
||||
assertThat(parse("{*foobar}").getNormalizedLength()).isEqualTo(1);
|
||||
assertThat(parse("/{*foobar}").getNormalizedLength()).isEqualTo(1);
|
||||
assertThat(parse("**").getNormalizedLength()).isEqualTo(1);
|
||||
assertThat(parse("/**").getNormalizedLength()).isEqualTo(1);
|
||||
assertThat(parse("{abc}asdf").getNormalizedLength()).isEqualTo(5);
|
||||
assertThat(parse("{abc}_*").getNormalizedLength()).isEqualTo(3);
|
||||
@@ -350,6 +445,15 @@ class PathPatternParserTests {
|
||||
assertThat(parse("/{foo}/{bar}_{goo}_{wibble}/abc/bar").getNormalizedLength()).isEqualTo(16);
|
||||
}
|
||||
|
||||
@Test
|
||||
void separatorTests() {
|
||||
PathPatternParser parser = new PathPatternParser();
|
||||
parser.setPathOptions(PathContainer.Options.create('.', false));
|
||||
String rawPattern = "first.second.{last}";
|
||||
PathPattern pattern = parser.parse(rawPattern);
|
||||
assertThat(pattern.computePatternString()).isEqualTo(rawPattern);
|
||||
}
|
||||
|
||||
@Test
|
||||
void compareTests() {
|
||||
PathPattern p1, p2, p3;
|
||||
@@ -414,96 +518,14 @@ class PathPatternParserTests {
|
||||
assertThat(patterns).element(1).isEqualTo(p2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void captureTheRestWithinPatternNotSupported() {
|
||||
PathPatternParser parser = new PathPatternParser();
|
||||
assertThatThrownBy(() -> parser.parse("/resources/**/details"))
|
||||
.isInstanceOf(PatternParseException.class)
|
||||
.extracting("messageType").isEqualTo(PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST);
|
||||
}
|
||||
|
||||
@Test
|
||||
void separatorTests() {
|
||||
PathPatternParser parser = new PathPatternParser();
|
||||
parser.setPathOptions(PathContainer.Options.create('.', false));
|
||||
String rawPattern = "first.second.{last}";
|
||||
PathPattern pattern = parser.parse(rawPattern);
|
||||
assertThat(pattern.computePatternString()).isEqualTo(rawPattern);
|
||||
}
|
||||
|
||||
private PathPattern parse(String pattern) {
|
||||
PathPatternParser patternParser = new PathPatternParser();
|
||||
return patternParser.parse(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the pattern string computed for a parsed pattern matches the original pattern text
|
||||
*/
|
||||
private PathPattern checkStructure(String pattern) {
|
||||
PathPattern pp = parse(pattern);
|
||||
assertThat(pp.computePatternString()).isEqualTo(pattern);
|
||||
return pp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link #checkError(String, int, PatternMessage, String...)},
|
||||
* passing {@code -1} as the {@code expectedPos}.
|
||||
* @since 5.2
|
||||
*/
|
||||
private void checkError(String pattern, PatternMessage expectedMessage, String... expectedInserts) {
|
||||
checkError(pattern, -1, expectedMessage, expectedInserts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expectedPos the expected position, or {@code -1} if the position should not be checked
|
||||
*/
|
||||
private void checkError(String pattern, int expectedPos, PatternMessage expectedMessage,
|
||||
String... expectedInserts) {
|
||||
|
||||
assertThatExceptionOfType(PatternParseException.class)
|
||||
.isThrownBy(() -> pathPattern = parse(pattern))
|
||||
.satisfies(ex -> {
|
||||
if (expectedPos >= 0) {
|
||||
assertThat(ex.getPosition()).as(ex.toDetailedString()).isEqualTo(expectedPos);
|
||||
}
|
||||
assertThat(ex.getMessageType()).as(ex.toDetailedString()).isEqualTo(expectedMessage);
|
||||
if (expectedInserts.length != 0) {
|
||||
assertThat(ex.getInserts()).isEqualTo(expectedInserts);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private void assertPathElements(PathPattern p, Class<? extends PathElement>... sectionClasses) {
|
||||
PathElement head = p.getHeadSection();
|
||||
for (Class<? extends PathElement> sectionClass : sectionClasses) {
|
||||
if (head == null) {
|
||||
fail("Ran out of data in parsed pattern. Pattern is: " + p.toChainString());
|
||||
}
|
||||
assertThat(head.getClass().getSimpleName()).as("Not expected section type. Pattern is: " + p.toChainString()).isEqualTo(sectionClass.getSimpleName());
|
||||
head = head.next;
|
||||
}
|
||||
}
|
||||
|
||||
// Mirrors the score computation logic in PathPattern
|
||||
private int computeScore(int capturedVariableCount, int wildcardCount) {
|
||||
return capturedVariableCount + wildcardCount * 100;
|
||||
}
|
||||
|
||||
private void assertMatches(PathPattern pp, String path) {
|
||||
assertThat(pp.matches(PathPatternTests.toPathContainer(path))).isTrue();
|
||||
}
|
||||
|
||||
private void assertNoMatch(PathPattern pp, String path) {
|
||||
assertThat(pp.matches(PathPatternTests.toPathContainer(path))).isFalse();
|
||||
}
|
||||
|
||||
private PathPattern.PathMatchInfo matchAndExtract(PathPattern pp, String path) {
|
||||
return pp.matchAndExtract(PathPatternTests.toPathContainer(path));
|
||||
}
|
||||
|
||||
private PathContainer toPSC(String path) {
|
||||
return PathPatternTests.toPathContainer(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+345
-267
@@ -23,6 +23,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.server.PathContainer;
|
||||
@@ -34,12 +35,303 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Exercise matching of {@link PathPattern} objects.
|
||||
* Tests for {@link PathPattern}.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class PathPatternTests {
|
||||
|
||||
@Nested
|
||||
class MatchingTests {
|
||||
|
||||
@Test
|
||||
void basicMatching() {
|
||||
checkMatches("", "");
|
||||
checkMatches("", null);
|
||||
checkNoMatch("/abc", "/");
|
||||
checkMatches("/", "/");
|
||||
checkNoMatch("/", "/a");
|
||||
checkMatches("foo/bar/", "foo/bar/");
|
||||
checkNoMatch("foo", "foobar");
|
||||
checkMatches("/foo/bar", "/foo/bar");
|
||||
checkNoMatch("/foo/bar", "/foo/baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
void literalPathElements() {
|
||||
checkMatches("foo", "foo");
|
||||
checkNoMatch("foo", "bar");
|
||||
checkNoMatch("foo", "/foo");
|
||||
checkNoMatch("/foo", "foo");
|
||||
checkMatches("/f", "/f");
|
||||
checkMatches("/foo", "/foo");
|
||||
checkNoMatch("/foo", "/food");
|
||||
checkNoMatch("/food", "/foo");
|
||||
checkMatches("/foo/", "/foo/");
|
||||
checkMatches("/foo/bar/woo", "/foo/bar/woo");
|
||||
checkMatches("foo/bar/woo", "foo/bar/woo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void questionMarks() {
|
||||
checkNoMatch("a", "ab");
|
||||
checkMatches("/f?o/bar", "/foo/bar");
|
||||
checkNoMatch("/foo/b2r", "/foo/bar");
|
||||
checkNoMatch("?", "te");
|
||||
checkMatches("?", "a");
|
||||
checkMatches("???", "abc");
|
||||
checkNoMatch("tes?", "te");
|
||||
checkNoMatch("tes?", "tes");
|
||||
checkNoMatch("tes?", "testt");
|
||||
checkNoMatch("tes?", "tsst");
|
||||
checkMatches(".?.a", ".a.a");
|
||||
checkNoMatch(".?.a", ".aba");
|
||||
checkMatches("/f?o/bar","/f%20o/bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleSeparatorsInPattern() {
|
||||
PathPattern pp = parse("a//b//c");
|
||||
assertThat(pp.toChainString()).isEqualTo("Literal(a) Separator(/) Separator(/) Literal(b) Separator(/) Separator(/) Literal(c)");
|
||||
assertMatches(pp,"a//b//c");
|
||||
assertThat(parse("a//**").toChainString()).isEqualTo("Literal(a) Separator(/) WildcardSegments(/**)");
|
||||
checkMatches("///abc", "///abc");
|
||||
checkNoMatch("///abc", "/abc");
|
||||
checkNoMatch("//", "/");
|
||||
checkMatches("//", "//");
|
||||
checkNoMatch("///abc//d/e", "/abc/d/e");
|
||||
checkMatches("///abc//d/e", "///abc//d/e");
|
||||
checkNoMatch("///abc//{def}//////xyz", "/abc/foo/xyz");
|
||||
checkMatches("///abc//{def}//////xyz", "///abc//p//////xyz");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleSelectorsInPath() {
|
||||
checkNoMatch("/abc", "////abc");
|
||||
checkNoMatch("/", "//");
|
||||
checkNoMatch("/abc/def/ghi", "/abc//def///ghi");
|
||||
checkNoMatch("/abc", "////abc");
|
||||
checkMatches("////abc", "////abc");
|
||||
checkNoMatch("/", "//");
|
||||
checkNoMatch("/abc//def", "/abc/def");
|
||||
checkNoMatch("/abc//def///ghi", "/abc/def/ghi");
|
||||
checkMatches("/abc//def///ghi", "/abc//def///ghi");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleSeparatorsInPatternAndPath() {
|
||||
checkNoMatch("///one///two///three", "//one/////two///////three");
|
||||
checkMatches("//one/////two///////three", "//one/////two///////three");
|
||||
checkNoMatch("//one//two//three", "/one/////two/three");
|
||||
checkMatches("/one/////two/three", "/one/////two/three");
|
||||
checkCapture("///{foo}///bar", "///one///bar", "foo", "one");
|
||||
}
|
||||
|
||||
@Test
|
||||
void captureSegmentsAtStart() {
|
||||
checkMatches("/{*foobar}/resource", "/resource");
|
||||
checkNoMatch("/{*foobar}/resource", "/resourceX");
|
||||
checkNoMatch("/{*foobar}/resource", "/foobar/resourceX");
|
||||
checkMatches("/{*foobar}/resource", "/foobar/resource");
|
||||
}
|
||||
|
||||
@Test
|
||||
void captureSegmentsAtEnd() {
|
||||
checkMatches("/resource/{*foobar}", "/resource");
|
||||
checkNoMatch("/resource/{*foobar}", "/resourceX");
|
||||
checkNoMatch("/resource/{*foobar}", "/resourceX/foobar");
|
||||
checkMatches("/resource/{*foobar}", "/resource/foobar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void wildcards() {
|
||||
checkMatches("/*/bar", "/foo/bar");
|
||||
checkNoMatch("/*/bar", "/foo/baz");
|
||||
checkNoMatch("/*/bar", "//bar");
|
||||
checkMatches("/f*/bar", "/foo/bar");
|
||||
checkMatches("/*/bar", "/foo/bar");
|
||||
checkMatches("a/*","a/");
|
||||
checkMatches("/*","/");
|
||||
checkMatches("/*/bar", "/foo/bar");
|
||||
checkNoMatch("/*/bar", "/foo/baz");
|
||||
checkMatches("/f*/bar", "/foo/bar");
|
||||
checkMatches("/*/bar", "/foo/bar");
|
||||
checkMatches("/a*b*c*d/bar", "/abcd/bar");
|
||||
checkMatches("*a*", "testa");
|
||||
checkMatches("a/*", "a/");
|
||||
checkNoMatch("a/*", "a//"); // no data for *
|
||||
PathPatternParser ppp = new PathPatternParser();
|
||||
assertThat(ppp.parse("a/*").matches(toPathContainer("a//"))).isFalse();
|
||||
checkMatches("a/*", "a/a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void wildcardSegmentsStart() {
|
||||
checkMatches("/**/resource", "/resource");
|
||||
checkNoMatch("/**/resource", "/Xresource");
|
||||
checkNoMatch("/**/resource", "/foobar/resourceX");
|
||||
checkMatches("/**/resource", "/foobar/resource");
|
||||
|
||||
checkMatches("/**/resource/test", "/foo/bar/resource/test");
|
||||
checkNoMatch("/**/resource/test", "/foo/bar/resource/t");
|
||||
}
|
||||
|
||||
@Test
|
||||
void wildcardSegmentsEnd() {
|
||||
checkMatches("/resource/**", "/resource");
|
||||
checkNoMatch("/resource/**", "/resourceX");
|
||||
checkNoMatch("/resource/**", "/resourceX/foobar");
|
||||
checkMatches("/resource/**", "/resource/foobar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void antPathMatcherTests() {
|
||||
// test exact matching
|
||||
checkMatches("test", "test");
|
||||
checkMatches("/test", "/test");
|
||||
checkMatches("https://example.org", "https://example.org");
|
||||
checkNoMatch("/test.jpg", "test.jpg");
|
||||
checkNoMatch("test", "/test");
|
||||
checkNoMatch("/test", "test");
|
||||
|
||||
// test matching with ?'s
|
||||
checkMatches("t?st", "test");
|
||||
checkMatches("??st", "test");
|
||||
checkMatches("tes?", "test");
|
||||
checkMatches("te??", "test");
|
||||
checkMatches("?es?", "test");
|
||||
checkNoMatch("tes?", "tes");
|
||||
checkNoMatch("tes?", "testt");
|
||||
checkNoMatch("tes?", "tsst");
|
||||
|
||||
// test matching with *'s
|
||||
checkMatches("*", "test");
|
||||
checkMatches("test*", "test");
|
||||
checkMatches("test*", "testTest");
|
||||
checkMatches("test/*", "test/Test");
|
||||
checkMatches("test/*", "test/t");
|
||||
checkMatches("test/*", "test/");
|
||||
checkMatches("*test*", "AnothertestTest");
|
||||
checkMatches("*test", "Anothertest");
|
||||
checkMatches("*.*", "test.");
|
||||
checkMatches("*.*", "test.test");
|
||||
checkMatches("*.*", "test.test.test");
|
||||
checkMatches("test*aaa", "testblaaaa");
|
||||
checkNoMatch("test*", "tst");
|
||||
checkNoMatch("test*", "tsttest");
|
||||
checkMatches("test*", "test"); // trailing slash is optional
|
||||
checkNoMatch("test*", "test/t");
|
||||
checkNoMatch("test/*", "test");
|
||||
checkNoMatch("*test*", "tsttst");
|
||||
checkNoMatch("*test", "tsttst");
|
||||
checkNoMatch("*.*", "tsttst");
|
||||
checkNoMatch("test*aaa", "test");
|
||||
checkNoMatch("test*aaa", "testblaaab");
|
||||
|
||||
// test matching with ?'s and /'s
|
||||
checkMatches("/?", "/a");
|
||||
checkMatches("/?/a", "/a/a");
|
||||
checkMatches("/a/?", "/a/b");
|
||||
checkMatches("/??/a", "/aa/a");
|
||||
checkMatches("/a/??", "/a/bb");
|
||||
checkMatches("/?", "/a");
|
||||
|
||||
checkMatches("/**", "");
|
||||
checkMatches("/books/**", "/books");
|
||||
checkMatches("/**", "/testing/testing");
|
||||
checkMatches("/*/**", "/testing/testing");
|
||||
checkMatches("/bla*bla/test", "/blaXXXbla/test");
|
||||
checkMatches("/*bla/test", "/XXXbla/test");
|
||||
checkNoMatch("/bla*bla/test", "/blaXXXbl/test");
|
||||
checkNoMatch("/*bla/test", "XXXblab/test");
|
||||
checkNoMatch("/*bla/test", "XXXbl/test");
|
||||
checkNoMatch("/????", "/bala/bla");
|
||||
checkMatches("/foo/bar/**", "/foo/bar/");
|
||||
checkMatches("/{bla}.html", "/testing.html");
|
||||
checkCapture("/{bla}.*", "/testing.html", "bla", "testing");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class VariableCaptureTests {
|
||||
|
||||
@Test
|
||||
void constrainedMatches() {
|
||||
checkCapture("{foo:[0-9]*}", "123", "foo", "123");
|
||||
checkNoMatch("{foo:[0-9]*}", "abc");
|
||||
checkNoMatch("/{foo:[0-9]*}", "abc");
|
||||
checkCapture("/*/{foo:....}/**", "/foo/barg/foo", "foo", "barg");
|
||||
checkCapture("/*/{foo:....}/**", "/foo/barg/abc/def/ghi", "foo", "barg");
|
||||
checkNoMatch("{foo:....}", "99");
|
||||
checkMatches("{foo:..}", "99");
|
||||
checkCapture("/{abc:\\{\\}}", "/{}", "abc", "{}");
|
||||
checkCapture("/{abc:\\[\\]}", "/[]", "abc", "[]");
|
||||
checkCapture("/{abc:\\\\\\\\}", "/\\\\"); // this is fun...
|
||||
}
|
||||
|
||||
@Test
|
||||
void captureSegmentsAtStart() {
|
||||
checkCapture("/{*foobar}/resource", "/foobar/resource", "foobar", "/foobar");
|
||||
checkCapture("/{*something}/customer", "/99/customer", "something", "/99");
|
||||
checkCapture("/{*something}/customer", "/aa/bb/cc/customer", "something", "/aa/bb/cc");
|
||||
checkCapture("/{*something}/customer", "/customer", "something", "");
|
||||
checkCapture("/{*something}/customer", "//////99/customer", "something", "//////99");
|
||||
}
|
||||
|
||||
@Test
|
||||
void captureSegmentsAtEnd() {
|
||||
checkCapture("/resource/{*foobar}", "/resource/foobar", "foobar", "/foobar");
|
||||
checkCapture("/customer/{*something}", "/customer/99", "something", "/99");
|
||||
checkCapture("/customer/{*something}", "/customer/aa/bb/cc", "something",
|
||||
"/aa/bb/cc");
|
||||
checkCapture("/customer/{*something}", "/customer/", "something", "/");
|
||||
checkCapture("/customer/////{*something}", "/customer/////", "something", "/");
|
||||
checkCapture("/customer/////{*something}", "/customer//////", "something", "//");
|
||||
checkCapture("/customer//////{*something}", "/customer//////99", "something", "/99");
|
||||
checkCapture("/customer//////{*something}", "/customer//////99", "something", "/99");
|
||||
checkCapture("/customer/{*something}", "/customer", "something", "");
|
||||
checkCapture("/{*something}", "", "something", "");
|
||||
checkCapture("/customer/{*something}", "/customer//////99", "something", "//////99");
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodingAndBoundVariablesCapturePathElement() {
|
||||
checkCapture("{var}","f%20o","var","f o");
|
||||
checkCapture("{var1}/{var2}","f%20o/f%7Co","var1","f o","var2","f|o");
|
||||
checkCapture("{var1}/{var2}","f%20o/f%7co","var1","f o","var2","f|o"); // lower case encoding
|
||||
checkCapture("{var:foo}","foo","var","foo");
|
||||
checkCapture("{var:f o}","f%20o","var","f o"); // constraint is expressed in non encoded form
|
||||
checkCapture("{var:f.o}","f%20o","var","f o");
|
||||
checkCapture("{var:f\\|o}","f%7co","var","f|o");
|
||||
checkCapture("{var:.*}","x\ny","var","x\ny");
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodingAndBoundVariablesCaptureTheRestPathElement() {
|
||||
checkCapture("/{*var}","/f%20o","var","/f o");
|
||||
checkCapture("{var1}/{*var2}","f%20o/f%7Co","var1","f o","var2","/f|o");
|
||||
checkCapture("/{*var}","/foo","var","/foo");
|
||||
checkCapture("/{*var}","/f%20o","var","/f o");
|
||||
checkCapture("/{*var}","/f%20o","var","/f o");
|
||||
checkCapture("/{*var}","/f%7co","var","/f|o");
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodingAndBoundVariablesRegexPathElement() {
|
||||
checkCapture("/{var1:f o}_ _{var2}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
||||
checkCapture("/{var1}_{var2}","/f%20o_foo","var1","f o","var2","foo");
|
||||
checkCapture("/{var1}_ _{var2}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
||||
checkCapture("/{var1}_ _{var2:f\\|o}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
||||
checkCapture("/{var1:f o}_ _{var2:f\\|o}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
||||
checkCapture("/{var1:f o}_ _{var2:f\\|o}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
||||
checkCapture("/{var1}_{var2}","/f\noo_foo","var1","f\noo","var2","foo");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void pathContainer() {
|
||||
assertThat(elementsToString(toPathContainer("/abc/def").elements())).isEqualTo("[/][abc][/][def]");
|
||||
@@ -62,44 +354,8 @@ class PathPatternTests {
|
||||
assertThat(parser.parse("/foo/bar").hasPatternSyntax()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matching_LiteralPathElement() {
|
||||
checkMatches("foo", "foo");
|
||||
checkNoMatch("foo", "bar");
|
||||
checkNoMatch("foo", "/foo");
|
||||
checkNoMatch("/foo", "foo");
|
||||
checkMatches("/f", "/f");
|
||||
checkMatches("/foo", "/foo");
|
||||
checkNoMatch("/foo", "/food");
|
||||
checkNoMatch("/food", "/foo");
|
||||
checkMatches("/foo/", "/foo/");
|
||||
checkMatches("/foo/bar/woo", "/foo/bar/woo");
|
||||
checkMatches("foo/bar/woo", "foo/bar/woo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void basicMatching() {
|
||||
checkMatches("", "");
|
||||
checkMatches("", null);
|
||||
checkNoMatch("/abc", "/");
|
||||
checkMatches("/", "/");
|
||||
checkNoMatch("/", "/a");
|
||||
checkMatches("foo/bar/", "foo/bar/");
|
||||
checkNoMatch("foo", "foobar");
|
||||
checkMatches("/foo/bar", "/foo/bar");
|
||||
checkNoMatch("/foo/bar", "/foo/baz");
|
||||
}
|
||||
|
||||
private void assertMatches(PathPattern pp, String path) {
|
||||
assertThat(pp.matches(toPathContainer(path))).isTrue();
|
||||
}
|
||||
|
||||
private void assertNoMatch(PathPattern pp, String path) {
|
||||
assertThat(pp.matches(toPathContainer(path))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathRemainderBasicCases_spr15336() {
|
||||
@Test // SPR-15336
|
||||
void pathRemainderBasicCases() {
|
||||
// Cover all PathElement kinds
|
||||
assertThat(getPathRemaining("/foo", "/foo/bar").getPathRemaining().value()).isEqualTo("/bar");
|
||||
assertThat(getPathRemaining("/foo", "/foo/").getPathRemaining().value()).isEqualTo("/");
|
||||
@@ -119,41 +375,8 @@ class PathPatternTests {
|
||||
assertThat(getPathRemaining("/foo//", "/foo///bar").getPathRemaining().value()).isEqualTo("/bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodingAndBoundVariablesCapturePathElement() {
|
||||
checkCapture("{var}","f%20o","var","f o");
|
||||
checkCapture("{var1}/{var2}","f%20o/f%7Co","var1","f o","var2","f|o");
|
||||
checkCapture("{var1}/{var2}","f%20o/f%7co","var1","f o","var2","f|o"); // lower case encoding
|
||||
checkCapture("{var:foo}","foo","var","foo");
|
||||
checkCapture("{var:f o}","f%20o","var","f o"); // constraint is expressed in non encoded form
|
||||
checkCapture("{var:f.o}","f%20o","var","f o");
|
||||
checkCapture("{var:f\\|o}","f%7co","var","f|o");
|
||||
checkCapture("{var:.*}","x\ny","var","x\ny");
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodingAndBoundVariablesCaptureTheRestPathElement() {
|
||||
checkCapture("/{*var}","/f%20o","var","/f o");
|
||||
checkCapture("{var1}/{*var2}","f%20o/f%7Co","var1","f o","var2","/f|o");
|
||||
checkCapture("/{*var}","/foo","var","/foo");
|
||||
checkCapture("/{*var}","/f%20o","var","/f o");
|
||||
checkCapture("/{*var}","/f%20o","var","/f o");
|
||||
checkCapture("/{*var}","/f%7co","var","/f|o");
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodingAndBoundVariablesRegexPathElement() {
|
||||
checkCapture("/{var1:f o}_ _{var2}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
||||
checkCapture("/{var1}_{var2}","/f%20o_foo","var1","f o","var2","foo");
|
||||
checkCapture("/{var1}_ _{var2}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
||||
checkCapture("/{var1}_ _{var2:f\\|o}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
||||
checkCapture("/{var1:f o}_ _{var2:f\\|o}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
||||
checkCapture("/{var1:f o}_ _{var2:f\\|o}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
||||
checkCapture("/{var1}_{var2}","/f\noo_foo","var1","f\noo","var2","foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathRemainingCornerCases_spr15336() {
|
||||
@Test // SPR-15336
|
||||
void pathRemainingCornerCases() {
|
||||
// No match when the literal path element is a longer form of the segment in the pattern
|
||||
assertThat(parse("/foo").matchStartOfPath(toPathContainer("/footastic/bar"))).isNull();
|
||||
assertThat(parse("/f?o").matchStartOfPath(toPathContainer("/footastic/bar"))).isNull();
|
||||
@@ -166,6 +389,11 @@ class PathPatternTests {
|
||||
assertThat(parse("/resource/**")
|
||||
.matchStartOfPath(toPathContainer("/resource")).getPathRemaining().value()).isEmpty();
|
||||
|
||||
assertThat(parse("/**/resource")
|
||||
.matchStartOfPath(toPathContainer("/test/resource")).getPathRemaining().value()).isEmpty();
|
||||
assertThat(parse("/**/resource")
|
||||
.matchStartOfPath(toPathContainer("/test/resource/other")).getPathRemaining().value()).isEqualTo("/other");
|
||||
|
||||
// Similar to above for the capture-the-rest variant
|
||||
assertThat(parse("/resource/{*foo}").matchStartOfPath(toPathContainer("/resourceX"))).isNull();
|
||||
assertThat(parse("/resource/{*foo}")
|
||||
@@ -194,191 +422,8 @@ class PathPatternTests {
|
||||
assertThat(parse("").matchStartOfPath(toPathContainer("")).getPathRemaining().value()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void questionMarks() {
|
||||
checkNoMatch("a", "ab");
|
||||
checkMatches("/f?o/bar", "/foo/bar");
|
||||
checkNoMatch("/foo/b2r", "/foo/bar");
|
||||
checkNoMatch("?", "te");
|
||||
checkMatches("?", "a");
|
||||
checkMatches("???", "abc");
|
||||
checkNoMatch("tes?", "te");
|
||||
checkNoMatch("tes?", "tes");
|
||||
checkNoMatch("tes?", "testt");
|
||||
checkNoMatch("tes?", "tsst");
|
||||
checkMatches(".?.a", ".a.a");
|
||||
checkNoMatch(".?.a", ".aba");
|
||||
checkMatches("/f?o/bar","/f%20o/bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void captureTheRest() {
|
||||
checkMatches("/resource/{*foobar}", "/resource");
|
||||
checkNoMatch("/resource/{*foobar}", "/resourceX");
|
||||
checkNoMatch("/resource/{*foobar}", "/resourceX/foobar");
|
||||
checkMatches("/resource/{*foobar}", "/resource/foobar");
|
||||
checkCapture("/resource/{*foobar}", "/resource/foobar", "foobar", "/foobar");
|
||||
checkCapture("/customer/{*something}", "/customer/99", "something", "/99");
|
||||
checkCapture("/customer/{*something}", "/customer/aa/bb/cc", "something",
|
||||
"/aa/bb/cc");
|
||||
checkCapture("/customer/{*something}", "/customer/", "something", "/");
|
||||
checkCapture("/customer/////{*something}", "/customer/////", "something", "/");
|
||||
checkCapture("/customer/////{*something}", "/customer//////", "something", "//");
|
||||
checkCapture("/customer//////{*something}", "/customer//////99", "something", "/99");
|
||||
checkCapture("/customer//////{*something}", "/customer//////99", "something", "/99");
|
||||
checkCapture("/customer/{*something}", "/customer", "something", "");
|
||||
checkCapture("/{*something}", "", "something", "");
|
||||
checkCapture("/customer/{*something}", "/customer//////99", "something", "//////99");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleSeparatorsInPattern() {
|
||||
PathPattern pp = parse("a//b//c");
|
||||
assertThat(pp.toChainString()).isEqualTo("Literal(a) Separator(/) Separator(/) Literal(b) Separator(/) Separator(/) Literal(c)");
|
||||
assertMatches(pp,"a//b//c");
|
||||
assertThat(parse("a//**").toChainString()).isEqualTo("Literal(a) Separator(/) WildcardTheRest(/**)");
|
||||
checkMatches("///abc", "///abc");
|
||||
checkNoMatch("///abc", "/abc");
|
||||
checkNoMatch("//", "/");
|
||||
checkMatches("//", "//");
|
||||
checkNoMatch("///abc//d/e", "/abc/d/e");
|
||||
checkMatches("///abc//d/e", "///abc//d/e");
|
||||
checkNoMatch("///abc//{def}//////xyz", "/abc/foo/xyz");
|
||||
checkMatches("///abc//{def}//////xyz", "///abc//p//////xyz");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleSelectorsInPath() {
|
||||
checkNoMatch("/abc", "////abc");
|
||||
checkNoMatch("/", "//");
|
||||
checkNoMatch("/abc/def/ghi", "/abc//def///ghi");
|
||||
checkNoMatch("/abc", "////abc");
|
||||
checkMatches("////abc", "////abc");
|
||||
checkNoMatch("/", "//");
|
||||
checkNoMatch("/abc//def", "/abc/def");
|
||||
checkNoMatch("/abc//def///ghi", "/abc/def/ghi");
|
||||
checkMatches("/abc//def///ghi", "/abc//def///ghi");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleSeparatorsInPatternAndPath() {
|
||||
checkNoMatch("///one///two///three", "//one/////two///////three");
|
||||
checkMatches("//one/////two///////three", "//one/////two///////three");
|
||||
checkNoMatch("//one//two//three", "/one/////two/three");
|
||||
checkMatches("/one/////two/three", "/one/////two/three");
|
||||
checkCapture("///{foo}///bar", "///one///bar", "foo", "one");
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
void wildcards() {
|
||||
checkMatches("/*/bar", "/foo/bar");
|
||||
checkNoMatch("/*/bar", "/foo/baz");
|
||||
checkNoMatch("/*/bar", "//bar");
|
||||
checkMatches("/f*/bar", "/foo/bar");
|
||||
checkMatches("/*/bar", "/foo/bar");
|
||||
checkMatches("a/*","a/");
|
||||
checkMatches("/*","/");
|
||||
checkMatches("/*/bar", "/foo/bar");
|
||||
checkNoMatch("/*/bar", "/foo/baz");
|
||||
checkMatches("/f*/bar", "/foo/bar");
|
||||
checkMatches("/*/bar", "/foo/bar");
|
||||
checkMatches("/a*b*c*d/bar", "/abcd/bar");
|
||||
checkMatches("*a*", "testa");
|
||||
checkMatches("a/*", "a/");
|
||||
checkNoMatch("a/*", "a//"); // no data for *
|
||||
PathPatternParser ppp = new PathPatternParser();
|
||||
assertThat(ppp.parse("a/*").matches(toPathContainer("a//"))).isFalse();
|
||||
checkMatches("a/*", "a/a");
|
||||
checkMatches("/resource/**", "/resource");
|
||||
checkNoMatch("/resource/**", "/resourceX");
|
||||
checkNoMatch("/resource/**", "/resourceX/foobar");
|
||||
checkMatches("/resource/**", "/resource/foobar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void constrainedMatches() {
|
||||
checkCapture("{foo:[0-9]*}", "123", "foo", "123");
|
||||
checkNoMatch("{foo:[0-9]*}", "abc");
|
||||
checkNoMatch("/{foo:[0-9]*}", "abc");
|
||||
checkCapture("/*/{foo:....}/**", "/foo/barg/foo", "foo", "barg");
|
||||
checkCapture("/*/{foo:....}/**", "/foo/barg/abc/def/ghi", "foo", "barg");
|
||||
checkNoMatch("{foo:....}", "99");
|
||||
checkMatches("{foo:..}", "99");
|
||||
checkCapture("/{abc:\\{\\}}", "/{}", "abc", "{}");
|
||||
checkCapture("/{abc:\\[\\]}", "/[]", "abc", "[]");
|
||||
checkCapture("/{abc:\\\\\\\\}", "/\\\\"); // this is fun...
|
||||
}
|
||||
|
||||
@Test
|
||||
void antPathMatcherTests() {
|
||||
// test exact matching
|
||||
checkMatches("test", "test");
|
||||
checkMatches("/test", "/test");
|
||||
checkMatches("https://example.org", "https://example.org");
|
||||
checkNoMatch("/test.jpg", "test.jpg");
|
||||
checkNoMatch("test", "/test");
|
||||
checkNoMatch("/test", "test");
|
||||
|
||||
// test matching with ?'s
|
||||
checkMatches("t?st", "test");
|
||||
checkMatches("??st", "test");
|
||||
checkMatches("tes?", "test");
|
||||
checkMatches("te??", "test");
|
||||
checkMatches("?es?", "test");
|
||||
checkNoMatch("tes?", "tes");
|
||||
checkNoMatch("tes?", "testt");
|
||||
checkNoMatch("tes?", "tsst");
|
||||
|
||||
// test matching with *'s
|
||||
checkMatches("*", "test");
|
||||
checkMatches("test*", "test");
|
||||
checkMatches("test*", "testTest");
|
||||
checkMatches("test/*", "test/Test");
|
||||
checkMatches("test/*", "test/t");
|
||||
checkMatches("test/*", "test/");
|
||||
checkMatches("*test*", "AnothertestTest");
|
||||
checkMatches("*test", "Anothertest");
|
||||
checkMatches("*.*", "test.");
|
||||
checkMatches("*.*", "test.test");
|
||||
checkMatches("*.*", "test.test.test");
|
||||
checkMatches("test*aaa", "testblaaaa");
|
||||
checkNoMatch("test*", "tst");
|
||||
checkNoMatch("test*", "tsttest");
|
||||
checkMatches("test*", "test"); // trailing slash is optional
|
||||
checkNoMatch("test*", "test/t");
|
||||
checkNoMatch("test/*", "test");
|
||||
checkNoMatch("*test*", "tsttst");
|
||||
checkNoMatch("*test", "tsttst");
|
||||
checkNoMatch("*.*", "tsttst");
|
||||
checkNoMatch("test*aaa", "test");
|
||||
checkNoMatch("test*aaa", "testblaaab");
|
||||
|
||||
// test matching with ?'s and /'s
|
||||
checkMatches("/?", "/a");
|
||||
checkMatches("/?/a", "/a/a");
|
||||
checkMatches("/a/?", "/a/b");
|
||||
checkMatches("/??/a", "/aa/a");
|
||||
checkMatches("/a/??", "/a/bb");
|
||||
checkMatches("/?", "/a");
|
||||
|
||||
checkMatches("/**", "");
|
||||
checkMatches("/books/**", "/books");
|
||||
checkMatches("/**", "/testing/testing");
|
||||
checkMatches("/*/**", "/testing/testing");
|
||||
checkMatches("/bla*bla/test", "/blaXXXbla/test");
|
||||
checkMatches("/*bla/test", "/XXXbla/test");
|
||||
checkNoMatch("/bla*bla/test", "/blaXXXbl/test");
|
||||
checkNoMatch("/*bla/test", "XXXblab/test");
|
||||
checkNoMatch("/*bla/test", "XXXbl/test");
|
||||
checkNoMatch("/????", "/bala/bla");
|
||||
checkMatches("/foo/bar/**", "/foo/bar/");
|
||||
checkMatches("/{bla}.html", "/testing.html");
|
||||
checkCapture("/{bla}.*", "/testing.html", "bla", "testing");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathRemainingEnhancements_spr15419() {
|
||||
@Test // SPR-15149
|
||||
void pathRemainingEnhancements() {
|
||||
PathPattern pp;
|
||||
PathPattern.PathRemainingMatchInfo pri;
|
||||
// It would be nice to partially match a path and get any bound variables in one step
|
||||
@@ -495,8 +540,8 @@ class PathPatternTests {
|
||||
assertMatches(p,"bAb");
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractPathWithinPattern_spr15259() {
|
||||
@Test // SPR-15259
|
||||
void extractPathWithinPatternWildards() {
|
||||
checkExtractPathWithinPattern("/**","//","");
|
||||
checkExtractPathWithinPattern("/**","/","");
|
||||
checkExtractPathWithinPattern("/**","","");
|
||||
@@ -553,9 +598,8 @@ class PathPatternTests {
|
||||
assertThat(result.elements()).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void extractUriTemplateVariables_spr15264() {
|
||||
@Test // SPR-15264
|
||||
public void extractUriTemplateVariables() {
|
||||
PathPattern pp;
|
||||
pp = new PathPatternParser().parse("/{foo}");
|
||||
assertMatches(pp,"/abc");
|
||||
@@ -611,10 +655,7 @@ class PathPatternTests {
|
||||
Map<String, String> vars = new AntPathMatcher().extractUriTemplateVariables("/{foo}{bar}", "/a");
|
||||
assertThat(vars).containsEntry("foo", "a");
|
||||
assertThat(vars.get("bar")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractUriTemplateVariables() {
|
||||
assertMatches(parse("{hotel}"),"1");
|
||||
assertMatches(parse("/hotels/{hotel}"),"/hotels/1");
|
||||
checkCapture("/hotels/{hotel}", "/hotels/1", "hotel", "1");
|
||||
@@ -1003,6 +1044,43 @@ class PathPatternTests {
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void regexPathElementPatterns() {
|
||||
PathPatternParser pp = new PathPatternParser();
|
||||
|
||||
PathPattern pattern = pp.parse("/{var:\\\\}");
|
||||
assertMatches(pattern, "/\\");
|
||||
|
||||
pattern = pp.parse("/{var:\\/}");
|
||||
assertNoMatch(pattern, "/aaa");
|
||||
|
||||
pattern = pp.parse("/{var:[^\\/]*}");
|
||||
PathPattern.PathMatchInfo result = matchAndExtract(pattern, "/foo");
|
||||
assertThat(result.getUriVariables().get("var")).isEqualTo("foo");
|
||||
|
||||
pattern = pp.parse("/{var:\\[*}");
|
||||
result = matchAndExtract(pattern, "/[[[");
|
||||
assertThat(result.getUriVariables().get("var")).isEqualTo("[[[");
|
||||
|
||||
pattern = pp.parse("/{var:[\\{]*}");
|
||||
result = matchAndExtract(pattern, "/{{{");
|
||||
assertThat(result.getUriVariables().get("var")).isEqualTo("{{{");
|
||||
|
||||
pattern = pp.parse("/{var:[\\}]*}");
|
||||
result = matchAndExtract(pattern, "/}}}");
|
||||
assertThat(result.getUriVariables().get("var")).isEqualTo("}}}");
|
||||
}
|
||||
|
||||
private void assertMatches(PathPattern pp, String path) {
|
||||
assertThat(pp.matches(toPathContainer(path))).isTrue();
|
||||
}
|
||||
|
||||
private void assertNoMatch(PathPattern pp, String path) {
|
||||
assertThat(pp.matches(toPathContainer(path))).isFalse();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private PathPattern.PathMatchInfo matchAndExtract(String pattern, String path) {
|
||||
return parse(pattern).matchAndExtract(PathPatternTests.toPathContainer(path));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user