Improve separator support in PathContainer

To make the switching of separators complete, it is also important to
know whether the decoding of path segment values and the parsing of
path param should be done as those are applied transparently.

This commit replaces the recently added separator argument to
PathContainer.parsePath with an Options type with two predefined
constants. One for HTTP URLs with automatic decoding and parsing of
path params, and another for "." separated message routes without
decoding except for encoded sequences of the separator itself.

See gh-23310
This commit is contained in:
Rossen Stoyanchev
2019-07-22 11:25:19 +01:00
parent fbe697061c
commit 358a6d6f10
10 changed files with 225 additions and 86 deletions

View File

@@ -27,7 +27,6 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Unit tests for {@link DefaultPathContainer}.
@@ -36,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
public class DefaultPathContainerTests {
@Test
public void pathSegment() throws Exception {
public void pathSegment() {
// basic
testPathSegment("cars", "cars", new LinkedMultiValueMap<>());
@@ -92,7 +91,7 @@ public class DefaultPathContainerTests {
}
@Test
public void path() throws Exception {
public void path() {
// basic
testPath("/a/b/c", "/a/b/c", Arrays.asList("/", "a", "/", "b", "/", "c"));
@@ -112,20 +111,20 @@ public class DefaultPathContainerTests {
testPath("//%20/%20", "//%20/%20", Arrays.asList("/", "/", "%20", "/", "%20"));
}
private void testPath(String input, String separator, String value, List<String> expectedElements) {
PathContainer path = PathContainer.parsePath(input, separator);
private void testPath(String input, PathContainer.Options options, String value, List<String> expectedElements) {
PathContainer path = PathContainer.parsePath(input, options);
assertThat(path.value()).as("value: '" + input + "'").isEqualTo(value);
assertThat(path.elements().stream()
.map(PathContainer.Element::value).collect(Collectors.toList())).as("elements: " + input).isEqualTo(expectedElements);
assertThat(path.elements().stream().map(PathContainer.Element::value).collect(Collectors.toList()))
.as("elements: " + input).isEqualTo(expectedElements);
}
private void testPath(String input, String value, List<String> expectedElements) {
testPath(input, "/", value, expectedElements);
testPath(input, PathContainer.Options.HTTP_PATH, value, expectedElements);
}
@Test
public void subPath() throws Exception {
public void subPath() {
// basic
PathContainer path = PathContainer.parsePath("/a/b/c");
assertThat(path.subPath(0)).isSameAs(path);
@@ -141,14 +140,16 @@ public class DefaultPathContainerTests {
assertThat(path.subPath(2).value()).isEqualTo("/b/");
}
@Test
public void pathWithCustomSeparator() throws Exception {
testPath("a.b.c", ".", "a.b.c", Arrays.asList("a", ".", "b", ".", "c"));
}
@Test // gh-23310
public void pathWithCustomSeparator() {
PathContainer path = PathContainer.parsePath("a.b%2Eb.c", PathContainer.Options.MESSAGE_ROUTE);
@Test
public void emptySeparator() {
assertThatIllegalArgumentException().isThrownBy(() -> PathContainer.parsePath("path", ""));
List<String> decodedSegments = path.elements().stream()
.filter(e -> e instanceof PathSegment)
.map(e -> ((PathSegment) e).valueToMatch())
.collect(Collectors.toList());
assertThat(decodedSegments).isEqualTo(Arrays.asList("a", "b.b", "c"));
}
}

View File

@@ -411,7 +411,7 @@ public class PathPatternParserTests {
@Test
public void separatorTests() {
PathPatternParser parser = new PathPatternParser();
parser.setSeparator('.');
parser.setPathOptions(PathContainer.Options.HTTP_PATH);
String rawPattern = "first.second.{last}";
PathPattern pattern = parser.parse(rawPattern);
assertThat(pattern.computePatternString()).isEqualTo(rawPattern);

View File

@@ -16,8 +16,11 @@
package org.springframework.web.util.pattern;
import java.util.Map;
import org.junit.Test;
import org.springframework.http.server.PathContainer;
import org.springframework.util.RouteMatcher;
import static org.assertj.core.api.Assertions.assertThat;
@@ -32,18 +35,33 @@ public class PathPatternRouteMatcherTests {
@Test
public void matchRoute() {
PathPatternRouteMatcher routeMatcher = new PathPatternRouteMatcher(new PathPatternParser());
RouteMatcher.Route route = routeMatcher.parseRoute("/projects/spring-framework");
assertThat(routeMatcher.match("/projects/{name}", route)).isTrue();
}
@Test
public void matchRouteWithCustomSeparator() {
PathPatternParser pathPatternParser = new PathPatternParser();
pathPatternParser.setSeparator('.');
PathPatternRouteMatcher routeMatcher = new PathPatternRouteMatcher(pathPatternParser);
PathPatternRouteMatcher routeMatcher = new PathPatternRouteMatcher();
RouteMatcher.Route route = routeMatcher.parseRoute("projects.spring-framework");
assertThat(routeMatcher.match("projects.{name}", route)).isTrue();
}
@Test
public void matchRouteWithCustomSeparator() {
PathPatternParser parser = new PathPatternParser();
parser.setPathOptions(PathContainer.Options.create('/', false));
PathPatternRouteMatcher routeMatcher = new PathPatternRouteMatcher(parser);
RouteMatcher.Route route = routeMatcher.parseRoute("/projects/spring-framework");
assertThat(routeMatcher.match("/projects/{name}", route)).isTrue();
}
@Test // gh-23310
public void noDecodingAndNoParamParsing() {
PathPatternRouteMatcher routeMatcher = new PathPatternRouteMatcher();
RouteMatcher.Route route = routeMatcher.parseRoute("projects.spring%20framework;p=1");
assertThat(routeMatcher.match("projects.spring%20framework;p=1", route)).isTrue();
}
@Test // gh-23310
public void separatorOnlyDecoded() {
PathPatternRouteMatcher routeMatcher = new PathPatternRouteMatcher();
RouteMatcher.Route route = routeMatcher.parseRoute("projects.spring%2Eframework");
Map<String, String> vars = routeMatcher.matchAndExtract("projects.{project}", route);
assertThat(vars).containsEntry("project", "spring.framework");
}
}

View File

@@ -710,9 +710,10 @@ public class PathPatternTests {
@Test
public void extractPathWithinPatternCustomSeparator() {
PathPatternParser ppp = new PathPatternParser();
ppp.setSeparator('.');
ppp.setPathOptions(PathContainer.Options.create('.', true));
PathPattern pp = ppp.parse("test.**");
PathContainer pathContainer = PathContainer.parsePath("test.projects..spring-framework", ".");
PathContainer pathContainer = PathContainer.parsePath(
"test.projects..spring-framework", PathContainer.Options.create('.', true));
PathContainer result = pp.extractPathWithinPattern(pathContainer);
assertThat(result.value()).isEqualTo("projects.spring-framework");
assertThat(result.elements()).hasSize(3);