]> granicus.if.org Git - neomutt/commitdiff
Rewrite address local-to-intl conversion functions.
authorKevin McCarthy <kevin@8t8.us>
Tue, 24 Nov 2015 23:49:27 +0000 (15:49 -0800)
committerKevin McCarthy <kevin@8t8.us>
Tue, 24 Nov 2015 23:49:27 +0000 (15:49 -0800)
This is patch 2 of 4 implementing support for SMTPUTF8 (RFC 6531).

Perform charset conversion from local to UTF-8 for both the user and
domain parts of the address.

If IDN is enabled and the options (added in the next patch) are turned
on, encode/decode the domain part.

Use the intl_checked and is_intl status bits to record the intl/local
status of the ADDRESS mailbox part.

mutt_idna.c

index 4169f788f98f5e6da2642ec91fede969f405f717..04a7c4caad5e4c5b21c9dc8b9b1b1b9ca602518d 100644 (file)
 #include "charset.h"
 #include "mutt_idna.h"
 
+#ifdef HAVE_LIBIDN
+static int check_idn (char *domain)
+{
+  if (! domain)
+    return 0;
 
+  if (ascii_strncasecmp (domain, "xn--", 4) == 0)
+    return 1;
 
-/* check whether an address is an IDN */
+  while ((domain = strchr (domain, '.')) != NULL)
+  {
+    if (ascii_strncasecmp (++domain, "xn--", 4) == 0)
+      return 1;
+  }
 
-static int check_idn (ADDRESS *ap)
-{
-  char *p = 0;
+  return 0;
+}
+#endif /* HAVE_LIBIDN */
 
-  if (!ap || !ap->mailbox)
-    return 0;
+static int mbox_to_udomain (const char *mbx, char **user, char **domain)
+{
+  char *buff = NULL;
+  char *p;
 
-  if (!ap->intl_checked)
+  buff = safe_strdup (mbx);
+  p = strchr (buff, '@');
+  if (!p || !p[1])
   {
-    ap->intl_checked = 1;
-    for (p = strchr (ap->mailbox, '@'); p && *p; p = strchr (p, '.')) 
-      if (ascii_strncasecmp (++p, "xn--", 4) == 0)
-      {
-       ap->is_intl = 1;
-       break;
-      }
+    FREE (&buff);
+    return -1;
   }
-  
-  return ap->is_intl;
+
+  *p = '\0';
+  *user = safe_strdup (buff);
+  *domain  = safe_strdup (p + 1);
+  FREE (&buff);
+  return 0;
 }
 
-static int intl_to_local (const char *in, char **out, int flags)
+static int addr_is_local (ADDRESS *a)
 {
-  *out = NULL;
+  return (a->intl_checked && !a->is_intl);
+}
 
-  if (!option (OPTUSEIDN))
-    goto notrans;
+static int addr_is_intl (ADDRESS *a)
+{
+  return (a->intl_checked && a->is_intl);
+}
 
-  if (!in)
-    goto notrans;
+static void set_local_mailbox (ADDRESS *a, char *local_mailbox)
+{
+  FREE (&a->mailbox);
+  a->mailbox = local_mailbox;
+  a->intl_checked = 1;
+  a->is_intl = 0;
+}
+
+static void set_intl_mailbox (ADDRESS *a, char *intl_mailbox)
+{
+  FREE (&a->mailbox);
+  a->mailbox = intl_mailbox;
+  a->intl_checked = 1;
+  a->is_intl = 1;
+}
+
+static char *intl_to_local (ADDRESS *a, int flags)
+{
+  char *user = NULL, *domain = NULL, *mailbox = NULL;
+  char *orig_domain = NULL, *reversed_domain = NULL;
+  char *tmp = NULL;
+#ifdef HAVE_LIBIDN
+  int is_idn_encoded = 0;
+#endif /* HAVE_LIBIDN */
+
+  if (mbox_to_udomain (a->mailbox, &user, &domain) == -1)
+    goto cleanup;
+  orig_domain = safe_strdup (domain);
 
-  /* Is this the right function?  Interesting effects with some bad identifiers! */
 #ifdef HAVE_LIBIDN
-  if (idna_to_unicode_8z8z (in, out, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
-    goto notrans;
+  is_idn_encoded = check_idn (domain);
+  if (is_idn_encoded && option (OPTUSEIDN))
+  {
+    if (idna_to_unicode_8z8z (domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
+      goto cleanup;
+    mutt_str_replace (&domain, tmp);
+    FREE (&tmp);
+  }
 #endif /* HAVE_LIBIDN */
 
   /* we don't want charset-hook effects, so we set flags to 0 */
-  if (mutt_convert_string (out, "utf-8", Charset, 0) == -1)
-    goto notrans;
+  if (mutt_convert_string (&user, "utf-8", Charset, 0) == -1)
+    goto cleanup;
 
-  /* 
+  if (mutt_convert_string (&domain, "utf-8", Charset, 0) == -1)
+    goto cleanup;
+
+  /*
    * make sure that we can convert back and come out with the same
-   * domain name. 
+   * domain name.
    */
-  
   if ((flags & MI_MAY_BE_IRREVERSIBLE) == 0)
   {
-    int irrev = 0;
-    char *t2 = NULL;
-    char *tmp = safe_strdup (*out);
+    reversed_domain = safe_strdup (domain);
+
+    if (mutt_convert_string (&reversed_domain, Charset, "utf-8", 0) == -1)
+    {
+      dprint (1, (debugfile,
+                  "intl_to_local: Not reversible. Charset conv to utf-8 failed for domain = '%s'.\n",
+                  reversed_domain));
+      goto cleanup;
+    }
 
-    /* we don't want charset-hook effects, so we set flags to 0 */
-    if (mutt_convert_string (&tmp, Charset, "utf-8", 0) == -1)
-      irrev = 1;
 #ifdef HAVE_LIBIDN
-    if (!irrev && idna_to_ascii_8z (tmp, &t2, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
-      irrev = 1;
-#endif /* HAVE_LIBIDN */
-    if (!irrev && ascii_strcasecmp (t2, in))
+    /* If the original domain was UTF-8, idna encoding here could
+     * produce a non-matching domain!  Thus we only want to do the
+     * idna_to_ascii_8z() if the original domain was IDNA encoded.
+     */
+    if (is_idn_encoded && option (OPTUSEIDN))
     {
-      dprint (1, (debugfile, "intl_to_local: Not reversible. in = '%s', t2 = '%s'.\n",
-                 in, t2));
-      irrev = 1;
+      if (idna_to_ascii_8z (reversed_domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
+      {
+        dprint (1, (debugfile,
+                    "intl_to_local: Not reversible. idna_to_ascii_8z failed for domain = '%s'.\n",
+                    reversed_domain));
+        goto cleanup;
+      }
+      mutt_str_replace (&reversed_domain, tmp);
     }
-    
-    FREE (&t2);
-    FREE (&tmp);
+#endif /* HAVE_LIBIDN */
 
-    if (irrev)
-      goto notrans;
+    if (ascii_strcasecmp (orig_domain, reversed_domain))
+    {
+      dprint (1, (debugfile, "intl_to_local: Not reversible. orig = '%s', reversed = '%s'.\n",
+                  orig_domain, reversed_domain));
+      goto cleanup;
+    }
   }
 
-  return 0;
-  
- notrans:
-  FREE (out);          /* __FREE_CHECKED__ */
-  *out = safe_strdup (in);
-  return 1;
+  mailbox = safe_malloc (mutt_strlen (user) + mutt_strlen (domain) + 2);
+  sprintf (mailbox, "%s@%s", NONULL(user), NONULL(domain)); /* __SPRINTF_CHECKED__ */
+
+cleanup:
+  FREE (&user);
+  FREE (&domain);
+  FREE (&tmp);
+  FREE (&orig_domain);
+  FREE (&reversed_domain);
+
+  return mailbox;
 }
 
-static int local_to_intl (const char *in, char **out)
+static char *local_to_intl (ADDRESS *a)
 {
-  int rv = 0;
-  char *tmp = safe_strdup (in);
-  *out = NULL;
+  char *user = NULL, *domain = NULL, *mailbox = NULL;
+  char *tmp = NULL;
+
+  if (mbox_to_udomain (a->mailbox, &user, &domain) == -1)
+    goto cleanup;
 
-  if (!in)
-  {
-    *out = NULL;
-    return -1;
-  }
-  
   /* we don't want charset-hook effects, so we set flags to 0 */
-  if (mutt_convert_string (&tmp, Charset, "utf-8", 0) == -1)
-    rv = -1;
+  if (mutt_convert_string (&user, Charset, "utf-8", 0) == -1)
+    goto cleanup;
+
+  if (mutt_convert_string (&domain, Charset, "utf-8", 0) == -1)
+    goto cleanup;
 
 #ifdef HAVE_LIBIDN
-  if (!rv && idna_to_ascii_8z (tmp, out, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
-    rv = -2;
+  if (idna_to_ascii_8z (domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
+    goto cleanup;
+  mutt_str_replace (&domain, tmp);
 #endif /* HAVE_LIBIDN */
-  
+
+  mailbox = safe_malloc (mutt_strlen (user) + mutt_strlen (domain) + 2);
+  sprintf (mailbox, "%s@%s", NONULL(user), NONULL(domain)); /* __SPRINTF_CHECKED__ */
+
+cleanup:
+  FREE (&user);
+  FREE (&domain);
   FREE (&tmp);
-  if (rv < 0)
-  {
-    FREE (out);                /* __FREE_CHECKED__ */
-    *out = safe_strdup (in);
-  }
-  return rv;
+
+  return mailbox;
 }
 
 /* higher level functions */
 
-static int mbox_to_udomain (const char *mbx, char **user, char **domain)
-{
-  static char *buff = NULL;
-  char *p;
-  
-  mutt_str_replace (&buff, mbx);
-  
-  p = strchr (buff, '@');
-  if (!p || !p[1])
-    return -1;
-  *p = '\0';
-  *user = buff;
-  *domain  = p + 1;
-  return 0;
-}
-
 int mutt_addrlist_to_intl (ADDRESS *a, char **err)
 {
-  char *user = NULL, *domain = NULL;
-  char *tmp = NULL;
-  int e = 0;
-  
+  char *intl_mailbox = NULL;
+  int rv = 0;
+
   if (err)
     *err = NULL;
 
   for (; a; a = a->next)
   {
-    if (!a->mailbox)
-      continue;
-    if (mbox_to_udomain (a->mailbox, &user, &domain) == -1)
+    if (!a->mailbox || addr_is_intl (a))
       continue;
-    
-    if (local_to_intl (domain, &tmp) < 0)
-    {
-      e = 1;
-      if (err)
-       *err = safe_strdup (domain);
-    }
-    else
+
+    intl_mailbox = local_to_intl (a);
+    if (! intl_mailbox)
     {
-      safe_realloc (&a->mailbox, mutt_strlen (user) + mutt_strlen (tmp) + 2);
-      sprintf (a->mailbox, "%s@%s", NONULL(user), NONULL(tmp)); /* __SPRINTF_CHECKED__ */
-      a->intl_checked = 0;
+      rv = -1;
+      if (err && !*err)
+        *err = safe_strdup (a->mailbox);
+      continue;
     }
-    
-    FREE (&tmp);
-    
-    if (e)
-      return -1;
+
+    set_intl_mailbox (a, intl_mailbox);
   }
-  
-  return 0;
+
+  return rv;
 }
 
 int mutt_addrlist_to_local (ADDRESS *a)
 {
-  char *user, *domain;
-  char *tmp = NULL;
-  
+  char *local_mailbox = NULL;
+
   for (; a; a = a->next)
   {
-    if (!a->mailbox)
+    if (!a->mailbox || addr_is_local (a))
       continue;
-    if (!check_idn (a))
-      continue;
-    if (mbox_to_udomain (a->mailbox, &user, &domain) == -1)
-      continue;
-    if (intl_to_local (domain, &tmp, 0) == 0)
-    {
-      safe_realloc (&a->mailbox, mutt_strlen (user) + mutt_strlen (tmp) + 2);
-      sprintf (a->mailbox, "%s@%s", NONULL (user), NONULL (tmp)); /* __SPRINTF_CHECKED__ */
-      a->intl_checked = 0;
-    }
-    
-    FREE (&tmp);
+
+    local_mailbox = intl_to_local (a, 0);
+    if (local_mailbox)
+      set_local_mailbox (a, local_mailbox);
   }
-  
+
   return 0;
 }
 
@@ -225,27 +258,19 @@ int mutt_addrlist_to_local (ADDRESS *a)
 const char *mutt_addr_for_display (ADDRESS *a)
 {
   static char *buff = NULL;
-  char *tmp = NULL;
-  /* user and domain will be either allocated or reseted to the NULL in
-   * the mbox_to_udomain(), but for safety... */
-  char *domain = NULL;
-  char *user = NULL;
-  
+  char *local_mailbox = NULL;
+
   FREE (&buff);
 
-  if (!check_idn (a))
-    return a->mailbox;
-  if (mbox_to_udomain (a->mailbox, &user, &domain) != 0)
+  if (!a->mailbox || addr_is_local (a))
     return a->mailbox;
-  if (intl_to_local (domain, &tmp, MI_MAY_BE_IRREVERSIBLE) != 0)
-  {
-    FREE (&tmp);
+
+  local_mailbox = intl_to_local (a, MI_MAY_BE_IRREVERSIBLE);
+  if (! local_mailbox)
     return a->mailbox;
-  }
-  
-  safe_realloc (&buff, mutt_strlen (tmp) + mutt_strlen (user) + 2);
-  sprintf (buff, "%s@%s", NONULL(user), NONULL(tmp)); /* __SPRINTF_CHECKED__ */
-  FREE (&tmp);
+
+  mutt_str_replace (&buff, local_mailbox);
+  FREE (&local_mailbox);
   return buff;
 }
 
@@ -265,6 +290,7 @@ void mutt_env_to_local (ENVELOPE *e)
 /* Note that `a' in the `env->a' expression is macro argument, not
  * "real" name of an `env' compound member.  Real name will be substituted
  * by preprocessor at the macro-expansion time.
+ * Note that #a escapes and double quotes the argument.
  */
 #define H_TO_INTL(a)   \
   if (mutt_addrlist_to_intl (env->a, err) && !e) \