NeoMutt  2023-03-22
Teaching an old dog new tricks
DOXYGEN
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 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? More...
 
int mutt_idna_to_ascii_lz (const char *input, char **output, uint8_t flags)
 Convert a domain to Punycode. More...
 
char * mutt_idna_intl_to_local (const char *user, const char *domain, uint8_t flags)
 Convert an email's domain from Punycode. More...
 
char * mutt_idna_local_to_intl (const char *user, const char *domain)
 Convert an email's domain to Punycode. More...
 
const char * mutt_idna_print_version (void)
 Create an IDN version string. More...
 

Detailed Description

Handling of international domain names.

Authors
  • Thomas Roessler

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 45 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 84 of file idna.c.

85{
86 if (!domain)
87 return false;
88
89 if (mutt_istr_startswith(domain, "xn--"))
90 return true;
91
92 while ((domain = strchr(domain, '.')))
93 {
94 if (mutt_istr_startswith(++domain, "xn--"))
95 return true;
96 }
97
98 return false;
99}
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:239
+ 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 113 of file idna.c.

114{
115 if (!input || !output)
116 return 1;
117
118#if (IDN_VERSION == 2)
119 return idn2_to_ascii_8z(input, output, flags | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
120#else
121 return idna_to_ascii_lz(input, output, flags);
122#endif
123}
+ 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 144 of file idna.c.

145{
146 char *mailbox = NULL;
147 char *reversed_user = NULL, *reversed_domain = NULL;
148 char *tmp = NULL;
149
150 char *local_user = mutt_str_dup(user);
151 char *local_domain = mutt_str_dup(domain);
152
153#ifdef HAVE_LIBIDN
154 bool is_idn_encoded = check_idn(local_domain);
155 const bool c_idn_decode = cs_subset_bool(NeoMutt->sub, "idn_decode");
156 if (is_idn_encoded && c_idn_decode)
157 {
158#if (IDN_VERSION == 2)
159 if (idn2_to_unicode_8z8z(local_domain, &tmp, IDN2_ALLOW_UNASSIGNED) != IDN2_OK)
160#else
161 if (idna_to_unicode_8z8z(local_domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
162#endif
163 {
164 goto cleanup;
165 }
166 mutt_str_replace(&local_domain, tmp);
167 FREE(&tmp);
168 }
169#endif /* HAVE_LIBIDN */
170
171 /* we don't want charset-hook effects, so we set flags to 0 */
172 const char *const c_charset = cs_subset_string(NeoMutt->sub, "charset");
173 if (mutt_ch_convert_string(&local_user, "utf-8", c_charset, MUTT_ICONV_NO_FLAGS) != 0)
174 goto cleanup;
175
176 if (mutt_ch_convert_string(&local_domain, "utf-8", c_charset, MUTT_ICONV_NO_FLAGS) != 0)
177 goto cleanup;
178
179 /* make sure that we can convert back and come out with the same
180 * user and domain name. */
181 if ((flags & MI_MAY_BE_IRREVERSIBLE) == 0)
182 {
183 reversed_user = mutt_str_dup(local_user);
184
185 if (mutt_ch_convert_string(&reversed_user, c_charset, "utf-8", MUTT_ICONV_NO_FLAGS) != 0)
186 {
187 mutt_debug(LL_DEBUG1, "Not reversible. Charset conv to utf-8 failed for user = '%s'\n",
188 reversed_user);
189 goto cleanup;
190 }
191
192 if (!mutt_istr_equal(user, reversed_user))
193 {
194 mutt_debug(LL_DEBUG1, "#1 Not reversible. orig = '%s', reversed = '%s'\n",
195 user, reversed_user);
196 goto cleanup;
197 }
198
199 reversed_domain = mutt_str_dup(local_domain);
200
201 if (mutt_ch_convert_string(&reversed_domain, c_charset, "utf-8", MUTT_ICONV_NO_FLAGS) != 0)
202 {
203 mutt_debug(LL_DEBUG1, "Not reversible. Charset conv to utf-8 failed for domain = '%s'\n",
204 reversed_domain);
205 goto cleanup;
206 }
207
208#ifdef HAVE_LIBIDN
209 /* If the original domain was UTF-8, idna encoding here could
210 * produce a non-matching domain! Thus we only want to do the
211 * idna_to_ascii_8z() if the original domain was IDNA encoded. */
212 if (is_idn_encoded && c_idn_decode)
213 {
214#if (IDN_VERSION == 2)
215 if (idn2_to_ascii_8z(reversed_domain, &tmp,
216 IDN2_ALLOW_UNASSIGNED | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL) != IDN2_OK)
217#else
218 if (idna_to_ascii_8z(reversed_domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
219#endif
220 {
221 mutt_debug(LL_DEBUG1, "Not reversible. idna_to_ascii_8z failed for domain = '%s'\n",
222 reversed_domain);
223 goto cleanup;
224 }
225 mutt_str_replace(&reversed_domain, tmp);
226 }
227#endif /* HAVE_LIBIDN */
228
229 if (!mutt_istr_equal(domain, reversed_domain))
230 {
231 mutt_debug(LL_DEBUG1, "#2 Not reversible. orig = '%s', reversed = '%s'\n",
232 domain, reversed_domain);
233 goto cleanup;
234 }
235 }
236
237 mailbox = mutt_mem_malloc(mutt_str_len(local_user) + mutt_str_len(local_domain) + 2);
238 sprintf(mailbox, "%s@%s", NONULL(local_user), NONULL(local_domain));
239
240cleanup:
241 FREE(&local_user);
242 FREE(&local_domain);
243 FREE(&tmp);
244 FREE(&reversed_domain);
245 FREE(&reversed_user);
246
247 return mailbox;
248}
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
#define mutt_debug(LEVEL,...)
Definition: logging.h:84
#define MI_MAY_BE_IRREVERSIBLE
Definition: idna2.h:30
static bool check_idn(char *domain)
Is domain in Punycode?
Definition: idna.c:84
@ LL_DEBUG1
Log at debug level 1.
Definition: logging.h:40
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:752
#define MUTT_ICONV_NO_FLAGS
No flags are set.
Definition: charset.h:71
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:819
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:250
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:567
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:326
#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
+ 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 264 of file idna.c.

265{
266 char *mailbox = NULL;
267 char *tmp = NULL;
268
269 char *intl_user = mutt_str_dup(user);
270 char *intl_domain = mutt_str_dup(domain);
271
272 /* we don't want charset-hook effects, so we set flags to 0 */
273 const char *const c_charset = cs_subset_string(NeoMutt->sub, "charset");
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 const bool c_idn_encode = cs_subset_bool(NeoMutt->sub, "idn_encode");
282 if (c_idn_encode)
283 {
284#if (IDN_VERSION == 2)
285 if (idn2_to_ascii_8z(intl_domain, &tmp,
286 IDN2_ALLOW_UNASSIGNED | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL) != IDN2_OK)
287#else
288 if (idna_to_ascii_8z(intl_domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
289#endif
290 {
291 goto cleanup;
292 }
293 mutt_str_replace(&intl_domain, tmp);
294 }
295#endif /* HAVE_LIBIDN */
296
297 mailbox = mutt_mem_malloc(mutt_str_len(intl_user) + mutt_str_len(intl_domain) + 2);
298 sprintf(mailbox, "%s@%s", NONULL(intl_user), NONULL(intl_domain));
299
300cleanup:
301 FREE(&intl_user);
302 FREE(&intl_domain);
303 FREE(&tmp);
304
305 return mailbox;
306}
+ 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 314 of file idna.c.

315{
316 static char vstring[256];
317
318#ifdef HAVE_LIBIDN
319#if (IDN_VERSION == 2)
320 snprintf(vstring, sizeof(vstring), "libidn2: %s (compiled with %s)",
321 idn2_check_version(NULL), IDN2_VERSION);
322#elif (IDN_VERSION == 1)
323 snprintf(vstring, sizeof(vstring), "libidn: %s (compiled with %s)",
324 stringprep_check_version(NULL), STRINGPREP_VERSION);
325#endif
326#endif /* HAVE_LIBIDN */
327
328 return vstring;
329}
+ Here is the caller graph for this function: