NeoMutt  2024-10-02-37-gfa9146
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
idna.c File Reference

Handling of international domain names. More...

#include "config.h"
#include <stdint.h>
#include <stdio.h>
#include "mutt/lib.h"
#include "config/lib.h"
#include "core/lib.h"
#include "idna2.h"
#include <stdbool.h>
#include <string.h>
#include <idn2.h>
+ Include dependency graph for idna.c:

Go to the source code of this file.

Macros

#define IDN2_SKIP_LIBIDN_COMPAT
 

Functions

static bool check_idn (char *domain)
 Is domain in Punycode?
 
int mutt_idna_to_ascii_lz (const char *input, char **output, uint8_t flags)
 Convert a domain to Punycode.
 
char * mutt_idna_intl_to_local (const char *user, const char *domain, uint8_t flags)
 Convert an email's domain from Punycode.
 
char * mutt_idna_local_to_intl (const char *user, const char *domain)
 Convert an email's domain to Punycode.
 
const char * mutt_idna_print_version (void)
 Create an IDN version string.
 

Detailed Description

Handling of international domain names.

Authors
  • Richard Russon
  • Pietro Cerutti

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.

Definition in file idna.c.

Macro Definition Documentation

◆ IDN2_SKIP_LIBIDN_COMPAT

#define IDN2_SKIP_LIBIDN_COMPAT

Definition at line 46 of file idna.c.

Function Documentation

◆ check_idn()

static bool check_idn ( char *  domain)
static

Is domain in Punycode?

Parameters
domainDomain to test
Return values
trueAt least one part of domain is in Punycode

Definition at line 61 of file idna.c.

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}
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
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_idna_to_ascii_lz()

int mutt_idna_to_ascii_lz ( const char *  input,
char **  output,
uint8_t  flags 
)

Convert a domain to Punycode.

Parameters
[in]inputDomain
[out]outputResult
[in]flagsFlags, e.g. IDNA_ALLOW_UNASSIGNED
Return values
0Success
>0Failure, error code

Convert a domain from the current locale to Punycode.

Note
The caller must free output

Definition at line 90 of file idna.c.

91{
92 if (!input || !output)
93 return 1;
94
95 return idn2_to_ascii_8z(input, output, flags | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
96}
+ Here is the caller graph for this function:

◆ 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.

Parameters
userUsername
domainDomain
flagsFlags, e.g. MI_MAY_BE_IRREVERSIBLE
Return values
ptrNewly allocated local email address
NULLError in conversion

If $idn_decode is set, then the domain will be converted from Punycode. For example, "xn--ls8h.la" becomes the emoji domain: ":poop:.la" Then the user and domain are changed from 'utf-8' to the encoding in $charset.

If the flag MI_MAY_BE_IRREVERSIBLE is NOT given, then the results will be checked to make sure that the transformation is "undo-able".

Note
The caller must free the returned string.

Definition at line 117 of file idna.c.

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}
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:47
const char * cc_charset(void)
Get the cached value of $charset.
Definition: config_cache.c:116
#define mutt_debug(LEVEL,...)
Definition: logging2.h:89
#define MI_MAY_BE_IRREVERSIBLE
Definition: idna2.h:30
static bool check_idn(char *domain)
Is domain in Punycode?
Definition: idna.c:61
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
#define FREE(x)
Definition: memory.h:45
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:73
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
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
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_idna_local_to_intl()

char * mutt_idna_local_to_intl ( const char *  user,
const char *  domain 
)

Convert an email's domain to Punycode.

Parameters
userUsername
domainDomain
Return values
ptrNewly allocated Punycode email address
NULLError in conversion

The user and domain are assumed to be encoded according to $charset. They are converted to 'utf-8'. If $idn_encode is set, then the domain will be converted to Punycode. For example, the emoji domain: ":poop:.la" becomes "xn--ls8h.la"

Note
The caller must free the returned string.

Definition at line 227 of file idna.c.

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}
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_idna_print_version()

const char * mutt_idna_print_version ( void  )

Create an IDN version string.

Return values
ptrVersion string
Note
This is a static string and must not be freed.

Definition at line 272 of file idna.c.

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}
+ Here is the caller graph for this function: