Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the proper way to escape URL variables with Spring's RestTemplate when calling a Spring RestController?

When calling RestTemplate.exchange to do a get request, such as:

String foo = "fo+o";
String bar = "ba r";
restTemplate.exchange("http://example.com/?foo={foo}&bar={bar}", HttpMethod.GET, null, foo, bar)

what's the proper to have the URL variables correctly escaped for the get request?

Specifically, how do I get pluses (+) correctly escaped because Spring is interpreting as spaces, so, I need to encode them.

I tried using UriComponentsBuilder like this:

String foo = "fo+o";
String bar = "ba r";
UriComponentsBuilder ucb = UriComponentsBuilder.fromUriString("http://example.com/?foo={foo}&bar={bar}");
System.out.println(ucb.build().expand(foo, bar).toUri());
System.out.println(ucb.build().expand(foo, bar).toString());
System.out.println(ucb.build().expand(foo, bar).toUriString());
System.out.println(ucb.build().expand(foo, bar).encode().toUri());
System.out.println(ucb.build().expand(foo, bar).encode().toString());
System.out.println(ucb.build().expand(foo, bar).encode().toUriString());
System.out.println(ucb.buildAndExpand(foo, bar).toUri());
System.out.println(ucb.buildAndExpand(foo, bar).toString());
System.out.println(ucb.buildAndExpand(foo, bar).toUriString());
System.out.println(ucb.buildAndExpand(foo, bar).encode().toUri());
System.out.println(ucb.buildAndExpand(foo, bar).encode().toString());
System.out.println(ucb.buildAndExpand(foo, bar).encode().toUriString());

and that printed:

http://example.com/?foo=fo+o&bar=ba%20r
http://example.com/?foo=fo+o&bar=ba r
http://example.com/?foo=fo+o&bar=ba r
http://example.com/?foo=fo+o&bar=ba%20r
http://example.com/?foo=fo+o&bar=ba%20r
http://example.com/?foo=fo+o&bar=ba%20r
http://example.com/?foo=fo+o&bar=ba%20r
http://example.com/?foo=fo+o&bar=ba r
http://example.com/?foo=fo+o&bar=ba r
http://example.com/?foo=fo+o&bar=ba%20r
http://example.com/?foo=fo+o&bar=ba%20r
http://example.com/?foo=fo+o&bar=ba%20r

The space is correctly escaped in some instances, but the plus is never escaped.

I also tried UriTemplate like this:

String foo = "fo+o";
String bar = "ba r";
UriTemplate uriTemplate = new UriTemplate("http://example.com/?foo={foo}&bar={bar}");
Map<String, String> vars = new HashMap<>();
vars.put("foo", foo);
vars.put("bar", bar);
URI uri = uriTemplate.expand(vars);
System.out.println(uri);

with the exact same result:

http://example.com/?foo=fo+o&bar=ba%20r
like image 656
pupeno Avatar asked May 20 '18 06:05

pupeno


People also ask

What is exchange method in RestTemplate?

To put it simply, the set of exchange functions are the most general/capable methods provided by RestTemplate , so you can use exchange when none of the other methods provides a complete enough parameter set to meet your needs.


2 Answers

I'm starting to believe this is a bug and I reported here: https://jira.spring.io/browse/SPR-16860

Currently, my workaround is this:

String foo = "fo+o";
String bar = "ba r";
String uri = UriComponentsBuilder.
    fromUriString("http://example.com/?foo={foo}&bar={bar}").
    buildAndExpand(vars).toUriString();
uri = uri.replace("+", "%2B"); // This is the horrible hack.
try {
    return new URI(uriString);
} catch (URISyntaxException e) {
    throw new RuntimeException("UriComponentsBuilder generated an invalid URI.", e);
}

which is a horrible hack that might fail in some situations.

like image 69
pupeno Avatar answered Oct 23 '22 02:10

pupeno


I think your problem here is that RFC 3986, on which UriComponents and by extension UriTemplate are based, does not mandate the escaping of + in a query string.

The spec's view on this is simply:

sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
                  / "*" / "+" / "," / ";" / "="

pchar       = unreserved / pct-encoded / sub-delims / ":" / "@"

query       = *( pchar / "/" / "?" )

URI         = scheme ":" hier-part [ "?" query ] [ "#" fragment ]

If your web framework (Spring MVC, for example!) is interpreting + as a space, then that is its decision and not required under the URI spec.

With reference to the above, you will also see that !$'()*+,; are not escaped by UriTemplate. = and & are escaped because Spring has taken an "opinionated" view of what a query string looks like -- a sequence of key=value pairs.

Likewise, #[] and whitespace are escaped because they are illegal in a query string under the spec.

Granted, none of this is likely to be any consolation to you if you just quite reasonably want your query parameters escaped!

To actually encode the query params so your web framework can tolerate them, you could use something like org.springframework.web.util.UriUtils.encode(foo, charset).

like image 11
ryanp Avatar answered Oct 23 '22 00:10

ryanp