From 107e8d36bf763045954d0416155aafa026703f07 Mon Sep 17 00:00:00 2001 From: raccoonback Date: Tue, 13 May 2025 16:29:35 +0900 Subject: [PATCH] Fix incorrect handling of '+' character in query parameter encoding Signed-off-by: raccoonback --- .../gateway/server/mvc/common/MvcUtils.java | 17 ++++++++++++++++- .../handler/ProxyExchangeHandlerFunction.java | 3 +-- .../ProxyExchangeHandlerFunctionTest.java | 7 ++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java index 05c073ed..ca81cc65 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2025 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. @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -40,11 +41,14 @@ import org.springframework.http.HttpInputMessage; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.StreamUtils; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.function.ServerRequest; import org.springframework.web.servlet.support.RequestContextUtils; import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.util.UriUtils; import static org.springframework.web.servlet.function.RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE; @@ -263,6 +267,17 @@ public abstract class MvcUtils { urls.add(url); } + public static MultiValueMap encodeQueryParams(MultiValueMap params) { + MultiValueMap encodedQueryParams = new LinkedMultiValueMap<>(params.size()); + for (Map.Entry> entry : params.entrySet()) { + for (String value : entry.getValue()) { + encodedQueryParams.add(UriUtils.encode(entry.getKey(), StandardCharsets.UTF_8), + UriUtils.encode(value, StandardCharsets.UTF_8)); + } + } + return CollectionUtils.unmodifiableMultiValueMap(encodedQueryParams); + } + private record ByteArrayInputMessage(ServerRequest request, ByteArrayInputStream body) implements HttpInputMessage { @Override diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunction.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunction.java index 94b78376..5a724800 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunction.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunction.java @@ -36,7 +36,6 @@ import org.springframework.web.servlet.function.HandlerFunction; import org.springframework.web.servlet.function.ServerRequest; import org.springframework.web.servlet.function.ServerResponse; import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; /** * @author raccoonback @@ -139,7 +138,7 @@ public class ProxyExchangeHandlerFunction boolean encoded = containsEncodedQuery(serverRequest.uri(), serverRequest.params()); MultiValueMap params = serverRequest.params(); if (!encoded) { - params = UriUtils.encodeQueryParams(serverRequest.params()); + params = MvcUtils.encodeQueryParams(serverRequest.params()); } return params; } diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunctionTest.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunctionTest.java index 3edf7ad7..e8f650c7 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunctionTest.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunctionTest.java @@ -103,7 +103,7 @@ class ProxyExchangeHandlerFunctionTest { function.onApplicationEvent(null); MockHttpServletRequest servletRequest = MockMvcRequestBuilders - .get("http://localhost/é?foo=value1 value2&bar=value3=") + .get("http://localhost/é?foo=value1 value2&bar=value3=&qux=value4+") .buildRequest(null); servletRequest.setAttribute(MvcUtils.GATEWAY_REQUEST_URL_ATTR, URI.create("http://localhost:8080")); ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); @@ -112,10 +112,11 @@ class ProxyExchangeHandlerFunctionTest { URI uri = proxyExchange.getRequest().getUri(); - assertThat(uri).hasToString("http://localhost:8080/%C3%A9?foo=value1%20value2&bar=value3%3D") + assertThat(uri).hasToString("http://localhost:8080/%C3%A9?foo=value1%20value2&bar=value3%3D&qux=value4%2B") .hasPath("/é") .hasParameter("foo", "value1 value2") - .hasParameter("bar", "value3="); + .hasParameter("bar", "value3=") + .hasParameter("qux", "value4+"); } private class TestProxyExchange extends AbstractProxyExchange {