1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-17 08:42:25 +03:00

IPv6 canonical string.

Closes https://github.com/square/okhttp/issues/1636
This commit is contained in:
jwilson
2015-08-01 21:04:54 -07:00
parent 1e5f3a9679
commit a8949b129d
3 changed files with 77 additions and 23 deletions

View File

@@ -269,27 +269,26 @@ public final class HttpUrlTest {
@Test public void hostIpv6() throws Exception {
// Square braces are absent from host()...
String address = "0:0:0:0:0:0:0:1";
assertEquals(address, HttpUrl.parse("http://[::1]/").host());
assertEquals("::1", HttpUrl.parse("http://[::1]/").host());
// ... but they're included in toString().
assertEquals("http://[0:0:0:0:0:0:0:1]/", HttpUrl.parse("http://[::1]/").toString());
assertEquals("http://[::1]/", HttpUrl.parse("http://[::1]/").toString());
// IPv6 colons don't interfere with port numbers or passwords.
assertEquals(8080, HttpUrl.parse("http://[::1]:8080/").port());
assertEquals("password", HttpUrl.parse("http://user:password@[::1]/").password());
assertEquals(address, HttpUrl.parse("http://user:password@[::1]:8080/").host());
assertEquals("::1", HttpUrl.parse("http://user:password@[::1]:8080/").host());
// Permit the contents of IPv6 addresses to be percent-encoded...
assertEquals(address, HttpUrl.parse("http://[%3A%3A%31]/").host());
assertEquals("::1", HttpUrl.parse("http://[%3A%3A%31]/").host());
// Including the Square braces themselves! (This is what Chrome does.)
assertEquals(address, HttpUrl.parse("http://%5B%3A%3A1%5D/").host());
assertEquals("::1", HttpUrl.parse("http://%5B%3A%3A1%5D/").host());
}
@Test public void hostIpv6AddressDifferentFormats() throws Exception {
// Multiple representations of the same address; see http://tools.ietf.org/html/rfc5952.
String a3 = "2001:db8:0:0:1:0:0:1";
String a3 = "2001:db8::1:0:0:1";
assertEquals(a3, HttpUrl.parse("http://[2001:db8:0:0:1:0:0:1]").host());
assertEquals(a3, HttpUrl.parse("http://[2001:0db8:0:0:1:0:0:1]").host());
assertEquals(a3, HttpUrl.parse("http://[2001:db8::1:0:0:1]").host());
@@ -301,19 +300,17 @@ public final class HttpUrlTest {
}
@Test public void hostIpv6AddressLeadingCompression() throws Exception {
String a1 = "0:0:0:0:0:0:0:1";
assertEquals(a1, HttpUrl.parse("http://[::0001]").host());
assertEquals(a1, HttpUrl.parse("http://[0000::0001]").host());
assertEquals(a1, HttpUrl.parse("http://[0000:0000:0000:0000:0000:0000:0000:0001]").host());
assertEquals(a1, HttpUrl.parse("http://[0000:0000:0000:0000:0000:0000::0001]").host());
assertEquals("::1", HttpUrl.parse("http://[::0001]").host());
assertEquals("::1", HttpUrl.parse("http://[0000::0001]").host());
assertEquals("::1", HttpUrl.parse("http://[0000:0000:0000:0000:0000:0000:0000:0001]").host());
assertEquals("::1", HttpUrl.parse("http://[0000:0000:0000:0000:0000:0000::0001]").host());
}
@Test public void hostIpv6AddressTrailingCompression() throws Exception {
String a2 = "1:0:0:0:0:0:0:0";
assertEquals(a2, HttpUrl.parse("http://[0001:0000::]").host());
assertEquals(a2, HttpUrl.parse("http://[0001::0000]").host());
assertEquals(a2, HttpUrl.parse("http://[0001::]").host());
assertEquals(a2, HttpUrl.parse("http://[1::]").host());
assertEquals("1::", HttpUrl.parse("http://[0001:0000::]").host());
assertEquals("1::", HttpUrl.parse("http://[0001::0000]").host());
assertEquals("1::", HttpUrl.parse("http://[0001::]").host());
assertEquals("1::", HttpUrl.parse("http://[1::]").host());
}
@Test public void hostIpv6AddressTooManyDigitsInGroup() throws Exception {
@@ -351,8 +348,8 @@ public final class HttpUrlTest {
}
@Test public void hostIpv6WithIpv4Suffix() throws Exception {
assertEquals("0:0:0:0:0:1:ffff:ffff", HttpUrl.parse("http://[::1:255.255.255.255]/").host());
assertEquals("0:0:0:0:0:1:0:0", HttpUrl.parse("http://[0:0:0:0:0:1:0.0.0.0]/").host());
assertEquals("::1:ffff:ffff", HttpUrl.parse("http://[::1:255.255.255.255]/").host());
assertEquals("::1:0:0", HttpUrl.parse("http://[0:0:0:0:0:1:0.0.0.0]/").host());
}
@Test public void hostIpv6WithIpv4SuffixWithOctalPrefix() throws Exception {
@@ -389,6 +386,29 @@ public final class HttpUrlTest {
assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:1:255.255.255]/"));
}
@Test public void hostIpv6CanonicalForm() throws Exception {
assertEquals("abcd:ef01:2345:6789:abcd:ef01:2345:6789",
HttpUrl.parse("http://[abcd:ef01:2345:6789:abcd:ef01:2345:6789]/").host());
assertEquals("a::b:0:0:0", HttpUrl.parse("http://[a:0:0:0:b:0:0:0]/").host());
assertEquals("a:b:0:0:c::", HttpUrl.parse("http://[a:b:0:0:c:0:0:0]/").host());
assertEquals("a:b::c:0:0", HttpUrl.parse("http://[a:b:0:0:0:c:0:0]/").host());
assertEquals("a::b:0:0:0", HttpUrl.parse("http://[a:0:0:0:b:0:0:0]/").host());
assertEquals("::a:b:0:0:0", HttpUrl.parse("http://[0:0:0:a:b:0:0:0]/").host());
assertEquals("::a:0:0:0:b", HttpUrl.parse("http://[0:0:0:a:0:0:0:b]/").host());
assertEquals("::a:b:c:d:e:f:1", HttpUrl.parse("http://[0:a:b:c:d:e:f:1]/").host());
assertEquals("a:b:c:d:e:f:1::", HttpUrl.parse("http://[a:b:c:d:e:f:1:0]/").host());
assertEquals("ff01::101", HttpUrl.parse("http://[FF01:0:0:0:0:0:0:101]/").host());
assertEquals("1::", HttpUrl.parse("http://[1:0:0:0:0:0:0:0]/").host());
assertEquals("::1", HttpUrl.parse("http://[0:0:0:0:0:0:0:1]/").host());
assertEquals("::", HttpUrl.parse("http://[0:0:0:0:0:0:0:0]/").host());
}
@Test public void hostIpv4CanonicalForm() throws Exception {
assertEquals("255.255.255.255", HttpUrl.parse("http://255.255.255.255/").host());
assertEquals("1.2.3.4", HttpUrl.parse("http://1.2.3.4/").host());
assertEquals("0.0.0.0", HttpUrl.parse("http://0.0.0.0/").host());
}
@Ignore("java.net.IDN strips trailing trailing dots on Java 7, but not on Java 8.")
@Test public void hostWithTrailingDot() throws Exception {
assertEquals("host.", HttpUrl.parse("http://host./").host());

View File

@@ -64,9 +64,7 @@ public final class WebPlatformUrlTest {
"Parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>",
"Parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>",
"Parsing: <http://192.168.0.257> against <http://other.com/>",
"Parsing: <http://> against <http://other.com/>",
"Parsing: <http://[2001::1]> against <http://example.org/foo/bar>",
"Parsing: <http://[2001::1]:80> against <http://example.org/foo/bar>"
"Parsing: <http://> against <http://other.com/>"
);
/** Test how {@link HttpUrl} does against the web platform test suite. */

View File

@@ -1189,7 +1189,10 @@ public final class HttpUrl {
// If the input is encased in square braces "[...]", drop 'em. We have an IPv6 address.
if (percentDecoded.startsWith("[") && percentDecoded.endsWith("]")) {
InetAddress inetAddress = decodeIpv6(percentDecoded, 1, percentDecoded.length() - 1);
return inetAddress != null ? inetAddress.getHostAddress() : null;
if (inetAddress == null) return null;
byte[] address = inetAddress.getAddress();
if (address.length == 16) return inet6AddressToAscii(address);
throw new AssertionError();
}
// Do IDN decoding. This converts {@code ☃.net} to {@code xn--n3h.net}.
@@ -1322,6 +1325,39 @@ public final class HttpUrl {
}
}
private static String inet6AddressToAscii(byte[] address) {
// Go through the address looking for the longest run of 0s. Each group is 2-bytes.
int longestRunOffset = -1;
int longestRunLength = 0;
for (int i = 0; i < address.length; i += 2) {
int currentRunOffset = i;
while (i < 16 && address[i] == 0 && address[i + 1] == 0) {
i += 2;
}
int currentRunLength = i - currentRunOffset;
if (currentRunLength > longestRunLength) {
longestRunOffset = currentRunOffset;
longestRunLength = currentRunLength;
}
}
// Emit each 2-byte group in hex, separated by ':'. The longest run of zeroes is "::".
Buffer result = new Buffer();
for (int i = 0; i < address.length; ) {
if (i == longestRunOffset) {
result.writeByte(':');
i += longestRunLength;
if (i == 16) result.writeByte(':');
} else {
if (i > 0) result.writeByte(':');
int group = (address[i] & 0xff) << 8 | address[i + 1] & 0xff;
result.writeHexadecimalUnsignedLong(group);
i += 2;
}
}
return result.readUtf8();
}
private static int parsePort(String input, int pos, int limit) {
try {
// Canonicalize the port string to skip '\n' etc.