Skip to content

Preserve original encoding of query parameters in ProxyExchangeHandlerFunction #3789

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

raccoonback
Copy link
Contributor

Description

This PR improves ProxyExchangeHandlerFunction to ensure that the original encoding of query parameters is preserved when proxying requests.

The return value of ServerRequest.params() is already decoded, so reconstructing the query using it results in the loss of the original encoding information.
To preserve the original encoded state, this PR removes the unnecessary query parameter replacement performed in ProxyExchangeHandlerFunction.handle().

For example.

[AS-IS]
"http://localhost/path?foo=A&bar=B%3D" -> "http://localhost/path?foo=A&bar=B="

[TO-BE]
"http://localhost/path?foo=A&bar=B%3D" -> "http://localhost/path?foo=A&bar=B%3D"

Related issue

@ryanjbaxter
Copy link
Contributor

Is this only present in the main branch?

@raccoonback
Copy link
Contributor Author

@ryanjbaxter
Good point.
Since this seems to occur starting from 4.1.x, I'll update it based on the 4.1.x branch.

@raccoonback raccoonback force-pushed the remove-decoded-query-param-replacement branch from e040e28 to ed5c74b Compare May 13, 2025 00:44
@raccoonback raccoonback changed the base branch from main to 4.1.x May 13, 2025 00:44
spring-builds and others added 2 commits May 13, 2025 09:47
Signed-off-by: raccoonback <[email protected]>
… query parameters during proxy request

ref. spring-cloud#3759

Signed-off-by: raccoonback <[email protected]>
@raccoonback raccoonback force-pushed the remove-decoded-query-param-replacement branch from ed5c74b to f8882d2 Compare May 13, 2025 00:47
@raccoonback raccoonback force-pushed the remove-decoded-query-param-replacement branch from f8882d2 to 9217b37 Compare May 13, 2025 01:02
@Shawyeok
Copy link

Related #3795

@Shawyeok
Copy link

Since this seems to occur starting from 4.1.x, I'll update it based on the 4.1.x branch.

I'm new to spring-cloud-gateway contribution workflow, does it should be merge to the main branch then have it cherry-picked to the 4.1.x branch?

boolean encoded = containsEncodedQuery(serverRequest.uri(), serverRequest.params());
MultiValueMap<String, String> params = serverRequest.params();
if (!encoded) {
params = UriUtils.encodeQueryParams(serverRequest.params());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since serverRequest.params() can be modified by user-defined filters that precede this step, we should always encode the parameters to ensure correctness.

Additionally, note that the plus symbol (+) is not encoded by UriUtils.encodeQueryParams. To handle this case properly, we should use UriUtils.encode instead, which will correctly encode all special characters, including the plus symbol, you could see more detail in #3795.

	private static MultiValueMap<String, String> encodeQueryParams(MultiValueMap<String, String> params) {
		Charset charset = StandardCharsets.UTF_8;
		MultiValueMap<String, String> result = new LinkedMultiValueMap<>(params.size());
		for (Map.Entry<String, List<String>> entry : params.entrySet()) {
			for (String value : entry.getValue()) {
				result.add(UriUtils.encode(entry.getKey(), charset), UriUtils.encode(value, charset));
			}
		}
		return result;
	}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Shawyeok
Good point. I'll check it.

assertThat(uri).hasToString("http://localhost:8080/%C3%A9?foo=value1%20value2&bar=value3%3D")
.hasPath("/é")
.hasParameter("foo", "value1 value2")
.hasParameter("bar", "value3=");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add a case for encode plus symbol (%2B).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Shawyeok
I have updated it.

@raccoonback
Copy link
Contributor Author

@Shawyeok

Since this seems to occur starting from 4.1.x, I'll update it based on the 4.1.x branch.

I'm new to spring-cloud-gateway contribution workflow, does it should be merge to the main branch then have it cherry-picked to the 4.1.x branch?

As far as I know, the changes are applied to 4.1.x, 4.2.x, and main.

@raccoonback raccoonback requested a review from Shawyeok May 13, 2025 07:35
@ryanjbaxter
Copy link
Contributor

@Shawyeok we merge into the lowest maintained branch and then merge the change forward to the other branches

@Shawyeok
Copy link

@Shawyeok we merge into the lowest maintained branch and then merge the change forward to the other branches

@ryanjbaxter Thanks for letting me know! That's interesting! Does this approach help to reduce the cost of maintaining multiple branches? Is there any documentation or notes where I can learn more about this?

@@ -84,14 +87,14 @@ private void init() {
@Override
public ServerResponse handle(ServerRequest serverRequest) {
URI uri = uriResolver.apply(serverRequest);
boolean encoded = containsEncodedQuery(serverRequest.uri(), serverRequest.params());
MultiValueMap<String, String> params = ensureEncodedQueryParameters(serverRequest);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User can modify the query parameters in preceding filters, for example, add additional parameters to the request, these parameters (serverRequest.params()) should be encoded regardless of whether the original request url contains % or not.

Suggested change
MultiValueMap<String, String> params = ensureEncodedQueryParameters(serverRequest);
MultiValueMap<String, String> params = MvcUtils.encodeQueryParams(serverRequest.params());

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Shawyeok

First of all, If query parameters were added in a preceding filter, I believe that filter should be responsible for ensuring those parameters are properly encoded.

serverRequest.params() relies on HttpServletRequest.getParameterMap(), and from what I can tell, most major servlet containers (Tomcat, Jetty, Undertow) return already-decoded values.

So as you pointed out, it's likely safe to assume that serverRequest.params() provides decoded query parameters.

(That said, if we can guarantee that serverRequest.params() always returns decoded values, then it's fine to encode them again here. But if that guarantee isn't solid, we risk double-encoding parameters that were already percent-encoded by earlier filters.)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we can guarantee that serverRequest.params() always returns decoded values, then it's fine to encode them again here.

IMO, the only reliable way to handle URI encoding is to treat the values returned by serverRequest.params() as raw (i.e. decoded values). We're unable to determine whether a string is URI encoded or not, for example: Is %25 a encoded string? We cannot tell, cause user might intend to send the literal string %25 rather than the percent symbol. Therefore, we should consistently treat all parameter values as decoded and handle encoding at this stage.

@spencergibb
Copy link
Member

@ryanjbaxter Thanks for letting me know! That's interesting! Does this approach help to reduce the cost of maintaining multiple branches? Is there any documentation or notes where I can learn more about this?

It does help. There's no documentation on it as we are a small team.

… parameters from serverRequest

Signed-off-by: raccoonback <[email protected]>
@ryanjbaxter ryanjbaxter merged commit 2158d6a into spring-cloud:4.1.x May 15, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Strip Prefix breaking OAuth Flow - spring-cloud-gateway-server-mvc-4.3.0-M3
6 participants