You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2023/04/06 09:37:30 UTC

[httpcomponents-core] 01/03: Add support for Internationalized Domain Names (IDN)

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

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

commit 716f64c744d227bca80d8ee6a0a5cc951a5910d9
Author: Arturo Bernal <ar...@gmail.com>
AuthorDate: Fri Feb 24 22:18:55 2023 +0100

    Add support for Internationalized Domain Names (IDN)
    
    This change updates the URI parsing logic to correctly handle non-ASCII domain names using the Internationalized Domain Names (IDN) system. When parsing a URI, the code now checks if the domain name contains non-ASCII characters, and if so, it converts the domain name to its ASCII-compatible encoding using the IDN.toASCII() method.
    
    This allows applications using this code to correctly handle URLs with non-ASCII domain names, which are increasingly common in today's globalized internet.
---
 .../main/java/org/apache/hc/core5/net/Host.java    | 23 +++++++++++++++++++---
 .../java/org/apache/hc/core5/util/TextUtils.java   | 18 +++++++++++++++++
 .../http/examples/ClassicPostExecutionExample.java |  2 +-
 .../http/support/TestBasicMessageBuilders.java     |  8 ++++++++
 .../java/org/apache/hc/core5/net/TestHost.java     | 11 +++++++++++
 5 files changed, 58 insertions(+), 4 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 3ef6192f1..008a4f9f2 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
@@ -27,6 +27,7 @@
 package org.apache.hc.core5.net;
 
 import java.io.Serializable;
+import java.net.IDN;
 import java.net.URISyntaxException;
 
 import org.apache.hc.core5.annotation.Contract;
@@ -50,10 +51,22 @@ public final class Host implements NamedEndpoint, Serializable {
     private final String lcName;
     private final int port;
 
+    static boolean isPunyCode(final CharSequence s) {
+        if (s == null || s.length() < 4) {
+            return false;
+        }
+        return ((s.charAt(0) == 'x' || s.charAt(0) == 'X') &&
+                (s.charAt(1) == 'n' || s.charAt(1) == 'N') &&
+                s.charAt(2) == '-' &&
+                s.charAt(3) == '-');
+    }
+
     public Host(final String name, final int port) {
         super();
-        this.name = Args.notNull(name, "Host name");
-        this.port = Ports.checkWithDefault(port);
+        Args.notNull(name, "Host name");
+        Ports.checkWithDefault(port);
+        this.name = isPunyCode(name) ? IDN.toUnicode(name) : name;
+        this.port = port;
         this.lcName = TextUtils.toLowerCase(this.name);
     }
 
@@ -105,7 +118,11 @@ public final class Host implements NamedEndpoint, Serializable {
         if (InetAddressUtils.isIPv6Address(hostName)) {
             buf.append('[').append(hostName).append(']');
         } else {
-            buf.append(hostName);
+            if (TextUtils.isAllASCII(hostName)) {
+                buf.append(hostName);
+            } else {
+                buf.append(IDN.toASCII(hostName));
+            }
         }
         if (endpoint.getPort() != -1) {
             buf.append(":");
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/util/TextUtils.java b/httpcore5/src/main/java/org/apache/hc/core5/util/TextUtils.java
index a8b6a6e28..867cb3786 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/util/TextUtils.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/util/TextUtils.java
@@ -141,4 +141,22 @@ public final class TextUtils {
         return s.toLowerCase(Locale.ROOT);
     }
 
+
+    /**
+     * Determines whether the given {@link CharSequence} contains only ASCII characters.
+     *
+     * @param s the {@link CharSequence} to check
+     * @return true if the {@link CharSequence} contains only ASCII characters, false otherwise
+     * @throws IllegalArgumentException if the input {@link CharSequence} is null
+     * @since 5.3
+     */
+    public static boolean isAllASCII(final CharSequence s) {
+        for (int i = 0; i < s.length(); i++) {
+            if (s.charAt(i) > 0x7F) {
+                return false;
+            }
+        }
+        return true;
+    }
+
 }
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicPostExecutionExample.java b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicPostExecutionExample.java
index e574def45..fa79ae635 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicPostExecutionExample.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicPostExecutionExample.java
@@ -106,7 +106,7 @@ public class ClassicPostExecutionExample {
 
         final String requestUri = "/post";
         for (int i = 0; i < requestBodies.length; i++) {
-            final ClassicHttpRequest request = ClassicRequestBuilder.post()
+            final ClassicHttpRequest request = ClassicRequestBuilder.get()
                     .setHttpHost(target)
                     .setPath(requestUri)
                     .build();
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/support/TestBasicMessageBuilders.java b/httpcore5/src/test/java/org/apache/hc/core5/http/support/TestBasicMessageBuilders.java
index 9c91c8ec6..a3c4705fb 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/http/support/TestBasicMessageBuilders.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/support/TestBasicMessageBuilders.java
@@ -28,6 +28,7 @@
 package org.apache.hc.core5.http.support;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import org.apache.hc.core5.http.HeaderMatcher;
 import org.apache.hc.core5.http.HeadersMatcher;
@@ -237,4 +238,11 @@ public class TestBasicMessageBuilders {
                 new BasicHeader("h1", "v1"), new BasicHeader("h1", "v2"), new BasicHeader("h2", "v2")));
     }
 
+    @Test
+    void testIDNIntegration() {
+        final String url = "http://müller.example.com:8080/path";
+        final HttpRequest request = new BasicHttpRequest(Method.GET, URI.create(url));
+        assertEquals(new URIAuthority("xn--mller-kva.example.com",8080), request.getAuthority());
+    }
+
 }
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 d2090923a..1a9a4ddb3 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
@@ -50,6 +50,15 @@ public class TestHost {
         final Host host2 = new Host("somehost", 0);
         Assertions.assertEquals("somehost", host2.getHostName());
         Assertions.assertEquals(0, host2.getPort());
+        final Host host3 = new Host("Яндекс.Ру", 0);
+        Assertions.assertEquals("Яндекс.Ру", host3.getHostName());
+        Assertions.assertEquals(0, host3.getPort());
+        final Host host4 = new Host("xn--d1acpjx3f.xn--p1ag", 0);
+        Assertions.assertEquals("яндекс.ру", host4.getHostName());
+        Assertions.assertEquals(0, host4.getPort());
+        final Host host5 = new Host("XN--D1Acpjx3f.xn--p1ag", 0);
+        Assertions.assertEquals("яндекс.ру", host5.getHostName());
+        Assertions.assertEquals(0, host5.getPort());
         Assertions.assertThrows(NullPointerException.class, () -> new Host(null, 0));
     }
 
@@ -104,6 +113,8 @@ public class TestHost {
         Assertions.assertEquals(new Host("somehost", 8080), Host.create("somehost:8080"));
         Assertions.assertEquals(new Host("somehost", 1234), Host.create("somehost:1234"));
         Assertions.assertEquals(new Host("somehost", 0), Host.create("somehost:0"));
+        Assertions.assertEquals(new Host("яндекс.ру", -1), Host.create("xn--d1acpjx3f.xn--p1ag"));
+        Assertions.assertEquals(new Host("Яндекс.Ру", -1), Host.create("Яндекс.Ру"));
     }
 
     @Test