#include <stdlib.h>
#include "cunit/unit.h"
#include "parseaddr.h"

static void test_single(void)
{
    struct address *a;

    a = NULL;
    parseaddr_list("Fred Bloggs <fbloggs@fastmail.fm>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Fred Bloggs");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);
}

static void test_nospace(void)
{
    struct address *a;

    a = NULL;
    parseaddr_list("Fred<fbloggs@fastmail.fm>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Fred");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);
}

static void test_bts3462(void)
{
    /* This tests a particular class of non-standard addresses. It's here
     * for regression and if this test fails, it might just as well mean
     * that the test should be fixed, rather than the code that made it break.
     */

    struct address *a;

    a = NULL;
    parseaddr_list("fbloggs@fastmail.fm<fbloggs@fastmail.fm>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_PTR_NULL(a->name);
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    CU_ASSERT_PTR_NOT_NULL(a->next);

    CU_ASSERT_PTR_NULL(a->next->name);
    CU_ASSERT_STRING_EQUAL(a->next->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->next->domain, "fastmail.fm");
    CU_ASSERT_PTR_NULL(a->next->next);

    parseaddr_free(a);
}

static void test_noname(void)
{
    struct address *a;

    a = NULL;
    parseaddr_list("fbloggs@fastmail.fm", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_PTR_NULL(a->name);
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);

    a = NULL;
    parseaddr_list("<fbloggs@fastmail.fm>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_PTR_NULL(a->name);
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);

    a = NULL;
    parseaddr_list(" <fbloggs@fastmail.fm>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_PTR_NULL(a->name);
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);

}

static void test_invalid(void)
{
    struct address *a;

    a = NULL;
    parseaddr_list("a@bad@domain@acme.local", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_PTR_NULL(a->name);
    CU_ASSERT_STRING_EQUAL(a->mailbox, "a");
    CU_ASSERT_STRING_EQUAL(a->domain, "bad@domain@acme.local");
    CU_ASSERT(a->invalid);
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);
}

static void test_single_append(void)
{
    struct address *a;
    struct address *origa;

    a = NULL;
    parseaddr_list("Fred Bloggs <fbloggs@fastmail.fm>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Fred Bloggs");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    CU_ASSERT_PTR_NULL(a->next);
    origa = a;

    parseaddr_list("Sarah Jane Smith <sjsmith@gmail.com>", &a);
    CU_ASSERT_PTR_EQUAL_FATAL(a, origa);
    CU_ASSERT_STRING_EQUAL(a->name, "Fred Bloggs");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    CU_ASSERT_PTR_NOT_NULL_FATAL(a->next);
    CU_ASSERT_STRING_EQUAL(a->next->name, "Sarah Jane Smith");
    CU_ASSERT_STRING_EQUAL(a->next->mailbox, "sjsmith");
    CU_ASSERT_STRING_EQUAL(a->next->domain, "gmail.com");
    CU_ASSERT_PTR_NULL(a->next->next);

    parseaddr_free(a);
}

static void test_multiple(void)
{
    struct address *a;

    a = NULL;
    parseaddr_list("Fred Bloggs <fbloggs@fastmail.fm>, Sarah Jane Smith <sjsmith@gmail.com>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Fred Bloggs");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    CU_ASSERT_PTR_NOT_NULL_FATAL(a->next);
    CU_ASSERT_STRING_EQUAL(a->next->name, "Sarah Jane Smith");
    CU_ASSERT_STRING_EQUAL(a->next->mailbox, "sjsmith");
    CU_ASSERT_STRING_EQUAL(a->next->domain, "gmail.com");
    CU_ASSERT_PTR_NULL(a->next->next);

    parseaddr_free(a);
}

static void test_quoted_name(void)
{
    struct address *a;

    a = NULL;
    parseaddr_list("\"Fred Bloggs\" <fbloggs@fastmail.fm>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Fred Bloggs");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);
}

static void test_quoted_name_comma(void)
{
    struct address *a;

    a = NULL;
    parseaddr_list("\"Bloggs, Fred\" <fbloggs@fastmail.fm>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Bloggs, Fred");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);
}

static void test_quoted_name_unbalanced(void)
{
    struct address *a;

    /* If a quoted string is not terminated, we are parsing a header
     * field with an unbalanced " and should fail. */

    a = NULL;
    parseaddr_list("\"Charles Manson <forever@prison.ca.us>", &a);
    CU_ASSERT_PTR_NULL_FATAL(a);
}

static void test_quoted_name_folded(void)
{
    struct address *a;

    /* If a quoted string contains an embedded CR+LF+WSP, because we're
     * parsing a header value directly, the CR+LF should be stripped out
     * and the WSP and any following WSP* should be preserved (i.e.  we
     * should perform header field unfolding per RFC 2822) */

    a = NULL;
    parseaddr_list("\"Akira\r\n \t Yoshizawa\" <akira@origami.jp>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Akira \t Yoshizawa");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "akira");
    CU_ASSERT_STRING_EQUAL(a->domain, "origami.jp");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);
}

static void test_quoted_name_crlf(void)
{
    struct address *a;

    /* If a quoted string contains an embedded CR+LF *without* trailing
     * WSP, then we have walked off the end of the header field and thus
     * we're parsing a header field which has an unbalanced ", and
     * should fail.  */

    a = NULL;
    parseaddr_list("\"Akira\r\nYoshizawa\" <akira@origami.jp>", &a);
    CU_ASSERT_PTR_NULL_FATAL(a);

    /* CR+LF+CR+LF is a subcase of that, for the last header field. */
    a = NULL;
    parseaddr_list("\"Akira\r\n\r\nYoshizawa\" <akira@origami.jp>", &a);
    CU_ASSERT_PTR_NULL_FATAL(a);

    /* A lone CR is replaced with space */
    a = NULL;
    parseaddr_list("\"Akira\rYoshizawa\" <akira@origami.jp>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Akira Yoshizawa");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "akira");
    CU_ASSERT_STRING_EQUAL(a->domain, "origami.jp");
    CU_ASSERT_PTR_NULL(a->next);
    parseaddr_free(a);

    /* A lone LF is replaced with space */
    a = NULL;
    parseaddr_list("\"Akira\nYoshizawa\" <akira@origami.jp>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Akira Yoshizawa");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "akira");
    CU_ASSERT_STRING_EQUAL(a->domain, "origami.jp");
    CU_ASSERT_PTR_NULL(a->next);
    parseaddr_free(a);
}


static void test_comment_name(void)
{
    struct address *a;

    a = NULL;
    parseaddr_list("fbloggs@fastmail.fm (Fred Bloggs)", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Fred Bloggs");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);
}

static void test_quoted_mailbox(void)
{
    struct address *a;

    a = NULL;
    parseaddr_list("Fred Bloggs <\"fred bloggs\"@fastmail.fm>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Fred Bloggs");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fred bloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);
}

static void test_mailbox_comment(void)
{
    struct address *a;

    /* This example is from the RFC 822 text */
    a = NULL;
    parseaddr_list("Wilt . (the Stilt) Chamberlain@NBA.US", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_PTR_NULL(a->name);
//     CU_ASSERT_STRING_EQUAL(a->mailbox, "Wilt.Chamberlain");
    CU_ASSERT_STRING_EQUAL(a->domain, "NBA.US");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);
}

static void test_domain_literal(void)
{
    struct address *a;

    a = NULL;
    parseaddr_list("Fred Bloggs <fbloggs@[192.168.0.1]>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Fred Bloggs");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "[192.168.0.1]");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);
}

static void test_dotted_mailbox(void)
{
    struct address *a;

    a = NULL;
    parseaddr_list("Fred Bloggs <Fred.Bloggs@fastmail.fm>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Fred Bloggs");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "Fred.Bloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);
}

static void test_long_domain(void)
{
    struct address *a;

    a = NULL;
    parseaddr_list("Fred Bloggs <fbloggs@a.really.quite.surprisingly.long.domain.name.com>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Fred Bloggs");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "a.really.quite.surprisingly.long.domain.name.com");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);
}

static void test_rfc2047_text(void)
{
    struct address *a;

    /* RFC 2047 MIME-encoded text in an address is passed through
     * unmolested, to be decoded by upper layers, or not, on demand */

    a = NULL;
    parseaddr_list("=?UTF-8?Q?=F0=92=8C=89=F0=92=8B=9B?= <Dumusi@lagash.sm>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "=?UTF-8?Q?=F0=92=8C=89=F0=92=8B=9B?=");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "Dumusi");
    CU_ASSERT_STRING_EQUAL(a->domain, "lagash.sm");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);
}

static void test_utf8_domain(void)
{
    struct address *a;

    a = NULL;
    parseaddr_list("J. Besteiro <jb@julián.example.com>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "J. Besteiro");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "jb");
    CU_ASSERT_STRING_EQUAL(a->domain, "julián.example.com");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(a);
}

static void test_group(void)
{
    struct address *a;

    a = NULL;
    parseaddr_list("Buddies: Fred Bloggs <fbloggs@fastmail.fm>, Sarah Jane Smith <sjsmith@gmail.com>;", &a);

    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_PTR_NULL(a->name);
    CU_ASSERT_STRING_EQUAL(a->mailbox, "Buddies");
    CU_ASSERT_PTR_NULL(a->domain);

    CU_ASSERT_PTR_NOT_NULL_FATAL(a->next);
    CU_ASSERT_STRING_EQUAL(a->next->name, "Fred Bloggs");
    CU_ASSERT_STRING_EQUAL(a->next->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->next->domain, "fastmail.fm");

    CU_ASSERT_PTR_NOT_NULL_FATAL(a->next->next);
    CU_ASSERT_STRING_EQUAL(a->next->next->name, "Sarah Jane Smith");
    CU_ASSERT_STRING_EQUAL(a->next->next->mailbox, "sjsmith");
    CU_ASSERT_STRING_EQUAL(a->next->next->domain, "gmail.com");

    CU_ASSERT_PTR_NOT_NULL_FATAL(a->next->next->next);
    CU_ASSERT_PTR_NULL(a->next->next->next->name);
    CU_ASSERT_PTR_NULL(a->next->next->next->mailbox);
    CU_ASSERT_PTR_NULL(a->next->next->next->domain);

    CU_ASSERT_PTR_NULL(a->next->next->next->next);

    parseaddr_free(a);
}

static void test_multi_group(void)
{
    struct address *a;
    struct address *cur;

    a = NULL;
    parseaddr_list("group: g1@d1.org, g2@d2.org;, group2: g3@d3.org;", &a);

    cur = a;
    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_PTR_NULL(cur->name);
    CU_ASSERT_STRING_EQUAL(cur->mailbox, "group");
    CU_ASSERT_PTR_NULL(cur->domain);

    cur = cur->next;
    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_PTR_NULL(cur->name);
    CU_ASSERT_STRING_EQUAL(cur->mailbox, "g1");
    CU_ASSERT_STRING_EQUAL(cur->domain, "d1.org");

    cur = cur->next;
    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_PTR_NULL(cur->name);
    CU_ASSERT_STRING_EQUAL(cur->mailbox, "g2");
    CU_ASSERT_STRING_EQUAL(cur->domain, "d2.org");

    cur = cur->next;
    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_PTR_NULL(cur->name);
    CU_ASSERT_PTR_NULL(cur->mailbox);
    CU_ASSERT_PTR_NULL(cur->domain);

    cur = cur->next;
    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_PTR_NULL(cur->name);
    CU_ASSERT_STRING_EQUAL(cur->mailbox, "group2");
    CU_ASSERT_PTR_NULL(cur->domain);

    cur = cur->next;
    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_PTR_NULL(cur->name);
    CU_ASSERT_STRING_EQUAL(cur->mailbox, "g3");
    CU_ASSERT_STRING_EQUAL(cur->domain, "d3.org");

    cur = cur->next;
    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_PTR_NULL(cur->name);
    CU_ASSERT_PTR_NULL(cur->mailbox);
    CU_ASSERT_PTR_NULL(cur->domain);

    cur = cur->next;
    CU_ASSERT_PTR_NULL(cur);

    parseaddr_free(a);
}

static void test_multi_group_mixed(void)
{
    struct address *a;
    struct address *cur;

    a = NULL;
    parseaddr_list("Buddies: Fred Bloggs <fbloggs@fastmail.fm>, "
                   "Sarah Jane Smith <sjsmith@gmail.com>;, "
                   "foobar@fastmail.fm, "
                   "group2: Fred Bloggs Junior <fbloggs_jr@fastmail.fm>;",
                   &a);

    cur = a;

    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_PTR_NULL(cur->name);
    CU_ASSERT_STRING_EQUAL(cur->mailbox, "Buddies");
    CU_ASSERT_PTR_NULL(cur->domain);

    cur = cur->next;
    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_STRING_EQUAL(cur->name, "Fred Bloggs");
    CU_ASSERT_STRING_EQUAL(cur->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(cur->domain, "fastmail.fm");

    cur = cur->next;
    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_STRING_EQUAL(cur->name, "Sarah Jane Smith");
    CU_ASSERT_STRING_EQUAL(cur->mailbox, "sjsmith");
    CU_ASSERT_STRING_EQUAL(cur->domain, "gmail.com");

    cur = cur->next;
    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_PTR_NULL(cur->name);
    CU_ASSERT_PTR_NULL(cur->mailbox);
    CU_ASSERT_PTR_NULL(cur->domain);

    cur = cur->next;
    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_PTR_NULL(cur->name);
    CU_ASSERT_STRING_EQUAL(cur->mailbox, "foobar");
    CU_ASSERT_STRING_EQUAL(cur->domain, "fastmail.fm");

    cur = cur->next;
    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_PTR_NULL(cur->name);
    CU_ASSERT_STRING_EQUAL(cur->mailbox, "group2");
    CU_ASSERT_PTR_NULL(cur->domain);

    cur = cur->next;
    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_STRING_EQUAL(cur->name, "Fred Bloggs Junior");
    CU_ASSERT_STRING_EQUAL(cur->mailbox, "fbloggs_jr");
    CU_ASSERT_STRING_EQUAL(cur->domain, "fastmail.fm");

    cur = cur->next;
    CU_ASSERT_PTR_NOT_NULL_FATAL(cur);
    CU_ASSERT_PTR_NULL(cur->name);
    CU_ASSERT_PTR_NULL(cur->mailbox);
    CU_ASSERT_PTR_NULL(cur->domain);

    cur = cur->next;
    CU_ASSERT_PTR_NULL(cur);

    parseaddr_free(a);
}

/* TODO: test the source routing feature */

/* Test the iterator interface */
static void test_iterator(void)
{
    struct address_itr ai;
    const struct address *a;

    address_itr_init(&ai, "", 0);
    a = address_itr_next(&ai);
    CU_ASSERT_PTR_NULL(a);
    address_itr_fini(&ai);

    address_itr_init(&ai, "Fred Bloggs <fbloggs@fastmail.fm>, Sarah Jane Smith <sjsmith@gmail.com>", 0);
    a = address_itr_next(&ai);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Fred Bloggs");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    a = address_itr_next(&ai);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Sarah Jane Smith");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "sjsmith");
    CU_ASSERT_STRING_EQUAL(a->domain, "gmail.com");
    CU_ASSERT_PTR_NULL(a->next);
    address_itr_fini(&ai);

    address_itr_init(&ai, "", 1);
    a = address_itr_next(&ai);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_PTR_NULL(a->name);
    CU_ASSERT_PTR_NULL(a->mailbox);
    CU_ASSERT_PTR_NULL(a->domain);
    CU_ASSERT_PTR_NULL(a->next);
    address_itr_fini(&ai);

    address_itr_init(&ai, "Fred Bloggs <fbloggs@fastmail.fm>, Sarah Jane Smith <sjsmith@gmail.com>", 1);
    a = address_itr_next(&ai);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Fred Bloggs");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "fbloggs");
    CU_ASSERT_STRING_EQUAL(a->domain, "fastmail.fm");
    a = address_itr_next(&ai);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    CU_ASSERT_STRING_EQUAL(a->name, "Sarah Jane Smith");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "sjsmith");
    CU_ASSERT_STRING_EQUAL(a->domain, "gmail.com");
    CU_ASSERT_PTR_NULL(a->next);
    address_itr_fini(&ai);
}

static void test_canonicalise(void)
{
    char *addr;

    addr = address_canonicalise("Fred Bloggs <F.Bloggs@FastMAIL.fm>");
    CU_ASSERT_STRING_EQUAL(addr, "F.Bloggs@fastmail.fm");
    free(addr);
}

/*
 * Test getting parts of a fully featured address
 */
static void test_getparts(void)
{
    struct address *a;
    char *s;

    a = NULL;
    parseaddr_list("Fred Bloggs <fbloggs+foo@FastMAIL.fm>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);

    s = address_get_all(a, 0);
    CU_ASSERT_STRING_EQUAL(s, "fbloggs+foo@FastMAIL.fm");
    free(s);

    s = address_get_all(a, 1);
    CU_ASSERT_STRING_EQUAL(s, "fbloggs+foo@fastmail.fm");
    free(s);

    s = address_get_localpart(a);
    CU_ASSERT_STRING_EQUAL(s, "fbloggs+foo");
    free(s);

    s = address_get_domain(a, 0);
    CU_ASSERT_STRING_EQUAL(s, "FastMAIL.fm");
    free(s);

    s = address_get_domain(a, 1);
    CU_ASSERT_STRING_EQUAL(s, "fastmail.fm");
    free(s);

    s = address_get_user(a);
    CU_ASSERT_STRING_EQUAL(s, "fbloggs");
    free(s);

    s = address_get_detail(a);
    CU_ASSERT_STRING_EQUAL(s, "foo");
    free(s);

    parseaddr_free(a);
}

/*
 * Test getting parts of an address with no detail part
 */
static void test_getparts_nodetail(void)
{
    struct address *a;
    char *s;

    a = NULL;
    parseaddr_list("Fred Bloggs <fbloggs@FastMAIL.fm>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);

    s = address_get_all(a, 0);
    CU_ASSERT_STRING_EQUAL(s, "fbloggs@FastMAIL.fm");
    free(s);

    s = address_get_all(a, 1);
    CU_ASSERT_STRING_EQUAL(s, "fbloggs@fastmail.fm");
    free(s);

    s = address_get_localpart(a);
    CU_ASSERT_STRING_EQUAL(s, "fbloggs");
    free(s);

    s = address_get_domain(a, 0);
    CU_ASSERT_STRING_EQUAL(s, "FastMAIL.fm");
    free(s);

    s = address_get_domain(a, 1);
    CU_ASSERT_STRING_EQUAL(s, "fastmail.fm");
    free(s);

    s = address_get_user(a);
    CU_ASSERT_STRING_EQUAL(s, "fbloggs");
    free(s);

    s = address_get_detail(a);
    CU_ASSERT_PTR_NULL(s);

    parseaddr_free(a);
}

/*
 * Test getting parts of an address with no domain part
 */
static void test_getparts_nodomain(void)
{
    struct address *a;
    char *s;

    a = NULL;
    parseaddr_list("Fred Bloggs <fbloggs>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);

    s = address_get_all(a, 0);
    CU_ASSERT_STRING_EQUAL(s, "fbloggs@unspecified-domain");
    free(s);

    s = address_get_all(a, 1);
    CU_ASSERT_STRING_EQUAL(s, "fbloggs@unspecified-domain");
    free(s);

    s = address_get_localpart(a);
    CU_ASSERT_STRING_EQUAL(s, "fbloggs");
    free(s);

    s = address_get_domain(a, 0);
    CU_ASSERT_STRING_EQUAL(s, "unspecified-domain");
    free(s);

    s = address_get_domain(a, 1);
    CU_ASSERT_STRING_EQUAL(s, "unspecified-domain");
    free(s);

    s = address_get_user(a);
    CU_ASSERT_STRING_EQUAL(s, "fbloggs");
    free(s);

    s = address_get_detail(a);
    CU_ASSERT_PTR_NULL(s);

    parseaddr_free(a);
}

static void test_quoted_crlf(void)
{
    struct address *head, *a;

    a = NULL;
    parseaddr_list("foo@example.com, bar@example.com,\r\n  \"Baz\\\r\n Baz\" <baz@example.com>,\r\n  bam@example.com, \"A\rB" "\x07" "C\" <abc@example.com>", &a);
    CU_ASSERT_PTR_NOT_NULL_FATAL(a);
    head = a;

    CU_ASSERT_PTR_NULL(a->name);
    CU_ASSERT_STRING_EQUAL(a->mailbox, "foo");
    CU_ASSERT_STRING_EQUAL(a->domain, "example.com");
    CU_ASSERT_PTR_NOT_NULL(a->next);

    a = a->next;
    CU_ASSERT_PTR_NULL(a->name);
    CU_ASSERT_STRING_EQUAL(a->mailbox, "bar");
    CU_ASSERT_STRING_EQUAL(a->domain, "example.com");
    CU_ASSERT_PTR_NOT_NULL(a->next);

    a = a->next;
    CU_ASSERT_STRING_EQUAL(a->name, "Baz Baz");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "baz");
    CU_ASSERT_STRING_EQUAL(a->domain, "example.com");
    CU_ASSERT_PTR_NOT_NULL(a->next);

    a = a->next;
    CU_ASSERT_PTR_NULL(a->name);
    CU_ASSERT_STRING_EQUAL(a->mailbox, "bam");
    CU_ASSERT_STRING_EQUAL(a->domain, "example.com");
    CU_ASSERT_PTR_NOT_NULL(a->next);

    a = a->next;
    CU_ASSERT_STRING_EQUAL(a->name, "A BC");
    CU_ASSERT_STRING_EQUAL(a->mailbox, "abc");
    CU_ASSERT_STRING_EQUAL(a->domain, "example.com");
    CU_ASSERT_PTR_NULL(a->next);

    parseaddr_free(head);
}

/* vim: set ft=c: */
