Skip to content

Commit

Permalink
IPv6 canonical string.
Browse files Browse the repository at this point in the history
  • Loading branch information
squarejesse committed Aug 2, 2015
1 parent 1e5f3a9 commit a8949b1
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 23 deletions.
58 changes: 39 additions & 19 deletions okhttp-tests/src/test/java/com/squareup/okhttp/HttpUrlTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ public static List<Object[]> parameters() {
"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://0Xc0.0250.01> 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://0Xc0.0250.01> against <http://other.com/>"
);

/** Test how {@link HttpUrl} does against the web platform test suite. */
Expand Down
38 changes: 37 additions & 1 deletion okhttp/src/main/java/com/squareup/okhttp/HttpUrl.java
Original file line number Diff line number Diff line change
Expand Up @@ -1189,7 +1189,10 @@ private static String canonicalizeHost(String input, int pos, int limit) {
// 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}.
Expand Down Expand Up @@ -1322,6 +1325,39 @@ private static String domainToAscii(String input) {
}
}

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.
Expand Down

0 comments on commit a8949b1

Please sign in to comment.