diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java index 3140a06483..942a5624ee 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java @@ -60,6 +60,12 @@ public class RequestMappingIntegrationTests extends AbstractRequestMappingIntegr assertEquals(expected, performGet("/param?name=George", new HttpHeaders(), String.class).getBody()); } + @Test // SPR-15140 + public void handleWithEncodedParam() throws Exception { + String expected = "Hello ++\u00e0!"; + assertEquals(expected, performGet("/param?name=%20%2B+%C3%A0", new HttpHeaders(), String.class).getBody()); + } + @Test public void longStreamResult() throws Exception { String[] expected = {"0", "1", "2", "3", "4"}; diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java index 6acf05c0d5..83bc27ca3b 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java @@ -16,9 +16,7 @@ package org.springframework.http.server.reactive; -import java.io.UnsupportedEncodingException; import java.net.URI; -import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -29,6 +27,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; +import org.springframework.web.util.UriUtils; /** * Common base class for {@link ServerHttpRequest} implementations. @@ -79,22 +78,6 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest { return this.queryParams; } - /** - * A method for decoding name and value string in a name-value pair. - *

Note that the plus sign "+" is converted into a space character " ".

- * @param encodedString the string to be decoded - * @return the decoded string - * @see java.net.URLDecoder#decode(String, String) - */ - private static String decodeQueryParam(final String encodedString) { - try { - return URLDecoder.decode(encodedString, StandardCharsets.UTF_8.name()); - } catch (UnsupportedEncodingException e) { - // StandardCharsets are guaranteed to be available on every implementation of the Java platform, so this should never happen. - throw new IllegalStateException(e); - } - } - /** * A method for parsing of the query into name-value pairs. The return * value is turned into an immutable map and cached. @@ -113,13 +96,16 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest { String eq = matcher.group(2); String value = matcher.group(3); value = (value != null ? value : (StringUtils.hasLength(eq) ? "" : null)); - queryParams.add(decodeQueryParam(name), - value != null ? decodeQueryParam(value) : null); + queryParams.add(decodeQueryParam(name), decodeQueryParam(value)); } } return queryParams; } + private static String decodeQueryParam(String value) { + return (value != null ? UriUtils.decode(value, StandardCharsets.UTF_8) : null); + } + @Override public MultiValueMap getCookies() { if (this.cookies == null) { diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java index eba9e34948..fee98d0af8 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java @@ -56,20 +56,17 @@ public class ReactorServerHttpRequest extends AbstractServerHttpRequest { private static URI initUri(HttpServerRequest channel) { Assert.notNull("'channel' must not be null"); + InetSocketAddress address = channel.remoteAddress(); + return (address == null ? URI.create(channel.uri()) : getBaseUrl(address).resolve(channel.uri())); + } + + private static URI getBaseUrl(InetSocketAddress address) { try { - URI uri = new URI(channel.uri()); - InetSocketAddress remoteAddress = channel.remoteAddress(); - return new URI( - uri.getScheme(), - uri.getUserInfo(), - (remoteAddress != null ? remoteAddress.getHostString() : null), - (remoteAddress != null ? remoteAddress.getPort() : -1), - uri.getPath(), - uri.getQuery(), - uri.getFragment()); + return new URI(null, null, address.getHostString(), address.getPort(), null, null, null); } catch (URISyntaxException ex) { - throw new IllegalStateException("Could not get URI: " + ex.getMessage(), ex); + // Should not happen... + throw new IllegalStateException(ex); } } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/RxNettyServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/RxNettyServerHttpRequest.java index 215bc82692..b1b38bc0ea 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/RxNettyServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/RxNettyServerHttpRequest.java @@ -63,25 +63,20 @@ public class RxNettyServerHttpRequest extends AbstractServerHttpRequest { private static URI initUri(HttpServerRequest request) { Assert.notNull("'request', request must not be null"); + return StringUtils.isEmpty(request.getHostHeader()) ? + URI.create(request.getUri()) : getBaseUrl(request).resolve(request.getUri()); + } + + private static URI getBaseUrl(HttpServerRequest request) { + HttpHeaders headers = new HttpHeaders(); + headers.add("Host", request.getHostHeader()); + InetSocketAddress address = headers.getHost(); try { - URI uri = new URI(request.getUri()); - InetSocketAddress remoteAddress = null; - if (!StringUtils.isEmpty(request.getHostHeader())) { - HttpHeaders headers = new HttpHeaders(); - headers.add("Host", request.getHostHeader()); - remoteAddress = headers.getHost(); - } - return new URI( - uri.getScheme(), - uri.getUserInfo(), - (remoteAddress != null ? remoteAddress.getHostString() : null), - (remoteAddress != null ? remoteAddress.getPort() : -1), - uri.getPath(), - uri.getQuery(), - uri.getFragment()); + return new URI(null, null, address.getHostString(), address.getPort(), null, null, null); } catch (URISyntaxException ex) { - throw new IllegalStateException("Could not get URI: " + ex.getMessage(), ex); + // Should not happen... + throw new IllegalStateException(ex); } } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpRequest.java index 08f67d27bb..bae47f5f3e 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpRequest.java @@ -63,11 +63,19 @@ public class UndertowServerHttpRequest extends AbstractServerHttpRequest { private static URI initUri(HttpServerExchange exchange) { Assert.notNull(exchange, "HttpServerExchange is required."); + String requestURI = exchange.getRequestURI(); + String query = exchange.getQueryString(); + String requestUriAndQuery = StringUtils.isEmpty(query) ? requestURI : requestURI + "?" + query; + return (exchange.isHostIncludedInRequestURI()) ? + URI.create(requestUriAndQuery) : getBaseUri(exchange).resolve(requestUriAndQuery); + } + + private static URI getBaseUri(HttpServerExchange exchange) { try { - String query = exchange.getQueryString(); - return new URI(exchange.getRequestScheme(), null, - exchange.getHostName(), exchange.getHostPort(), - exchange.getRequestURI(), StringUtils.hasText(query) ? query : null, null); + String scheme = exchange.getRequestScheme(); + String host = exchange.getHostName(); + int port = exchange.getHostPort(); + return new URI(scheme, null, host, port, null, null, null); } catch (URISyntaxException ex) { throw new IllegalStateException("Could not get URI: " + ex.getMessage(), ex); diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestIntegrationTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestIntegrationTests.java index 3b2569d92b..eed78bd62c 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestIntegrationTests.java @@ -36,9 +36,9 @@ public class ServerHttpRequestIntegrationTests extends AbstractHttpHandlerIntegr @Test public void checkUri() throws Exception { - RestTemplate restTemplate = new RestTemplate(); - RequestEntity request = RequestEntity.post(new URI("http://localhost:" + port + "/foo?param=bar")).build(); - ResponseEntity response = restTemplate.exchange(request, Void.class); + URI url = new URI("http://localhost:" + port + "/foo?param=bar"); + RequestEntity request = RequestEntity.post(url).build(); + ResponseEntity response = new RestTemplate().exchange(request, Void.class); assertEquals(HttpStatus.OK, response.getStatusCode()); } diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java index 922fb503ec..c79534ae2e 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java @@ -64,11 +64,11 @@ public class ServerHttpRequestTests { assertEquals(Arrays.asList("1", "2"), params.get("a")); } - @Test - public void queryParamsWithUrlEncodedValue() throws Exception { + @Test // SPR-15140 + public void queryParamsWithEncodedValue() throws Exception { MultiValueMap params = createHttpRequest("/path?a=%20%2B+%C3%A0").getQueryParams(); assertEquals(1, params.size()); - assertEquals(Collections.singletonList(" + \u00e0"), params.get("a")); + assertEquals(Collections.singletonList(" ++\u00e0"), params.get("a")); } @Test