move rfc822.c as well
[apps/madmutt.git] / lib-mime / rfc822.c
diff --git a/lib-mime/rfc822.c b/lib-mime/rfc822.c
new file mode 100644 (file)
index 0000000..82c3e7c
--- /dev/null
@@ -0,0 +1,693 @@
+/*
+ *  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.
+ *
+ *  Copyright © 2006 Pierre Habouzit
+ */
+
+/*
+ * 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.
+ */
+
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include <lib-lib/mem.h>
+#include <lib-lib/str.h>
+#include <lib-lib/ascii.h>
+#include <lib-lib/macros.h>
+
+#include "mutt_idna.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;
+    p_delete(&t->personal);
+    p_delete(&t->mailbox);
+    p_delete(&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) {
+    s = vskipspaces(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 = m_strdup(token);
+
+  if (*commentlen && !addr->personal) {
+    terminate_string (comment, *commentlen, commentmax);
+    addr->personal = m_strdup(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;
+
+  s = vskipspaces(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 = m_strdup("@");
+
+  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);
+
+  begin = s = vskipspaces(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 = m_strdup(comment);
+      }
+
+      commentlen = 0;
+      phraselen = 0;
+      s++;
+      begin = vskipspaces(s);
+    }
+    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 = m_strdup(phrase);
+      cur->group = 1;
+
+      if (last)
+        last->next = cur;
+      else
+        top = cur;
+      last = cur;
+
+      phraselen = 0;
+      commentlen = 0;
+      s++;
+      begin = vskipspaces(s);
+    }
+    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 = m_strdup(comment);
+      }
+
+      /* add group terminator */
+      cur = rfc822_new_address ();
+      if (last) {
+        last->next = cur;
+        last = cur;
+      }
+
+      phraselen = 0;
+      commentlen = 0;
+      s++;
+      begin = vskipspaces(s);
+    }
+    else if (*s == '<') {
+      terminate_buffer (phrase, phraselen);
+      cur = rfc822_new_address ();
+      if (phraselen) {
+        if (cur->personal)
+          p_delete(&cur->personal);
+        /* if we get something like "Michael R. Elkins" remove the quotes */
+        rfc822_dequote_comment (phrase);
+        cur->personal = m_strdup(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);
+    s = vskipspaces(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 = m_strdup(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 = p_new(char, m_strlen(addr->mailbox) + m_strlen(host) + 2);
+      sprintf (p, "%s@%s", addr->mailbox, host);        /* __SPRINTF_CHECKED__ */
+      p_delete(&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;
+    m_strcpy(buf, buflen, tmp);
+  }
+  else
+    m_strcpy(buf, buflen, value);
+}
+
+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;
+      m_strcpy(pbuf, buflen, addr->personal);
+      len = m_strlen(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) {
+      m_strcpy(pbuf, buflen, addr->mailbox);
+      len = m_strlen(pbuf);
+    }
+    else if (ascii_strcmp (addr->mailbox, "@") && display) {
+      m_strcpy(pbuf, buflen, mutt_addr_for_display(addr));
+      len = m_strlen(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 = m_strlen(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 = m_strlen(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 = m_strdup(addr->personal);
+  p->mailbox = m_strdup(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;
+}