diff --git a/spring-web/src/main/java/org/springframework/web/server/support/HttpRequestPathHelper.java b/spring-web/src/main/java/org/springframework/web/server/support/HttpRequestPathHelper.java index 3842184ade..3476de157a 100644 --- a/spring-web/src/main/java/org/springframework/web/server/support/HttpRequestPathHelper.java +++ b/spring-web/src/main/java/org/springframework/web/server/support/HttpRequestPathHelper.java @@ -19,6 +19,7 @@ package org.springframework.web.server.support; import java.io.UnsupportedEncodingException; import java.util.LinkedHashMap; import java.util.Map; +import java.util.StringTokenizer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.LinkedMultiValueMap; @@ -108,6 +109,43 @@ public class HttpRequestPathHelper { return decodedVars; } + /** + * Parse the given string with matrix variables. An example string would look + * like this {@code "q1=a;q1=b;q2=a,b,c"}. The resulting map would contain + * keys {@code "q1"} and {@code "q2"} with values {@code ["a","b"]} and + * {@code ["a","b","c"]} respectively. + *

The returned values are decoded unless {@link #setUrlDecode(boolean)} + * is set to {@code true} in which case it is assumed the URL path from + * which the variables were extracted is already decoded through a call to + * {@link #getLookupPathForRequest(ServerWebExchange)}. + * @param semicolonContent path parameter content to parse + * @return a map with matrix variable names and values (never {@code null}) + */ + public MultiValueMap parseMatrixVariables(ServerWebExchange exchange, + String semicolonContent) { + + MultiValueMap result = new LinkedMultiValueMap<>(); + if (!StringUtils.hasText(semicolonContent)) { + return result; + } + StringTokenizer pairs = new StringTokenizer(semicolonContent, ";"); + while (pairs.hasMoreTokens()) { + String pair = pairs.nextToken(); + int index = pair.indexOf('='); + if (index != -1) { + String name = pair.substring(0, index); + String rawValue = pair.substring(index + 1); + for (String value : StringUtils.commaDelimitedListToStringArray(rawValue)) { + result.add(name, value); + } + } + else { + result.add(pair, ""); + } + } + return decodeMatrixVariables(exchange, result); + } + /** * Decode the given matrix variables unless {@link #setUrlDecode(boolean)} * is set to {@code true} in which case it is assumed the URL path from @@ -117,7 +155,7 @@ public class HttpRequestPathHelper { * @param vars URI variables extracted from the URL path * @return the same Map or a new Map instance */ - public MultiValueMap decodeMatrixVariables(ServerWebExchange exchange, + private MultiValueMap decodeMatrixVariables(ServerWebExchange exchange, MultiValueMap vars) { if (this.urlDecode) { diff --git a/spring-web/src/test/java/org/springframework/web/server/support/HttpRequestPathHelperTests.java b/spring-web/src/test/java/org/springframework/web/server/support/HttpRequestPathHelperTests.java new file mode 100644 index 0000000000..6721a18ae9 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/web/server/support/HttpRequestPathHelperTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.web.server.support; + +import java.util.Arrays; + +import org.junit.Test; + +import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for {@link HttpRequestPathHelper}. + * @author Rossen Stoyanchev + */ +public class HttpRequestPathHelperTests { + + + @Test + public void parseMatrixVariables() { + + HttpRequestPathHelper pathHelper = new HttpRequestPathHelper(); + ServerWebExchange exchange = MockServerHttpRequest.get("").toExchange(); + MultiValueMap variables; + + variables = pathHelper.parseMatrixVariables(exchange, null); + assertEquals(0, variables.size()); + + variables = pathHelper.parseMatrixVariables(exchange, "year"); + assertEquals(1, variables.size()); + assertEquals("", variables.getFirst("year")); + + variables = pathHelper.parseMatrixVariables(exchange, "year=2012"); + assertEquals(1, variables.size()); + assertEquals("2012", variables.getFirst("year")); + + variables = pathHelper.parseMatrixVariables(exchange, "year=2012;colors=red,blue,green"); + assertEquals(2, variables.size()); + assertEquals(Arrays.asList("red", "blue", "green"), variables.get("colors")); + assertEquals("2012", variables.getFirst("year")); + + variables = pathHelper.parseMatrixVariables(exchange, ";year=2012;colors=red,blue,green;"); + assertEquals(2, variables.size()); + assertEquals(Arrays.asList("red", "blue", "green"), variables.get("colors")); + assertEquals("2012", variables.getFirst("year")); + + variables = pathHelper.parseMatrixVariables(exchange, "colors=red;colors=blue;colors=green"); + assertEquals(1, variables.size()); + assertEquals(Arrays.asList("red", "blue", "green"), variables.get("colors")); + } + + @Test + public void parseMatrixVariablesAndDecode() { + + HttpRequestPathHelper pathHelper = new HttpRequestPathHelper(); + pathHelper.setUrlDecode(false); + + ServerWebExchange exchange = MockServerHttpRequest.get("").toExchange(); + MultiValueMap variables; + + variables = pathHelper.parseMatrixVariables(exchange, "mvar=a%2fb"); + assertEquals(1, variables.size()); + assertEquals("a/b", variables.getFirst("mvar")); + } + +} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java index 3b6eed42b2..44fea7cc21 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.StringTokenizer; import java.util.stream.Collectors; import org.springframework.http.HttpHeaders; @@ -34,7 +35,9 @@ import org.springframework.http.HttpMethod; import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.result.condition.NameValueExpression; @@ -44,7 +47,7 @@ import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; import org.springframework.web.server.UnsupportedMediaTypeStatusException; import org.springframework.web.server.support.LookupPath; -import org.springframework.web.util.WebUtils; + /** * Abstract base class for classes for which {@link RequestMappingInfo} defines @@ -145,19 +148,16 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe continue; } - String matrixVariables; - + String semicolonContent; int semicolonIndex = uriVarValue.indexOf(';'); if ((semicolonIndex == -1) || (semicolonIndex == 0) || (equalsIndex < semicolonIndex)) { - matrixVariables = uriVarValue; + semicolonContent = uriVarValue; } else { - matrixVariables = uriVarValue.substring(semicolonIndex + 1); + semicolonContent = uriVarValue.substring(semicolonIndex + 1); uriVariables.put(uriVar.getKey(), uriVarValue.substring(0, semicolonIndex)); } - - MultiValueMap vars = WebUtils.parseMatrixVariables(matrixVariables); - result.put(uriVar.getKey(), getPathHelper().decodeMatrixVariables(exchange, vars)); + result.put(uriVar.getKey(), getPathHelper().parseMatrixVariables(exchange, semicolonContent)); } return result; }