Skip to content

Commit 9819558

Browse files
committed
Support encoded query string in reactive requests
In addition to the updates from PR#1297 this commit adds an integration test and fixes for the resulting failures with RxNetty, Reactor Netty, and Undertow. Also replaced use of URLDecoder which is not for decoding URIs with use of UriUtils for decoding query parameters. Issue: SPR-15140
1 parent 613e65f commit 9819558

File tree

7 files changed

+49
-57
lines changed

7 files changed

+49
-57
lines changed

spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ public void handleWithParam() throws Exception {
6060
assertEquals(expected, performGet("/param?name=George", new HttpHeaders(), String.class).getBody());
6161
}
6262

63+
@Test // SPR-15140
64+
public void handleWithEncodedParam() throws Exception {
65+
String expected = "Hello ++\u00e0!";
66+
assertEquals(expected, performGet("/param?name=%20%2B+%C3%A0", new HttpHeaders(), String.class).getBody());
67+
}
68+
6369
@Test
6470
public void longStreamResult() throws Exception {
6571
String[] expected = {"0", "1", "2", "3", "4"};

spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616

1717
package org.springframework.http.server.reactive;
1818

19-
import java.io.UnsupportedEncodingException;
2019
import java.net.URI;
21-
import java.net.URLDecoder;
2220
import java.nio.charset.StandardCharsets;
2321
import java.util.regex.Matcher;
2422
import java.util.regex.Pattern;
@@ -29,6 +27,7 @@
2927
import org.springframework.util.LinkedMultiValueMap;
3028
import org.springframework.util.MultiValueMap;
3129
import org.springframework.util.StringUtils;
30+
import org.springframework.web.util.UriUtils;
3231

3332
/**
3433
* Common base class for {@link ServerHttpRequest} implementations.
@@ -79,22 +78,6 @@ public MultiValueMap<String, String> getQueryParams() {
7978
return this.queryParams;
8079
}
8180

82-
/**
83-
* A method for decoding name and value string in a name-value pair.
84-
* <p>Note that the plus sign "+" is converted into a space character " ".</p>
85-
* @param encodedString the string to be decoded
86-
* @return the decoded string
87-
* @see java.net.URLDecoder#decode(String, String)
88-
*/
89-
private static String decodeQueryParam(final String encodedString) {
90-
try {
91-
return URLDecoder.decode(encodedString, StandardCharsets.UTF_8.name());
92-
} catch (UnsupportedEncodingException e) {
93-
// StandardCharsets are guaranteed to be available on every implementation of the Java platform, so this should never happen.
94-
throw new IllegalStateException(e);
95-
}
96-
}
97-
9881
/**
9982
* A method for parsing of the query into name-value pairs. The return
10083
* value is turned into an immutable map and cached.
@@ -113,13 +96,16 @@ protected MultiValueMap<String, String> initQueryParams() {
11396
String eq = matcher.group(2);
11497
String value = matcher.group(3);
11598
value = (value != null ? value : (StringUtils.hasLength(eq) ? "" : null));
116-
queryParams.add(decodeQueryParam(name),
117-
value != null ? decodeQueryParam(value) : null);
99+
queryParams.add(decodeQueryParam(name), decodeQueryParam(value));
118100
}
119101
}
120102
return queryParams;
121103
}
122104

105+
private static String decodeQueryParam(String value) {
106+
return (value != null ? UriUtils.decode(value, StandardCharsets.UTF_8) : null);
107+
}
108+
123109
@Override
124110
public MultiValueMap<String, HttpCookie> getCookies() {
125111
if (this.cookies == null) {

spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,20 +56,17 @@ public ReactorServerHttpRequest(HttpServerRequest request, NettyDataBufferFactor
5656

5757
private static URI initUri(HttpServerRequest channel) {
5858
Assert.notNull("'channel' must not be null");
59+
InetSocketAddress address = channel.remoteAddress();
60+
return (address == null ? URI.create(channel.uri()) : getBaseUrl(address).resolve(channel.uri()));
61+
}
62+
63+
private static URI getBaseUrl(InetSocketAddress address) {
5964
try {
60-
URI uri = new URI(channel.uri());
61-
InetSocketAddress remoteAddress = channel.remoteAddress();
62-
return new URI(
63-
uri.getScheme(),
64-
uri.getUserInfo(),
65-
(remoteAddress != null ? remoteAddress.getHostString() : null),
66-
(remoteAddress != null ? remoteAddress.getPort() : -1),
67-
uri.getPath(),
68-
uri.getQuery(),
69-
uri.getFragment());
65+
return new URI(null, null, address.getHostString(), address.getPort(), null, null, null);
7066
}
7167
catch (URISyntaxException ex) {
72-
throw new IllegalStateException("Could not get URI: " + ex.getMessage(), ex);
68+
// Should not happen...
69+
throw new IllegalStateException(ex);
7370
}
7471
}
7572

spring-web/src/main/java/org/springframework/http/server/reactive/RxNettyServerHttpRequest.java

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,25 +63,20 @@ public RxNettyServerHttpRequest(HttpServerRequest<ByteBuf> request,
6363

6464
private static URI initUri(HttpServerRequest<ByteBuf> request) {
6565
Assert.notNull("'request', request must not be null");
66+
return StringUtils.isEmpty(request.getHostHeader()) ?
67+
URI.create(request.getUri()) : getBaseUrl(request).resolve(request.getUri());
68+
}
69+
70+
private static URI getBaseUrl(HttpServerRequest<ByteBuf> request) {
71+
HttpHeaders headers = new HttpHeaders();
72+
headers.add("Host", request.getHostHeader());
73+
InetSocketAddress address = headers.getHost();
6674
try {
67-
URI uri = new URI(request.getUri());
68-
InetSocketAddress remoteAddress = null;
69-
if (!StringUtils.isEmpty(request.getHostHeader())) {
70-
HttpHeaders headers = new HttpHeaders();
71-
headers.add("Host", request.getHostHeader());
72-
remoteAddress = headers.getHost();
73-
}
74-
return new URI(
75-
uri.getScheme(),
76-
uri.getUserInfo(),
77-
(remoteAddress != null ? remoteAddress.getHostString() : null),
78-
(remoteAddress != null ? remoteAddress.getPort() : -1),
79-
uri.getPath(),
80-
uri.getQuery(),
81-
uri.getFragment());
75+
return new URI(null, null, address.getHostString(), address.getPort(), null, null, null);
8276
}
8377
catch (URISyntaxException ex) {
84-
throw new IllegalStateException("Could not get URI: " + ex.getMessage(), ex);
78+
// Should not happen...
79+
throw new IllegalStateException(ex);
8580
}
8681
}
8782

spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpRequest.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,19 @@ public UndertowServerHttpRequest(HttpServerExchange exchange, DataBufferFactory
6363

6464
private static URI initUri(HttpServerExchange exchange) {
6565
Assert.notNull(exchange, "HttpServerExchange is required.");
66+
String requestURI = exchange.getRequestURI();
67+
String query = exchange.getQueryString();
68+
String requestUriAndQuery = StringUtils.isEmpty(query) ? requestURI : requestURI + "?" + query;
69+
return (exchange.isHostIncludedInRequestURI()) ?
70+
URI.create(requestUriAndQuery) : getBaseUri(exchange).resolve(requestUriAndQuery);
71+
}
72+
73+
private static URI getBaseUri(HttpServerExchange exchange) {
6674
try {
67-
String query = exchange.getQueryString();
68-
return new URI(exchange.getRequestScheme(), null,
69-
exchange.getHostName(), exchange.getHostPort(),
70-
exchange.getRequestURI(), StringUtils.hasText(query) ? query : null, null);
75+
String scheme = exchange.getRequestScheme();
76+
String host = exchange.getHostName();
77+
int port = exchange.getHostPort();
78+
return new URI(scheme, null, host, port, null, null, null);
7179
}
7280
catch (URISyntaxException ex) {
7381
throw new IllegalStateException("Could not get URI: " + ex.getMessage(), ex);

spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestIntegrationTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ protected CheckRequestHandler createHttpHandler() {
3636

3737
@Test
3838
public void checkUri() throws Exception {
39-
RestTemplate restTemplate = new RestTemplate();
40-
RequestEntity<Void> request = RequestEntity.post(new URI("http://localhost:" + port + "/foo?param=bar")).build();
41-
ResponseEntity<Void> response = restTemplate.exchange(request, Void.class);
39+
URI url = new URI("http://localhost:" + port + "/foo?param=bar");
40+
RequestEntity<Void> request = RequestEntity.post(url).build();
41+
ResponseEntity<Void> response = new RestTemplate().exchange(request, Void.class);
4242
assertEquals(HttpStatus.OK, response.getStatusCode());
4343
}
4444

spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ public void queryParamsWithMulitpleValues() throws Exception {
6464
assertEquals(Arrays.asList("1", "2"), params.get("a"));
6565
}
6666

67-
@Test
68-
public void queryParamsWithUrlEncodedValue() throws Exception {
67+
@Test // SPR-15140
68+
public void queryParamsWithEncodedValue() throws Exception {
6969
MultiValueMap<String, String> params = createHttpRequest("/path?a=%20%2B+%C3%A0").getQueryParams();
7070
assertEquals(1, params.size());
71-
assertEquals(Collections.singletonList(" + \u00e0"), params.get("a"));
71+
assertEquals(Collections.singletonList(" ++\u00e0"), params.get("a"));
7272
}
7373

7474
@Test

0 commit comments

Comments
 (0)