PathContainer parses URL paths only
Collapse non-URL vs URL based path parsing into one essentially supporting URL paths only.
This commit is contained in:
parent
af83d2332a
commit
f813a63fd8
|
|
@ -21,7 +21,6 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
|
@ -90,16 +89,11 @@ class DefaultPathContainer implements PathContainer {
|
|||
}
|
||||
|
||||
|
||||
static PathContainer createFromPath(String path, String separator) {
|
||||
return parsePathInternal(path, separator, DefaultPathSegment::new);
|
||||
}
|
||||
|
||||
private static PathContainer parsePathInternal(String path, String separator,
|
||||
Function<String, PathSegment> segmentParser) {
|
||||
|
||||
static PathContainer createFromUrlPath(String path) {
|
||||
if (path.equals("")) {
|
||||
return EMPTY_PATH;
|
||||
}
|
||||
String separator = "/";
|
||||
Separator separatorElement = separator.equals(SEPARATOR.value()) ? SEPARATOR : () -> separator;
|
||||
List<Element> elements = new ArrayList<>();
|
||||
int begin;
|
||||
|
|
@ -114,7 +108,7 @@ class DefaultPathContainer implements PathContainer {
|
|||
int end = path.indexOf(separator, begin);
|
||||
String segment = (end != -1 ? path.substring(begin, end) : path.substring(begin));
|
||||
if (!segment.equals("")) {
|
||||
elements.add(segmentParser.apply(segment));
|
||||
elements.add(parsePathSegment(segment));
|
||||
}
|
||||
if (end == -1) {
|
||||
break;
|
||||
|
|
@ -125,21 +119,19 @@ class DefaultPathContainer implements PathContainer {
|
|||
return new DefaultPathContainer(path, elements);
|
||||
}
|
||||
|
||||
static PathContainer createFromUrlPath(String path) {
|
||||
return parsePathInternal(path, "/", segment -> {
|
||||
Charset charset = StandardCharsets.UTF_8;
|
||||
int index = segment.indexOf(';');
|
||||
if (index == -1) {
|
||||
String valueToMatch = StringUtils.uriDecode(segment, charset);
|
||||
return new DefaultUrlPathSegment(segment, valueToMatch, EMPTY_MAP);
|
||||
}
|
||||
else {
|
||||
String valueToMatch = StringUtils.uriDecode(segment.substring(0, index), charset);
|
||||
String pathParameterContent = segment.substring(index);
|
||||
MultiValueMap<String, String> parameters = parsePathParams(pathParameterContent, charset);
|
||||
return new DefaultUrlPathSegment(segment, valueToMatch, parameters);
|
||||
}
|
||||
});
|
||||
private static PathSegment parsePathSegment(String segment) {
|
||||
Charset charset = StandardCharsets.UTF_8;
|
||||
int index = segment.indexOf(';');
|
||||
if (index == -1) {
|
||||
String valueToMatch = StringUtils.uriDecode(segment, charset);
|
||||
return new DefaultPathSegment(segment, valueToMatch, EMPTY_MAP);
|
||||
}
|
||||
else {
|
||||
String valueToMatch = StringUtils.uriDecode(segment.substring(0, index), charset);
|
||||
String pathParameterContent = segment.substring(index);
|
||||
MultiValueMap<String, String> parameters = parsePathParams(pathParameterContent, charset);
|
||||
return new DefaultPathSegment(segment, valueToMatch, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
private static MultiValueMap<String, String> parsePathParams(String input, Charset charset) {
|
||||
|
|
@ -204,10 +196,20 @@ class DefaultPathContainer implements PathContainer {
|
|||
|
||||
private final char[] valueAsChars;
|
||||
|
||||
private final String valueToMatch;
|
||||
|
||||
DefaultPathSegment(String value) {
|
||||
private final char[] valueToMatchAsChars;
|
||||
|
||||
private final MultiValueMap<String, String> parameters;
|
||||
|
||||
|
||||
DefaultPathSegment(String value, String valueToMatch, MultiValueMap<String, String> params) {
|
||||
Assert.isTrue(!value.contains("/"), () -> "Invalid path segment value: " + value);
|
||||
this.value = value;
|
||||
this.valueAsChars = value.toCharArray();
|
||||
this.valueToMatch = valueToMatch;
|
||||
this.valueToMatchAsChars = valueToMatch.toCharArray();
|
||||
this.parameters = CollectionUtils.unmodifiableMultiValueMap(params);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -218,14 +220,20 @@ class DefaultPathContainer implements PathContainer {
|
|||
|
||||
@Override
|
||||
public String valueToMatch() {
|
||||
return this.value;
|
||||
return this.valueToMatch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] valueToMatchAsChars() {
|
||||
return this.valueAsChars;
|
||||
return this.valueToMatchAsChars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiValueMap<String, String> parameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (this == other) {
|
||||
|
|
@ -246,40 +254,5 @@ class DefaultPathContainer implements PathContainer {
|
|||
return "[value='" + this.value + "']"; }
|
||||
}
|
||||
|
||||
|
||||
private static class DefaultUrlPathSegment extends DefaultPathSegment implements UrlPathSegment {
|
||||
|
||||
private final String valueToMatch;
|
||||
|
||||
private final char[] valueToMatchAsChars;
|
||||
|
||||
private final MultiValueMap<String, String> parameters;
|
||||
|
||||
|
||||
DefaultUrlPathSegment(String value, String valueToMatch, MultiValueMap<String, String> params) {
|
||||
super(value);
|
||||
Assert.isTrue(!value.contains("/"), () -> "Invalid path segment value: " + value);
|
||||
this.valueToMatch = valueToMatch;
|
||||
this.valueToMatchAsChars = valueToMatch.toCharArray();
|
||||
this.parameters = CollectionUtils.unmodifiableMultiValueMap(params);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String valueToMatch() {
|
||||
return this.valueToMatch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] valueToMatchAsChars() {
|
||||
return this.valueToMatchAsChars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiValueMap<String, String> parameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import java.net.URI;
|
|||
import java.util.List;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -39,7 +38,7 @@ class DefaultRequestPath implements RequestPath {
|
|||
|
||||
|
||||
DefaultRequestPath(URI uri, @Nullable String contextPath) {
|
||||
this.fullPath = PathContainer.parseUrlPath(uri.getRawPath());
|
||||
this.fullPath = PathContainer.parsePath(uri.getRawPath());
|
||||
this.contextPath = initContextPath(this.fullPath, contextPath);
|
||||
this.pathWithinApplication = extractPathWithinApplication(this.fullPath, this.contextPath);
|
||||
}
|
||||
|
|
@ -52,7 +51,7 @@ class DefaultRequestPath implements RequestPath {
|
|||
|
||||
private static PathContainer initContextPath(PathContainer path, @Nullable String contextPath) {
|
||||
if (!StringUtils.hasText(contextPath) || "/".equals(contextPath)) {
|
||||
return PathContainer.parseUrlPath("");
|
||||
return PathContainer.parsePath("");
|
||||
}
|
||||
|
||||
validateContextPath(path.value(), contextPath);
|
||||
|
|
|
|||
|
|
@ -21,16 +21,14 @@ import java.util.List;
|
|||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Structured representation of a path whose elements are parsed into a sequence
|
||||
* of {@link Separator Separator} and {@link PathSegment PathSegment} elements.
|
||||
* Structured representation of a URI path whose elements have been pre-parsed
|
||||
* into a sequence of {@link Separator Separator} and {@link PathSegment
|
||||
* PathSegment} elements.
|
||||
*
|
||||
* <p>An instance of this class can be created via {@link #parsePath(String)} or
|
||||
* {@link #parseUrlPath(String)}.
|
||||
*
|
||||
* <p>For a URL path each {@link UrlPathSegment UrlPathSegment} exposes its
|
||||
* structure decoded safely without the risk of encoded reserved characters
|
||||
* altering the path or segment structure and without path parameters for
|
||||
* path matching purposes.
|
||||
* <p>An instance of this class can be created via {@link #parsePath(String)}.
|
||||
* Each {@link PathSegment PathSegment} exposes its structure decoded
|
||||
* safely without the risk of encoded reserved characters altering the path or
|
||||
* segment structure and without path parameters for path matching purposes.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
|
|
@ -71,31 +69,10 @@ public interface PathContainer {
|
|||
/**
|
||||
* Parse the path value into a sequence of {@link Separator Separator} and
|
||||
* {@link PathSegment PathSegment} elements.
|
||||
* @param path the path value to parse
|
||||
* @return the parsed path
|
||||
*/
|
||||
static PathContainer parsePath(String path) {
|
||||
return parsePath(path, "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the path value into a sequence of {@link Separator Separator} and
|
||||
* {@link PathSegment PathSegment} elements.
|
||||
* @param path the path value to parse
|
||||
* @param separator the value to treat as separator
|
||||
* @return the parsed path
|
||||
*/
|
||||
static PathContainer parsePath(String path, String separator) {
|
||||
return DefaultPathContainer.createFromPath(path, separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the path value into a sequence of {@link Separator Separator} and
|
||||
* {@link UrlPathSegment UrlPathSegment} elements.
|
||||
* @param path the encoded, raw URL path value to parse
|
||||
* @return the parsed path
|
||||
*/
|
||||
static PathContainer parseUrlPath(String path) {
|
||||
static PathContainer parsePath(String path) {
|
||||
return DefaultPathContainer.createFromUrlPath(path);
|
||||
}
|
||||
|
||||
|
|
@ -135,14 +112,6 @@ public interface PathContainer {
|
|||
* The same as {@link #valueToMatch()} but as a {@code char[]}.
|
||||
*/
|
||||
char[] valueToMatchAsChars();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specialization of {@link PathSegment} for a URL path.
|
||||
* The {@link #valueToMatch()} is decoded and without path parameters.
|
||||
*/
|
||||
interface UrlPathSegment extends PathSegment {
|
||||
|
||||
/**
|
||||
* Path parameters parsed from the path segment.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ package org.springframework.web.util.pattern;
|
|||
import java.util.List;
|
||||
|
||||
import org.springframework.http.server.PathContainer.Element;
|
||||
import org.springframework.http.server.PathContainer.UrlPathSegment;
|
||||
import org.springframework.http.server.PathContainer.PathSegment;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.util.pattern.PathPattern.MatchingContext;
|
||||
|
|
@ -65,8 +65,8 @@ class CaptureTheRestPathElement extends PathElement {
|
|||
MultiValueMap<String,String> parametersCollector = null;
|
||||
for (int i = pathIndex; i < matchingContext.pathLength; i++) {
|
||||
Element element = matchingContext.pathElements.get(i);
|
||||
if (element instanceof UrlPathSegment) {
|
||||
MultiValueMap<String, String> parameters = ((UrlPathSegment) element).parameters();
|
||||
if (element instanceof PathSegment) {
|
||||
MultiValueMap<String, String> parameters = ((PathSegment) element).parameters();
|
||||
if (!parameters.isEmpty()) {
|
||||
if (parametersCollector == null) {
|
||||
parametersCollector = new LinkedMultiValueMap<>();
|
||||
|
|
@ -85,8 +85,8 @@ class CaptureTheRestPathElement extends PathElement {
|
|||
StringBuilder buf = new StringBuilder();
|
||||
for (int i = fromSegment, max = pathElements.size(); i < max; i++) {
|
||||
Element element = pathElements.get(i);
|
||||
if (element instanceof UrlPathSegment) {
|
||||
buf.append(((UrlPathSegment)element).valueToMatch());
|
||||
if (element instanceof PathSegment) {
|
||||
buf.append(((PathSegment)element).valueToMatch());
|
||||
}
|
||||
else {
|
||||
buf.append(element.value());
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ package org.springframework.web.util.pattern;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.http.server.PathContainer.UrlPathSegment;
|
||||
import org.springframework.http.server.PathContainer.PathSegment;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
|
|
@ -120,7 +120,7 @@ class CaptureVariablePathElement extends PathElement {
|
|||
}
|
||||
|
||||
if (match && matchingContext.extractingVariables) {
|
||||
matchingContext.set(this.variableName, candidateCapture, ((UrlPathSegment)matchingContext.pathElements.get(pathIndex-1)).parameters());
|
||||
matchingContext.set(this.variableName, candidateCapture, ((PathSegment)matchingContext.pathElements.get(pathIndex-1)).parameters());
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,26 +60,26 @@ public class ParsingPathMatcher implements PathMatcher {
|
|||
@Override
|
||||
public boolean match(String pattern, String path) {
|
||||
PathPattern pathPattern = getPathPattern(pattern);
|
||||
return pathPattern.matches(PathContainer.parseUrlPath(path));
|
||||
return pathPattern.matches(PathContainer.parsePath(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchStart(String pattern, String path) {
|
||||
PathPattern pathPattern = getPathPattern(pattern);
|
||||
return pathPattern.matchStart(PathContainer.parseUrlPath(path));
|
||||
return pathPattern.matchStart(PathContainer.parsePath(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String extractPathWithinPattern(String pattern, String path) {
|
||||
PathPattern pathPattern = getPathPattern(pattern);
|
||||
PathContainer pathContainer = PathContainer.parseUrlPath(path);
|
||||
PathContainer pathContainer = PathContainer.parsePath(path);
|
||||
return pathPattern.extractPathWithinPattern(pathContainer).value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
|
||||
PathPattern pathPattern = getPathPattern(pattern);
|
||||
PathContainer pathContainer = PathContainer.parseUrlPath(path);
|
||||
PathContainer pathContainer = PathContainer.parsePath(path);
|
||||
PathMatchResult results = pathPattern.matchAndExtract(pathContainer);
|
||||
// Collapse PathMatchResults to simple value results
|
||||
// TODO: (path parameters are lost in this translation)
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
public PathContainer extractPathWithinPattern(PathContainer path) {
|
||||
// TODO: implement extractPathWithinPattern for PathContainer
|
||||
String result = extractPathWithinPattern(path.value());
|
||||
return PathContainer.parseUrlPath(result);
|
||||
return PathContainer.parsePath(result);
|
||||
}
|
||||
|
||||
private String extractPathWithinPattern(String path) {
|
||||
|
|
@ -403,7 +403,7 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
// /usr + /user => /usr/user
|
||||
// /{foo} + /bar => /{foo}/bar
|
||||
if (!this.patternString.equals(pattern2string.patternString) && this.capturedVariableCount == 0 &&
|
||||
matches(PathContainer.parseUrlPath(pattern2string.patternString))) {
|
||||
matches(PathContainer.parsePath(pattern2string.patternString))) {
|
||||
return pattern2string;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import java.util.List;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.http.server.PathContainer.UrlPathSegment;
|
||||
import org.springframework.http.server.PathContainer.PathSegment;
|
||||
import org.springframework.web.util.pattern.PathPattern.MatchingContext;
|
||||
|
||||
/**
|
||||
|
|
@ -173,7 +173,7 @@ class RegexPathElement extends PathElement {
|
|||
String value = matcher.group(i);
|
||||
matchingContext.set(name, value,
|
||||
(i == this.variableNames.size())?
|
||||
((UrlPathSegment)matchingContext.pathElements.get(pathIndex)).parameters():
|
||||
((PathSegment)matchingContext.pathElements.get(pathIndex)).parameters():
|
||||
NO_PARAMETERS);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.http.server.PathContainer.UrlPathSegment;
|
||||
import org.springframework.http.server.PathContainer.PathSegment;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ public class DefaultPathContainerTests {
|
|||
|
||||
private void testPathSegment(String rawValue, String valueToMatch, MultiValueMap<String, String> params) {
|
||||
|
||||
PathContainer container = PathContainer.parseUrlPath(rawValue);
|
||||
PathContainer container = PathContainer.parsePath(rawValue);
|
||||
|
||||
if ("".equals(rawValue)) {
|
||||
assertEquals(0, container.elements().size());
|
||||
|
|
@ -84,7 +84,7 @@ public class DefaultPathContainerTests {
|
|||
}
|
||||
|
||||
assertEquals(1, container.elements().size());
|
||||
UrlPathSegment segment = (UrlPathSegment) container.elements().get(0);
|
||||
PathSegment segment = (PathSegment) container.elements().get(0);
|
||||
|
||||
assertEquals("value: '" + rawValue + "'", rawValue, segment.value());
|
||||
assertEquals("valueToMatch: '" + rawValue + "'", valueToMatch, segment.valueToMatch());
|
||||
|
|
@ -114,7 +114,7 @@ public class DefaultPathContainerTests {
|
|||
|
||||
private void testPath(String input, String value, List<String> expectedElements) {
|
||||
|
||||
PathContainer path = PathContainer.parseUrlPath(input);
|
||||
PathContainer path = PathContainer.parsePath(input);
|
||||
|
||||
assertEquals("value: '" + input + "'", value, path.value());
|
||||
assertEquals("elements: " + input, expectedElements, path.elements().stream()
|
||||
|
|
@ -124,17 +124,17 @@ public class DefaultPathContainerTests {
|
|||
@Test
|
||||
public void subPath() throws Exception {
|
||||
// basic
|
||||
PathContainer path = PathContainer.parseUrlPath("/a/b/c");
|
||||
PathContainer path = PathContainer.parsePath("/a/b/c");
|
||||
assertSame(path, path.subPath(0));
|
||||
assertEquals("/b/c", path.subPath(2).value());
|
||||
assertEquals("/c", path.subPath(4).value());
|
||||
|
||||
// root path
|
||||
path = PathContainer.parseUrlPath("/");
|
||||
path = PathContainer.parsePath("/");
|
||||
assertEquals("/", path.subPath(0).value());
|
||||
|
||||
// trailing slash
|
||||
path = PathContainer.parseUrlPath("/a/b/");
|
||||
path = PathContainer.parsePath("/a/b/");
|
||||
assertEquals("/b/", path.subPath(2).value());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1316,7 +1316,7 @@ public class PathPatternMatcherTests {
|
|||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
return PathContainer.parseUrlPath(path);
|
||||
return PathContainer.parsePath(path);
|
||||
}
|
||||
|
||||
private void checkMatches(String uriTemplate, String path) {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ public interface ServerRequest {
|
|||
* Return the request path as {@code PathContainer}.
|
||||
*/
|
||||
default PathContainer pathContainer() {
|
||||
return PathContainer.parseUrlPath(path());
|
||||
return PathContainer.parsePath(path());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
|||
int queryIndex = getQueryIndex(uriString);
|
||||
String lookupPath = uriString.substring(0, queryIndex);
|
||||
String query = uriString.substring(queryIndex);
|
||||
PathContainer parsedLookupPath = PathContainer.parseUrlPath(lookupPath);
|
||||
PathContainer parsedLookupPath = PathContainer.parsePath(lookupPath);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Getting resource URL for lookup path \"" + lookupPath + "\"");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue