move parse.c into rfc822parse.c in the lib-mime as it's what it's about
[apps/madmutt.git] / lib-mime / rfc822address.c
diff --git a/lib-mime/rfc822address.c b/lib-mime/rfc822address.c
new file mode 100644 (file)
index 0000000..9009d40
--- /dev/null
@@ -0,0 +1,472 @@
+/*
+ *  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"
+
+void address_wipe(address_t *addr)
+{
+    p_delete(&addr->personal);
+    p_delete(&addr->mailbox);
+    address_delete(&addr->next);
+}
+
+
+void rfc822_qualify(address_t *addr, const char *host)
+{
+    char *p;
+
+    for (; addr; addr = addr->next) {
+        if (!addr->group && addr->mailbox && !strchr(addr->mailbox, '@')) {
+            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;
+        }
+    }
+}
+
+address_t *address_dup(address_t *addr)
+{
+    address_t *res = address_new();
+
+    res->personal = m_strdup(addr->personal);
+    res->mailbox  = m_strdup(addr->mailbox);
+    res->group    = addr->group;
+    return res;
+}
+
+address_t *address_list_dup(address_t *addr)
+{
+    address_t *res = NULL, **resp = &res;
+
+    for (; addr; addr = addr->next) {
+        *resp = address_dup(addr);
+        resp = &(*resp)->next;
+    }
+
+    return res;
+}
+
+
+/****************************************************************************/
+/* Parsing functions                                                        */
+/****************************************************************************/
+
+typedef struct static_buf {
+    char buf[STRING];
+    int  len;
+} static_buf;
+
+static inline void stbuf_append(static_buf *buf, int c) {
+    if (buf->len < ssizeof(buf->buf) - 1) {
+        buf->buf[buf->len++] = c;
+        buf->buf[buf->len]   = '\0';
+    }
+}
+
+static inline void stbuf_append_sp(static_buf *buf) {
+    if (buf->len)
+        stbuf_append(buf, ' ');
+}
+
+static char *rfc822_dequote_comment(static_buf *buf)
+{
+    char *res = p_new(char, buf->len + 1);
+    char *q   = res;
+    char *p   = buf->buf;
+    int i;
+
+    for (i = 0; i < buf->len; i++) {
+        if (p[i] == '\"')
+            continue;
+
+        if (p[i] == '\\') {
+            if (++i >= buf->len) /* should not happen */
+                break;
+        }
+
+        *q++ = p[i];
+    }
+
+    *q++ = '\0';
+    return res;
+}
+
+static const char *parse_comment(const char *s, static_buf *buf)
+{
+    int level = 1;
+
+    for (; *s; s++) {
+        switch (*s) {
+          case '(':
+            level++;
+            break;
+
+          case ')':
+            level--;
+            if (!level)
+                return s;
+            break;
+
+          case '\\':
+            s++; /* if *++s is NUL it will be an error anyway */
+            break;
+
+          default:
+            break;
+        }
+
+        stbuf_append(buf, *s);
+    }
+
+    return NULL;
+}
+
+static const char *parse_quote(const char *s, static_buf *buf)
+{
+    for (; *s; s++) {
+        switch (*s) {
+          case '"':
+            stbuf_append(buf, *s);
+            return s + 1;
+
+          case '\\':
+            if (!*++s)
+                return NULL;
+            /* fallthrough */
+
+          default:
+            stbuf_append(buf, *s);
+            break;
+        }
+    }
+
+    return NULL;
+}
+
+const char RFC822Specials[] = "@.,:;<>[]\\\"()";
+
+static const char *next_phrase(const char *s, static_buf *buf)
+{
+    if (*s == '"') {
+        stbuf_append(buf, '"');
+        return parse_quote(s + 1, buf);
+    }
+
+    if (strchr(RFC822Specials, *s)) {
+        stbuf_append(buf, *s);
+        return s + 1;
+    }
+
+    while (*s) {
+        if (ISSPACE(*s) || strchr(RFC822Specials, *s))
+            break;
+        stbuf_append(buf, *s++);
+    }
+
+    return s;
+}
+
+static const char *
+parse_mailboxdomain(const char *s, const char *nonspecial, static_buf *mbox,
+                    static_buf *comment)
+{
+    while (*s) {
+        s = skipspaces(s);
+
+        if (!strchr(nonspecial, *s) && strchr(RFC822Specials, *s))
+            return s;
+
+        if (*s == '(') {
+            stbuf_append_sp(comment);
+            s = parse_comment(s + 1, comment);
+        } else {
+            s = next_phrase(s, mbox);
+        }
+
+        if (!s)
+            return NULL;
+    }
+
+    return s;
+}
+
+static const char *
+parse_address(const char *s, static_buf *comment, address_t *cur)
+{
+    static_buf token = {"", 0};
+
+    s = parse_mailboxdomain(s, ".\"(\\", &token, comment);
+    if (!s)
+        return NULL;
+
+    if (*s == '@') {
+        stbuf_append(&token, '@');
+        s = parse_mailboxdomain(s + 1, ".([]\\", &token, comment);
+        if (!s)
+            return NULL;
+    }
+
+    cur->mailbox = p_dupstr(token.buf, token.len);
+
+    if (comment->len && !cur->personal) {
+        cur->personal = p_dupstr(comment->buf, comment->len);
+    }
+
+    return s;
+}
+
+address_t **rfc822_eotoken(address_t **last, static_buf *phrase, static_buf *comment)
+{
+    if (phrase->len) {
+        const char *s;
+        address_t *cur = address_new();
+
+        s = parse_address(phrase->buf, comment, cur);
+        if (s && *s && *s != ',' && *s != ';') {
+            address_delete(&cur);
+            return last;
+        }
+
+        *last = cur;
+        return &(*last)->next;
+    }
+
+    return last;
+}
+
+address_t *rfc822_parse_adrlist(address_t *top, const char *s)
+{
+    static_buf comment = {"", 0};
+    static_buf phrase  = {"", 0};
+
+    address_t **last = address_list_last(&top);
+    int ws_pending = 0;
+
+    for (;;) {
+        ws_pending = ISSPACE(*s);
+        s = skipspaces(s);
+
+        switch (*s) {
+            address_t *cur;
+
+          default:
+            if (ws_pending)
+                stbuf_append_sp(&phrase);
+            s = next_phrase(s, &phrase);
+            if (!s) {
+                address_delete(&top);
+                return NULL;
+            }
+            continue;
+
+          case '(':
+            stbuf_append_sp(&comment);
+            s = parse_comment(s + 1, &comment);
+            if (!s) {
+                address_delete(&top);
+                return NULL;
+            }
+            continue;
+
+
+          case '<':
+            cur = address_new();
+            if (phrase.len) {
+                /* if we get something like "Michael R. Elkins" remove the quotes */
+                cur->personal = rfc822_dequote_comment(&phrase);
+            }
+
+            s = parse_address(skipspaces(s + 1), &comment, cur);
+            if (!s || *s != '>' || !cur->mailbox) {
+                address_delete(&top);
+                address_delete(&cur);
+                return NULL;
+            }
+
+            *last = cur;
+            last = &(*last)->next;
+            break;
+
+          case ',':
+            last = rfc822_eotoken(last, &phrase, &comment);
+            break;
+
+          case ':': /* group start */
+            *last = address_new();
+            (*last)->mailbox = p_dupstr(phrase.buf, phrase.len);
+            (*last)->group = 1;
+            last = &(*last)->next;
+            break;
+
+          case ';':
+            last = rfc822_eotoken(last, &phrase, &comment);
+            /* add group terminator */
+            *last = address_new();
+            last = &(*last)->next;
+            break;
+
+          case '\0':
+            last = rfc822_eotoken(last, &phrase, &comment);
+            return top;
+        }
+
+        comment.len = phrase.len  = 0;
+        s++;
+    }
+
+    return NULL;
+}
+
+
+/****************************************************************************/
+/* Output functions                                                         */
+/****************************************************************************/
+
+ssize_t
+rfc822_strcpy(char *buf, ssize_t buflen, const char *p, const char *specials)
+{
+    if (strpbrk(p, specials)) {
+        ssize_t pos = 0;
+
+        buf[pos++] = '"';
+
+        while (*p && pos < buflen - 2) {
+            if (*p == '\\' || *p == '"') {
+                if (pos >= buflen - 4)
+                    break;
+                buf[pos++] = '\\';
+            }
+
+            buf[pos++] = *p++;
+        }
+
+        buf[pos++] = '"';
+        buf[pos]   = '\0';
+        return pos;
+    } else {
+        return m_strcpy(buf, buflen, p);
+    }
+}
+
+ssize_t rfc822_write_address_single(char *buf, ssize_t buflen,
+                                    address_t *addr, int display)
+{
+    ssize_t pos = 0;
+
+    if (!addr)
+        return 0;
+
+    buflen--;                     /* save room for the terminal nul */
+
+    if (addr->personal) {
+        pos = rfc822_strcpy(buf, buflen, addr->personal, RFC822Specials);
+        if (pos + 2 >= buflen)
+            goto done;
+
+        buf[pos++] = ' ';
+        buf[pos++] = '<';
+    }
+
+    if (addr->mailbox) {
+        if (!display) {
+            pos += m_strcpy(buf + pos, buflen - pos, addr->mailbox);
+        } else {
+            pos += m_strcpy(buf + pos, buflen - pos, mutt_addr_for_display(addr));
+        }
+
+        if (addr->personal) {
+            if (pos + 1 >= buflen)
+                goto done;
+            buf[pos++] = '>';
+        }
+
+        if (addr->group) {
+            if (pos + 1 >= buflen)
+                goto done;
+            buf[pos++] = ':';
+        }
+    } else {
+        if (pos + 1 >= buflen)
+            goto done;
+        buf[pos++] = ';';
+    }
+
+  done:
+    /* no need to check for length here since we already save space at the
+       beginning of this routine */
+    buf[pos] = 0;
+    return pos;
+}
+
+/* note: it is assumed that `buf' is nul terminated! */
+ssize_t
+rfc822_write_address(char *buf, ssize_t buflen, address_t *addr, int display)
+{
+    ssize_t pos;
+
+    buflen--;                     /* save room for the terminal nul */
+    pos = m_strnlen(buf, buflen);
+
+    if (pos) {
+        if (pos + 2 >= buflen)
+            goto done;
+
+        buf[pos++] = ',';
+        buf[pos++] = ' ';
+    }
+
+    for (; addr; addr = addr->next) {
+        pos += rfc822_write_address_single(buf + pos, buflen + 1 - pos,
+                                           addr, display);
+
+        if (!addr->group && addr->next && addr->next->mailbox) {
+            /* if there is another address, and its not a group mailbox name or
+               group terminator, add a comma to separate the addresses */
+            if (pos + 2 >= buflen)
+                break;
+
+            buf[pos++] = ',';
+            buf[pos++] = ' ';
+        }
+    }
+
+  done:
+    buf[pos] = '\0';
+    return pos;
+}
+