NeoMutt  2023-05-17-56-ga67199
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_LIBIDN
47#include <idn2.h>
48#elif defined(HAVE_IDN_IDN2_H)
49#include <idn/idn2.h>
50#elif defined(HAVE_IDN_IDNA_H)
51#include <idn/idna.h>
52#endif
53
54#ifdef HAVE_LIBIDN
60static bool check_idn(char *domain)
61{
62 if (!domain)
63 return false;
64
65 if (mutt_istr_startswith(domain, "xn--"))
66 return true;
67
68 while ((domain = strchr(domain, '.')))
69 {
70 if (mutt_istr_startswith(++domain, "xn--"))
71 return true;
72 }
73
74 return false;
75}
76
89int mutt_idna_to_ascii_lz(const char *input, char **output, uint8_t flags)
90{
91 if (!input || !output)
92 return 1;
93
94 return idn2_to_ascii_8z(input, output, flags | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
95}
96#endif
97
116char *mutt_idna_intl_to_local(const char *user, const char *domain, uint8_t flags)
117{
118 char *mailbox = NULL;
119 char *reversed_user = NULL, *reversed_domain = NULL;
120 char *tmp = NULL;
121
122 char *local_user = mutt_str_dup(user);
123 char *local_domain = mutt_str_dup(domain);
124
125#ifdef HAVE_LIBIDN
126 bool is_idn_encoded = check_idn(local_domain);
127 const bool c_idn_decode = cs_subset_bool(NeoMutt->sub, "idn_decode");
128 if (is_idn_encoded && c_idn_decode)
129 {
130 if (idn2_to_unicode_8z8z(local_domain, &tmp, IDN2_ALLOW_UNASSIGNED) != IDN2_OK)
131 {
132 goto cleanup;
133 }
134 mutt_str_replace(&local_domain, tmp);
135 FREE(&tmp);
136 }
137#endif
138
139 /* we don't want charset-hook effects, so we set flags to 0 */
140 if (mutt_ch_convert_string(&local_user, "utf-8", cc_charset(), MUTT_ICONV_NO_FLAGS) != 0)
141 goto cleanup;
142
143 if (mutt_ch_convert_string(&local_domain, "utf-8", cc_charset(), MUTT_ICONV_NO_FLAGS) != 0)
144 goto cleanup;
145
146 /* make sure that we can convert back and come out with the same
147 * user and domain name. */
148 if ((flags & MI_MAY_BE_IRREVERSIBLE) == 0)
149 {
150 reversed_user = mutt_str_dup(local_user);
151
152 if (mutt_ch_convert_string(&reversed_user, cc_charset(), "utf-8", MUTT_ICONV_NO_FLAGS) != 0)
153 {
154 mutt_debug(LL_DEBUG1, "Not reversible. Charset conv to utf-8 failed for user = '%s'\n",
155 reversed_user);
156 goto cleanup;
157 }
158
159 if (!mutt_istr_equal(user, reversed_user))
160 {
161 mutt_debug(LL_DEBUG1, "#1 Not reversible. orig = '%s', reversed = '%s'\n",
162 user, reversed_user);
163 goto cleanup;
164 }
165
166 reversed_domain = mutt_str_dup(local_domain);
167
168 if (mutt_ch_convert_string(&reversed_domain, cc_charset(), "utf-8", MUTT_ICONV_NO_FLAGS) != 0)
169 {
170 mutt_debug(LL_DEBUG1, "Not reversible. Charset conv to utf-8 failed for domain = '%s'\n",
171 reversed_domain);
172 goto cleanup;
173 }
174
175#ifdef HAVE_LIBIDN
176 /* If the original domain was UTF-8, idna encoding here could
177 * produce a non-matching domain! Thus we only want to do the
178 * idn2_to_ascii_8z() if the original domain was IDNA encoded. */
179 if (is_idn_encoded && c_idn_decode)
180 {
181 if (idn2_to_ascii_8z(reversed_domain, &tmp,
182 IDN2_ALLOW_UNASSIGNED | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL) != IDN2_OK)
183 {
184 mutt_debug(LL_DEBUG1, "Not reversible. idn2_to_ascii_8z failed for domain = '%s'\n",
185 reversed_domain);
186 goto cleanup;
187 }
188 mutt_str_replace(&reversed_domain, tmp);
189 }
190#endif
191
192 if (!mutt_istr_equal(domain, reversed_domain))
193 {
194 mutt_debug(LL_DEBUG1, "#2 Not reversible. orig = '%s', reversed = '%s'\n",
195 domain, reversed_domain);
196 goto cleanup;
197 }
198 }
199
200 mailbox = mutt_mem_malloc(mutt_str_len(local_user) + mutt_str_len(local_domain) + 2);
201 sprintf(mailbox, "%s@%s", NONULL(local_user), NONULL(local_domain));
202
203cleanup:
204 FREE(&local_user);
205 FREE(&local_domain);
206 FREE(&tmp);
207 FREE(&reversed_domain);
208 FREE(&reversed_user);
209
210 return mailbox;
211}
212
227char *mutt_idna_local_to_intl(const char *user, const char *domain)
228{
229 char *mailbox = NULL;
230 char *tmp = NULL;
231
232 char *intl_user = mutt_str_dup(user);
233 char *intl_domain = mutt_str_dup(domain);
234
235 /* we don't want charset-hook effects, so we set flags to 0 */
236 if (mutt_ch_convert_string(&intl_user, cc_charset(), "utf-8", MUTT_ICONV_NO_FLAGS) != 0)
237 goto cleanup;
238
239 if (mutt_ch_convert_string(&intl_domain, cc_charset(), "utf-8", MUTT_ICONV_NO_FLAGS) != 0)
240 goto cleanup;
241
242#ifdef HAVE_LIBIDN
243 const bool c_idn_encode = cs_subset_bool(NeoMutt->sub, "idn_encode");
244 if (c_idn_encode)
245 {
246 if (idn2_to_ascii_8z(intl_domain, &tmp,
247 IDN2_ALLOW_UNASSIGNED | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL) != IDN2_OK)
248 {
249 goto cleanup;
250 }
251 mutt_str_replace(&intl_domain, tmp);
252 }
253#endif
254
255 mailbox = mutt_mem_malloc(mutt_str_len(intl_user) + mutt_str_len(intl_domain) + 2);
256 sprintf(mailbox, "%s@%s", NONULL(intl_user), NONULL(intl_domain));
257
258cleanup:
259 FREE(&intl_user);
260 FREE(&intl_domain);
261 FREE(&tmp);
262
263 return mailbox;
264}
265
266#ifdef HAVE_LIBIDN
273const char *mutt_idna_print_version(void)
274{
275 static char vstring[256] = { 0 };
276
277 snprintf(vstring, sizeof(vstring), "libidn2: %s (compiled with %s)",
278 idn2_check_version(NULL), IDN2_VERSION);
279
280 return vstring;
281}
282#endif
const char * cc_charset(void)
Get the cached value of $charset.
Definition: cache.c:106
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
Convenience wrapper for the config headers.
Convenience wrapper for the core headers.
#define mutt_debug(LEVEL,...)
Definition: logging2.h:87
Handling of international domain names.
#define MI_MAY_BE_IRREVERSIBLE
Definition: idna2.h:30
const char * mutt_idna_print_version(void)
Create an IDN version string.
Definition: idna.c:273
int mutt_idna_to_ascii_lz(const char *input, char **output, uint8_t flags)
Convert a domain to Punycode.
Definition: idna.c:89
char * mutt_idna_local_to_intl(const char *user, const char *domain)
Convert an email's domain to Punycode.
Definition: idna.c:227
static bool check_idn(char *domain)
Is domain in Punycode?
Definition: idna.c:60
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:116
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
#define FREE(x)
Definition: memory.h:43
int mutt_ch_convert_string(char **ps, const char *from, const char *to, uint8_t flags)
Convert a string between encodings.
Definition: charset.c:822
#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:810
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:251
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:568
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:240
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:327
#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