Encoding URI query parameter with slash (without double encoding) and special characters in Java

168 views Asked by At

I have 2 string variables with sample input:

  1. content: "m/=a"
  2. sms: "TEST123 ~!@#$%^&*()_+{}:<>?"

I need to send these two variables as query params using GET method. Below is my code.

Creating rest template:

DefaultUriBuilderFactory defaultUriBuilderFactory = new DefaultUriBuilderFactory();
defaultUriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);

RestTemplate restTemplate = constructRestTemplate(cpConnectivity);
restTemplate.setUriTemplateHandler(defaultUriBuilderFactory);

Encode function:

return URLEncoder.encode(value, StandardCharsets.UTF_8);

Sending request:

UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.newInstance()
.queryParam("content", encode(content)
.queryParam("sms", sms.replace(" ", "+"));

ResponseEntity<String> responseEntity = restTemplate.exchange(uriComponentsBuilder.build(false).toUriString(), HttpMethod.GET, httpEntity, String.class);

The expected query param values for those variables is:

content: "m%2F%3Da"

Special characters should be encoded, including slash (/).

sms: "TEST123+~!@%23$%25%5E%26*()_+%7B%7D:%3C%3E?"

Space should be replaced with + sign, reserved special characters should be encoded.

Using the existing code, content is encoded as expected, but sms cannot contain reserved special characters, otherwise it will give errors like "Illegal character in query at index 45:" and "Malformed escape pair at index 151: ".

I have tried other solutions:

  • uriComponentsBuilder.build(true).toUriString()
  • uriComponentsBuilder.build(false).encode().toUriString()
  • uriComponentsBuilder.build(true).encode().toUriString()
  • uriComponentsBuilder.encode().toUriString()
  • defaultUriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.TEMPLATE_AND_VALUES);
  • defaultUriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
  • defaultUriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT);
  • URLDecoder.decode first then encode()

Without calling encode(), slash (/) will not be encoded, because it is a valid character. If I call encode(), the problem is % is double encoded, so slash (/) will be %252F. If I set .build(true), I get errors like "Invalid character" or "Invalid encoded sequence".

2

There are 2 answers

1
Reilas On

"... I need to send these two variables as query params using GET method. ..."

Here is an example, which will encode all non-alphanumerics.

String encode(String s) {
    StringBuilder b = new StringBuilder();
    for (char c : s.toCharArray())
        if (c >= 'a' && c <= 'z') b.append(c);
        else if (c >= 'A' && c <= 'Z') b.append(c);
        else if (c >= '0' && c <= '9') b.append(c);
        else b.append("%%%x".formatted((int) c));
    return b.toString();
}
String decode(String s) {
    StringBuilder b = new StringBuilder();
    Pattern p = Pattern.compile("(?i)%[a-f\\d]{2}");
    Matcher m = p.matcher(s);
    char c;
    while (m.find()) {
        c = (char) Integer.parseInt(m.group().substring(1), 16);
        m.appendReplacement(b, Matcher.quoteReplacement(String.valueOf(c)));
    }
    m.appendTail(b);
    return b.toString();
}

And, here is an example usage.

String s;
out.println(s = encode("TEST123 ~!@#$%^&*()_+{}:<>?"));
out.println(decode(s));

Output

TEST123%20%7e%21%40%23%24%25%5e%26%2a%28%29%5f%2b%7b%7d%3a%3c%3e%3f
TEST123 ~!@#$%^&*()_+{}:<>?
0
Bryan Bezter On

I still use the same rest template code and encode function. I adjusted the code when sending request to:

UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.newInstance()
.queryParam("content", "contentplaceholder")
.queryParam("sms", sms.replace(" ", "+"));

String url = uriComponentsBuilder.build().encode().toUriString();
url = url.replace("contentplaceholder", encode(request.getMsisdn()));
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);

UriComponentsBuilder encodes the unsafe special characters. Then I manually encode the content param, then send the request.