/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 | } |