Skip to content

Commit 1f4743a

Browse files
committed
Lenient handling of malformed query in ServletServerHttpRequest
Closes gh-30489
1 parent af85d19 commit 1f4743a

File tree

3 files changed

+78
-35
lines changed

3 files changed

+78
-35
lines changed

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

+23-16
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.springframework.util.Assert;
5151
import org.springframework.util.LinkedCaseInsensitiveMap;
5252
import org.springframework.util.StringUtils;
53+
import org.springframework.web.util.UriComponentsBuilder;
5354

5455
/**
5556
* {@link ServerHttpRequest} implementation that is based on a {@link HttpServletRequest}.
@@ -119,31 +120,37 @@ public URI getURI() {
119120
*/
120121
public static URI initURI(HttpServletRequest servletRequest) {
121122
String urlString = null;
123+
String query = null;
122124
boolean hasQuery = false;
123125
try {
124-
StringBuffer url = servletRequest.getRequestURL();
125-
String query = servletRequest.getQueryString();
126+
StringBuffer requestURL = servletRequest.getRequestURL();
127+
query = servletRequest.getQueryString();
126128
hasQuery = StringUtils.hasText(query);
127129
if (hasQuery) {
128-
url.append('?').append(query);
130+
requestURL.append('?').append(query);
129131
}
130-
urlString = url.toString();
132+
urlString = requestURL.toString();
131133
return new URI(urlString);
132134
}
133135
catch (URISyntaxException ex) {
134-
if (!hasQuery) {
135-
throw new IllegalStateException(
136-
"Could not resolve HttpServletRequest as URI: " + urlString, ex);
137-
}
138-
// Maybe a malformed query string... try plain request URL
139-
try {
140-
urlString = servletRequest.getRequestURL().toString();
141-
return new URI(urlString);
142-
}
143-
catch (URISyntaxException ex2) {
144-
throw new IllegalStateException(
145-
"Could not resolve HttpServletRequest as URI: " + urlString, ex2);
136+
if (hasQuery) {
137+
try {
138+
// Maybe malformed query, try to parse and encode it
139+
query = UriComponentsBuilder.fromUriString("?" + query).build().toUri().getRawQuery();
140+
return new URI(servletRequest.getRequestURL().toString() + "?" + query);
141+
}
142+
catch (URISyntaxException ex2) {
143+
try {
144+
// Try leaving it out
145+
return new URI(servletRequest.getRequestURL().toString());
146+
}
147+
catch (URISyntaxException ex3) {
148+
// ignore
149+
}
150+
}
146151
}
152+
throw new IllegalStateException(
153+
"Could not resolve HttpServletRequest as URI: " + urlString, ex);
147154
}
148155
}
149156

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

+39-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -51,6 +51,7 @@
5151
import org.springframework.util.LinkedMultiValueMap;
5252
import org.springframework.util.MultiValueMap;
5353
import org.springframework.util.StringUtils;
54+
import org.springframework.web.util.UriComponentsBuilder;
5455

5556
/**
5657
* Adapt {@link ServerHttpRequest} to the Servlet {@link HttpServletRequest}.
@@ -90,8 +91,8 @@ public ServletServerHttpRequest(MultiValueMap<String, String> headers, HttpServl
9091
AsyncContext asyncContext, String servletPath, DataBufferFactory bufferFactory, int bufferSize)
9192
throws IOException, URISyntaxException {
9293

93-
super(HttpMethod.valueOf(request.getMethod()), initUri(request), request.getContextPath() + servletPath,
94-
initHeaders(headers, request));
94+
super(HttpMethod.valueOf(request.getMethod()), initUri(request),
95+
request.getContextPath() + servletPath, initHeaders(headers, request));
9596

9697
Assert.notNull(bufferFactory, "'bufferFactory' must not be null");
9798
Assert.isTrue(bufferSize > 0, "'bufferSize' must be greater than 0");
@@ -121,14 +122,42 @@ private static MultiValueMap<String, String> createDefaultHttpHeaders(HttpServle
121122
return headers;
122123
}
123124

124-
private static URI initUri(HttpServletRequest request) throws URISyntaxException {
125-
Assert.notNull(request, "'request' must not be null");
126-
StringBuffer url = request.getRequestURL();
127-
String query = request.getQueryString();
128-
if (StringUtils.hasText(query)) {
129-
url.append('?').append(query);
125+
@SuppressWarnings("JavaExistingMethodCanBeUsed")
126+
private static URI initUri(HttpServletRequest servletRequest) {
127+
Assert.notNull(servletRequest, "'request' must not be null");
128+
String urlString = null;
129+
String query = null;
130+
boolean hasQuery = false;
131+
try {
132+
StringBuffer requestURL = servletRequest.getRequestURL();
133+
query = servletRequest.getQueryString();
134+
hasQuery = StringUtils.hasText(query);
135+
if (hasQuery) {
136+
requestURL.append('?').append(query);
137+
}
138+
urlString = requestURL.toString();
139+
return new URI(urlString);
140+
}
141+
catch (URISyntaxException ex) {
142+
if (hasQuery) {
143+
try {
144+
// Maybe malformed query, try to parse and encode it
145+
query = UriComponentsBuilder.fromUriString("?" + query).build().toUri().getRawQuery();
146+
return new URI(servletRequest.getRequestURL().toString() + "?" + query);
147+
}
148+
catch (URISyntaxException ex2) {
149+
try {
150+
// Try leaving it out
151+
return new URI(servletRequest.getRequestURL().toString());
152+
}
153+
catch (URISyntaxException ex3) {
154+
// ignore
155+
}
156+
}
157+
}
158+
throw new IllegalStateException(
159+
"Could not resolve HttpServletRequest as URI: " + urlString, ex);
130160
}
131-
return new URI(url.toString());
132161
}
133162

134163
@SuppressWarnings("NullAway")

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

+16-9
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
import org.junit.jupiter.api.BeforeEach;
2525
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.params.ParameterizedTest;
27+
import org.junit.jupiter.params.provider.CsvSource;
2628

2729
import org.springframework.http.HttpHeaders;
2830
import org.springframework.http.HttpMethod;
@@ -31,6 +33,7 @@
3133
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
3234

3335
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
3437

3538
/**
3639
* @author Arjen Poutsma
@@ -78,24 +81,28 @@ void getUriWithQueryString() {
7881
assertThat(request.getURI()).isEqualTo(uri);
7982
}
8083

81-
@Test // SPR-16414
82-
void getUriWithQueryParam() {
84+
// gh-20960
85+
@ParameterizedTest(name = "{displayName}({arguments})")
86+
@CsvSource(delimiter='|', value = {
87+
"query=foo | ?query=foo",
88+
"query=foo%%x | ?query=foo%25%25x"
89+
})
90+
void getUriWithMalformedQueryParam(String inputQuery, String expectedQuery) {
8391
mockRequest.setScheme("https");
8492
mockRequest.setServerPort(443);
8593
mockRequest.setServerName("example.com");
8694
mockRequest.setRequestURI("/path");
87-
mockRequest.setQueryString("query=foo");
88-
assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/path?query=foo"));
95+
mockRequest.setQueryString(inputQuery);
96+
assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/path" + expectedQuery));
8997
}
9098

91-
@Test // SPR-16414
92-
void getUriWithMalformedQueryParam() {
99+
@Test
100+
void getUriWithMalformedPath() {
93101
mockRequest.setScheme("https");
94102
mockRequest.setServerPort(443);
95103
mockRequest.setServerName("example.com");
96-
mockRequest.setRequestURI("/path");
97-
mockRequest.setQueryString("query=foo%%x");
98-
assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/path"));
104+
mockRequest.setRequestURI("/p%th");
105+
assertThatIllegalStateException().isThrownBy(() -> request.getURI());
99106
}
100107

101108
@Test // SPR-13876

0 commit comments

Comments
 (0)