diff --git a/spring-web/src/main/java/org/springframework/http/server/DefaultPathContainer.java b/spring-web/src/main/java/org/springframework/http/server/DefaultPathContainer.java index ce2644cc60..df550a1147 100644 --- a/spring-web/src/main/java/org/springframework/http/server/DefaultPathContainer.java +++ b/spring-web/src/main/java/org/springframework/http/server/DefaultPathContainer.java @@ -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 segmentParser) { - + static PathContainer createFromUrlPath(String path) { if (path.equals("")) { return EMPTY_PATH; } + String separator = "/"; Separator separatorElement = separator.equals(SEPARATOR.value()) ? SEPARATOR : () -> separator; List 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 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 parameters = parsePathParams(pathParameterContent, charset); + return new DefaultPathSegment(segment, valueToMatch, parameters); + } } private static MultiValueMap 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 parameters; + + + DefaultPathSegment(String value, String valueToMatch, MultiValueMap 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 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 parameters; - - - DefaultUrlPathSegment(String value, String valueToMatch, MultiValueMap 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 parameters() { - return this.parameters; - } - } - } diff --git a/spring-web/src/main/java/org/springframework/http/server/DefaultRequestPath.java b/spring-web/src/main/java/org/springframework/http/server/DefaultRequestPath.java index 7258738213..0fa21e803e 100644 --- a/spring-web/src/main/java/org/springframework/http/server/DefaultRequestPath.java +++ b/spring-web/src/main/java/org/springframework/http/server/DefaultRequestPath.java @@ -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); diff --git a/spring-web/src/main/java/org/springframework/http/server/PathContainer.java b/spring-web/src/main/java/org/springframework/http/server/PathContainer.java index e383d72353..a63d77f059 100644 --- a/spring-web/src/main/java/org/springframework/http/server/PathContainer.java +++ b/spring-web/src/main/java/org/springframework/http/server/PathContainer.java @@ -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. * - *

An instance of this class can be created via {@link #parsePath(String)} or - * {@link #parseUrlPath(String)}. - * - *

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. + *

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. diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/CaptureTheRestPathElement.java b/spring-web/src/main/java/org/springframework/web/util/pattern/CaptureTheRestPathElement.java index fb2f05bfac..51d6438381 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/CaptureTheRestPathElement.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/CaptureTheRestPathElement.java @@ -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 parametersCollector = null; for (int i = pathIndex; i < matchingContext.pathLength; i++) { Element element = matchingContext.pathElements.get(i); - if (element instanceof UrlPathSegment) { - MultiValueMap parameters = ((UrlPathSegment) element).parameters(); + if (element instanceof PathSegment) { + MultiValueMap 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()); diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/CaptureVariablePathElement.java b/spring-web/src/main/java/org/springframework/web/util/pattern/CaptureVariablePathElement.java index 7e92608ddb..3c30085e1b 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/CaptureVariablePathElement.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/CaptureVariablePathElement.java @@ -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; } diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/ParsingPathMatcher.java b/spring-web/src/main/java/org/springframework/web/util/pattern/ParsingPathMatcher.java index e45783bad6..07b4c39aaa 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/ParsingPathMatcher.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/ParsingPathMatcher.java @@ -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 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) diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java b/spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java index 0cfd474f6c..9a5d9f3a70 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java @@ -261,7 +261,7 @@ public class PathPattern implements Comparable { 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 { // /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; } diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/RegexPathElement.java b/spring-web/src/main/java/org/springframework/web/util/pattern/RegexPathElement.java index 6b1bde971e..0315f75aee 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/RegexPathElement.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/RegexPathElement.java @@ -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); } } diff --git a/spring-web/src/test/java/org/springframework/http/server/DefaultPathContainerTests.java b/spring-web/src/test/java/org/springframework/http/server/DefaultPathContainerTests.java index b1a5739c51..8bb392b9a9 100644 --- a/spring-web/src/test/java/org/springframework/http/server/DefaultPathContainerTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/DefaultPathContainerTests.java @@ -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 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 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()); } diff --git a/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternMatcherTests.java b/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternMatcherTests.java index a16e5cb3b9..753d1162de 100644 --- a/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternMatcherTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternMatcherTests.java @@ -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) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java index 618afa7fa3..ddf272ad61 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java @@ -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()); } /** diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java index f957506c33..5498e55599 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java @@ -142,7 +142,7 @@ public class ResourceUrlProvider implements ApplicationListener