/*
 * Copyright notice from original mutt:
 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
 *
 * This file is part of mutt-ng, see http://www.muttng.org/.
 * It's licensed under the GNU General Public License,
 * please see the file GPL in the top level source directory.
 */

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

#include <string.h>
#include <ctype.h>
#include <stdlib.h>

#include "mutt.h"
#include "ascii.h"
#include "mutt_idna.h"

#include "lib/mem.h"
#include "lib/intl.h"
#include "lib/str.h"

#define terminate_string(a, b, c) do { if ((b) < (c)) a[(b)] = 0; else \
	a[(c)] = 0; } while (0)

#define terminate_buffer(a, b) terminate_string(a, b, sizeof (a) - 1)


const char RFC822Specials[] = "@.,:;<>[]\\\"()";

#define is_special(x) strchr(RFC822Specials,x)

int RFC822Error = 0;

/* these must defined in the same order as the numerated errors given in rfc822.h */
const char *RFC822Errors[] = {
  "out of memory",
  "mismatched parenthesis",
  "mismatched quotes",
  "bad route in <>",
  "bad address in <>",
  "bad address spec"
};

void rfc822_dequote_comment (char *s)
{
  char *w = s;

  for (; *s; s++) {
    if (*s == '\\') {
      if (!*++s)
        break;                  /* error? */
      *w++ = *s;
    }
    else if (*s != '\"') {
      if (w != s)
        *w = *s;
      w++;
    }
  }
  *w = 0;
}

void rfc822_free_address (ADDRESS ** p)
{
  ADDRESS *t;

  while (*p) {
    t = *p;
    *p = (*p)->next;
    mem_free (&t->personal);
    mem_free (&t->mailbox);
    mem_free (&t);
  }
}

static const char *parse_comment (const char *s,
                                  char *comment, size_t * commentlen,
                                  size_t commentmax)
{
  int level = 1;

  while (*s && level) {
    if (*s == '(')
      level++;
    else if (*s == ')') {
      if (--level == 0) {
        s++;
        break;
      }
    }
    else if (*s == '\\') {
      if (!*++s)
        break;
    }
    if (*commentlen < commentmax)
      comment[(*commentlen)++] = *s;
    s++;
  }
  if (level) {
    RFC822Error = ERR_MISMATCH_PAREN;
    return NULL;
  }
  return s;
}

static const char *parse_quote (const char *s, char *token, size_t * tokenlen,
                                size_t tokenmax)
{
  if (*tokenlen < tokenmax)
    token[(*tokenlen)++] = '"';
  while (*s) {
    if (*tokenlen < tokenmax)
      token[*tokenlen] = *s;
    if (*s == '"') {
      (*tokenlen)++;
      return (s + 1);
    }
    if (*s == '\\') {
      if (!*++s)
        break;

      if (*tokenlen < tokenmax)
        token[*tokenlen] = *s;
    }
    (*tokenlen)++;
    s++;
  }
  RFC822Error = ERR_MISMATCH_QUOTE;
  return NULL;
}

static const char *next_token (const char *s, char *token, size_t * tokenlen,
                               size_t tokenmax)
{
  if (*s == '(')
    return (parse_comment (s + 1, token, tokenlen, tokenmax));
  if (*s == '"')
    return (parse_quote (s + 1, token, tokenlen, tokenmax));
  if (is_special (*s)) {
    if (*tokenlen < tokenmax)
      token[(*tokenlen)++] = *s;
    return (s + 1);
  }
  while (*s) {
    if (ISSPACE ((unsigned char) *s) || is_special (*s))
      break;
    if (*tokenlen < tokenmax)
      token[(*tokenlen)++] = *s;
    s++;
  }
  return s;
}

static const char *parse_mailboxdomain (const char *s, const char *nonspecial,
                                        char *mailbox, size_t * mailboxlen,
                                        size_t mailboxmax, char *comment,
                                        size_t * commentlen,
                                        size_t commentmax)
{
  const char *ps;

  while (*s) {
    SKIPWS (s);
    if (strchr (nonspecial, *s) == NULL && is_special (*s))
      return s;

    if (*s == '(') {
      if (*commentlen && *commentlen < commentmax)
        comment[(*commentlen)++] = ' ';
      ps = next_token (s, comment, commentlen, commentmax);
    }
    else
      ps = next_token (s, mailbox, mailboxlen, mailboxmax);
    if (!ps)
      return NULL;
    s = ps;
  }

  return s;
}

static const char *parse_address (const char *s,
                                  char *token, size_t * tokenlen,
                                  size_t tokenmax, char *comment,
                                  size_t * commentlen, size_t commentmax,
                                  ADDRESS * addr)
{
  s = parse_mailboxdomain (s, ".\"(\\",
                           token, tokenlen, tokenmax,
                           comment, commentlen, commentmax);
  if (!s)
    return NULL;

  if (*s == '@') {
    if (*tokenlen < tokenmax)
      token[(*tokenlen)++] = '@';
    s = parse_mailboxdomain (s + 1, ".([]\\",
                             token, tokenlen, tokenmax,
                             comment, commentlen, commentmax);
    if (!s)
      return NULL;
  }

  terminate_string (token, *tokenlen, tokenmax);
  addr->mailbox = str_dup (token);

  if (*commentlen && !addr->personal) {
    terminate_string (comment, *commentlen, commentmax);
    addr->personal = str_dup (comment);
  }

  return s;
}

static const char *parse_route_addr (const char *s,
                                     char *comment, size_t * commentlen,
                                     size_t commentmax, ADDRESS * addr)
{
  char token[STRING];
  size_t tokenlen = 0;

  SKIPWS (s);

  /* find the end of the route */
  if (*s == '@') {
    while (s && *s == '@') {
      if (tokenlen < sizeof (token) - 1)
        token[tokenlen++] = '@';
      s = parse_mailboxdomain (s + 1, ",.\\[](", token,
                               &tokenlen, sizeof (token) - 1,
                               comment, commentlen, commentmax);
    }
    if (!s || *s != ':') {
      RFC822Error = ERR_BAD_ROUTE;
      return NULL;              /* invalid route */
    }

    if (tokenlen < sizeof (token) - 1)
      token[tokenlen++] = ':';
    s++;
  }

  if ((s =
       parse_address (s, token, &tokenlen, sizeof (token) - 1, comment,
                      commentlen, commentmax, addr)) == NULL)
    return NULL;

  if (*s != '>') {
    RFC822Error = ERR_BAD_ROUTE_ADDR;
    return NULL;
  }

  if (!addr->mailbox)
    addr->mailbox = str_dup ("@");

  s++;
  return s;
}

static const char *parse_addr_spec (const char *s,
                                    char *comment, size_t * commentlen,
                                    size_t commentmax, ADDRESS * addr)
{
  char token[STRING];
  size_t tokenlen = 0;

  s =
    parse_address (s, token, &tokenlen, sizeof (token) - 1, comment,
                   commentlen, commentmax, addr);
  if (s && *s && *s != ',' && *s != ';') {
    RFC822Error = ERR_BAD_ADDR_SPEC;
    return NULL;
  }
  return s;
}

static void
add_addrspec (ADDRESS ** top, ADDRESS ** last, const char *phrase,
              char *comment, size_t * commentlen, size_t commentmax)
{
  ADDRESS *cur = rfc822_new_address ();

  if (parse_addr_spec (phrase, comment, commentlen, commentmax, cur) == NULL) {
    rfc822_free_address (&cur);
    return;
  }

  if (*last)
    (*last)->next = cur;
  else
    *top = cur;
  *last = cur;
}

ADDRESS *rfc822_parse_adrlist (ADDRESS * top, const char *s)
{
  int ws_pending;
  const char *begin, *ps;
  char comment[STRING], phrase[STRING];
  size_t phraselen = 0, commentlen = 0;
  ADDRESS *cur, *last = NULL;

  RFC822Error = 0;

  last = top;
  while (last && last->next)
    last = last->next;

  ws_pending = isspace ((unsigned char) *s);

  SKIPWS (s);
  begin = s;
  while (*s) {
    if (*s == ',') {
      if (phraselen) {
        terminate_buffer (phrase, phraselen);
        add_addrspec (&top, &last, phrase, comment, &commentlen,
                      sizeof (comment) - 1);
      }
      else if (commentlen && last && !last->personal) {
        terminate_buffer (comment, commentlen);
        last->personal = str_dup (comment);
      }

      commentlen = 0;
      phraselen = 0;
      s++;
      begin = s;
      SKIPWS (begin);
    }
    else if (*s == '(') {
      if (commentlen && commentlen < sizeof (comment) - 1)
        comment[commentlen++] = ' ';
      if ((ps =
           next_token (s, comment, &commentlen,
                       sizeof (comment) - 1)) == NULL) {
        rfc822_free_address (&top);
        return NULL;
      }
      s = ps;
    }
    else if (*s == ':') {
      cur = rfc822_new_address ();
      terminate_buffer (phrase, phraselen);
      cur->mailbox = str_dup (phrase);
      cur->group = 1;

      if (last)
        last->next = cur;
      else
        top = cur;
      last = cur;

      phraselen = 0;
      commentlen = 0;
      s++;
      begin = s;
      SKIPWS (begin);
    }
    else if (*s == ';') {
      if (phraselen) {
        terminate_buffer (phrase, phraselen);
        add_addrspec (&top, &last, phrase, comment, &commentlen,
                      sizeof (comment) - 1);
      }
      else if (commentlen && last && !last->personal) {
        terminate_buffer (comment, commentlen);
        last->personal = str_dup (comment);
      }

      /* add group terminator */
      cur = rfc822_new_address ();
      if (last) {
        last->next = cur;
        last = cur;
      }

      phraselen = 0;
      commentlen = 0;
      s++;
      begin = s;
      SKIPWS (begin);
    }
    else if (*s == '<') {
      terminate_buffer (phrase, phraselen);
      cur = rfc822_new_address ();
      if (phraselen) {
        if (cur->personal)
          mem_free (&cur->personal);
        /* if we get something like "Michael R. Elkins" remove the quotes */
        rfc822_dequote_comment (phrase);
        cur->personal = str_dup (phrase);
      }
      if ((ps =
           parse_route_addr (s + 1, comment, &commentlen,
                             sizeof (comment) - 1, cur)) == NULL) {
        rfc822_free_address (&top);
        rfc822_free_address (&cur);
        return NULL;
      }

      if (last)
        last->next = cur;
      else
        top = cur;
      last = cur;

      phraselen = 0;
      commentlen = 0;
      s = ps;
    }
    else {
      if (phraselen && phraselen < sizeof (phrase) - 1 && ws_pending)
        phrase[phraselen++] = ' ';
      if ((ps =
           next_token (s, phrase, &phraselen, sizeof (phrase) - 1)) == NULL) {
        rfc822_free_address (&top);
        return NULL;
      }
      s = ps;
    }
    ws_pending = isspace ((unsigned char) *s);
    SKIPWS (s);
  }

  if (phraselen) {
    terminate_buffer (phrase, phraselen);
    terminate_buffer (comment, commentlen);
    add_addrspec (&top, &last, phrase, comment, &commentlen,
                  sizeof (comment) - 1);
  }
  else if (commentlen && last && !last->personal) {
    terminate_buffer (comment, commentlen);
    last->personal = str_dup (comment);
  }

  return top;
}

void rfc822_qualify (ADDRESS * addr, const char *host)
{
  char *p;

  for (; addr; addr = addr->next)
    if (!addr->group && addr->mailbox && strchr (addr->mailbox, '@') == NULL) {
      p = mem_malloc (str_len (addr->mailbox) + str_len (host) + 2);
      sprintf (p, "%s@%s", addr->mailbox, host);        /* __SPRINTF_CHECKED__ */
      mem_free (&addr->mailbox);
      addr->mailbox = p;
    }
}

void
rfc822_cat (char *buf, size_t buflen, const char *value, const char *specials)
{
  if (strpbrk (value, specials)) {
    char tmp[256], *pc = tmp;
    size_t tmplen = sizeof (tmp) - 3;

    *pc++ = '"';
    for (; *value && tmplen > 1; value++) {
      if (*value == '\\' || *value == '"') {
        *pc++ = '\\';
        tmplen--;
      }
      *pc++ = *value;
      tmplen--;
    }
    *pc++ = '"';
    *pc = 0;
    strfcpy (buf, tmp, buflen);
  }
  else
    strfcpy (buf, value, buflen);
}

void rfc822_write_address_single (char *buf, size_t buflen, ADDRESS * addr,
                                  int display)
{
  size_t len;
  char *pbuf = buf;
  char *pc;

  if (!addr)
    return;

  buflen--;                     /* save room for the terminal nul */

  if (addr->personal) {
    if (strpbrk (addr->personal, RFC822Specials)) {
      if (!buflen)
        goto done;
      *pbuf++ = '"';
      buflen--;
      for (pc = addr->personal; *pc && buflen > 0; pc++) {
        if (*pc == '"' || *pc == '\\') {
          if (!buflen)
            goto done;
          *pbuf++ = '\\';
          buflen--;
        }
        if (!buflen)
          goto done;
        *pbuf++ = *pc;
        buflen--;
      }
      if (!buflen)
        goto done;
      *pbuf++ = '"';
      buflen--;
    }
    else {
      if (!buflen)
        goto done;
      strfcpy (pbuf, addr->personal, buflen);
      len = str_len (pbuf);
      pbuf += len;
      buflen -= len;
    }

    if (!buflen)
      goto done;
    *pbuf++ = ' ';
    buflen--;
  }

  if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) {
    if (!buflen)
      goto done;
    *pbuf++ = '<';
    buflen--;
  }

  if (addr->mailbox) {
    if (!buflen)
      goto done;
    if (ascii_strcmp (addr->mailbox, "@") && !display) {
      strfcpy (pbuf, addr->mailbox, buflen);
      len = str_len (pbuf);
    }
    else if (ascii_strcmp (addr->mailbox, "@") && display) {
      strfcpy (pbuf, mutt_addr_for_display (addr), buflen);
      len = str_len (pbuf);
    }
    else {
      *pbuf = '\0';
      len = 0;
    }
    pbuf += len;
    buflen -= len;

    if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) {
      if (!buflen)
        goto done;
      *pbuf++ = '>';
      buflen--;
    }

    if (addr->group) {
      if (!buflen)
        goto done;
      *pbuf++ = ':';
      buflen--;
      if (!buflen)
        goto done;
      *pbuf++ = ' ';
      buflen--;
    }
  }
  else {
    if (!buflen)
      goto done;
    *pbuf++ = ';';
    buflen--;
  }
done:
  /* no need to check for length here since we already save space at the
     beginning of this routine */
  *pbuf = 0;
}

/* note: it is assumed that `buf' is nul terminated! */
void rfc822_write_address (char *buf, size_t buflen, ADDRESS * addr,
                           int display)
{
  char *pbuf = buf;
  size_t len = str_len (buf);

  buflen--;                     /* save room for the terminal nul */

  if (len > 0) {
    if (len > buflen)
      return;                   /* safety check for bogus arguments */

    pbuf += len;
    buflen -= len;
    if (!buflen)
      goto done;
    *pbuf++ = ',';
    buflen--;
    if (!buflen)
      goto done;
    *pbuf++ = ' ';
    buflen--;
  }

  for (; addr && buflen > 0; addr = addr->next) {
    /* use buflen+1 here because we already saved space for the trailing
       nul char, and the subroutine can make use of it */
    rfc822_write_address_single (pbuf, buflen + 1, addr, display);

    /* this should be safe since we always have at least 1 char passed into
       the above call, which means `pbuf' should always be nul terminated */
    len = str_len (pbuf);
    pbuf += len;
    buflen -= len;

    /* if there is another address, and its not a group mailbox name or
       group terminator, add a comma to separate the addresses */
    if (addr->next && addr->next->mailbox && !addr->group) {
      if (!buflen)
        goto done;
      *pbuf++ = ',';
      buflen--;
      if (!buflen)
        goto done;
      *pbuf++ = ' ';
      buflen--;
    }
  }
done:
  *pbuf = 0;
}

/* this should be rfc822_cpy_adr */
ADDRESS *rfc822_cpy_adr_real (ADDRESS * addr)
{
  ADDRESS *p = rfc822_new_address ();

  p->personal = str_dup (addr->personal);
  p->mailbox = str_dup (addr->mailbox);
  p->group = addr->group;
  return p;
}

/* this should be rfc822_cpy_adrlist */
ADDRESS *rfc822_cpy_adr (ADDRESS * addr)
{
  ADDRESS *top = NULL, *last = NULL;

  for (; addr; addr = addr->next) {
    if (last) {
      last->next = rfc822_cpy_adr_real (addr);
      last = last->next;
    }
    else
      top = last = rfc822_cpy_adr_real (addr);
  }
  return top;
}

/* append list 'b' to list 'a' and return the last element in the new list */
ADDRESS *rfc822_append (ADDRESS ** a, ADDRESS * b)
{
  ADDRESS *tmp = *a;

  while (tmp && tmp->next)
    tmp = tmp->next;
  if (!b)
    return tmp;
  if (tmp)
    tmp->next = rfc822_cpy_adr (b);
  else
    tmp = *a = rfc822_cpy_adr (b);
  while (tmp && tmp->next)
    tmp = tmp->next;
  return tmp;
}


syntax highlighted by Code2HTML, v. 0.9.1