/*
 * Copyright (C) 2003,2005,2008-2009 Thomas Roessler <roessler@does-not-exist.org>
 *
 *     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.
 *
 *     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, write to the Free Software
 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include "mutt.h"
#include "charset.h"
#include "mutt_idna.h"

#if defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2)
static int check_idn(char *domain)
{
  if (! domain)
    return 0;

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

  while ((domain = strchr(domain, '.')) != NULL)
  {
    if (ascii_strncasecmp(++domain, "xn--", 4) == 0)
      return 1;
  }

  return 0;
}
#endif /* defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2) */

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

static int addr_is_local(ADDRESS *a)
{
  return (a->intl_checked && !a->is_intl);
}

static int addr_is_intl(ADDRESS *a)
{
  return (a->intl_checked && a->is_intl);
}

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

/* NOTE: this doesn't set a state flag like the other two
 * right now, because it's only used for a one-way transition
 * when getting encryption keys.
 */
static void set_utf8_mailbox(ADDRESS *a, char *utf8_mailbox)
{
  FREE(&a->mailbox);
  a->mailbox = utf8_mailbox;
}

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_utf8(char *orig_user, char *orig_domain)
{
  char *utf8_user, *utf8_domain, *mailbox = NULL, *tmp = NULL;

  utf8_user = orig_user;
  utf8_domain = safe_strdup(orig_domain);

#if defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2)
  /* NOTE: since this transform is currently used for key lookups,
   * the check for option(OPTIDNDECODE) is not included here.
   * Compare to the invocation in intl_to_local()
   */
  if (check_idn(utf8_domain))
  {
    if (idna_to_unicode_8z8z(utf8_domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
      goto cleanup;
    mutt_str_replace(&utf8_domain, tmp);
  }
#endif

  safe_asprintf(&mailbox, "%s@%s", NONULL(utf8_user), NONULL(utf8_domain));

#if defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2)
cleanup:
#endif
  FREE(&utf8_domain);
  FREE(&tmp);

  return mailbox;
}

static char *intl_to_local(char *orig_user, char *orig_domain, int flags)
{
  char *local_user = NULL, *local_domain = NULL, *mailbox = NULL;
  char *reversed_user = NULL, *reversed_domain = NULL;
  char *tmp = NULL;
#if defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2)
  int is_idn_encoded = 0;
#endif /* defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2) */

  local_user = safe_strdup(orig_user);
  local_domain = safe_strdup(orig_domain);

#if defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2)
  is_idn_encoded = check_idn(local_domain);
  if (is_idn_encoded && option(OPTIDNDECODE))
  {
    if (idna_to_unicode_8z8z(local_domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
      goto cleanup;
    mutt_str_replace(&local_domain, tmp);
    FREE(&tmp);
  }
#endif /* defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2) */

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

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

  /*
   * make sure that we can convert back and come out with the same
   * user and domain name.
   */
  if ((flags & MI_MAY_BE_IRREVERSIBLE) == 0)
  {
    reversed_user = safe_strdup(local_user);

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

    if (ascii_strcasecmp(orig_user, reversed_user))
    {
      muttdbg(1, "Not reversible. orig = '%s', reversed = '%s'.",
              orig_user, reversed_user);
      goto cleanup;
    }

    reversed_domain = safe_strdup(local_domain);

    if (mutt_convert_string(&reversed_domain, Charset, "utf-8", 0) == -1)
    {
      muttdbg(1, "Not reversible. Charset conv to utf-8 failed for domain = '%s'.",
              reversed_domain);
      goto cleanup;
    }

#if defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2)
    /* 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(OPTIDNDECODE))
    {
      if (idna_to_ascii_8z(reversed_domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
      {
        muttdbg(1, "Not reversible. idna_to_ascii_8z failed for domain = '%s'.",
                reversed_domain);
        goto cleanup;
      }
      mutt_str_replace(&reversed_domain, tmp);
    }
#endif /* defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2) */

    if (ascii_strcasecmp(orig_domain, reversed_domain))
    {
      muttdbg(1, "Not reversible. orig = '%s', reversed = '%s'.",
              orig_domain, reversed_domain);
      goto cleanup;
    }
  }

  safe_asprintf(&mailbox, "%s@%s", NONULL(local_user), NONULL(local_domain));

cleanup:
  FREE(&local_user);
  FREE(&local_domain);
  FREE(&tmp);
  FREE(&reversed_domain);
  FREE(&reversed_user);

  return mailbox;
}

static char *local_to_intl(char *user, char *domain)
{
  char *intl_user = NULL, *intl_domain = NULL;
  char *mailbox = NULL;
  char *tmp = NULL;

  intl_user = safe_strdup(user);
  intl_domain = safe_strdup(domain);

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

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

#if defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2)
  if (option(OPTIDNENCODE) &&
      intl_domain && *intl_domain != '[')  /* don't encode domain-literals */
  {
    if (idna_to_ascii_8z(intl_domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
      goto cleanup;
    mutt_str_replace(&intl_domain, tmp);
  }
#endif /* defined(HAVE_LIBIDN) || defined(HAVE_LIBIDN2) */

  safe_asprintf(&mailbox, "%s@%s", NONULL(intl_user), NONULL(intl_domain));

cleanup:
  FREE(&intl_user);
  FREE(&intl_domain);
  FREE(&tmp);

  return mailbox;
}

/* higher level functions */

int mutt_addrlist_to_intl(ADDRESS *a, char **err)
{
  char *user = NULL, *domain = NULL;
  char *intl_mailbox = NULL;
  int rv = 0;

  if (err)
    *err = NULL;

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

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

    intl_mailbox = local_to_intl(user, domain);
    if (! intl_mailbox)
    {
      rv = -1;
      if (err && !*err)
        *err = safe_strdup(a->mailbox);
      continue;
    }

    set_intl_mailbox(a, intl_mailbox);
  }

  return rv;
}

/* NOTE: this function currently only handles a one-way transition
 * from idn to utf-8, because it's only used for looking up
 * encryption keys in crypt_get_keys().
 *
 * No state bit is set.  The resulting addresses should not be passed through
 * other mutt_addrlist_to_xxx() functions.  They should be used and freed.
 */
int mutt_addrlist_to_utf8(ADDRESS *a)
{
  char *user = NULL, *domain = NULL;
  char *utf8_mailbox = NULL;

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

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

    utf8_mailbox = intl_to_utf8(user, domain);
    if (utf8_mailbox)
      set_utf8_mailbox(a, utf8_mailbox);
  }

  return 0;
}

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

  for (; a; a = a->next)
  {
    if (!a->mailbox || addr_is_local(a))
      continue;

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

    local_mailbox = intl_to_local(user, domain, 0);
    if (local_mailbox)
      set_local_mailbox(a, local_mailbox);
  }

  return 0;
}

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

  FREE(&buff);

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

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

  local_mailbox = intl_to_local(user, domain, MI_MAY_BE_IRREVERSIBLE);
  if (! local_mailbox)
    return a->mailbox;

  mutt_str_replace(&buff, local_mailbox);
  FREE(&local_mailbox);
  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);
}

/* 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)         \
  {                                                     \
    if (tag)                                            \
      *tag = #a;                                        \
    e = 1;                                              \
    err = NULL;                                         \
  }

int mutt_env_to_intl(ENVELOPE *env, char **tag, char **err)
{
  int e = 0;
  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);
  return e;
}

#undef H_TO_INTL
