Affects: 5.2.8.RELEASE
There is an issue when using UriComponentsBuilder to build a URI for an address that contains a . followed by a number. The address is valid as an internal Kubernetes cluster address but fails the UriComponentsBuilder validation for the "illegal character" of "." This issue can be overcome by wrapping the String output in a URL object and then calling the .toUri method on the result, where no error is thrown.
See following code for example:
@Test
public void test() throws URISyntaxException, MalformedURLException {
UriComponentsBuilder uriComponentsBuilder = getUriBuilder();
String query = String.format("(eq,attribute,%s)", "23426342786242");
//Without .toUri and Cast to URL does not throw error results in:
// http://deployment-name.20-14/api/path/to/resource?filter=(eq,attribute,23426342786242)
String noToUri = uriComponentsBuilder
.queryParam("filter", query).build().toString();
URI uri2 = new URL(noToUri).toURI();
//With .toUri straight from build results in :
// java.lang.IllegalStateException: Could not create URI object: Illegal character in hostname at index 23:
// http://deployment-name.20-14/api/path/to/resource?filter=(eq,attribute,23426342786242)
URI test =getUriBuilder()
.queryParam("filter", query).build().toUri();
}
private UriComponentsBuilder getUriBuilder(String... pathSegments) {
return UriComponentsBuilder.fromHttpUrl("http://deployment-name.20-14")
.path("/api/path/to/resource")
.pathSegment(pathSegments);
}
Comment From: jhoeller
@rstoyanchev @poutsma This looks valid in that we could relax the pattern a bit there, accepting such Kubernetes cluster addresses as well? If this doesn't have any negative side effects that I might be missing, I suppose we can apply this to 5.2.10...
Comment From: bclozel
This is not strictly related to Spring Framework actually, but a consequence of which method we're using to instantiate a URI.
In the first case, we're implicitly creating the URI with URI uri = new URI(url.toString());
In java.net, this method does not perform additional validations with its parser, namely the URI.Parser.requireServerAuthority.
In the second case, our UriComponentsBuilder has not encoded the resulting URI yet so we can use the alternative constructor new URI(getScheme(), getUserInfo(), getHost(), getPort(), path, getQuery(), getFragment()). This constructor performs the additional URI.Parser.requireServerAuthority validation and fails.
We can reproduce this without involving Spring Framework at all:
➜ jshell
| Welcome to JShell -- Version 15
| For an introduction type: /help intro
jshell> new URI("http", "", "deployment-name.20-14", -1, "/path", "", "")
| Exception java.net.URISyntaxException: Illegal character in hostname at index 24: http://@deployment-name.20-14/path?#
| at URI$Parser.fail (URI.java:2963)
| at URI$Parser.parseHostname (URI.java:3506)
| at URI$Parser.parseServer (URI.java:3347)
| at URI$Parser.parseAuthority (URI.java:3266)
| at URI$Parser.parseHierarchical (URI.java:3208)
| at URI$Parser.parse (URI.java:3164)
| at URI.<init> (URI.java:708)
| at (#1:1)
jshell> var uri = URI.create("http://deployment-name.20-14/path")
uri ==> http://deployment-name.20-14/path
You can work around this issue by encoding the UriComponents before calling the toURI() method:
@Test
void gh25784() {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl("http://deployment-name.20-14")
.path("/api/path/to/resource");
String query = String.format("(eq,attribute,%s)", "23426342786242");
URI uri = uriComponentsBuilder.queryParam("filter", query).build().encode().toUri();
assertThat(uri.toASCIIString()).isEqualTo("http://deployment-name.20-14/api/path/to/resource?filter=(eq,attribute,23426342786242)");
}
I think we need to consider this as a limitation of the JDK URI; we can't really encode the URI builder before turning it into an URI in all cases, because we should reflect the behavior difference of the JDK (and we have a test for that in org.springframework.web.util.UriComponentsTests#toUriNotEncoded.
In summary, I'm closing this issue, but with a known workaround: you should encode the URI builder with the appropriate charset before turning this into an URI.
Thanks!
Comment From: bclozel
Adding this comment for future reference.
The URI implementation calls this case out with:
// for a fully qualified hostname check that the rightmost
// label starts with an alpha character.
if (l > start && !match(charAt(l), L_ALPHA, H_ALPHA)) {
fail("Illegal character in hostname", l);
}
The ABNF defined by RFC 2396 looks like alphanum are allowed after the last -, and it seems to be acknowledged by the OpenJDK team in JDK-8188305.