mutt_idna.c 8.97 KB
Newer Older
1
/*
2
 * Copyright (C) 2003,2005,2008-2009 Thomas Roessler <roessler@does-not-exist.org>
3
 *
4 5 6 7
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
8
 *
9 10 11 12
 *     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.
13
 *
14 15
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
16
 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17
 */
18

19 20 21 22
#if HAVE_CONFIG_H
# include "config.h"
#endif

23 24 25 26
#include "mutt.h"
#include "charset.h"
#include "mutt_idna.h"

Matej Muzila's avatar
Matej Muzila committed
27
#if defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2)
28 29 30 31
static int check_idn (char *domain)
{
  if (! domain)
    return 0;
32

33 34
  if (ascii_strncasecmp (domain, "xn--", 4) == 0)
    return 1;
35

36 37 38 39 40
  while ((domain = strchr (domain, '.')) != NULL)
  {
    if (ascii_strncasecmp (++domain, "xn--", 4) == 0)
      return 1;
  }
Thomas Roessler's avatar
Thomas Roessler committed
41

42 43
  return 0;
}
Matej Muzila's avatar
Matej Muzila committed
44
#endif /* defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2) */
Thomas Roessler's avatar
Thomas Roessler committed
45

46 47
static int mbox_to_udomain (const char *mbx, char **user, char **domain)
{
48
  static char *buff = NULL;
49
  char *p;
Thomas Roessler's avatar
Thomas Roessler committed
50

51 52
  mutt_str_replace (&buff, mbx);

53 54 55 56
  p = strchr (buff, '@');
  if (!p || !p[1])
    return -1;
  *p = '\0';
57 58
  *user = buff;
  *domain  = p + 1;
59
  return 0;
Thomas Roessler's avatar
Thomas Roessler committed
60 61
}

62
static int addr_is_local (ADDRESS *a)
63
{
64 65
  return (a->intl_checked && !a->is_intl);
}
66

67 68 69 70
static int addr_is_intl (ADDRESS *a)
{
  return (a->intl_checked && a->is_intl);
}
71

72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
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;
}

88
static char *intl_to_local (char *orig_user, char *orig_domain, int flags)
89
{
90 91
  char *local_user = NULL, *local_domain = NULL, *mailbox = NULL;
  char *reversed_user = NULL, *reversed_domain = NULL;
92
  char *tmp = NULL;
Matej Muzila's avatar
Matej Muzila committed
93
#if defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2)
94
  int is_idn_encoded = 0;
Matej Muzila's avatar
Matej Muzila committed
95
#endif /* defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2) */
96

97 98
  local_user = safe_strdup (orig_user);
  local_domain = safe_strdup (orig_domain);
Thomas Roessler's avatar
Thomas Roessler committed
99

Matej Muzila's avatar
Matej Muzila committed
100
#if defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2)
101
  is_idn_encoded = check_idn (local_domain);
102
  if (is_idn_encoded && option (OPTIDNDECODE))
103
  {
104
    if (idna_to_unicode_8z8z (local_domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
105
      goto cleanup;
106
    mutt_str_replace (&local_domain, tmp);
107 108
    FREE (&tmp);
  }
Matej Muzila's avatar
Matej Muzila committed
109
#endif /* defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2) */
110 111

  /* we don't want charset-hook effects, so we set flags to 0 */
112
  if (mutt_convert_string (&local_user, "utf-8", Charset, 0) == -1)
113
    goto cleanup;
114

115
  if (mutt_convert_string (&local_domain, "utf-8", Charset, 0) == -1)
116 117 118
    goto cleanup;

  /*
119
   * make sure that we can convert back and come out with the same
120
   * user and domain name.
121
   */
122 123
  if ((flags & MI_MAY_BE_IRREVERSIBLE) == 0)
  {
124
    reversed_user = safe_strdup (local_user);
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140

    if (mutt_convert_string (&reversed_user, Charset, "utf-8", 0) == -1)
    {
      dprint (1, (debugfile,
                  "intl_to_local: Not reversible. Charset conv to utf-8 failed for user = '%s'.\n",
                  reversed_user));
      goto cleanup;
    }

    if (ascii_strcasecmp (orig_user, reversed_user))
    {
      dprint (1, (debugfile, "intl_to_local: Not reversible. orig = '%s', reversed = '%s'.\n",
                  orig_user, reversed_user));
      goto cleanup;
    }

141
    reversed_domain = safe_strdup (local_domain);
142 143 144 145 146 147 148 149

    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;
    }
150

Matej Muzila's avatar
Matej Muzila committed
151
#if defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2)
152 153 154 155
    /* 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.
     */
156
    if (is_idn_encoded && option (OPTIDNDECODE))
157
    {
158 159 160 161 162 163 164 165
      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);
166
    }
Matej Muzila's avatar
Matej Muzila committed
167
#endif /* defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2) */
168

169 170 171 172 173 174
    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;
    }
175 176
  }

177 178
  mailbox = safe_malloc (mutt_strlen (local_user) + mutt_strlen (local_domain) + 2);
  sprintf (mailbox, "%s@%s", NONULL(local_user), NONULL(local_domain)); /* __SPRINTF_CHECKED__ */
179 180

cleanup:
181 182
  FREE (&local_user);
  FREE (&local_domain);
183 184
  FREE (&tmp);
  FREE (&reversed_domain);
185
  FREE (&reversed_user);
186 187

  return mailbox;
188 189
}

190
static char *local_to_intl (char *user, char *domain)
191
{
192 193
  char *intl_user = NULL, *intl_domain = NULL;
  char *mailbox = NULL;
194 195
  char *tmp = NULL;

196 197
  intl_user = safe_strdup (user);
  intl_domain = safe_strdup (domain);
198

199
  /* we don't want charset-hook effects, so we set flags to 0 */
200
  if (mutt_convert_string (&intl_user, Charset, "utf-8", 0) == -1)
201 202
    goto cleanup;

203
  if (mutt_convert_string (&intl_domain, Charset, "utf-8", 0) == -1)
204
    goto cleanup;
205

Matej Muzila's avatar
Matej Muzila committed
206
#if defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2)
207 208
  if (option (OPTIDNENCODE))
  {
209
    if (idna_to_ascii_8z (intl_domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
210
      goto cleanup;
211
    mutt_str_replace (&intl_domain, tmp);
212
  }
Matej Muzila's avatar
Matej Muzila committed
213
#endif /* defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2) */
214

215 216
  mailbox = safe_malloc (mutt_strlen (intl_user) + mutt_strlen (intl_domain) + 2);
  sprintf (mailbox, "%s@%s", NONULL(intl_user), NONULL(intl_domain)); /* __SPRINTF_CHECKED__ */
217 218

cleanup:
219 220
  FREE (&intl_user);
  FREE (&intl_domain);
221
  FREE (&tmp);
222 223

  return mailbox;
224 225 226 227
}

/* higher level functions */

228
int mutt_addrlist_to_intl (ADDRESS *a, char **err)
229
{
230
  char *user = NULL, *domain = NULL;
231 232 233
  char *intl_mailbox = NULL;
  int rv = 0;

234 235 236 237 238
  if (err)
    *err = NULL;

  for (; a; a = a->next)
  {
239
    if (!a->mailbox || addr_is_intl (a))
240
      continue;
241

242 243 244 245
    if (mbox_to_udomain (a->mailbox, &user, &domain) == -1)
      continue;

    intl_mailbox = local_to_intl (user, domain);
246
    if (! intl_mailbox)
247
    {
248 249 250 251
      rv = -1;
      if (err && !*err)
        *err = safe_strdup (a->mailbox);
      continue;
252
    }
253 254

    set_intl_mailbox (a, intl_mailbox);
255
  }
256 257

  return rv;
258 259 260 261
}

int mutt_addrlist_to_local (ADDRESS *a)
{
262
  char *user = NULL, *domain = NULL;
263 264
  char *local_mailbox = NULL;

265 266
  for (; a; a = a->next)
  {
267
    if (!a->mailbox || addr_is_local (a))
268
      continue;
269

270 271 272 273
    if (mbox_to_udomain (a->mailbox, &user, &domain) == -1)
      continue;

    local_mailbox = intl_to_local (user, domain, 0);
274 275
    if (local_mailbox)
      set_local_mailbox (a, local_mailbox);
276
  }
277

278 279 280 281 282 283
  return 0;
}

/* convert just for displaying purposes */
const char *mutt_addr_for_display (ADDRESS *a)
{
284
  char *user = NULL, *domain = NULL;
285
  static char *buff = NULL;
286 287
  char *local_mailbox = NULL;

288
  FREE (&buff);
Thomas Roessler's avatar
Thomas Roessler committed
289

290
  if (!a->mailbox || addr_is_local (a))
291
    return a->mailbox;
292

293 294 295 296
  if (mbox_to_udomain (a->mailbox, &user, &domain) == -1)
    return a->mailbox;

  local_mailbox = intl_to_local (user, domain, MI_MAY_BE_IRREVERSIBLE);
297
  if (! local_mailbox)
298
    return a->mailbox;
299 300 301

  mutt_str_replace (&buff, local_mailbox);
  FREE (&local_mailbox);
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
  return buff;
}

/* Convert an ENVELOPE structure */

void mutt_env_to_local (ENVELOPE *e)
{
  mutt_addrlist_to_local (e->return_path);
  mutt_addrlist_to_local (e->from);
  mutt_addrlist_to_local (e->to);
  mutt_addrlist_to_local (e->cc);
  mutt_addrlist_to_local (e->bcc);
  mutt_addrlist_to_local (e->reply_to);
  mutt_addrlist_to_local (e->mail_followup_to);
}

318 319 320
/* 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.
321
 * Note that #a escapes and double quotes the argument.
322
 */
323 324 325 326
#define H_TO_INTL(a)                                    \
  if (mutt_addrlist_to_intl (env->a, err) && !e)        \
  {                                                     \
    if (tag) *tag = #a; e = 1; err = NULL;              \
327 328
  }

329
int mutt_env_to_intl (ENVELOPE *env, char **tag, char **err)
330 331
{
  int e = 0;
332 333 334 335 336 337 338
  H_TO_INTL(return_path);
  H_TO_INTL(from);
  H_TO_INTL(to);
  H_TO_INTL(cc);
  H_TO_INTL(bcc);
  H_TO_INTL(reply_to);
  H_TO_INTL(mail_followup_to);
339 340 341
  return e;
}

342
#undef H_TO_INTL