Skip to content

Commit 2dd4480

Browse files
committed
Add IPv6 support in RestTemplate
Prior to this commit, RestTemplate would not would not accept IPv6 raw addresses in URLs because UriComponentsBuilder would not parse/encode the Host part correctly. The UriComponentsBuilder now parses and encode raw IPv6 addresses in the "[1abc:2abc:3abc::5ABC:6abc]" format and also supports the use of IPv6 scope_ids (see JDK8 java.net.Inet6Address), like "[1abc:2abc:3abc::5ABC:6abc%eth0]". Issue: SPR-10539
1 parent beaf699 commit 2dd4480

File tree

3 files changed

+48
-3
lines changed

3 files changed

+48
-3
lines changed

spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,13 @@ public HierarchicalUriComponents encode(String encoding) throws UnsupportedEncod
182182
}
183183
String encodedScheme = encodeUriComponent(this.getScheme(), encoding, Type.SCHEME);
184184
String encodedUserInfo = encodeUriComponent(this.userInfo, encoding, Type.USER_INFO);
185-
String encodedHost = encodeUriComponent(this.host, encoding, Type.HOST);
185+
String encodedHost;
186+
if(StringUtils.hasLength(this.host) && this.host.startsWith("[")) {
187+
encodedHost = encodeUriComponent(this.host, encoding, Type.HOST_IPV6);
188+
} else {
189+
encodedHost = encodeUriComponent(this.host, encoding, Type.HOST);
190+
}
191+
186192
PathComponent encodedPath = this.path.encode(encoding);
187193
MultiValueMap<String, String> encodedQueryParams =
188194
new LinkedMultiValueMap<String, String>(this.queryParams.size());
@@ -468,6 +474,12 @@ public boolean isAllowed(int c) {
468474
return isUnreserved(c) || isSubDelimiter(c);
469475
}
470476
},
477+
HOST_IPV6 {
478+
@Override
479+
public boolean isAllowed(int c) {
480+
return isUnreserved(c) || isSubDelimiter(c) || '[' == c || ']' == c || ':' == c;
481+
}
482+
},
471483
PORT {
472484
@Override
473485
public boolean isAllowed(int c) {

spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,11 @@ public class UriComponentsBuilder {
6464

6565
private static final String USERINFO_PATTERN = "([^@/]*)";
6666

67-
private static final String HOST_PATTERN = "([^/?#:]*)";
67+
private static final String HOST_IPv4_PATTERN = "[^\\[/?#:]*";
68+
69+
private static final String HOST_IPV6_PATTERN = "\\[[\\p{XDigit}\\:\\.]*[%\\p{Alnum}]*\\]";
70+
71+
private static final String HOST_PATTERN = "("+HOST_IPV6_PATTERN + "|" + HOST_IPv4_PATTERN + ")";
6872

6973
private static final String PORT_PATTERN = "(\\d*)";
7074

@@ -226,7 +230,11 @@ public static UriComponentsBuilder fromHttpUrl(String httpUrl) {
226230
String scheme = m.group(1);
227231
builder.scheme((scheme != null) ? scheme.toLowerCase() : scheme);
228232
builder.userInfo(m.group(4));
229-
builder.host(m.group(5));
233+
String host = m.group(5);
234+
if(StringUtils.hasLength(scheme) && !StringUtils.hasLength(host)) {
235+
throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL");
236+
}
237+
builder.host(host);
230238
String port = m.group(7);
231239
if (StringUtils.hasLength(port)) {
232240
builder.port(Integer.parseInt(port));

spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,31 @@ public void fromHttpUrlStringCaseInsesitiveScheme() {
169169
assertEquals("https", UriComponentsBuilder.fromHttpUrl("HTTPS://www.google.com").build().getScheme());
170170
}
171171

172+
// SPR-10539
173+
174+
@Test(expected = IllegalArgumentException.class)
175+
public void fromHttpUrlStringInvalidIPv6Host() throws URISyntaxException {
176+
UriComponents result = UriComponentsBuilder
177+
.fromHttpUrl("http://[1abc:2abc:3abc::5ABC:6abc:8080/resource").build().encode();
178+
}
179+
180+
// SPR-10539
181+
182+
@Test
183+
public void fromUriStringIPv6Host() throws URISyntaxException {
184+
UriComponents result = UriComponentsBuilder
185+
.fromUriString("http://[1abc:2abc:3abc::5ABC:6abc]:8080/resource").build().encode();
186+
assertEquals("[1abc:2abc:3abc::5ABC:6abc]",result.getHost());
187+
188+
UriComponents resultWithScopeId = UriComponentsBuilder
189+
.fromUriString("http://[1abc:2abc:3abc::5ABC:6abc%eth0]:8080/resource").build().encode();
190+
assertEquals("[1abc:2abc:3abc::5ABC:6abc%25eth0]",resultWithScopeId.getHost());
191+
192+
UriComponents resultIPv4compatible = UriComponentsBuilder
193+
.fromUriString("http://[::192.168.1.1]:8080/resource").build().encode();
194+
assertEquals("[::192.168.1.1]",resultIPv4compatible.getHost());
195+
}
196+
172197
@Test
173198
public void path() throws URISyntaxException {
174199
UriComponentsBuilder builder = UriComponentsBuilder.fromPath("/foo/bar");

0 commit comments

Comments
 (0)