Coverage Report

Created: 2026-07-14 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Utilities/cmcurl/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
  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, 0, in, -1, in_w, IDN_MAX_LENGTH);
176
  if(in_w_len) {
177
    wchar_t punycode[IDN_MAX_LENGTH];
178
    int chars = IdnToAscii(0, in_w, in_w_len, punycode, IDN_MAX_LENGTH);
179
    if(chars > 0) {
180
      *out = idn_curlx_convert_wchar_to_UTF8(punycode, chars);
181
      if(!*out)
182
        return CURLE_OUT_OF_MEMORY;
183
    }
184
    else
185
      return CURLE_URL_MALFORMAT;
186
  }
187
  else
188
    return CURLE_URL_MALFORMAT;
189
190
  return CURLE_OK;
191
}
192
193
static CURLcode win32_ascii_to_idn(const char *in, char **out)
194
{
195
  wchar_t in_w[IDN_MAX_LENGTH];
196
  int in_w_len;
197
  *out = NULL;
198
  /* Returned in_w_len includes the null-terminator, which then gets
199
     preserved across the calls that follow, ending up terminating
200
     the buffer returned to the caller. */
201
  in_w_len = MultiByteToWideChar(CP_UTF8, 0, in, -1, in_w, IDN_MAX_LENGTH);
202
  if(in_w_len) {
203
    WCHAR idn[IDN_MAX_LENGTH]; /* stores a UTF-16 string */
204
    int chars = IdnToUnicode(0, in_w, in_w_len, idn, IDN_MAX_LENGTH);
205
    if(chars > 0) {  /* 'chars' is "the number of characters retrieved" */
206
      *out = idn_curlx_convert_wchar_to_UTF8(idn, chars);
207
      if(!*out)
208
        return CURLE_OUT_OF_MEMORY;
209
    }
210
    else
211
      return CURLE_URL_MALFORMAT;
212
  }
213
  else
214
    return CURLE_URL_MALFORMAT;
215
216
  return CURLE_OK;
217
}
218
219
#endif /* USE_WIN32_IDN */
220
221
/*
222
 * Helpers for IDNA conversions.
223
 */
224
bool Curl_is_ASCII_name(const char *hostname)
225
0
{
226
0
  if(hostname) {
227
0
    struct Curl_str s;
228
0
    s.str = hostname;
229
0
    s.len = strlen(hostname);
230
0
    return Curl_is_ASCII_str(&s);
231
0
  }
232
0
  return TRUE;
233
0
}
234
235
bool Curl_is_ASCII_str(struct Curl_str *s)
236
0
{
237
0
  if(s && s->len) {
238
0
    const unsigned char *ch = (const unsigned char *)s->str;
239
0
    size_t i;
240
0
    for(i = 0; i < s->len; ++i) {
241
0
      if(ch[i] & 0x80)
242
0
        return FALSE;
243
0
    }
244
0
  }
245
0
  return TRUE;
246
0
}
247
248
#ifdef USE_IDN
249
/*
250
 * Curl_idn_decode() returns an allocated IDN decoded string if it was
251
 * possible. NULL on error.
252
 *
253
 * CURLE_URL_MALFORMAT - the hostname could not be converted
254
 * CURLE_OUT_OF_MEMORY - memory problem
255
 *
256
 */
257
static CURLcode idn_decode(const char *input, char **output)
258
{
259
  char *decoded = NULL;
260
  CURLcode result = CURLE_OK;
261
#ifdef USE_LIBIDN2
262
  if(idn2_check_version(IDN2_VERSION)) {
263
    int flags = IDN2_NFC_INPUT
264
#if IDN2_VERSION_NUMBER >= 0x00140000
265
      /* IDN2_NFC_INPUT: Normalize input string using normalization form C.
266
         IDN2_NONTRANSITIONAL: Perform Unicode TR46 non-transitional
267
         processing. */
268
      | IDN2_NONTRANSITIONAL
269
#endif
270
      ;
271
    int rc = IDN2_LOOKUP(input, &decoded, flags);
272
    if(rc != IDN2_OK)
273
      /* fallback to TR46 Transitional mode for better IDNA2003
274
         compatibility */
275
      rc = IDN2_LOOKUP(input, &decoded, IDN2_TRANSITIONAL);
276
    if(rc != IDN2_OK)
277
      result = CURLE_URL_MALFORMAT;
278
  }
279
  else
280
    /* a too old libidn2 version */
281
    result = CURLE_NOT_BUILT_IN;
282
#elif defined(USE_WIN32_IDN)
283
  result = win32_idn_to_ascii(input, &decoded);
284
#elif defined(USE_APPLE_IDN)
285
  result = mac_idn_to_ascii(input, &decoded);
286
#endif
287
  if(!result)
288
    *output = decoded;
289
  return result;
290
}
291
292
static CURLcode idn_encode(const char *puny, char **output)
293
{
294
  char *enc = NULL;
295
#ifdef USE_LIBIDN2
296
  int rc = idn2_to_unicode_8z8z(puny, &enc, 0);
297
  if(rc != IDNA_SUCCESS)
298
    return rc == IDNA_MALLOC_ERROR ? CURLE_OUT_OF_MEMORY : CURLE_URL_MALFORMAT;
299
#elif defined(USE_WIN32_IDN)
300
  CURLcode result = win32_ascii_to_idn(puny, &enc);
301
  if(result)
302
    return result;
303
#elif defined(USE_APPLE_IDN)
304
  CURLcode result = mac_ascii_to_idn(puny, &enc);
305
  if(result)
306
    return result;
307
#endif
308
  *output = enc;
309
  return CURLE_OK;
310
}
311
312
CURLcode Curl_idn_decode(const char *input, char **output)
313
{
314
  char *d = NULL;
315
  CURLcode result = idn_decode(input, &d);
316
#ifdef USE_LIBIDN2
317
  if(!result) {
318
    char *c = curlx_strdup(d);
319
    idn2_free(d);
320
    if(c)
321
      d = c;
322
    else
323
      result = CURLE_OUT_OF_MEMORY;
324
  }
325
#endif
326
  if(!result) {
327
    if(!d[0]) { /* ended up zero length, not acceptable */
328
      result = CURLE_URL_MALFORMAT;
329
      curlx_free(d);
330
    }
331
    else
332
      *output = d;
333
  }
334
  return result;
335
}
336
337
CURLcode Curl_idn_encode(const char *puny, char **output)
338
{
339
  char *d = NULL;
340
  CURLcode result = idn_encode(puny, &d);
341
#ifdef USE_LIBIDN2
342
  if(!result) {
343
    char *c = curlx_strdup(d);
344
    idn2_free(d);
345
    if(c)
346
      d = c;
347
    else
348
      result = CURLE_OUT_OF_MEMORY;
349
  }
350
#endif
351
  if(!result)
352
    *output = d;
353
  return result;
354
}
355
356
/*
357
 * Frees data allocated by idnconvert_hostname()
358
 */
359
void Curl_free_idnconverted_hostname(struct hostname *host)
360
{
361
  curlx_safefree(host->encalloc);
362
}
363
364
#endif /* USE_IDN */
365
366
/*
367
 * Perform any necessary IDN conversion of hostname
368
 */
369
CURLcode Curl_idnconvert_hostname(struct hostname *host)
370
0
{
371
  /* set the name we use to display the hostname */
372
0
  host->dispname = host->name;
373
374
#ifdef USE_IDN
375
  /* Check name for non-ASCII and convert hostname if we can */
376
  if(!Curl_is_ASCII_name(host->name)) {
377
    char *decoded;
378
    CURLcode result = Curl_idn_decode(host->name, &decoded);
379
    if(result)
380
      return result;
381
    /* successful */
382
    host->name = host->encalloc = decoded;
383
  }
384
#endif
385
0
  return CURLE_OK;
386
0
}