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