HTTPCORE-671: URIBuilder does not precent-encode bracketed IPv6 addresses

URIBuilder complies with https://tools.ietf.org/html/rfc3986
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/InetAddressUtils.java b/httpcore5/src/main/java/org/apache/hc/core5/net/InetAddressUtils.java
index cf5e9f2..a3aacbf 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/net/InetAddressUtils.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/net/InetAddressUtils.java
@@ -138,6 +138,17 @@
     }
 
     /**
+     * Checks whether the parameter is a valid URL formatted bracketed IPv6 address (including compressed).
+     * This matches only bracketed values e.g. {@code [::1]}.
+     *
+     * @param input the address string to check for validity
+     * @return true if the input parameter is a valid URL-formatted bracketed IPv6 address
+     */
+    public static boolean isIPv6URLBracketedAddress(final String input) {
+        return input.startsWith("[") && input.endsWith("]") && isIPv6Address(input.substring(1, input.length() - 1));
+    }
+
+    /**
      * Formats {@link SocketAddress} as text.
      *
      * @since 5.0
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/URIBuilder.java b/httpcore5/src/main/java/org/apache/hc/core5/net/URIBuilder.java
index a7f2f82..ab864d5 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/net/URIBuilder.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/net/URIBuilder.java
@@ -333,7 +333,12 @@
         this.scheme = uri.getScheme();
         this.encodedSchemeSpecificPart = uri.getRawSchemeSpecificPart();
         this.encodedAuthority = uri.getRawAuthority();
-        this.host = uri.getHost();
+        final String uriHost = uri.getHost();
+        // URI.getHost incorrectly returns bracketed (encoded) IPv6 values. Brackets are an
+        // encoding detail of the URI and not part of the host string.
+        this.host = uriHost != null && InetAddressUtils.isIPv6URLBracketedAddress(uriHost)
+                ? uriHost.substring(1, uriHost.length() - 1)
+                : uriHost;
         this.port = uri.getPort();
         this.encodedUserInfo = uri.getRawUserInfo();
         this.userInfo = uri.getUserInfo();
@@ -454,7 +459,9 @@
     }
 
     /**
-     * Sets URI host.
+     * Sets URI host. The input value must not already be URI encoded, for example {@code ::1} is valid however
+     * {@code [::1]} is not. It is dangerous to call {@code uriBuilder.setHost(uri.getHost())} due
+     * to {@link URI#getHost()} returning URI encoded values.
      *
      * @return this.
      */
@@ -779,6 +786,12 @@
         return this.userInfo;
     }
 
+    /**
+     * Gets the host portion of the {@link URI}. This method returns unencoded IPv6 addresses (without brackets).
+     * This behavior differs from values returned by {@link URI#getHost()}.
+     *
+     * @return The host portion of the URI.
+     */
     public String getHost() {
         return this.host;
     }
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/net/TestInetAddressUtils.java b/httpcore5/src/test/java/org/apache/hc/core5/net/TestInetAddressUtils.java
index 9d2e9f1..13a742d 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/net/TestInetAddressUtils.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestInetAddressUtils.java
@@ -79,6 +79,56 @@
     }
 
     @Test
+    public void testValidIPv6BracketAddress() {
+        Assert.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[2001:0db8:0000:0000:0000:0000:1428:57ab]"));
+        Assert.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[2001:db8:0:0:0:0:1428:57ab]"));
+        Assert.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[0:0:0:0:0:0:0:0]"));
+        Assert.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[0:0:0:0:0:0:0:1]"));
+        Assert.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[2001:0db8:0:0::1428:57ab]"));
+        Assert.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[2001:0db8::1428:57ab]"));
+        Assert.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[2001:db8::1428:57ab]"));
+        Assert.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[::1]"));
+        // http://tools.ietf.org/html/rfc4291#section-2.2
+        Assert.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[::]"));
+    }
+
+    @Test
+    public void testInvalidIPv6BracketAddress() {
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("2001:0db8:0000:garb:age0:0000:1428:57ab"));
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[2001:0db8:0000:garb:age0:0000:1428:57ab]"));
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("2001:0gb8:0000:0000:0000:0000:1428:57ab"));
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[2001:0gb8:0000:0000:0000:0000:1428:57ab]"));
+        // Too many
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("0:0:0:0:0:0:0:0:0"));
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[0:0:0:0:0:0:0:0:0]"));
+        // Too few
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("0:0:0:0:0:0:0"));
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[0:0:0:0:0:0:0]"));
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress(":1"));
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[:1]"));
+        // Cannot have two contractions
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("2001:0db8::0000::57ab"));
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[2001:0db8::0000::57ab]"));
+        // too many fields before ::
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("1:2:3:4:5:6:7::9"));
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[1:2:3:4:5:6:7::9]"));
+        // too many fields after ::
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("1::3:4:5:6:7:8:9"));
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[1::3:4:5:6:7:8:9]"));
+        // too many fields after ::
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("::3:4:5:6:7:8:9"));
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[::3:4:5:6:7:8:9]"));
+        // empty
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress(""));
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[]"));
+
+        // missing brackets
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("::"));
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("::1"));
+        Assert.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("2001:db8::1428:57ab"));
+    }
+
+    @Test
     // Test HTTPCLIENT-1319
     public void testInvalidIPv6AddressIncorrectGroupCount() {
         Assert.assertFalse(InetAddressUtils.isIPv6HexCompressedAddress("1:2::4:5:6:7:8:9")); // too many fields in total
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIBuilder.java b/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIBuilder.java
index 125dd5b..739634a 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIBuilder.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIBuilder.java
@@ -813,4 +813,47 @@
                 new URIBuilder("http:///../../.././").normalizeSyntax().build().toASCIIString());
     }
 
+    @Test
+    public void testIpv6Host() throws Exception {
+        final URIBuilder builder = new URIBuilder("https://[::1]:432/path");
+        final URI uri = builder.build();
+        Assert.assertEquals(432, builder.getPort());
+        Assert.assertEquals(432, uri.getPort());
+        Assert.assertEquals("https", builder.getScheme());
+        Assert.assertEquals("https", uri.getScheme());
+        Assert.assertEquals("::1", builder.getHost());
+        Assert.assertEquals("[::1]", uri.getHost());
+        Assert.assertEquals("/path", builder.getPath());
+        Assert.assertEquals("/path", uri.getPath());
+    }
+
+    @Test
+    public void testIpv6HostWithPortUpdate() throws Exception {
+        // Updating the port clears URIBuilder.encodedSchemeSpecificPart
+        // and bypasses the fast/simple path which preserves input.
+        final URIBuilder builder = new URIBuilder("https://[::1]:432/path").setPort(123);
+        final URI uri = builder.build();
+        Assert.assertEquals(123, builder.getPort());
+        Assert.assertEquals(123, uri.getPort());
+        Assert.assertEquals("https", builder.getScheme());
+        Assert.assertEquals("https", uri.getScheme());
+        Assert.assertEquals("::1", builder.getHost());
+        Assert.assertEquals("[::1]", uri.getHost());
+        Assert.assertEquals("/path", builder.getPath());
+        Assert.assertEquals("/path", uri.getPath());
+    }
+
+    @Test
+    public void testBuilderWithUnbracketedIpv6Host() throws Exception {
+        final URIBuilder builder = new URIBuilder().setScheme("https").setHost("::1").setPort(443).setPath("/path");
+        final URI uri = builder.build();
+        Assert.assertEquals("https", builder.getScheme());
+        Assert.assertEquals("https", uri.getScheme());
+        Assert.assertEquals(443, builder.getPort());
+        Assert.assertEquals(443, uri.getPort());
+        Assert.assertEquals("::1", builder.getHost());
+        Assert.assertEquals("[::1]", uri.getHost());
+        Assert.assertEquals("/path", builder.getPath());
+        Assert.assertEquals("/path", uri.getPath());
+    }
 }