/*
 * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu>
 * 
 *     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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */ 

#include "mutt.h"

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

/* special chars from RFC822 */
const char *rspecials = "@<>()[];:.,\\\"";

/* MIME specials */
const char *tspecials = "@<>()[];:.,\\\"?/=";

/* word specials */
const char *wspecials = " @<>()[];:,\\\"";

/* ----------------------------------------------------------------------------
 * RFC822 parsing routines
 */

static const char *parse_comment (const char *s, char *comment, size_t len)
{
  int n = 1;

  if (comment)
    len--; /* save room for the terminal \0 */
  else
    len = 0;

  while (*s)
  {
    if (*s == '\\')
    {
      s++;
      if (! *s)
	break; /* don't move past a nul */
    }
    else if (*s == '(')
      n++;
    else if (*s == ')')
    {
      n--;
      if (n == 0)
      {
	s++;
	break;
      }
    }
    else if (ISSPACE (*s))
    {
      /* collapse whitespace runs to a single char */
      SKIPWS (s);
      if (len)
      {
	*comment++ = ' '; /* convert to a single space */
	len--;
      }
      continue;
    }

    if (len)
    {
      *comment++ = *s;
      len--;
    }

    s++;
  }

  if (comment)
    *comment = 0;

  return (s);
}

static const char *parse_quote (const char *s, char *quote, size_t len)
{
  while (*s)
  {
    if (*s == '\\')
    {
      s++;
      if (!*s)
	break; /* don't move past a null */
    }
    else if (*s == '\"')
    {
      s++;
      break;
    }

    if (len > 1)
    {
      *quote++ = *s;
      len--;
    }

    s++;
  }
  *quote = 0;
  return s;
}

static void parse_mailbox (ADDRESS *a, const char *s, const char *host)
{
  char *p;

  if ((p = strrchr (s, '@')) != NULL)
  {
    *p++ = 0;
    a->host = safe_strdup (p);
  }
  else
    a->host = safe_strdup (host);

  a->mailbox = safe_strdup (s);
}

static const char *parse_addrspec (ADDRESS *a, const char *s, const char *host)
{
  char buf[1024];
  char *p = buf;
  size_t buflen = sizeof (buf);

  while (*s)
  {
    if (*s == '(')
    {
      /* ignore comments inside of <> */
      s = parse_comment (s, NULL, 0);
    }
    else if (*s == '\"')
    {
      size_t len;

      /* FOO - this assumes that the quoted-string is the first word in the
       * addrspec.
       */
      s = parse_quote (s + 1, buf, sizeof (buf));
      len = strlen (buf);
      buflen = sizeof (buf) - len;
      p = buf + len;
    }
    else if (*s == ':')
    {
      /* routing info. */
      *p = 0;
      safe_free ((void **) &a->adl);
      a->adl = safe_strdup (buf);
      p = buf;
      buflen = sizeof (buflen);
      s++;
    }
    else if (*s == '>')
    {
      /* end of address */
      s++;
      break;
    }
    else if (ISSPACE (*s))
    {
      /* skip all whitespace */
      s++;
    }
    else
    {
      if (buflen > 1)
	*p++ = *s;
      s++;
    }
  }
  *p = 0;

  if (buf[0])
    parse_mailbox (a, buf, host);

  return (s);
}

static const char *parse_address (ADDRESS **a, const char *s, const char *host)
{
  ADDRESS *tmp = mutt_new_address ();
  char addr[1024];
  char buf[1024];
  char *p = addr;
  size_t addrlen = sizeof (addr);

  while (*s)
  {
    if (*s == '(')
    {
      s = parse_comment (s + 1, buf, sizeof (buf));
      if (!tmp->personal)
	tmp->personal = safe_strdup (buf);
    }
    else if (*s == '\"')
    {
      s = parse_quote (s + 1, p, addrlen);
      while (*p)
      {
	p++;
	addrlen--;
      }
    }
    else if (*s == ':')
    {
      /* group mailbox format */
      *p = 0;
      tmp->mailbox = safe_strdup (addr);

      /* reset */
      p = addr;
      addrlen = sizeof (addr);

      s++;
      break;
    }
    else if (*s == ';')
    {
      /* end of group mailbox */
      *p = 0;
      if (addr[0])
	parse_mailbox (tmp, addr, host);

      if (tmp->mailbox)
      {
	/* create a null entry as group terminator */
	tmp->next = mutt_new_address ();
      }

      *a = tmp;

      return (s + 1);
    }
    else if (*s == '<')
    {
      /* start of real address */
      *p = 0;
      if (addr[0] && !tmp->personal)
	tmp->personal = safe_strdup (addr);

      /* reset */
      p = addr;
      addrlen = sizeof (addr);

      s = parse_addrspec (tmp, s + 1, host);
    }
    else if (*s == ',')
    {
      *p = 0;
      if (addr[0])
	parse_mailbox (tmp, addr, host);

      /* reset */
      p = addr;
      addrlen = sizeof (addr);

      s++;
      break;
    }
    else if (ISSPACE (*s))
    {
      SKIPWS (s);

      if (p != addr)
      {
	if (strchr (rspecials, *s) == NULL &&
	    strchr (rspecials, *(p-1)) == NULL)
	{
	  if (p != addr)
	  {
	    if (addrlen > 1)
	    {
	      *p++ = ' ';
	      addrlen--;
	    }
	  }
	}
      }
    }
    else
    {
      if (addrlen > 1)
      {
	*p++ = *s++;
	addrlen--;
      }
    }
  }

  *p = 0;

  if (addr[0])
    parse_mailbox (tmp, addr, host);

  if (tmp->personal || tmp->mailbox || tmp->adl || tmp->host)
    *a = tmp;
  else
    mutt_free_address (&tmp);

  return (s);
}

void rfc822_parse_adrlist (ADDRESS **a, const char *s, const char *host)
{
  ADDRESS *pa = *a;
  ADDRESS *tmp;

  while (pa && pa->next)
    pa = pa->next;

  while (*s)
  {
    tmp = NULL;
    SKIPWS (s);
    s = parse_address (&tmp, s, host);
    if (pa)
      pa->next = tmp;
    else
      *a = pa = tmp;
    while (pa && pa->next)
      pa = pa->next;
  }
}

/* ----------------------------------------------------------------------------
 * RFC822 writing routines
 */

int rfc822_cat (char *d, size_t dlen, const char *s, const char *specials)
{
  size_t l;

  l = strlen (d);
  d += l;
  dlen -= l;

  if (strpbrk (s, specials) != NULL)
  {
    dlen--; /* save room for the \0 */

    if (dlen < 2)
      return (-1);

    *d++ = '\"';
    dlen--;

    while (*s)
    {
      if (*s == '\"')
      {
	if (dlen < 2)
	  return (-1);

	*d++ = '\\';
	dlen--;
      }
      else if (dlen < 1)
	return (-1);

      *d++ = *s++;
      dlen--;
    }

    if (dlen < 1)
      return (-1);

    *d++ = '\"';
    *d = 0;
  }
  else
    strfcpy (d, s, dlen);

  return 0;
}

int rfc822_address (char *s, size_t len, ADDRESS *a)
{
  size_t l = strlen (s);

  s += l;
  len -= l;

  if (a->adl)
  {
    strfcpy (s, a->adl, len);
    l = strlen (s);
    s += l;
    len -= l;

    if (len < 2)
    {
      *s = 0;
      return (-1);
    }

    *s++ = ':';
    len--;
  }

  if (a->mailbox)
  {
    if (rfc822_cat (s, len, a->mailbox, wspecials) == -1)
      return (-1);

    l = strlen (s);
    s += l;
    len -= l;

    if (a->host)
    {
      if (*a->host != '@')
      {
	if (len < 2)
	{
	  *s = 0;
	  return (-1);
	}

	*s++ = '@';
	len--;

	strfcpy (s, a->host, len);
	l = strlen (s);
	s += l;
	len -= l;
      }
    }
    else
    {
      /* group mailbox */

      if (len < 3)
      {
	*s = 0;
	return (-1);
      }

      *s++ = ':';
      *s++ = ' ';
      *s = 0;
    }
  }
  else if (!a->host)
  {
    /* end of group mailbox */

    if (len < 2)
    {
      *s = 0;
      return (-1);
    }

    *s++ = ';';
    *s = 0;
  }

  return 0;
}

int rfc822_write_address (char *s, size_t len, ADDRESS *a)
{
  size_t l = strlen (s);

  s += l;
  len -= l;

  if (l)
  {
    /* previous stuff, add a comma to separate addresses */

    if (len < 3)
      return (-1);

    *s++ = ',';
    *s++ = ' ';
    *s = 0;
    len -= 2;
  }

  while (a)
  {
    if (a->personal)
    {
      if (rfc822_cat (s, len, a->personal, rspecials) == -1)
	return (-1);

      l = strlen (s);
      s += l;
      len -= l;

      if (len < 3)
	return (-1);

      *s++ = ' ';
      *s++ = '<';
      *s = 0;
      len -= 2;

      if (rfc822_address (s, len, a) == -1)
	return (-1);

      l = strlen (s);
      s += l;
      len -= l;

      if (len < 2)
	return (-1);

      *s++ = '>';
      len--;
      *s = 0;
    }
    else
    {
      if (rfc822_address (s, len, a) == -1)
	return (-1);

      l = strlen (s);
      s += l;
      len -= l;
    }

    /* add a comma for the next address unless we have a group mailbox */
    if (a->next && (a->next->personal || (a->next->mailbox && a->host) || (!a->mailbox && !a->host)))
    {
      if (len < 3)
	return (-1);

      *s++ = ',';
      *s++ = ' ';
      *s = 0;
      len -= 2;
    }

    a = a->next;
  }

  return 0;
}

/* ----------------------------------------------------------------------------
 * miscellaneous address routines
 */

ADDRESS *rfc822_cpy_adr (ADDRESS *a)
{
  ADDRESS *p = NULL, *tmp = NULL;

  while (a)
  {
    if (p)
    {
      tmp->next = mutt_new_address ();
      tmp = tmp->next;
    }
    else
      p = tmp = mutt_new_address ();

    tmp->personal = safe_strdup (a->personal);
    tmp->mailbox = safe_strdup (a->mailbox);
    tmp->adl = safe_strdup (a->adl);
    tmp->host = safe_strdup (a->host);

    a = a->next;
  }

  return (p);
}
