diff --git a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java index 60ac5932475..1bddd7a4e6b 100644 --- a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java +++ b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java @@ -182,7 +182,13 @@ final class HierarchicalUriComponents extends UriComponents { } String encodedScheme = encodeUriComponent(this.getScheme(), encoding, Type.SCHEME); String encodedUserInfo = encodeUriComponent(this.userInfo, encoding, Type.USER_INFO); - String encodedHost = encodeUriComponent(this.host, encoding, Type.HOST); + String encodedHost; + if(StringUtils.hasLength(this.host) && this.host.startsWith("[")) { + encodedHost = encodeUriComponent(this.host, encoding, Type.HOST_IPV6); + } else { + encodedHost = encodeUriComponent(this.host, encoding, Type.HOST); + } + PathComponent encodedPath = this.path.encode(encoding); MultiValueMap encodedQueryParams = new LinkedMultiValueMap(this.queryParams.size()); @@ -468,6 +474,12 @@ final class HierarchicalUriComponents extends UriComponents { return isUnreserved(c) || isSubDelimiter(c); } }, + HOST_IPV6 { + @Override + public boolean isAllowed(int c) { + return isUnreserved(c) || isSubDelimiter(c) || '[' == c || ']' == c || ':' == c; + } + }, PORT { @Override public boolean isAllowed(int c) { diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index 006abc3c4be..f9d20ebf1bf 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -64,7 +64,11 @@ public class UriComponentsBuilder { private static final String USERINFO_PATTERN = "([^@/]*)"; - private static final String HOST_PATTERN = "([^/?#:]*)"; + private static final String HOST_IPv4_PATTERN = "[^\\[/?#:]*"; + + private static final String HOST_IPV6_PATTERN = "\\[[\\p{XDigit}\\:\\.]*[%\\p{Alnum}]*\\]"; + + private static final String HOST_PATTERN = "("+HOST_IPV6_PATTERN + "|" + HOST_IPv4_PATTERN + ")"; private static final String PORT_PATTERN = "(\\d*)"; @@ -226,7 +230,11 @@ public class UriComponentsBuilder { String scheme = m.group(1); builder.scheme((scheme != null) ? scheme.toLowerCase() : scheme); builder.userInfo(m.group(4)); - builder.host(m.group(5)); + String host = m.group(5); + if(StringUtils.hasLength(scheme) && !StringUtils.hasLength(host)) { + throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL"); + } + builder.host(host); String port = m.group(7); if (StringUtils.hasLength(port)) { builder.port(Integer.parseInt(port)); diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java index beb276fc3c6..a62e593b219 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java @@ -169,6 +169,31 @@ public class UriComponentsBuilderTests { assertEquals("https", UriComponentsBuilder.fromHttpUrl("HTTPS://www.google.com").build().getScheme()); } + // SPR-10539 + + @Test(expected = IllegalArgumentException.class) + public void fromHttpUrlStringInvalidIPv6Host() throws URISyntaxException { + UriComponents result = UriComponentsBuilder + .fromHttpUrl("http://[1abc:2abc:3abc::5ABC:6abc:8080/resource").build().encode(); + } + + // SPR-10539 + + @Test + public void fromUriStringIPv6Host() throws URISyntaxException { + UriComponents result = UriComponentsBuilder + .fromUriString("http://[1abc:2abc:3abc::5ABC:6abc]:8080/resource").build().encode(); + assertEquals("[1abc:2abc:3abc::5ABC:6abc]",result.getHost()); + + UriComponents resultWithScopeId = UriComponentsBuilder + .fromUriString("http://[1abc:2abc:3abc::5ABC:6abc%eth0]:8080/resource").build().encode(); + assertEquals("[1abc:2abc:3abc::5ABC:6abc%25eth0]",resultWithScopeId.getHost()); + + UriComponents resultIPv4compatible = UriComponentsBuilder + .fromUriString("http://[::192.168.1.1]:8080/resource").build().encode(); + assertEquals("[::192.168.1.1]",resultIPv4compatible.getHost()); + } + @Test public void path() throws URISyntaxException { UriComponentsBuilder builder = UriComponentsBuilder.fromPath("/foo/bar");