Encoding query parameters with UriComponentsBuilder
Asked Answered
M

2

10

I am struggling to understand the behavior of UriComponentsBuilder. I want to use it to encode a URL in a query parameter, however it appears to only escape % characters, but not other necessary characters such as &.

An example of a URL in a query parameter that is not encoded at all:

UriComponentsBuilder.fromUri("http://example.com/endpoint")
                    .queryParam("query", "/path?foo=foo&bar=bar")
                    .build();

Output: http://example.com/endpoint?query=/path?foo=foo&bar=bar

This is not correct, because the unencoded & causes bar=bar to be interpreted as a query parameter to /endpoint instead of /path.

However, if I use an input that contains a % character::

UriComponentsBuilder.fromUri("http://example.com/endpoint")
                    .queryParam("query", "/path?foo=%20bar")
                    .build();

Output: http://example.com/endpoint?query=/path?foo=%2520bar

The % character is escaped.

It seems inconsistent that UriComponentsBuilder would automatically escape the % characters but not the other reserved characters.

What is the correct process to encode a URL into a query parameter with UriComponentsBuilder?

Mckissick answered 10/11, 2017 at 7:24 Comment(2)
But, as I understand, & is a valid character in URL. Barring certain characters, everything else must be url encoded.Strove
Did you find a solution? I am facing a similar problemOverjoy
F
14

In your example the build UriComponents object is not encoded or normalised. To ensure that encoding is applied:

  1. Encode it yourself by calling encode() method (see also normalize() method):

    UriComponents u = UriComponentsBuilder.fromHttpUrl("http://example.com/endpoint")
      .queryParam("query", "/path?foo=foo&bar=bar")
      .build()
      .encode(); 
    // http://example.com/endpoint?query=/path?foo%3Dfoo%26bar%3Dbar
    
  2. Use build(true) method if the parameters used for constructing the UriComponents are already encoded

    UriComponents u = UriComponentsBuilder.fromHttpUrl("http://example.com/endpoint")
      .queryParam("query", "/path?foo=foo&bar=bar")
      .build(true);
    // IllegalArgumentException: Invalid character '=' for QUERY_PARAM in "/path?foo=foo&bar=bar"
    

Under the hood HierarchicalUriComponents.encode(String) method performs the actual encoding. After few internal calls it invokes HierarchicalUriComponents.encodeBytes(byte[], HierarchicalUriComponents.Type) where HierarchicalUriComponents.Type enum controls which characters are allowed in which part of the URL. This check is based on RFC 3986. In short, Spring has it's own encoding logic for every single part of the URL.

Filigreed answered 3/8, 2018 at 13:32 Comment(3)
I'm not understanding the later part of this answer. What will I have to do in order to fix it?Arvie
great. I didn't know build()can take an argument to avoid encoding.Arvie
What is the normalize() method you referred to? It doesn't exist on UriComponentsBuilder. There is one on UriComponents but it's not related to encoding.Mckissick
Z
2
  1. The syntax is incorrect as you are using UriComponentsBuilder.fromUri() with String parameter instead of an URI. If you want to pass the URL as a String use it as :

      UriComponentsBuilder
      .fromUriString("http://example.com/endpoint")
      .queryParam("query", URLEncoder.encode("/path?foo=%20bar","UTF-8"))
      .build();
    
  2. & is a valid URL character so it will not be encoded but % is not that's why it gets decoded to %25.

If you want to see how to use the same with RestTemplate refer : RestTemplate.exchange() does not encode '+'?

Zachariahzacharias answered 3/8, 2018 at 15:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.