diff options
Diffstat (limited to 'lib/str-idna.c')
-rw-r--r-- | lib/str-idna.c | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/lib/str-idna.c b/lib/str-idna.c new file mode 100644 index 0000000..74b8d22 --- /dev/null +++ b/lib/str-idna.c @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2017 Tim Rühsen + * Copyright (C) 2016, 2017 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/> + * + */ + +#include "gnutls_int.h" +#include "errors.h" +#include "str.h" +#include <unistr.h> + +#ifdef HAVE_LIBIDN2 + +# include <idn2.h> + +# define ICAST char + +/** + * gnutls_idna_map: + * @input: contain the UTF-8 formatted domain name + * @ilen: the length of the provided string + * @out: the result in an null-terminated allocated string + * @flags: should be zero + * + * This function will convert the provided UTF-8 domain name, to + * its IDNA mapping in an allocated variable. Note that depending on the flags the used gnutls + * library was compiled with, the output of this function may vary (i.e., + * may be IDNA2008, or IDNA2003). + * + * To force IDNA2008 specify the flag %GNUTLS_IDNA_FORCE_2008. In + * the case GnuTLS is not compiled with the necessary dependencies, + * %GNUTLS_E_UNIMPLEMENTED_FEATURE will be returned to indicate that + * gnutls is unable to perform the requested conversion. + * + * Note also, that this function will return an empty string if an + * empty string is provided as input. + * + * Returns: %GNUTLS_E_INVALID_UTF8_STRING on invalid UTF-8 data, or 0 on success. + * + * Since: 3.5.8 + **/ +int gnutls_idna_map(const char *input, unsigned ilen, gnutls_datum_t *out, unsigned flags) +{ + char *idna = NULL; + int rc, ret; + gnutls_datum_t istr; + unsigned int idn2_flags = IDN2_NFC_INPUT; + unsigned int idn2_tflags = IDN2_NFC_INPUT; + + /* IDN2_NONTRANSITIONAL automatically converts to lowercase + * IDN2_NFC_INPUT converts to NFC before toASCII conversion + * + * Since IDN2_NONTRANSITIONAL implicitly does NFC conversion, we don't need + * the additional IDN2_NFC_INPUT. But just for the unlikely case that the linked + * library is not matching the headers when building and it doesn't support TR46, + * we provide IDN2_NFC_INPUT. + * + * Without IDN2_USE_STD3_ASCII_RULES, the result could contain any ASCII characters, + * e.g. 'evil.c\u2100.example.com' will be converted into + * 'evil.ca/c.example.com', which seems no good idea. */ + idn2_flags |= IDN2_NONTRANSITIONAL | IDN2_USE_STD3_ASCII_RULES; + idn2_tflags |= IDN2_TRANSITIONAL | IDN2_USE_STD3_ASCII_RULES; + + if (ilen == 0) { + out->data = (uint8_t*)gnutls_strdup(""); + out->size = 0; + if (out->data == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + return 0; + } + + if (_gnutls_str_is_print(input, ilen)) { + return _gnutls_set_strdatum(out, input, ilen); + } + + ret = _gnutls_set_strdatum(&istr, input, ilen); + if (ret < 0) { + gnutls_assert(); + return ret; + } + + rc = idn2_to_ascii_8z((ICAST*)istr.data, (ICAST**)&idna, idn2_flags); + if (rc == IDN2_DISALLOWED && !(flags & GNUTLS_IDNA_FORCE_2008)) + rc = idn2_to_ascii_8z((ICAST*)istr.data, (ICAST**)&idna, idn2_tflags); + + if (rc != IDN2_OK) { + gnutls_assert(); + idna = NULL; /* in case idn2_lookup_u8 modifies &idna */ + _gnutls_debug_log("unable to convert name '%s' to IDNA format: %s\n", istr.data, idn2_strerror(rc)); + ret = GNUTLS_E_INVALID_UTF8_STRING; + goto fail; + } + + if (gnutls_free != idn2_free) { + ret = _gnutls_set_strdatum(out, idna, strlen(idna)); + } else { + out->data = (unsigned char*)idna; + out->size = strlen(idna); + idna = NULL; + ret = 0; + } + + fail: + idn2_free(idna); + gnutls_free(istr.data); + return ret; +} + +/** + * gnutls_idna_reverse_map: + * @input: contain the ACE (IDNA) formatted domain name + * @ilen: the length of the provided string + * @out: the result in an null-terminated allocated UTF-8 string + * @flags: should be zero + * + * This function will convert an ACE (ASCII-encoded) domain name to a UTF-8 domain name. + * + * If GnuTLS is compiled without IDNA support, then this function + * will return %GNUTLS_E_UNIMPLEMENTED_FEATURE. + * + * Note also, that this function will return an empty string if an + * empty string is provided as input. + * + * Returns: A negative error code on error, or 0 on success. + * + * Since: 3.5.8 + **/ +int gnutls_idna_reverse_map(const char *input, unsigned ilen, gnutls_datum_t *out, unsigned flags) +{ + char *u8 = NULL; + int rc, ret; + gnutls_datum_t istr; + + if (ilen == 0) { + out->data = (uint8_t*)gnutls_strdup(""); + out->size = 0; + if (out->data == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + return 0; + } + + ret = _gnutls_set_strdatum(&istr, input, ilen); + if (ret < 0) { + gnutls_assert(); + return ret; + } + + /* currently libidn2 just converts single labels, thus a wrapper function */ + rc = idn2_to_unicode_8z8z((char*)istr.data, &u8, 0); + if (rc != IDN2_OK) { + gnutls_assert(); + _gnutls_debug_log("unable to convert ACE name '%s' to UTF-8 format: %s\n", istr.data, idn2_strerror(rc)); + ret = GNUTLS_E_INVALID_UTF8_STRING; + goto fail; + } + + if (gnutls_malloc != malloc) { + ret = _gnutls_set_strdatum(out, u8, strlen(u8)); + } else { + out->data = (unsigned char*)u8; + out->size = strlen(u8); + u8 = NULL; + ret = 0; + } + fail: + idn2_free(u8); + gnutls_free(istr.data); + return ret; +} + +#else /* no HAVE_LIBIDN2 */ + +# undef gnutls_idna_map +int gnutls_idna_map(const char *input, unsigned ilen, gnutls_datum_t *out, unsigned flags) +{ + if (!_gnutls_str_is_print(input, ilen)) { + return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE); + } + + return _gnutls_set_strdatum(out, input, ilen); +} + +int gnutls_idna_reverse_map(const char *input, unsigned ilen, gnutls_datum_t *out, unsigned flags) +{ + return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE); +} +#endif /* HAVE_LIBIDN2 */ + +int _gnutls_idna_email_map(const char *input, unsigned ilen, gnutls_datum_t *output) +{ + const char *p = input; + + while(*p != 0 && *p != '@') { + if (!c_isprint(*p)) + return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL); + p++; + } + + if (_gnutls_str_is_print(input, ilen)) { + return _gnutls_set_strdatum(output, input, ilen); + } + + if (*p == '@') { + unsigned name_part = p-input; + int ret; + gnutls_datum_t domain; + + ret = gnutls_idna_map(p+1, ilen-name_part-1, &domain, 0); + if (ret < 0) + return gnutls_assert_val(ret); + + output->data = gnutls_malloc(name_part+1+domain.size+1); + if (output->data == NULL) { + gnutls_free(domain.data); + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + } + memcpy(output->data, input, name_part); + output->data[name_part] = '@'; + memcpy(&output->data[name_part+1], domain.data, domain.size); + output->data[name_part+domain.size+1] = 0; + output->size = name_part+domain.size+1; + gnutls_free(domain.data); + return 0; + } else { + return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL); + } +} + +int _gnutls_idna_email_reverse_map(const char *input, unsigned ilen, gnutls_datum_t *output) +{ + const char *p = input; + + while(*p != 0 && *p != '@') { + if (!c_isprint(*p)) + return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL); + p++; + } + + if (*p == '@') { + unsigned name_part = p-input; + int ret; + gnutls_datum_t domain; + + ret = gnutls_idna_reverse_map(p+1, ilen-name_part-1, &domain, 0); + if (ret < 0) + return gnutls_assert_val(ret); + + output->data = gnutls_malloc(name_part+1+domain.size+1); + if (output->data == NULL) { + gnutls_free(domain.data); + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + } + memcpy(output->data, input, name_part); + output->data[name_part] = '@'; + memcpy(&output->data[name_part+1], domain.data, domain.size); + output->data[name_part+domain.size+1] = 0; + output->size = name_part+domain.size+1; + gnutls_free(domain.data); + return 0; + } else { + return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL); + } +} |