Affects: Tested on 4,5,6.
URIComponentsBuilder
double encodes the request parameter when replaceQueryParam
is called. This pattern is used in spring-cloud-gateway
Here is an accompanying unit test for UriComponentsBuilderTests.java
class:
@Test
void doubleEncodingOfQueryParamReplacement() throws Exception {
String url = "http://localhost:8080/app/api/device/queues?devices=100%2C200%2C300%2C400&tenant=root";
URI uri = new URI(url);
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(uri);
uriComponentsBuilder.replaceQueryParam("tenant", "default");
URI replaced = uriComponentsBuilder.build().toUri();
assertThat(replaced.toString()).isEqualTo("http://localhost:8080/app/api/device/queues?devices=100%2C200%2C300%2C400&tenant=default");
// ^Fails with "http://localhost:8080/app/api/device/queues?devices=100%252C200%252C300%252C400&tenant=default"
}
Comment From: VenkatramanMuthukrishnan
Hello @bahrigencsoy , https://github.com/spring-projects/spring-framework/pull/32237 raised a PR, Pls review
Comment From: rstoyanchev
From the Javadoc of replaceQueryParam
:
please, review the Javadoc of queryParam(String, Object...) for further notes on the treatment and encoding of individual query parameters.
That in turn says:
Note: encoding, if applied, will only encode characters that are illegal in a query parameter name or value such as "=" or "&". All others that are legal as per syntax rules in RFC 3986 are not encoded. This includes "+" which sometimes needs to be encoded to avoid its interpretation as an encoded space. Stricter encoding may be applied by using a URI template variable along with stricter encoding on variable values. For more details please read the "URI Encoding" section of the Spring Framework reference.
In other words, this is expected behavior. We will encode by default, but you have more options.
One option is to indicate that all components are already encoded, but keep in mind that in this case you need to encode yourself any updated components (e.g. via UriUtiles
) before passing them in:
String url = "http://localhost:8080/app/api/device/queues?devices=100%2C200%2C300%2C400&tenant=root";
URI uri = new URI(url);
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(uri);
uriComponentsBuilder.replaceQueryParam("tenant", UriUtils.encodeQueryParam("default", UTF_8));
URI replaced = uriComponentsBuilder.build().toUri();
assertThat(replaced.toString()).isEqualTo("http://localhost:8080/app/api/device/queues?devices=100%2C200%2C300%2C400&tenant=default");
You can also use an encoding mode where only URI template variable values are encoded:
String url = "http://localhost:8080/app/api/device/queues?devices=100%2C200%2C300%2C400&tenant=root";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
URI replaced = factory.uriString(url)
.replaceQueryParam("tenant", "default")
.build();
assertThat(replaced.toString()).isEqualTo("http://localhost:8080/app/api/device/queues?devices=100%2C200%2C300%2C400&tenant=default");
You can read the section on URI Encoding in the reference docs.
Comment From: nickdrummond
Just in case anyone else missed this, you need to use uriComponentsBuilder.build(true) to indicate the params are already encoded.