NeoMutt  2021-10-29-33-g41675a
Teaching an old dog new tricks
DOXYGEN
idna.c
Go to the documentation of this file.
1 
29 #include "config.h"
30 #include <stdint.h>
31 #include <stdio.h>
32 #include "mutt/lib.h"
33 #include "config/lib.h"
34 #include "core/lib.h"
35 #include "idna2.h"
36 #ifdef HAVE_LIBIDN
37 #include <stdbool.h>
38 #include <string.h>
39 #endif
40 #ifdef HAVE_STRINGPREP_H
41 #include <stringprep.h>
42 #elif defined(HAVE_IDN_STRINGPREP_H)
43 #include <idn/stringprep.h>
44 #endif
45 #define IDN2_SKIP_LIBIDN_COMPAT
46 #ifdef HAVE_IDN2_H
47 #include <idn2.h>
48 #elif defined(HAVE_IDN_IDN2_H)
49 #include <idn/idn2.h>
50 #elif defined(HAVE_IDNA_H)
51 #include <idna.h>
52 #elif defined(HAVE_IDN_IDNA_H)
53 #include <idn/idna.h>
54 #endif
55 
56 #if defined(HAVE_IDN2_H) || defined(HAVE_IDN_IDN2_H)
57 #define IDN_VERSION 2
58 #elif defined(HAVE_IDNA_H) || defined(HAVE_IDN_IDNA_H)
59 #define IDN_VERSION 1
60 #endif
61 
62 #ifdef HAVE_LIBIDN
63 /* Work around incompatibilities in the libidn API */
64 #if (!defined(HAVE_IDNA_TO_ASCII_8Z) && defined(HAVE_IDNA_TO_ASCII_FROM_UTF8))
65 #define idna_to_ascii_8z(input, output, flags) \
66  idna_to_ascii_from_utf8(input, output, (flags) &1, ((flags) &2) ? 1 : 0)
67 #endif
68 #if (!defined(HAVE_IDNA_TO_ASCII_LZ) && defined(HAVE_IDNA_TO_ASCII_FROM_LOCALE))
69 #define idna_to_ascii_lz(input, output, flags) \
70  idna_to_ascii_from_locale(input, output, (flags) &1, ((flags) &2) ? 1 : 0)
71 #endif
72 #if (!defined(HAVE_IDNA_TO_UNICODE_8Z8Z) && defined(HAVE_IDNA_TO_UNICODE_UTF8_FROM_UTF8))
73 #define idna_to_unicode_8z8z(input, output, flags) \
74  idna_to_unicode_utf8_from_utf8(input, output, (flags) &1, ((flags) &2) ? 1 : 0)
75 #endif
76 #endif /* HAVE_LIBIDN */
77 
78 #ifdef HAVE_LIBIDN
84 static bool check_idn(char *domain)
85 {
86  if (!domain)
87  return false;
88 
89  if (mutt_istr_startswith(domain, "xn--"))
90  return true;
91 
92  while ((domain = strchr(domain, '.')))
93  {
94  if (mutt_istr_startswith(++domain, "xn--"))
95  return true;
96  }
97 
98  return false;
99 }
100 
113 int mutt_idna_to_ascii_lz(const char *input, char **output, uint8_t flags)
114 {
115  if (!input || !output)
116  return 1;
117 
118 #if (IDN_VERSION == 2)
119  return idn2_to_ascii_8z(input, output, flags | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
120 #else
121  return idna_to_ascii_lz(input, output, flags);
122 #endif
123 }
124 #endif /* HAVE_LIBIDN */
125 
144 char *mutt_idna_intl_to_local(const char *user, const char *domain, uint8_t flags)
145 {
146  char *mailbox = NULL;
147  char *reversed_user = NULL, *reversed_domain = NULL;
148  char *tmp = NULL;
149 
150  char *local_user = mutt_str_dup(user);
151  char *local_domain = mutt_str_dup(domain);
152 
153 #ifdef HAVE_LIBIDN
154  bool is_idn_encoded = check_idn(local_domain);
155  const bool c_idn_decode = cs_subset_bool(NeoMutt->sub, "idn_decode");
156  if (is_idn_encoded && c_idn_decode)
157  {
158 #if (IDN_VERSION == 2)
159  if (idn2_to_unicode_8z8z(local_domain, &tmp, IDN2_ALLOW_UNASSIGNED) != IDN2_OK)
160 #else
161  if (idna_to_unicode_8z8z(local_domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
162 #endif
163  {
164  goto cleanup;
165  }
166  mutt_str_replace(&local_domain, tmp);
167  FREE(&tmp);
168  }
169 #endif /* HAVE_LIBIDN */
170 
171  /* we don't want charset-hook effects, so we set flags to 0 */
172  const char *const c_charset = cs_subset_string(NeoMutt->sub, "charset");
173  if (mutt_ch_convert_string(&local_user, "utf-8", c_charset, MUTT_ICONV_NO_FLAGS) != 0)
174  goto cleanup;
175 
176  if (mutt_ch_convert_string(&local_domain, "utf-8", c_charset, MUTT_ICONV_NO_FLAGS) != 0)
177  goto cleanup;
178 
179  /* make sure that we can convert back and come out with the same
180  * user and domain name. */
181  if ((flags & MI_MAY_BE_IRREVERSIBLE) == 0)
182  {
183  reversed_user = mutt_str_dup(local_user);
184 
185  if (mutt_ch_convert_string(&reversed_user, c_charset, "utf-8", MUTT_ICONV_NO_FLAGS) != 0)
186  {
187  mutt_debug(LL_DEBUG1, "Not reversible. Charset conv to utf-8 failed for user = '%s'\n",
188  reversed_user);
189  goto cleanup;
190  }
191 
192  if (!mutt_istr_equal(user, reversed_user))
193  {
194  mutt_debug(LL_DEBUG1, "#1 Not reversible. orig = '%s', reversed = '%s'\n",
195  user, reversed_user);
196  goto cleanup;
197  }
198 
199  reversed_domain = mutt_str_dup(local_domain);
200 
201  if (mutt_ch_convert_string(&reversed_domain, c_charset, "utf-8", MUTT_ICONV_NO_FLAGS) != 0)
202  {
203  mutt_debug(LL_DEBUG1, "Not reversible. Charset conv to utf-8 failed for domain = '%s'\n",
204  reversed_domain);
205  goto cleanup;
206  }
207 
208 #ifdef HAVE_LIBIDN
209  /* If the original domain was UTF-8, idna encoding here could
210  * produce a non-matching domain! Thus we only want to do the
211  * idna_to_ascii_8z() if the original domain was IDNA encoded. */
212  if (is_idn_encoded && c_idn_decode)
213  {
214 #if (IDN_VERSION == 2)
215  if (idn2_to_ascii_8z(reversed_domain, &tmp,
216  IDN2_ALLOW_UNASSIGNED | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL) != IDN2_OK)
217 #else
218  if (idna_to_ascii_8z(reversed_domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
219 #endif
220  {
221  mutt_debug(LL_DEBUG1, "Not reversible. idna_to_ascii_8z failed for domain = '%s'\n",
222  reversed_domain);
223  goto cleanup;
224  }
225  mutt_str_replace(&reversed_domain, tmp);
226  }
227 #endif /* HAVE_LIBIDN */
228 
229  if (!mutt_istr_equal(domain, reversed_domain))
230  {
231  mutt_debug(LL_DEBUG1, "#2 Not reversible. orig = '%s', reversed = '%s'\n",
232  domain, reversed_domain);
233  goto cleanup;
234  }
235  }
236 
237  mailbox = mutt_mem_malloc(mutt_str_len(local_user) + mutt_str_len(local_domain) + 2);
238  sprintf(mailbox, "%s@%s", NONULL(local_user), NONULL(local_domain));
239 
240 cleanup:
241  FREE(&local_user);
242  FREE(&local_domain);
243  FREE(&tmp);
244  FREE(&reversed_domain);
245  FREE(&reversed_user);
246 
247  return mailbox;
248 }
249 
264 char *mutt_idna_local_to_intl(const char *user, const char *domain)
265 {
266  char *mailbox = NULL;
267  char *tmp = NULL;
268 
269  char *intl_user = mutt_str_dup(user);
270  char *intl_domain = mutt_str_dup(domain);
271 
272  /* we don't want charset-hook effects, so we set flags to 0 */
273  const char *const c_charset = cs_subset_string(NeoMutt->sub, "charset");
274  if (mutt_ch_convert_string(&intl_user, c_charset, "utf-8", MUTT_ICONV_NO_FLAGS) != 0)
275  goto cleanup;
276 
277  if (mutt_ch_convert_string(&intl_domain, c_charset, "utf-8", MUTT_ICONV_NO_FLAGS) != 0)
278  goto cleanup;
279 
280 #ifdef HAVE_LIBIDN
281  const bool c_idn_encode = cs_subset_bool(NeoMutt->sub, "idn_encode");
282  if (c_idn_encode)
283  {
284 #if (IDN_VERSION == 2)
285  if (idn2_to_ascii_8z(intl_domain, &tmp,
286  IDN2_ALLOW_UNASSIGNED | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL) != IDN2_OK)
287 #else
288  if (idna_to_ascii_8z(intl_domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
289 #endif
290  {
291  goto cleanup;
292  }
293  mutt_str_replace(&intl_domain, tmp);
294  }
295 #endif /* HAVE_LIBIDN */
296 
297  mailbox = mutt_mem_malloc(mutt_str_len(intl_user) + mutt_str_len(intl_domain) + 2);
298  sprintf(mailbox, "%s@%s", NONULL(intl_user), NONULL(intl_domain));
299 
300 cleanup:
301  FREE(&intl_user);
302  FREE(&intl_domain);
303  FREE(&tmp);
304 
305  return mailbox;
306 }
307 
314 const char *mutt_idna_print_version(void)
315 {
316  static char vstring[256];
317 
318 #ifdef HAVE_LIBIDN
319 #if (IDN_VERSION == 2)
320  snprintf(vstring, sizeof(vstring), "libidn2: %s (compiled with %s)",
321  idn2_check_version(NULL), IDN2_VERSION);
322 #elif (IDN_VERSION == 1)
323  snprintf(vstring, sizeof(vstring), "libidn: %s (compiled with %s)",
324  stringprep_check_version(NULL), STRINGPREP_VERSION);
325 #endif
326 #endif /* HAVE_LIBIDN */
327 
328  return vstring;
329 }
Convenience wrapper for the config headers.
Convenience wrapper for the core headers.
#define mutt_debug(LEVEL,...)
Definition: logging.h:84
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
Handling of international domain names.
int mutt_idna_to_ascii_lz(const char *input, char **output, uint8_t flags)
#define MI_MAY_BE_IRREVERSIBLE
Definition: idna2.h:30
const char * mutt_idna_print_version(void)
Create an IDN version string.
Definition: idna.c:314
char * mutt_idna_local_to_intl(const char *user, const char *domain)
Convert an email's domain to Punycode.
Definition: idna.c:264
char * mutt_idna_intl_to_local(const char *user, const char *domain, uint8_t flags)
Convert an email's domain from Punycode.
Definition: idna.c:144
@ LL_DEBUG1
Log at debug level 1.
Definition: logging.h:40
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
#define FREE(x)
Definition: memory.h:40
int mutt_ch_convert_string(char **ps, const char *from, const char *to, uint8_t flags)
Convert a string between encodings.
Definition: charset.c:765
#define MUTT_ICONV_NO_FLAGS
No flags are set.
Definition: charset.h:71
Convenience wrapper for the library headers.
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:727
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:181
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:475
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:257
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:170
#define NONULL(x)
Definition: string2.h:37
Container for Accounts, Notifications.
Definition: neomutt.h:37
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39