Coverage Report

Created: 2025-12-31 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gnutls/lib/str-idna.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2017 Tim Rühsen
3
 * Copyright (C) 2016, 2017 Red Hat, Inc.
4
 *
5
 * Author: Nikos Mavrogiannopoulos
6
 *
7
 * This file is part of GnuTLS.
8
 *
9
 * The GnuTLS is free software; you can redistribute it and/or
10
 * modify it under the terms of the GNU Lesser General Public License
11
 * as published by the Free Software Foundation; either version 2.1 of
12
 * the License, or (at your option) any later version.
13
 *
14
 * This library is distributed in the hope that it will be useful, but
15
 * WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
 * Lesser General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Lesser General Public License
20
 * along with this program.  If not, see <https://www.gnu.org/licenses/>
21
 *
22
 */
23
24
#include "gnutls_int.h"
25
#include "errors.h"
26
#include "str.h"
27
#include <unistr.h>
28
29
#ifdef HAVE_LIBIDN2
30
31
#include <idn2.h>
32
33
#define ICAST char
34
35
/**
36
 * gnutls_idna_map:
37
 * @input: contain the UTF-8 formatted domain name
38
 * @ilen: the length of the provided string
39
 * @out: the result in an null-terminated allocated string
40
 * @flags: should be zero
41
 *
42
 * This function will convert the provided UTF-8 domain name, to
43
 * its IDNA mapping in an allocated variable. Note that depending on the flags the used gnutls
44
 * library was compiled with, the output of this function may vary (i.e.,
45
 * may be IDNA2008, or IDNA2003).
46
 *
47
 * To force IDNA2008 specify the flag %GNUTLS_IDNA_FORCE_2008. In
48
 * the case GnuTLS is not compiled with the necessary dependencies,
49
 * %GNUTLS_E_UNIMPLEMENTED_FEATURE will be returned to indicate that
50
 * gnutls is unable to perform the requested conversion.
51
 *
52
 * Note also, that this function will return an empty string if an
53
 * empty string is provided as input.
54
 *
55
 * Returns: %GNUTLS_E_INVALID_UTF8_STRING on invalid UTF-8 data, or 0 on success.
56
 *
57
 * Since: 3.5.8
58
 **/
59
int gnutls_idna_map(const char *input, unsigned ilen, gnutls_datum_t *out,
60
        unsigned flags)
61
45.6k
{
62
45.6k
  char *idna = NULL;
63
45.6k
  int rc, ret;
64
45.6k
  gnutls_datum_t istr;
65
45.6k
  unsigned int idn2_flags = IDN2_NFC_INPUT;
66
45.6k
  unsigned int idn2_tflags = IDN2_NFC_INPUT;
67
68
  /* IDN2_NONTRANSITIONAL automatically converts to lowercase
69
   * IDN2_NFC_INPUT converts to NFC before toASCII conversion
70
   *
71
   * Since IDN2_NONTRANSITIONAL implicitly does NFC conversion, we don't need
72
   * the additional IDN2_NFC_INPUT. But just for the unlikely case that the linked
73
   * library is not matching the headers when building and it doesn't support TR46,
74
   * we provide IDN2_NFC_INPUT.
75
   *
76
   * Without IDN2_USE_STD3_ASCII_RULES, the result could contain any ASCII characters,
77
   * e.g. 'evil.c\u2100.example.com' will be converted into
78
   * 'evil.ca/c.example.com', which seems no good idea. */
79
45.6k
  idn2_flags |= IDN2_NONTRANSITIONAL | IDN2_USE_STD3_ASCII_RULES;
80
45.6k
  idn2_tflags |= IDN2_TRANSITIONAL | IDN2_USE_STD3_ASCII_RULES;
81
82
45.6k
  if (ilen == 0) {
83
0
    out->data = (uint8_t *)gnutls_strdup("");
84
0
    out->size = 0;
85
0
    if (out->data == NULL)
86
0
      return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
87
0
    return 0;
88
0
  }
89
90
45.6k
  if (_gnutls_str_is_print(input, ilen)) {
91
43.0k
    return _gnutls_set_strdatum(out, input, ilen);
92
43.0k
  }
93
94
2.64k
  ret = _gnutls_set_strdatum(&istr, input, ilen);
95
2.64k
  if (ret < 0) {
96
0
    gnutls_assert();
97
0
    return ret;
98
0
  }
99
100
2.64k
  rc = idn2_to_ascii_8z((ICAST *)istr.data, (ICAST **)&idna, idn2_flags);
101
2.64k
  if (rc == IDN2_DISALLOWED && !(flags & GNUTLS_IDNA_FORCE_2008))
102
811
    rc = idn2_to_ascii_8z((ICAST *)istr.data, (ICAST **)&idna,
103
811
              idn2_tflags);
104
105
2.64k
  if (rc != IDN2_OK) {
106
1.20k
    gnutls_assert();
107
1.20k
    idna = NULL; /* in case idn2_lookup_u8 modifies &idna */
108
1.20k
    _gnutls_debug_log(
109
1.20k
      "unable to convert name '%s' to IDNA format: %s\n",
110
1.20k
      istr.data, idn2_strerror(rc));
111
1.20k
    ret = GNUTLS_E_INVALID_UTF8_STRING;
112
1.20k
    goto fail;
113
1.20k
  }
114
115
1.43k
  if (gnutls_free != idn2_free) {
116
1.43k
    ret = _gnutls_set_strdatum(out, idna, strlen(idna));
117
1.43k
  } else {
118
0
    out->data = (unsigned char *)idna;
119
0
    out->size = strlen(idna);
120
0
    idna = NULL;
121
0
    ret = 0;
122
0
  }
123
124
2.64k
fail:
125
2.64k
  idn2_free(idna);
126
2.64k
  gnutls_free(istr.data);
127
2.64k
  return ret;
128
1.43k
}
129
130
/**
131
 * gnutls_idna_reverse_map:
132
 * @input: contain the ACE (IDNA) formatted domain name
133
 * @ilen: the length of the provided string
134
 * @out: the result in an null-terminated allocated UTF-8 string
135
 * @flags: should be zero
136
 *
137
 * This function will convert an ACE (ASCII-encoded) domain name to a UTF-8 domain name.
138
 *
139
 * If GnuTLS is compiled without IDNA support, then this function
140
 * will return %GNUTLS_E_UNIMPLEMENTED_FEATURE.
141
 *
142
 * Note also, that this function will return an empty string if an
143
 * empty string is provided as input.
144
 *
145
 * Returns: A negative error code on error, or 0 on success.
146
 *
147
 * Since: 3.5.8
148
 **/
149
int gnutls_idna_reverse_map(const char *input, unsigned ilen,
150
          gnutls_datum_t *out, unsigned flags)
151
3.75k
{
152
3.75k
  char *u8 = NULL;
153
3.75k
  int rc, ret;
154
3.75k
  gnutls_datum_t istr;
155
156
3.75k
  if (ilen == 0) {
157
29
    out->data = (uint8_t *)gnutls_strdup("");
158
29
    out->size = 0;
159
29
    if (out->data == NULL)
160
0
      return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
161
29
    return 0;
162
29
  }
163
164
3.72k
  ret = _gnutls_set_strdatum(&istr, input, ilen);
165
3.72k
  if (ret < 0) {
166
0
    gnutls_assert();
167
0
    return ret;
168
0
  }
169
170
  /* currently libidn2 just converts single labels, thus a wrapper function */
171
3.72k
  rc = idn2_to_unicode_8z8z((char *)istr.data, &u8, 0);
172
3.72k
  if (rc != IDN2_OK) {
173
1.87k
    gnutls_assert();
174
1.87k
    _gnutls_debug_log(
175
1.87k
      "unable to convert ACE name '%s' to UTF-8 format: %s\n",
176
1.87k
      istr.data, idn2_strerror(rc));
177
1.87k
    ret = GNUTLS_E_INVALID_UTF8_STRING;
178
1.87k
    goto fail;
179
1.87k
  }
180
181
1.84k
  if (gnutls_malloc != malloc) {
182
0
    ret = _gnutls_set_strdatum(out, u8, strlen(u8));
183
1.84k
  } else {
184
1.84k
    out->data = (unsigned char *)u8;
185
1.84k
    out->size = strlen(u8);
186
1.84k
    u8 = NULL;
187
1.84k
    ret = 0;
188
1.84k
  }
189
3.72k
fail:
190
3.72k
  idn2_free(u8);
191
3.72k
  gnutls_free(istr.data);
192
3.72k
  return ret;
193
1.84k
}
194
195
#else /* no HAVE_LIBIDN2 */
196
197
#undef gnutls_idna_map
198
int gnutls_idna_map(const char *input, unsigned ilen, gnutls_datum_t *out,
199
        unsigned flags)
200
{
201
  if (!_gnutls_str_is_print(input, ilen)) {
202
    return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE);
203
  }
204
205
  return _gnutls_set_strdatum(out, input, ilen);
206
}
207
208
int gnutls_idna_reverse_map(const char *input, unsigned ilen,
209
          gnutls_datum_t *out, unsigned flags)
210
{
211
  return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE);
212
}
213
#endif /* HAVE_LIBIDN2 */
214
215
int _gnutls_idna_email_map(const char *input, unsigned ilen,
216
         gnutls_datum_t *output)
217
0
{
218
0
  const char *p = input;
219
220
0
  while (*p != 0 && *p != '@') {
221
0
    if (!c_isprint(*p))
222
0
      return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL);
223
0
    p++;
224
0
  }
225
226
0
  if (_gnutls_str_is_print(input, ilen)) {
227
0
    return _gnutls_set_strdatum(output, input, ilen);
228
0
  }
229
230
0
  if (*p == '@') {
231
0
    unsigned name_part = p - input;
232
0
    int ret;
233
0
    gnutls_datum_t domain;
234
235
0
    ret = gnutls_idna_map(p + 1, ilen - name_part - 1, &domain, 0);
236
0
    if (ret < 0)
237
0
      return gnutls_assert_val(ret);
238
239
0
    output->data = gnutls_malloc(name_part + 1 + domain.size + 1);
240
0
    if (output->data == NULL) {
241
0
      gnutls_free(domain.data);
242
0
      return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
243
0
    }
244
0
    memcpy(output->data, input, name_part);
245
0
    output->data[name_part] = '@';
246
0
    memcpy(&output->data[name_part + 1], domain.data, domain.size);
247
0
    output->data[name_part + domain.size + 1] = 0;
248
0
    output->size = name_part + domain.size + 1;
249
0
    gnutls_free(domain.data);
250
0
    return 0;
251
0
  } else {
252
0
    return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL);
253
0
  }
254
0
}
255
256
int _gnutls_idna_email_reverse_map(const char *input, unsigned ilen,
257
           gnutls_datum_t *output)
258
1.20k
{
259
1.20k
  const char *p = input;
260
261
7.22k
  while (*p != 0 && *p != '@') {
262
6.01k
    if (!c_isprint(*p))
263
0
      return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL);
264
6.01k
    p++;
265
6.01k
  }
266
267
1.20k
  if (*p == '@') {
268
760
    unsigned name_part = p - input;
269
760
    int ret;
270
760
    gnutls_datum_t domain;
271
272
760
    ret = gnutls_idna_reverse_map(p + 1, ilen - name_part - 1,
273
760
                &domain, 0);
274
760
    if (ret < 0)
275
314
      return gnutls_assert_val(ret);
276
277
446
    output->data = gnutls_malloc(name_part + 1 + domain.size + 1);
278
446
    if (output->data == NULL) {
279
0
      gnutls_free(domain.data);
280
0
      return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
281
0
    }
282
446
    memcpy(output->data, input, name_part);
283
446
    output->data[name_part] = '@';
284
446
    memcpy(&output->data[name_part + 1], domain.data, domain.size);
285
446
    output->data[name_part + domain.size + 1] = 0;
286
446
    output->size = name_part + domain.size + 1;
287
446
    gnutls_free(domain.data);
288
446
    return 0;
289
449
  } else {
290
449
    return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL);
291
449
  }
292
1.20k
}