NeoMutt  2025-12-11-980-ge38c27
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
idna.c
Go to the documentation of this file.
1
23
29
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;
121 char *reversed_domain = NULL;
122 char *tmp = NULL;
123
124 char *local_user = mutt_str_dup(user);
125 char *local_domain = mutt_str_dup(domain);
126
127#ifdef HAVE_LIBIDN
128 bool is_idn_encoded = check_idn(local_domain);
129 const bool c_idn_decode = cs_subset_bool(NeoMutt->sub, "idn_decode");
130 if (is_idn_encoded && c_idn_decode)
131 {
132 if (idn2_to_unicode_8z8z(local_domain, &tmp, IDN2_ALLOW_UNASSIGNED) != IDN2_OK)
133 {
134 goto cleanup;
135 }
136 mutt_str_replace(&local_domain, tmp);
137 FREE(&tmp);
138 }
139#endif
140
141 /* we don't want charset-hook effects, so we set flags to 0 */
142 if (mutt_ch_convert_string(&local_user, "utf-8", cc_charset(), MUTT_ICONV_NONE) != 0)
143 goto cleanup;
144
145 if (mutt_ch_convert_string(&local_domain, "utf-8", cc_charset(), MUTT_ICONV_NONE) != 0)
146 goto cleanup;
147
148 /* make sure that we can convert back and come out with the same
149 * user and domain name. */
150 if ((flags & MI_MAY_BE_IRREVERSIBLE) == 0)
151 {
152 reversed_user = mutt_str_dup(local_user);
153
154 if (mutt_ch_convert_string(&reversed_user, cc_charset(), "utf-8", MUTT_ICONV_NONE) != 0)
155 {
156 mutt_debug(LL_DEBUG1, "Not reversible. Charset conv to utf-8 failed for user = '%s'\n",
157 reversed_user);
158 goto cleanup;
159 }
160
161 if (!mutt_istr_equal(user, reversed_user))
162 {
163 mutt_debug(LL_DEBUG1, "#1 Not reversible. orig = '%s', reversed = '%s'\n",
164 user, reversed_user);
165 goto cleanup;
166 }
167
168 reversed_domain = mutt_str_dup(local_domain);
169
170 if (mutt_ch_convert_string(&reversed_domain, cc_charset(), "utf-8", MUTT_ICONV_NONE) != 0)
171 {
172 mutt_debug(LL_DEBUG1, "Not reversible. Charset conv to utf-8 failed for domain = '%s'\n",
173 reversed_domain);
174 goto cleanup;
175 }
176
177#ifdef HAVE_LIBIDN
178 /* If the original domain was UTF-8, idna encoding here could
179 * produce a non-matching domain! Thus we only want to do the
180 * idn2_to_ascii_8z() if the original domain was IDNA encoded. */
181 if (is_idn_encoded && c_idn_decode)
182 {
183 if (idn2_to_ascii_8z(reversed_domain, &tmp,
184 IDN2_ALLOW_UNASSIGNED | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL) != IDN2_OK)
185 {
186 mutt_debug(LL_DEBUG1, "Not reversible. idn2_to_ascii_8z failed for domain = '%s'\n",
187 reversed_domain);
188 goto cleanup;
189 }
190 mutt_str_replace(&reversed_domain, tmp);
191 }
192#endif
193
194 if (!mutt_istr_equal(domain, reversed_domain))
195 {
196 mutt_debug(LL_DEBUG1, "#2 Not reversible. orig = '%s', reversed = '%s'\n",
197 domain, reversed_domain);
198 goto cleanup;
199 }
200 }
201
202 mutt_str_asprintf(&mailbox, "%s@%s", NONULL(local_user), NONULL(local_domain));
203
204cleanup:
205 FREE(&local_user);
206 FREE(&local_domain);
207 FREE(&tmp);
208 FREE(&reversed_domain);
209 FREE(&reversed_user);
210
211 return mailbox;
212}
213
228char *mutt_idna_local_to_intl(const char *user, const char *domain)
229{
230 char *mailbox = NULL;
231 char *tmp = NULL;
232
233 char *intl_user = mutt_str_dup(user);
234 char *intl_domain = mutt_str_dup(domain);
235
236 /* we don't want charset-hook effects, so we set flags to 0 */
237 if (mutt_ch_convert_string(&intl_user, cc_charset(), "utf-8", MUTT_ICONV_NONE) != 0)
238 goto cleanup;
239
240 if (mutt_ch_convert_string(&intl_domain, cc_charset(), "utf-8", MUTT_ICONV_NONE) != 0)
241 goto cleanup;
242
243#ifdef HAVE_LIBIDN
244 const bool c_idn_encode = cs_subset_bool(NeoMutt->sub, "idn_encode");
245 if (c_idn_encode)
246 {
247 if (idn2_to_ascii_8z(intl_domain, &tmp,
248 IDN2_ALLOW_UNASSIGNED | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL) != IDN2_OK)
249 {
250 goto cleanup;
251 }
252 mutt_str_replace(&intl_domain, tmp);
253 }
254#endif
255
256 mutt_str_asprintf(&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), "%s (compiled with %s)",
278 idn2_check_version(NULL), IDN2_VERSION);
279
280 return vstring;
281}
282#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.
Convenience wrapper for the core headers.
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
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:90
char * mutt_idna_local_to_intl(const char *user, const char *domain)
Convert an email's domain to Punycode.
Definition idna.c:228
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:45
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
int mutt_ch_convert_string(char **ps, const char *from, const char *to, uint8_t flags)
Convert a string between encodings.
Definition charset.c:819
#define MUTT_ICONV_NONE
No flags are set.
Definition charset.h:66
Convenience wrapper for the library headers.
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition string.c:678
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition string.c:257
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition string.c:809
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition string.c:246
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition string.c:284
#define NONULL(x)
Definition string2.h:44
Container for Accounts, Notifications.
Definition neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49