Ensure DefaultPathSegment does not allow parameters to be mutated
Prior to this commit, if a PathContainer was created using Options.MESSAGE_ROUTE, DefaultPathSegment#parameters() returned a mutable map which would allow the user to modify the contents of the static, shared EMPTY_PARAMS map in DefaultPathContainer. This commit prevents corruption of the shared EMPTY_PARAMS map by ensuring that parameters stored in DefaultPathSegment are always immutable. Closes gh-27064
This commit is contained in:
@@ -20,11 +20,14 @@ import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.server.PathContainer.Element;
|
||||
import org.springframework.http.server.PathContainer.Options;
|
||||
import org.springframework.http.server.PathContainer.PathSegment;
|
||||
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.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultPathContainer}.
|
||||
@@ -37,20 +40,20 @@ class DefaultPathContainerTests {
|
||||
@Test
|
||||
void pathSegment() {
|
||||
// basic
|
||||
testPathSegment("cars", "cars", new LinkedMultiValueMap<>());
|
||||
testPathSegment("cars", "cars", emptyMap());
|
||||
|
||||
// empty
|
||||
testPathSegment("", "", new LinkedMultiValueMap<>());
|
||||
testPathSegment("", "", emptyMap());
|
||||
|
||||
// spaces
|
||||
testPathSegment("%20%20", " ", new LinkedMultiValueMap<>());
|
||||
testPathSegment("%20a%20", " a ", new LinkedMultiValueMap<>());
|
||||
testPathSegment("%20%20", " ", emptyMap());
|
||||
testPathSegment("%20a%20", " a ", emptyMap());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathSegmentParams() {
|
||||
// basic
|
||||
LinkedMultiValueMap<String, String> params = new LinkedMultiValueMap<>();
|
||||
LinkedMultiValueMap<String, String> params = emptyMap();
|
||||
params.add("colors", "red");
|
||||
params.add("colors", "blue");
|
||||
params.add("colors", "green");
|
||||
@@ -58,21 +61,45 @@ class DefaultPathContainerTests {
|
||||
testPathSegment("cars;colors=red,blue,green;year=2012", "cars", params);
|
||||
|
||||
// trailing semicolon
|
||||
params = new LinkedMultiValueMap<>();
|
||||
params = emptyMap();
|
||||
params.add("p", "1");
|
||||
testPathSegment("path;p=1;", "path", params);
|
||||
|
||||
// params with spaces
|
||||
params = new LinkedMultiValueMap<>();
|
||||
params = emptyMap();
|
||||
params.add("param name", "param value");
|
||||
testPathSegment("path;param%20name=param%20value;%20", "path", params);
|
||||
|
||||
// empty params
|
||||
params = new LinkedMultiValueMap<>();
|
||||
params = emptyMap();
|
||||
params.add("p", "1");
|
||||
testPathSegment("path;;;%20;%20;p=1;%20", "path", params);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathSegmentParamsAreImmutable() {
|
||||
assertPathSegmentParamsAreImmutable("cars", emptyMap(), Options.HTTP_PATH);
|
||||
|
||||
LinkedMultiValueMap<String, String> params = emptyMap();
|
||||
params.add("colors", "red");
|
||||
params.add("colors", "blue");
|
||||
params.add("colors", "green");
|
||||
assertPathSegmentParamsAreImmutable(";colors=red,blue,green", params, Options.HTTP_PATH);
|
||||
|
||||
assertPathSegmentParamsAreImmutable(";colors=red,blue,green", emptyMap(), Options.MESSAGE_ROUTE);
|
||||
}
|
||||
|
||||
private void assertPathSegmentParamsAreImmutable(String path, LinkedMultiValueMap<String, String> params, Options options) {
|
||||
PathContainer container = PathContainer.parsePath(path, options);
|
||||
assertThat(container.elements()).hasSize(1);
|
||||
|
||||
PathSegment segment = (PathSegment) container.elements().get(0);
|
||||
MultiValueMap<String, String> segmentParams = segment.parameters();
|
||||
assertThat(segmentParams).isEqualTo(params);
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class)
|
||||
.isThrownBy(() -> segmentParams.add("enigma", "boom"));
|
||||
}
|
||||
|
||||
private void testPathSegment(String rawValue, String valueToMatch, MultiValueMap<String, String> params) {
|
||||
PathContainer container = PathContainer.parsePath(rawValue);
|
||||
|
||||
@@ -111,10 +138,10 @@ class DefaultPathContainerTests {
|
||||
}
|
||||
|
||||
private void testPath(String input, String value, String... expectedElements) {
|
||||
PathContainer path = PathContainer.parsePath(input, PathContainer.Options.HTTP_PATH);
|
||||
PathContainer path = PathContainer.parsePath(input, Options.HTTP_PATH);
|
||||
|
||||
assertThat(path.value()).as("value: '" + input + "'").isEqualTo(value);
|
||||
assertThat(path.elements()).map(PathContainer.Element::value).as("elements: " + input)
|
||||
assertThat(path.elements()).map(Element::value).as("elements: " + input)
|
||||
.containsExactly(expectedElements);
|
||||
}
|
||||
|
||||
@@ -137,7 +164,7 @@ class DefaultPathContainerTests {
|
||||
|
||||
@Test // gh-23310
|
||||
void pathWithCustomSeparator() {
|
||||
PathContainer path = PathContainer.parsePath("a.b%2Eb.c", PathContainer.Options.MESSAGE_ROUTE);
|
||||
PathContainer path = PathContainer.parsePath("a.b%2Eb.c", Options.MESSAGE_ROUTE);
|
||||
|
||||
Stream<String> decodedSegments = path.elements().stream()
|
||||
.filter(PathSegment.class::isInstance)
|
||||
@@ -147,4 +174,8 @@ class DefaultPathContainerTests {
|
||||
assertThat(decodedSegments).containsExactly("a", "b.b", "c");
|
||||
}
|
||||
|
||||
private static LinkedMultiValueMap<String, String> emptyMap() {
|
||||
return new LinkedMultiValueMap<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user