You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ck...@apache.org on 2021/04/20 12:35:32 UTC

[httpcomponents-core] branch master updated: HTTPCORE-616: Make parsing IPv6 ready

This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git


The following commit(s) were added to refs/heads/master by this push:
     new 041b4a7  HTTPCORE-616: Make parsing IPv6 ready
041b4a7 is described below

commit 041b4a7f2b6d7beecdce7ac7b1b304d017a3f8d1
Author: Carter Kozak <ck...@apache.org>
AuthorDate: Mon Apr 5 15:25:23 2021 -0400

    HTTPCORE-616: Make parsing IPv6 ready
    
    This adds support for bracketed IPv6 host parsing to the following:
    * `org.apache.hc.core5.net.Host.create(String)`
    * `org.apache.hc.core5.net.URIAuthority.create(String)`
    * `org.apache.hc.core5.http.HttpHost.create(String)`
    
    This commit does not address `InetAddressUtils: Parsing may fail when an
    IPv6 scope id might be provided.`
---
 .../main/java/org/apache/hc/core5/net/Host.java    | 26 ++++++++++++--
 .../java/org/apache/hc/core5/net/URISupport.java   |  2 ++
 .../org/apache/hc/core5/http/TestHttpHost.java     | 40 ++++++++++++++++++++++
 .../java/org/apache/hc/core5/net/TestHost.java     | 33 ++++++++++++++++++
 .../org/apache/hc/core5/net/TestURIAuthority.java  | 31 +++++++++++++++++
 5 files changed, 130 insertions(+), 2 deletions(-)

diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/Host.java b/httpcore5/src/main/java/org/apache/hc/core5/net/Host.java
index d915b0e..2feda01 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/net/Host.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/net/Host.java
@@ -60,7 +60,21 @@ public final class Host implements NamedEndpoint, Serializable {
 
     static Host parse(final CharSequence s, final Tokenizer.Cursor cursor) throws URISyntaxException {
         final Tokenizer tokenizer = Tokenizer.INSTANCE;
-        final String hostName = tokenizer.parseContent(s, cursor, URISupport.PORT_SEPARATORS);
+        final String hostName;
+        final boolean ipv6Brackets = !cursor.atEnd() && s.charAt(cursor.getPos()) == '[';
+        if (ipv6Brackets) {
+            cursor.updatePos(cursor.getPos() + 1);
+            hostName = tokenizer.parseContent(s, cursor, URISupport.IPV6_HOST_TERMINATORS);
+            if (cursor.atEnd() || !(s.charAt(cursor.getPos()) == ']')) {
+                throw URISupport.createException(s, cursor, "Expected an IPv6 closing bracket ']'");
+            }
+            cursor.updatePos(cursor.getPos() + 1);
+            if (!InetAddressUtils.isIPv6Address(hostName)) {
+                throw URISupport.createException(s, cursor, "Expected an IPv6 address");
+            }
+        } else {
+            hostName = tokenizer.parseContent(s, cursor, URISupport.PORT_SEPARATORS);
+        }
         String portText = null;
         if (!cursor.atEnd() && s.charAt(cursor.getPos()) == ':') {
             cursor.updatePos(cursor.getPos() + 1);
@@ -68,6 +82,9 @@ public final class Host implements NamedEndpoint, Serializable {
         }
         final int port;
         if (!TextUtils.isBlank(portText)) {
+            if (!ipv6Brackets && portText.contains(":")) {
+                throw URISupport.createException(s, cursor, "Expected IPv6 address to be enclosed in brackets");
+            }
             try {
                 port = Integer.parseInt(portText);
             } catch (final NumberFormatException ex) {
@@ -85,7 +102,12 @@ public final class Host implements NamedEndpoint, Serializable {
     }
 
     static void format(final StringBuilder buf, final NamedEndpoint endpoint) {
-        buf.append(endpoint.getHostName());
+        final String hostName = endpoint.getHostName();
+        if (InetAddressUtils.isIPv6Address(hostName)) {
+            buf.append('[').append(hostName).append(']');
+        } else {
+            buf.append(hostName);
+        }
         if (endpoint.getPort() != -1) {
             buf.append(":");
             buf.append(endpoint.getPort());
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/URISupport.java b/httpcore5/src/main/java/org/apache/hc/core5/net/URISupport.java
index 6948dc8..e884794 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/net/URISupport.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/net/URISupport.java
@@ -34,6 +34,7 @@ import org.apache.hc.core5.util.Tokenizer;
 final class URISupport {
 
     static final BitSet HOST_SEPARATORS = new BitSet(256);
+    static final BitSet IPV6_HOST_TERMINATORS = new BitSet(256);
     static final BitSet PORT_SEPARATORS = new BitSet(256);
     static final BitSet TERMINATORS = new BitSet(256);
 
@@ -43,6 +44,7 @@ final class URISupport {
         TERMINATORS.set('?');
         HOST_SEPARATORS.or(TERMINATORS);
         HOST_SEPARATORS.set('@');
+        IPV6_HOST_TERMINATORS.set(']');
         PORT_SEPARATORS.or(TERMINATORS);
         PORT_SEPARATORS.set(':');
     }
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpHost.java b/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpHost.java
index 54019d1..f944f2f 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpHost.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpHost.java
@@ -230,4 +230,44 @@ public class TestHttpHost {
         }
     }
 
+    @Test
+    public void testIpv6HostAndPort() throws Exception {
+        final HttpHost host = HttpHost.create("[::1]:80");
+        Assert.assertEquals("http", host.getSchemeName());
+        Assert.assertEquals("::1", host.getHostName());
+        Assert.assertEquals(80, host.getPort());
+    }
+
+    @Test
+    public void testIpv6HostAndPortWithScheme() throws Exception {
+        final HttpHost host = HttpHost.create("https://[::1]:80");
+        Assert.assertEquals("https", host.getSchemeName());
+        Assert.assertEquals("::1", host.getHostName());
+        Assert.assertEquals(80, host.getPort());
+    }
+
+    @Test
+    public void testIpv6HostAndPortWithoutBrackets() throws Exception {
+        try {
+            // ambiguous
+            HttpHost.create("::1:80");
+            Assert.fail("URISyntaxException expected");
+        } catch (final URISyntaxException expected) {
+        }
+    }
+
+    @Test
+    public void testIpv6HostWithoutPort() throws Exception {
+        try {
+            HttpHost.create("::1");
+            Assert.fail("URISyntaxException expected");
+        } catch (final URISyntaxException expected) {
+        }
+    }
+
+    @Test
+    public void testIpv6HostToString() {
+        Assert.assertEquals("http://[::1]:80", new HttpHost("::1", 80).toString());
+        Assert.assertEquals("http://[::1]", new HttpHost("::1", -1).toString());
+    }
 }
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/net/TestHost.java b/httpcore5/src/test/java/org/apache/hc/core5/net/TestHost.java
index 71f35d5..d03a35a 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/net/TestHost.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestHost.java
@@ -129,4 +129,37 @@ public class TestHost {
         }
     }
 
+    @Test
+    public void testIpv6HostAndPort() throws Exception {
+        final Host host = Host.create("[::1]:80");
+        Assert.assertEquals("::1", host.getHostName());
+        Assert.assertEquals(80, host.getPort());
+    }
+
+    @Test
+    public void testIpv6HostAndPortWithoutBrackets() {
+        try {
+            // ambiguous
+            Host.create("::1:80");
+            Assert.fail("URISyntaxException expected");
+        } catch (final URISyntaxException expected) {
+            Assert.assertTrue(expected.getMessage().contains("Expected IPv6 address to be enclosed in brackets"));
+        }
+    }
+
+    @Test
+    public void testIpv6HostWithoutPort() {
+        try {
+            Host.create("::1");
+            Assert.fail("URISyntaxException expected");
+        } catch (final URISyntaxException expected) {
+            Assert.assertTrue(expected.getMessage().contains("Expected IPv6 address to be enclosed in brackets"));
+        }
+    }
+
+    @Test
+    public void testIpv6HostToString() {
+        Assert.assertEquals("[::1]:80", new Host("::1", 80).toString());
+        Assert.assertEquals("[::1]", new Host("::1", -1).toString());
+    }
 }
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIAuthority.java b/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIAuthority.java
index 8aa4a22..3fd972c 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIAuthority.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIAuthority.java
@@ -220,4 +220,35 @@ public class TestURIAuthority {
         }
     }
 
+    @Test
+    public void testCreateFromIPv6String() throws Exception {
+        Assert.assertEquals(new URIAuthority("::1", 8080), URIAuthority.create("[::1]:8080"));
+        Assert.assertEquals(new URIAuthority("::1", -1), URIAuthority.create("[::1]"));
+        try {
+            URIAuthority.create("::1");
+            Assert.fail("URISyntaxException expected");
+        } catch (final URISyntaxException expected) {
+        }
+        try {
+            URIAuthority.create("[::1");
+            Assert.fail("URISyntaxException expected");
+        } catch (final URISyntaxException expected) {
+            Assert.assertTrue(expected.getMessage().contains("Expected an IPv6 closing bracket"));
+        }
+
+        try {
+            URIAuthority.create("[a]:8080");
+            Assert.fail("URISyntaxException expected");
+        } catch (final URISyntaxException expected) {
+            Assert.assertTrue(expected.getMessage().contains("Expected an IPv6 address"));
+        }
+    }
+
+    @Test
+    public void testIpv6HostToString() {
+        Assert.assertEquals("[::1]:80", new URIAuthority("::1", 80).toString());
+        Assert.assertEquals("user@[::1]:80", new URIAuthority("user", "::1", 80).toString());
+        Assert.assertEquals("[::1]", new URIAuthority("::1", -1).toString());
+        Assert.assertEquals("user@[::1]", new URIAuthority("user", "::1", -1).toString());
+    }
 }