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:
Sam Brannen
2021-06-15 15:11:57 +02:00
parent bcb0580492
commit 3676084472
3 changed files with 79 additions and 35 deletions

View File

@@ -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<>();
}
}