NeoMutt  2024-12-12-19-ge4b57e
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
idna.c
Go to the documentation of this file.
1
30#include "config.h"
31#include <stdint.h>
32#include <stdio.h>
33#include "mutt/lib.h"
34#include "config/lib.h"
35#include "core/lib.h"
36#include "idna2.h"
37#ifdef HAVE_LIBIDN
38#include <stdbool.h>
39#include <string.h>
40#endif
41#ifdef HAVE_STRINGPREP_H
42#include <stringprep.h>
43#elif defined(HAVE_IDN_STRINGPREP_H)
44#include <idn/stringprep.h>
45#endif
46#define IDN2_SKIP_LIBIDN_COMPAT
47#ifdef HAVE_LIBIDN
48#include <idn2.h>
49#elif defined(HAVE_IDN_IDN2_H)
50#include <idn/idn2.h>
51#elif defined(HAVE_IDN_IDNA_H)
52#include <idn/idna.h>
53#endif
54
55#ifdef HAVE_LIBIDN
61static bool check_idn(char *domain)
62{
63 if (!domain)
64 return false;
65
66 if (mutt_istr_startswith(domain, "xn--"))
67 return true;
68
69 while ((domain = strchr(domain, '.')))
70 {
71 if (mutt_istr_startswith(++domain, "xn--"))
72 return true;
73 }
74
75 return false;
76}
77
90int mutt_idna_to_ascii_lz(const char *input, char **output, uint8_t flags)
91{
92 if (!input || !output)
93 return 1;
94
95 return idn2_to_ascii_8z(input, output, flags | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
96}
97#endif
98
117char *mutt_idna_intl_to_local(const char *user, const char *domain, uint8_t flags)
118{
119 char *mailbox = NULL;
120 char *reversed_user = NULL, *reversed_domain = NULL;
121 char *tmp = NULL;
122
123 char *local_user = mutt_str_dup(user);
124 char *local_domain = mutt_str_dup(domain);
125
126#ifdef HAVE_LIBIDN
127 bool is_idn_encoded = check_idn(local_domain);
128 const bool c_idn_decode = cs_subset_bool(NeoMutt->sub, "idn_decode");
129 if (is_idn_encoded && c_idn_decode)
130 {
131 if (idn2_to_unicode_8z8z(local_domain, &tmp, IDN2_ALLOW_UNASSIGNED) != IDN2_OK)
132 {
133 goto cleanup;
134 }
135 mutt_str_replace(&local_domain, tmp);
136 FREE(&tmp);
137 }
138#endif
139
140 /* we don't want charset-hook effects, so we set flags to 0 */
141 if (mutt_ch_convert_string(&local_user, "utf-8", cc_charset(), MUTT_ICONV_NO_FLAGS) != 0)
142 goto cleanup;
143
144 if (mutt_ch_convert_string(&local_domain, "utf-8", cc_charset(), MUTT_ICONV_NO_FLAGS) != 0)
145 goto cleanup;
146
147 /* make sure that we can convert back and come out with the same
148 * user and domain name. */
149 if ((flags & MI_MAY_BE_IRREVERSIBLE) == 0)
150 {
151 reversed_user = mutt_str_dup(local_user);
152
153 if (mutt_ch_convert_string(&reversed_user, cc_charset(), "utf-8", MUTT_ICONV_NO_FLAGS) != 0)
154 {
155 mutt_debug(LL_DEBUG1, "Not reversible. Charset conv to utf-8 failed for user = '%s'\n",
156 reversed_user);
157 goto cleanup;
158 }
159
160 if (!mutt_istr_equal(user, reversed_user))
161 {
162 mutt_debug(LL_DEBUG1, "#1 Not reversible. orig = '%s', reversed = '%s'\n",
163 user, reversed_user);
164 goto cleanup;
165 }
166
167 reversed_domain = mutt_str_dup(local_domain);
168
169 if (mutt_ch_convert_string(&reversed_domain, cc_charset(), "utf-8", MUTT_ICONV_NO_FLAGS) != 0)
170 {
171 mutt_debug(LL_DEBUG1, "Not reversible. Charset conv to utf-8 failed for domain = '%s'\n",
172 reversed_domain);
173 goto cleanup;
174 }
175
176#ifdef HAVE_LIBIDN
177 /* If the original domain was UTF-8, idna encoding here could
178 * produce a non-matching domain! Thus we only want to do the
179 * idn2_to_ascii_8z() if the original domain was IDNA encoded. */
180 if (is_idn_encoded && c_idn_decode)
181 {
182 if (idn2_to_ascii_8z(reversed_domain, &tmp,
183 IDN2_ALLOW_UNASSIGNED | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL) != IDN2_OK)
184 {
185 mutt_debug(LL_DEBUG1, "Not reversible. idn2_to_ascii_8z failed for domain = '%s'\n",
186 reversed_domain);
187 goto cleanup;
188 }
189 mutt_str_replace(&reversed_domain, tmp);
190 }
191#endif
192
193 if (!mutt_istr_equal(domain, reversed_domain))
194 {
195 mutt_debug(LL_DEBUG1, "#2 Not reversible. orig = '%s', reversed = '%s'\n",
196 domain, reversed_domain);
197 goto cleanup;
198 }
199 }
200
201 mutt_str_asprintf(&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 mutt_str_asprintf(&mailbox, "%s@%s", NONULL(intl_user), NONULL(intl_domain));
256
257cleanup:
258 FREE(&intl_user);
259 FREE(&intl_domain);
260 FREE(&tmp);
261
262 return mailbox;
263}
264
265#ifdef HAVE_LIBIDN
272const char *mutt_idna_print_version(void)
273{
274 static char vstring[256] = { 0 };
275
276 snprintf(vstring, sizeof(vstring), "%s (compiled with %s)",
277 idn2_check_version(NULL), IDN2_VERSION);
278
279 return vstring;
280}
281#endif
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:47
Convenience wrapper for the config headers.
const char * cc_charset(void)
Get the cached value of $charset.
Definition: config_cache.c:116
Convenience wrapper for the core headers.
#define mutt_debug(LEVEL,...)
Definition: logging2.h:89
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:272
int mutt_idna_to_ascii_lz(const char *input, char **output, uint8_t flags)
Convert a domain to Punycode.
Definition: idna.c:90
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:61
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:117
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
#define FREE(x)
Definition: memory.h:55
int mutt_ch_convert_string(char **ps, const char *from, const char *to, uint8_t flags)
Convert a string between encodings.
Definition: charset.c:831
#define MUTT_ICONV_NO_FLAGS
No flags are set.
Definition: charset.h:64
Convenience wrapper for the library headers.
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:672
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:253
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:803
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:242
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:280
#define NONULL(x)
Definition: string2.h:37
Container for Accounts, Notifications.
Definition: neomutt.h:42
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:46