Coverage Report

Created: 2023-03-26 07:33

/src/gnutls/lib/str-idna.c
Line
Count
Source (jump to first uncovered line)
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
0
{
62
0
  char *idna = NULL;
63
0
  int rc, ret;
64
0
  gnutls_datum_t istr;
65
0
  unsigned int idn2_flags = IDN2_NFC_INPUT;
66
0
  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
0
  idn2_flags |= IDN2_NONTRANSITIONAL | IDN2_USE_STD3_ASCII_RULES;
80
0
  idn2_tflags |= IDN2_TRANSITIONAL | IDN2_USE_STD3_ASCII_RULES;
81
82
0
  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
0
  if (_gnutls_str_is_print(input, ilen)) {
91
0
    return _gnutls_set_strdatum(out, input, ilen);
92
0
  }
93
94
0
  ret = _gnutls_set_strdatum(&istr, input, ilen);
95
0
  if (ret < 0) {
96
0
    gnutls_assert();
97
0
    return ret;
98
0
  }
99
100
0
  rc = idn2_to_ascii_8z((ICAST *) istr.data, (ICAST **) & idna,
101
0
            idn2_flags);
102
0
  if (rc == IDN2_DISALLOWED && !(flags & GNUTLS_IDNA_FORCE_2008))
103
0
    rc = idn2_to_ascii_8z((ICAST *) istr.data, (ICAST **) & idna,
104
0
              idn2_tflags);
105
106
0
  if (rc != IDN2_OK) {
107
0
    gnutls_assert();
108
0
    idna = NULL;  /* in case idn2_lookup_u8 modifies &idna */
109
0
    _gnutls_debug_log
110
0
        ("unable to convert name '%s' to IDNA format: %s\n",
111
0
         istr.data, idn2_strerror(rc));
112
0
    ret = GNUTLS_E_INVALID_UTF8_STRING;
113
0
    goto fail;
114
0
  }
115
116
0
  if (gnutls_free != idn2_free) {
117
0
    ret = _gnutls_set_strdatum(out, idna, strlen(idna));
118
0
  } else {
119
0
    out->data = (unsigned char *)idna;
120
0
    out->size = strlen(idna);
121
0
    idna = NULL;
122
0
    ret = 0;
123
0
  }
124
125
0
 fail:
126
0
  idn2_free(idna);
127
0
  gnutls_free(istr.data);
128
0
  return ret;
129
0
}
130
131
/**
132
 * gnutls_idna_reverse_map:
133
 * @input: contain the ACE (IDNA) formatted domain name
134
 * @ilen: the length of the provided string
135
 * @out: the result in an null-terminated allocated UTF-8 string
136
 * @flags: should be zero
137
 *
138
 * This function will convert an ACE (ASCII-encoded) domain name to a UTF-8 domain name.
139
 *
140
 * If GnuTLS is compiled without IDNA support, then this function
141
 * will return %GNUTLS_E_UNIMPLEMENTED_FEATURE.
142
 *
143
 * Note also, that this function will return an empty string if an
144
 * empty string is provided as input.
145
 *
146
 * Returns: A negative error code on error, or 0 on success.
147
 *
148
 * Since: 3.5.8
149
 **/
150
int gnutls_idna_reverse_map(const char *input, unsigned ilen,
151
          gnutls_datum_t * out, unsigned flags)
152
0
{
153
0
  char *u8 = NULL;
154
0
  int rc, ret;
155
0
  gnutls_datum_t istr;
156
157
0
  if (ilen == 0) {
158
0
    out->data = (uint8_t *) gnutls_strdup("");
159
0
    out->size = 0;
160
0
    if (out->data == NULL)
161
0
      return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
162
0
    return 0;
163
0
  }
164
165
0
  ret = _gnutls_set_strdatum(&istr, input, ilen);
166
0
  if (ret < 0) {
167
0
    gnutls_assert();
168
0
    return ret;
169
0
  }
170
171
  /* currently libidn2 just converts single labels, thus a wrapper function */
172
0
  rc = idn2_to_unicode_8z8z((char *)istr.data, &u8, 0);
173
0
  if (rc != IDN2_OK) {
174
0
    gnutls_assert();
175
0
    _gnutls_debug_log
176
0
        ("unable to convert ACE name '%s' to UTF-8 format: %s\n",
177
0
         istr.data, idn2_strerror(rc));
178
0
    ret = GNUTLS_E_INVALID_UTF8_STRING;
179
0
    goto fail;
180
0
  }
181
182
0
  if (gnutls_malloc != malloc) {
183
0
    ret = _gnutls_set_strdatum(out, u8, strlen(u8));
184
0
  } else {
185
0
    out->data = (unsigned char *)u8;
186
0
    out->size = strlen(u8);
187
0
    u8 = NULL;
188
0
    ret = 0;
189
0
  }
190
0
 fail:
191
0
  idn2_free(u8);
192
0
  gnutls_free(istr.data);
193
0
  return ret;
194
0
}
195
196
#else       /* no HAVE_LIBIDN2 */
197
198
# undef gnutls_idna_map
199
int gnutls_idna_map(const char *input, unsigned ilen, gnutls_datum_t * out,
200
        unsigned flags)
201
{
202
  if (!_gnutls_str_is_print(input, ilen)) {
203
    return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE);
204
  }
205
206
  return _gnutls_set_strdatum(out, input, ilen);
207
}
208
209
int gnutls_idna_reverse_map(const char *input, unsigned ilen,
210
          gnutls_datum_t * out, unsigned flags)
211
{
212
  return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE);
213
}
214
#endif        /* HAVE_LIBIDN2 */
215
216
int _gnutls_idna_email_map(const char *input, unsigned ilen,
217
         gnutls_datum_t * output)
218
0
{
219
0
  const char *p = input;
220
221
0
  while (*p != 0 && *p != '@') {
222
0
    if (!c_isprint(*p))
223
0
      return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL);
224
0
    p++;
225
0
  }
226
227
0
  if (_gnutls_str_is_print(input, ilen)) {
228
0
    return _gnutls_set_strdatum(output, input, ilen);
229
0
  }
230
231
0
  if (*p == '@') {
232
0
    unsigned name_part = p - input;
233
0
    int ret;
234
0
    gnutls_datum_t domain;
235
236
0
    ret = gnutls_idna_map(p + 1, ilen - name_part - 1, &domain, 0);
237
0
    if (ret < 0)
238
0
      return gnutls_assert_val(ret);
239
240
0
    output->data = gnutls_malloc(name_part + 1 + domain.size + 1);
241
0
    if (output->data == NULL) {
242
0
      gnutls_free(domain.data);
243
0
      return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
244
0
    }
245
0
    memcpy(output->data, input, name_part);
246
0
    output->data[name_part] = '@';
247
0
    memcpy(&output->data[name_part + 1], domain.data, domain.size);
248
0
    output->data[name_part + domain.size + 1] = 0;
249
0
    output->size = name_part + domain.size + 1;
250
0
    gnutls_free(domain.data);
251
0
    return 0;
252
0
  } else {
253
0
    return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL);
254
0
  }
255
0
}
256
257
int _gnutls_idna_email_reverse_map(const char *input, unsigned ilen,
258
           gnutls_datum_t * output)
259
0
{
260
0
  const char *p = input;
261
262
0
  while (*p != 0 && *p != '@') {
263
0
    if (!c_isprint(*p))
264
0
      return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL);
265
0
    p++;
266
0
  }
267
268
0
  if (*p == '@') {
269
0
    unsigned name_part = p - input;
270
0
    int ret;
271
0
    gnutls_datum_t domain;
272
273
0
    ret =
274
0
        gnutls_idna_reverse_map(p + 1, ilen - name_part - 1,
275
0
              &domain, 0);
276
0
    if (ret < 0)
277
0
      return gnutls_assert_val(ret);
278
279
0
    output->data = gnutls_malloc(name_part + 1 + domain.size + 1);
280
0
    if (output->data == NULL) {
281
0
      gnutls_free(domain.data);
282
0
      return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
283
0
    }
284
0
    memcpy(output->data, input, name_part);
285
0
    output->data[name_part] = '@';
286
0
    memcpy(&output->data[name_part + 1], domain.data, domain.size);
287
0
    output->data[name_part + domain.size + 1] = 0;
288
0
    output->size = name_part + domain.size + 1;
289
0
    gnutls_free(domain.data);
290
0
    return 0;
291
0
  } else {
292
0
    return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL);
293
0
  }
294
0
}