]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
urlapi: CURLU_PUNY2IDN - convert from punycode to IDN name
authorDaniel Stenberg <daniel@haxx.se>
Fri, 11 Aug 2023 07:41:28 +0000 (09:41 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Sun, 13 Aug 2023 13:34:38 +0000 (15:34 +0200)
Asssisted-by: Jay Satiro
Closes #11655

docs/libcurl/curl_url_get.3
docs/libcurl/symbols-in-versions
include/curl/urlapi.h
lib/idn.c
lib/idn.h
lib/urlapi.c
tests/libtest/lib1560.c

index ba669711bd1f96e1d99635f45dcc3f039341de79..ea712b1bb2b4fa5a0ac4cc7033f666c48be6b472 100644 (file)
@@ -91,6 +91,16 @@ If libcurl is built without IDN capabilities, using this bit will make
 anything outside the ASCII range.
 
 (Added in curl 7.88.0)
+.IP CURLU_PUNY2IDN
+If set and asked to retrieve the \fBCURLUPART_HOST\fP or \fBCURLUPART_URL\fP
+parts, libcurl returns the host name in its IDN (International Domain Name)
+UTF-8 version if it otherwise is a punycode version.
+
+If libcurl is built without IDN capabilities, using this bit will make
+\fIcurl_url_get(3)\fP return \fICURLUE_LACKS_IDN\fP if the host name is using
+punycode.
+
+(Added in curl 8.3.0)
 .SH PARTS
 .IP CURLUPART_URL
 When asked to return the full URL, \fIcurl_url_get(3)\fP will return a
index 6f88bf5e552fdbcce6550f3f2b014ef67c3fda9c..7adf5c4ae49e0c277a2babe1186f5ebbee98b5e5 100644 (file)
@@ -1063,6 +1063,7 @@ CURLU_NO_AUTHORITY              7.67.0
 CURLU_NO_DEFAULT_PORT           7.62.0
 CURLU_NON_SUPPORT_SCHEME        7.62.0
 CURLU_PATH_AS_IS                7.62.0
+CURLU_PUNY2IDN                  8.3.0
 CURLU_PUNYCODE                  7.88.0
 CURLU_URLDECODE                 7.62.0
 CURLU_URLENCODE                 7.62.0
index 992e9f6019cc0f6154e65e41bd754e58c0982b98..88cdeb3bca6db02fa7f888029bb350a7a395f804 100644 (file)
@@ -97,6 +97,7 @@ typedef enum {
                                            scheme is unknown. */
 #define CURLU_ALLOW_SPACE (1<<11)       /* Allow spaces in the URL */
 #define CURLU_PUNYCODE (1<<12)          /* get the host name in punycode */
+#define CURLU_PUNY2IDN (1<<13)          /* punycode => IDN conversion */
 
 typedef struct Curl_URL CURLU;
 
index 5f4b07e01847f2cb391898a87d07befa3bad75be..ff1808a63b7d94f52d2bb36c95c6e42044b0ae44 100644 (file)
--- a/lib/idn.c
+++ b/lib/idn.c
@@ -75,7 +75,8 @@ bool Curl_win32_idn_to_ascii(const char *in, char **out)
   wchar_t *in_w = curlx_convert_UTF8_to_wchar(in);
   if(in_w) {
     wchar_t punycode[IDN_MAX_LENGTH];
-    int chars = IdnToAscii(0, in_w, -1, punycode, IDN_MAX_LENGTH);
+    int chars = IdnToAscii(0, in_w, (int)(wcslen(in_w) + 1), punycode,
+                           IDN_MAX_LENGTH);
     curlx_unicodefree(in_w);
     if(chars) {
       char *mstr = curlx_convert_wchar_to_UTF8(punycode);
@@ -91,6 +92,27 @@ bool Curl_win32_idn_to_ascii(const char *in, char **out)
   return success;
 }
 
+char *Curl_win32_ascii_to_idn(const char *in)
+{
+  char *out = NULL;
+
+  wchar_t *in_w = curlx_convert_UTF8_to_wchar(in);
+  if(in_w) {
+    WCHAR idn[IDN_MAX_LENGTH]; /* stores a UTF-16 string */
+    int chars = IdnToUnicode(0, in_w, (int)(wcslen(in_w) + 1), idn,
+                             IDN_MAX_LENGTH);
+    if(chars) {
+      /* 'chars' is "the number of characters retrieved" */
+      char *mstr = curlx_convert_wchar_to_UTF8(idn);
+      if(mstr) {
+        out = strdup(mstr);
+        curlx_unicodefree(mstr);
+      }
+    }
+  }
+  return out;
+}
+
 #endif /* USE_WIN32_IDN */
 
 /*
@@ -144,6 +166,19 @@ static char *idn_decode(const char *input)
   return decoded;
 }
 
+static char *idn_encode(const char *puny)
+{
+  char *enc = NULL;
+#ifdef USE_LIBIDN2
+  int rc = idn2_to_unicode_8z8z(puny, &enc, 0);
+  if(rc != IDNA_SUCCESS)
+    return NULL;
+#elif defined(USE_WIN32_IDN)
+  enc = Curl_win32_ascii_to_idn(puny);
+#endif
+  return enc;
+}
+
 char *Curl_idn_decode(const char *input)
 {
   char *d = idn_decode(input);
@@ -157,6 +192,19 @@ char *Curl_idn_decode(const char *input)
   return d;
 }
 
+char *Curl_idn_encode(const char *puny)
+{
+  char *d = idn_encode(puny);
+#ifdef USE_LIBIDN2
+  if(d) {
+    char *c = strdup(d);
+    idn2_free(d);
+    d = c;
+  }
+#endif
+  return d;
+}
+
 /*
  * Frees data allocated by idnconvert_hostname()
  */
index 6c0bbb7109331c5720958b940020bed8b603d81b..2c292cdd92dd390d4ca44863f5f101cccfe2b4a2 100644 (file)
--- a/lib/idn.h
+++ b/lib/idn.h
@@ -26,6 +26,7 @@
 
 #ifdef USE_WIN32_IDN
 bool Curl_win32_idn_to_ascii(const char *in, char **out);
+char *Curl_win32_ascii_to_idn(const char *in);
 #endif /* USE_WIN32_IDN */
 bool Curl_is_ASCII_name(const char *hostname);
 CURLcode Curl_idnconvert_hostname(struct hostname *host);
@@ -33,6 +34,7 @@ CURLcode Curl_idnconvert_hostname(struct hostname *host);
 #define USE_IDN
 void Curl_free_idnconverted_hostname(struct hostname *host);
 char *Curl_idn_decode(const char *input);
+char *Curl_idn_encode(const char *input);
 #ifdef USE_LIBIDN2
 #define Curl_idn_free(x) idn2_free(x)
 #else
index b1a126d548213048e22a71fa48437d3146919328..74bd67be6fc637f98bb359756a4a9fbcdcb38087 100644 (file)
@@ -1403,6 +1403,7 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
   bool urldecode = (flags & CURLU_URLDECODE)?1:0;
   bool urlencode = (flags & CURLU_URLENCODE)?1:0;
   bool punycode = FALSE;
+  bool depunyfy = FALSE;
   bool plusdecode = FALSE;
   (void)flags;
   if(!u)
@@ -1433,6 +1434,7 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
     ptr = u->host;
     ifmissing = CURLUE_NO_HOST;
     punycode = (flags & CURLU_PUNYCODE)?1:0;
+    depunyfy = (flags & CURLU_PUNY2IDN)?1:0;
     break;
   case CURLUPART_ZONEID:
     ptr = u->zoneid;
@@ -1483,6 +1485,7 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
     char *port = u->port;
     char *allochost = NULL;
     punycode = (flags & CURLU_PUNYCODE)?1:0;
+    depunyfy = (flags & CURLU_PUNY2IDN)?1:0;
     if(u->scheme && strcasecompare("file", u->scheme)) {
       url = aprintf("file://%s%s%s",
                     u->path,
@@ -1548,6 +1551,17 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
 #endif
         }
       }
+      else if(depunyfy) {
+        if(Curl_is_ASCII_name(u->host) && !strncmp("xn--", u->host, 4)) {
+#ifndef USE_IDN
+          return CURLUE_LACKS_IDN;
+#else
+          allochost = Curl_idn_encode(u->host);
+          if(!allochost)
+            return CURLUE_OUT_OF_MEMORY;
+#endif
+        }
+      }
 
       url = aprintf("%s://%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
                     scheme,
@@ -1626,6 +1640,19 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
 #endif
       }
     }
+    else if(depunyfy) {
+      if(Curl_is_ASCII_name(u->host)  && !strncmp("xn--", u->host, 4)) {
+#ifndef USE_IDN
+        return CURLUE_LACKS_IDN;
+#else
+        char *allochost = Curl_idn_encode(*part);
+        if(!allochost)
+          return CURLUE_OUT_OF_MEMORY;
+        free(*part);
+        *part = allochost;
+#endif
+      }
+    }
 
     return CURLUE_OK;
   }
index ff03bec9391a4ea176c03b00f3e09cc1e365c5c7..f09f05f03ed41523fa42723d18b73dabe3f32b3f 100644 (file)
@@ -179,6 +179,9 @@ static const struct testcase get_parts_list[] ={
   {"https://räksmörgås.se",
    "https | [11] | [12] | [13] | xn--rksmrgs-5wao1o.se | "
    "[15] | / | [16] | [17]", 0, CURLU_PUNYCODE, CURLUE_OK},
+  {"https://xn--rksmrgs-5wao1o.se",
+   "https | [11] | [12] | [13] | räksmörgås.se | "
+   "[15] | / | [16] | [17]", 0, CURLU_PUNY2IDN, CURLUE_OK},
 #else
   {"https://räksmörgås.se",
    "https | [11] | [12] | [13] | [30] | [15] | / | [16] | [17]",