Coverage Report

Created: 2026-08-13 07:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/idn.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
/*
25
 * IDN conversions
26
 */
27
#include "curl_setup.h"
28
29
#include "urldata.h"
30
#include "curlx/strparse.h"
31
#include "idn.h"
32
33
#ifdef USE_LIBIDN2
34
#include <idn2.h>
35
36
#if defined(_WIN32) && defined(UNICODE)
37
#define IDN2_LOOKUP(name, host, flags)                           \
38
  idn2_lookup_u8((const uint8_t *)name, (uint8_t **)host, flags)
39
#else
40
#define IDN2_LOOKUP(name, host, flags)                         \
41
4.20k
  idn2_lookup_ul((const char *)(name), (char **)(host), flags)
42
#endif
43
#endif /* USE_LIBIDN2 */
44
45
/* for macOS and iOS targets */
46
#ifdef USE_APPLE_IDN
47
#include <unicode/uidna.h>
48
#include <iconv.h>
49
#include <langinfo.h>
50
51
#define MAX_HOST_LENGTH 512
52
53
static CURLcode iconv_to_utf8(const char *in, size_t inlen,
54
                              char **out, size_t *outlen)
55
{
56
  iconv_t cd = iconv_open("UTF-8", nl_langinfo(CODESET));
57
  if(cd != (iconv_t)-1) {
58
    size_t iconv_outlen = *outlen;
59
    char *iconv_in = (char *)CURL_UNCONST(in);
60
    size_t iconv_inlen = inlen;
61
    size_t iconv_result = iconv(cd, &iconv_in, &iconv_inlen,
62
                                out, &iconv_outlen);
63
    *outlen -= iconv_outlen;
64
    iconv_close(cd);
65
    if(iconv_result == (size_t)-1) {
66
      /* !checksrc! disable ERRNOVAR 1 */
67
      if(errno == ENOMEM)
68
        return CURLE_OUT_OF_MEMORY;
69
      else
70
        return CURLE_URL_MALFORMAT;
71
    }
72
73
    return CURLE_OK;
74
  }
75
  else {
76
    /* !checksrc! disable ERRNOVAR 1 */
77
    if(errno == ENOMEM)
78
      return CURLE_OUT_OF_MEMORY;
79
    else
80
      return CURLE_FAILED_INIT;
81
  }
82
}
83
84
static CURLcode mac_idn_to_ascii(const char *in, char **out)
85
{
86
  size_t inlen = strlen(in);
87
  if(inlen < MAX_HOST_LENGTH) {
88
    char iconv_buffer[MAX_HOST_LENGTH] = { 0 };
89
    char *iconv_outptr = iconv_buffer;
90
    size_t iconv_outlen = sizeof(iconv_buffer);
91
    CURLcode iconv_result = iconv_to_utf8(in, inlen,
92
                                          &iconv_outptr, &iconv_outlen);
93
    if(!iconv_result) {
94
      UErrorCode err = U_ZERO_ERROR;
95
      UIDNA *idna = uidna_openUTS46(
96
        UIDNA_CHECK_BIDI | UIDNA_NONTRANSITIONAL_TO_ASCII, &err);
97
      if(!U_FAILURE(err)) {
98
        UIDNAInfo info = UIDNA_INFO_INITIALIZER;
99
        char buffer[MAX_HOST_LENGTH] = { 0 };
100
        (void)uidna_nameToASCII_UTF8(idna, iconv_buffer, (int)iconv_outlen,
101
                                     buffer, sizeof(buffer) - 1, &info, &err);
102
        uidna_close(idna);
103
        if(!U_FAILURE(err) && !info.errors) {
104
          *out = curlx_strdup(buffer);
105
          if(*out)
106
            return CURLE_OK;
107
          else
108
            return CURLE_OUT_OF_MEMORY;
109
        }
110
      }
111
    }
112
    else
113
      return iconv_result;
114
  }
115
  return CURLE_URL_MALFORMAT;
116
}
117
118
static CURLcode mac_ascii_to_idn(const char *in, char **out)
119
{
120
  size_t inlen = strlen(in);
121
  if(inlen < MAX_HOST_LENGTH) {
122
    UErrorCode err = U_ZERO_ERROR;
123
    UIDNA *idna = uidna_openUTS46(
124
      UIDNA_CHECK_BIDI | UIDNA_NONTRANSITIONAL_TO_UNICODE, &err);
125
    if(!U_FAILURE(err)) {
126
      UIDNAInfo info = UIDNA_INFO_INITIALIZER;
127
      char buffer[MAX_HOST_LENGTH] = { 0 };
128
      (void)uidna_nameToUnicodeUTF8(idna, in, -1, buffer,
129
                                    sizeof(buffer) - 1, &info, &err);
130
      uidna_close(idna);
131
      if(!U_FAILURE(err)) {
132
        *out = curlx_strdup(buffer);
133
        if(*out)
134
          return CURLE_OK;
135
        else
136
          return CURLE_OUT_OF_MEMORY;
137
      }
138
    }
139
  }
140
  return CURLE_URL_MALFORMAT;
141
}
142
#endif
143
144
#ifdef USE_WIN32_IDN
145
/* using Windows kernel32 and normaliz libraries. */
146
147
#define IDN_MAX_LENGTH 255
148
149
static char *idn_curlx_convert_wchar_to_UTF8(const wchar_t *str_w, int chars)
150
{
151
  char *str_utf8 = NULL;
152
  int bytes = WideCharToMultiByte(CP_UTF8, 0, str_w, chars, NULL, 0,
153
                                  NULL, NULL);
154
  if(bytes > 0) {
155
    str_utf8 = curlx_malloc(bytes);
156
    if(str_utf8) {
157
      if(WideCharToMultiByte(CP_UTF8, 0, str_w, chars, str_utf8, bytes,
158
                             NULL, NULL) == 0) {
159
        curlx_free(str_utf8);
160
        return NULL;
161
      }
162
    }
163
  }
164
  return str_utf8;
165
}
166
167
static CURLcode win32_idn_to_ascii(const char *in, char **out)
168
{
169
  wchar_t in_w[IDN_MAX_LENGTH];
170
  int in_w_len;
171
  *out = NULL;
172
  /* Returned in_w_len includes the null-terminator, which then gets
173
     preserved across the calls that follow, ending up terminating
174
     the buffer returned to the caller. */
175
  in_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
176
                                 in, -1, in_w, IDN_MAX_LENGTH);
177
  if(in_w_len) {
178
    wchar_t punycode[IDN_MAX_LENGTH];
179
    int chars = IdnToAscii(0, in_w, in_w_len, punycode, IDN_MAX_LENGTH);
180
    if(chars > 0) {
181
      *out = idn_curlx_convert_wchar_to_UTF8(punycode, chars);
182
      if(!*out)
183
        return CURLE_OUT_OF_MEMORY;
184
    }
185
    else
186
      return CURLE_URL_MALFORMAT;
187
  }
188
  else
189
    return CURLE_URL_MALFORMAT;
190
191
  return CURLE_OK;
192
}
193
194
static CURLcode win32_ascii_to_idn(const char *in, char **out)
195
{
196
  wchar_t in_w[IDN_MAX_LENGTH];
197
  int in_w_len;
198
  *out = NULL;
199
  /* Returned in_w_len includes the null-terminator, which then gets
200
     preserved across the calls that follow, ending up terminating
201
     the buffer returned to the caller. */
202
  in_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
203
                                 in, -1, in_w, IDN_MAX_LENGTH);
204
  if(in_w_len) {
205
    WCHAR idn[IDN_MAX_LENGTH]; /* stores a UTF-16 string */
206
    int chars = IdnToUnicode(0, in_w, in_w_len, idn, IDN_MAX_LENGTH);
207
    if(chars > 0) {  /* 'chars' is "the number of characters retrieved" */
208
      *out = idn_curlx_convert_wchar_to_UTF8(idn, chars);
209
      if(!*out)
210
        return CURLE_OUT_OF_MEMORY;
211
    }
212
    else
213
      return CURLE_URL_MALFORMAT;
214
  }
215
  else
216
    return CURLE_URL_MALFORMAT;
217
218
  return CURLE_OK;
219
}
220
221
#endif /* USE_WIN32_IDN */
222
223
/*
224
 * Helpers for IDNA conversions.
225
 */
226
bool Curl_is_ASCII_name(const char *hostname)
227
208
{
228
208
  if(hostname) {
229
196
    struct Curl_str s;
230
196
    s.str = hostname;
231
196
    s.len = strlen(hostname);
232
196
    return Curl_is_ASCII_str(&s);
233
196
  }
234
12
  return TRUE;
235
208
}
236
237
bool Curl_is_ASCII_str(struct Curl_str *s)
238
350k
{
239
350k
  if(s && s->len) {
240
350k
    const unsigned char *ch = (const unsigned char *)s->str;
241
350k
    size_t i;
242
8.34M
    for(i = 0; i < s->len; ++i) {
243
7.99M
      if(ch[i] & 0x80)
244
2.11k
        return FALSE;
245
7.99M
    }
246
350k
  }
247
348k
  return TRUE;
248
350k
}
249
250
#ifdef USE_IDN
251
/*
252
 * Curl_idn_decode() returns an allocated IDN decoded string if it was
253
 * possible. NULL on error.
254
 *
255
 * CURLE_URL_MALFORMAT - the hostname could not be converted
256
 * CURLE_OUT_OF_MEMORY - memory problem
257
 *
258
 */
259
static CURLcode idn_decode(const char *input, char **output)
260
2.10k
{
261
2.10k
  char *decoded = NULL;
262
2.10k
  CURLcode result = CURLE_OK;
263
2.10k
#ifdef USE_LIBIDN2
264
2.10k
  if(idn2_check_version(IDN2_VERSION)) {
265
2.10k
    int flags = IDN2_NFC_INPUT
266
2.10k
#if IDN2_VERSION_NUMBER >= 0x00140000
267
      /* IDN2_NFC_INPUT: Normalize input string using normalization form C.
268
         IDN2_NONTRANSITIONAL: Perform Unicode TR46 non-transitional
269
         processing. */
270
2.10k
      | IDN2_NONTRANSITIONAL
271
2.10k
#endif
272
2.10k
      ;
273
2.10k
    int rc = IDN2_LOOKUP(input, &decoded, flags);
274
2.10k
    if(rc != IDN2_OK)
275
      /* fallback to TR46 Transitional mode for better IDNA2003
276
         compatibility */
277
2.10k
      rc = IDN2_LOOKUP(input, &decoded, IDN2_TRANSITIONAL);
278
2.10k
    if(rc != IDN2_OK)
279
2.10k
      result = CURLE_URL_MALFORMAT;
280
2.10k
  }
281
0
  else
282
    /* a too old libidn2 version */
283
0
    result = CURLE_NOT_BUILT_IN;
284
#elif defined(USE_WIN32_IDN)
285
  result = win32_idn_to_ascii(input, &decoded);
286
#elif defined(USE_APPLE_IDN)
287
  result = mac_idn_to_ascii(input, &decoded);
288
#endif
289
2.10k
  if(!result)
290
0
    *output = decoded;
291
2.10k
  return result;
292
2.10k
}
293
294
static CURLcode idn_encode(const char *puny, char **output)
295
0
{
296
0
  char *enc = NULL;
297
0
#ifdef USE_LIBIDN2
298
0
  int rc = idn2_to_unicode_8z8z(puny, &enc, 0);
299
0
  if(rc != IDNA_SUCCESS)
300
0
    return rc == IDNA_MALLOC_ERROR ? CURLE_OUT_OF_MEMORY : CURLE_URL_MALFORMAT;
301
#elif defined(USE_WIN32_IDN)
302
  CURLcode result = win32_ascii_to_idn(puny, &enc);
303
  if(result)
304
    return result;
305
#elif defined(USE_APPLE_IDN)
306
  CURLcode result = mac_ascii_to_idn(puny, &enc);
307
  if(result)
308
    return result;
309
#endif
310
0
  *output = enc;
311
0
  return CURLE_OK;
312
0
}
313
314
CURLcode Curl_idn_decode(const char *input, char **output)
315
2.10k
{
316
2.10k
  char *d = NULL;
317
2.10k
  CURLcode result = idn_decode(input, &d);
318
2.10k
#ifdef USE_LIBIDN2
319
2.10k
  if(!result) {
320
0
    char *c = curlx_strdup(d);
321
0
    idn2_free(d);
322
0
    if(c)
323
0
      d = c;
324
0
    else
325
0
      result = CURLE_OUT_OF_MEMORY;
326
0
  }
327
2.10k
#endif
328
2.10k
  if(!result) {
329
0
    if(!d[0]) { /* ended up zero length, not acceptable */
330
0
      result = CURLE_URL_MALFORMAT;
331
0
      curlx_free(d);
332
0
    }
333
0
    else
334
0
      *output = d;
335
0
  }
336
2.10k
  return result;
337
2.10k
}
338
339
CURLcode Curl_idn_encode(const char *puny, char **output)
340
0
{
341
0
  char *d = NULL;
342
0
  CURLcode result = idn_encode(puny, &d);
343
0
#ifdef USE_LIBIDN2
344
0
  if(!result) {
345
0
    char *c = curlx_strdup(d);
346
0
    idn2_free(d);
347
0
    if(c)
348
0
      d = c;
349
0
    else
350
0
      result = CURLE_OUT_OF_MEMORY;
351
0
  }
352
0
#endif
353
0
  if(!result)
354
0
    *output = d;
355
0
  return result;
356
0
}
357
358
/*
359
 * Frees data allocated by idnconvert_hostname()
360
 */
361
void Curl_free_idnconverted_hostname(struct hostname *host)
362
656
{
363
656
  curlx_safefree(host->encalloc);
364
656
}
365
366
#endif /* USE_IDN */
367
368
/*
369
 * Perform any necessary IDN conversion of hostname
370
 */
371
CURLcode Curl_idnconvert_hostname(struct hostname *host)
372
61
{
373
  /* set the name we use to display the hostname */
374
61
  host->dispname = host->name;
375
376
61
#ifdef USE_IDN
377
  /* Check name for non-ASCII and convert hostname if we can */
378
61
  if(!Curl_is_ASCII_name(host->name)) {
379
18
    char *decoded;
380
18
    CURLcode result = Curl_idn_decode(host->name, &decoded);
381
18
    if(result)
382
18
      return result;
383
    /* successful */
384
0
    host->name = host->encalloc = decoded;
385
0
  }
386
43
#endif
387
43
  return CURLE_OK;
388
61
}