Victor Romero opened SPR-16522 and commented
UriComponentsBuilder doesn't escape pathSegments that have an url inside:
The following code:
URI uri = UriComponentsBuilder.fromHttpUrl("http://127.0.0.1:" + randomServerPort + "/")
.pathSegment("http://url.com/within")
.build().toUri();
System.out.println(uri.toString());
Will generate:
http://127.0.0.1:43963/http://url.com/within
Project that reproduces: https://github.com/vromero/webclient-issue-uri-in-uri
Affects: 5.0.4
Reference URL: https://github.com/vromero/webclient-issue-uri-in-uri
Comment From: spring-projects-issues
Rossen Stoyanchev commented
You're missing the call to encode()
:
URI uri = UriComponentsBuilder.fromHttpUrl("http://127.0.0.1:" + randomServerPort + "/")
.pathSegment("http://url.com/within")
.build().encode().toUri();
System.out.println(uri.toString());
Comment From: mauromol
@rstoyanchev please note that as of Spring Framework 5.3.4, the Javadoc of org.springframework.web.util.UriBuilder.pathSegment(String...)
is at least misleading, because it says:
If slashes are present in a path segment, they are encoded:
builder.pathSegment("ba/z", "{id}").build("a/b")
// Results is "/ba%2Fz/a%2Fb"
But, from what I see, this is wrong: result is /ba/z/a/b
, unless I also call encode()
before build()
.
Comment From: rstoyanchev
build()
returns UriComponents
and I've no idea what you do then to see this result? If it is followed by toUriString()
that simply concatenates as explained in the Javadoc.
Comment From: mauromol
Ok, I understood what cheated me.
If you do:
UriComponentsBuilder b = UriComponentsBuilder.fromHttpUrl("http://www.example.com");
System.out.println(b.pathSegment("ba/z", "{id}").build(Map.of("id", "a/b")));
you'll get: http://www.example.com/ba%2Fz/a%2Fb
, which is coherent with the Javadoc.
But if you do:
UriComponentsBuilder b = UriComponentsBuilder.fromHttpUrl("http://www.example.com");
System.out.println(b.pathSegment("ba/z", "{id}") .buildAndExpand(Map.of("id", "a/b")).toUri());
you'll get: http://www.example.com/ba/z/a/b
, unless you add encode()
, either before or after the call to buildAndExpand()
:
UriComponentsBuilder b = UriComponentsBuilder.fromHttpUrl("http://www.example.com");
System.out.println(b.pathSegment("ba/z", "{id}").buildAndExpand(Map.of("id", "a/b")).encode().toUri());
I see now that build(Object...)
and build(Map<String, ?> uriVariables)
return a URI
and perform encoding, while build()
, build(boolean)
, buildAndExpand(Object...)
and buildAndExpand(Map)
return a UriComponents
instead and do NOT perform encoding. You must dig into the implementation of UriComponents
to understand what is really going on there.
From a user point of view, these two variants:
b.pathSegment("ba/z", "{id}").build(Map.of("id", "a/b"))
b.pathSegment("ba/z", "{id}").buildAndExpand(Map.of("id", "a/b")).toUri()
look very similar to me and I can't find anywhere in the Javadoc any "WARNING" about their different behaviour.
The Javadoc of build(Map<String, ?> uriVariables)
says:
Build(*) a URI instance and replaces URI template variables with the values from a map
while the Javadoc of buildAndExpand(Map<String, ?>)
says:
Build a UriComponents instance and replaces URI template variables with the values from a map.
So the only difference is the result type, but having UriComponents
a toUri()
methods sounds like you should get the same final result, but you won't. No mention at all on what the two methods do w.r.t. encoding.
Also, org.springframework.web.util.UriComponents.toUri()
seems to suggest that, if the UriComponents is not encoded, the encoding step will be done by the multi-argument URI
constructor, but given the example above you see that the result is indeed different. So once again, the user may expect the above two statements to be equivalent, but they are not.
(*) as a final minor note, see the incoherent use of the third-person: "build" - "replaces".