#if HAVE_CONFIG_H
#include <config.h>
#endif
#include "cunit/cyrunit.h"
#include "imap/global.h"

static void test_simple(void)
{
#define C_MSGID1    "<001.02.00003@example.com>"
    static const char C_MSGIDS[] = C_MSGID1;
    char *buf;
    char *s;
    char *m;

    buf = strdup(C_MSGIDS);
    CU_ASSERT_PTR_NOT_NULL_FATAL(buf);
    s = buf;

    /* first call returns a newly allocated string which is the
     * only msgid in the input */
    m = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m);
    CU_ASSERT_PTR_NOT_EQUAL(m, s);
    CU_ASSERT_PTR_NOT_EQUAL(m, buf);
    CU_ASSERT_STRING_EQUAL(m, C_MSGID1);

    /* 's' should point somewhere into the buffer */
    CU_ASSERT(s >= buf && s <= buf+sizeof(C_MSGIDS));

    /* the buffer should be unmolested */
    CU_ASSERT(memcmp(buf, C_MSGIDS, sizeof(C_MSGIDS)) == 0);

    free(m);

    /* second call returns NULL, there are no more msgids */
    m = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NULL(m);

    free(buf);
#undef C_MSGID1
}


/* multiple msgids separated by a single space */
static void test_multiple(void)
{
#define C_MSGID1    "<004.05.00006@example.com>"
#define C_MSGID2    "<007.08.09@gmail.com>"
#define C_MSGID3    "<0000A-0B-0000C@apple.com>"
#define C_MSGID4    "<010309849374836@fastmail.fm>"
    static const char C_MSGIDS[] = C_MSGID1 " " C_MSGID2 " " C_MSGID3 " " C_MSGID4;
    char *s;
    char *m1;
    char *m2;
    char *m3;
    char *m4;
    char *m5;

    /* We checked in the "simple" test that buffers are unmolested,
     * so this time just pass message_iter_msgid() a const variable */
    s = (char *)C_MSGIDS;

    /* each call should returns a separate newly allocated string
     * which is the next msgid in the input */

    m1 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m1);
    CU_ASSERT_STRING_EQUAL(m1, C_MSGID1);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m2 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m2);
    CU_ASSERT_STRING_EQUAL(m2, C_MSGID2);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m3 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m3);
    CU_ASSERT_STRING_EQUAL(m3, C_MSGID3);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m4 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m4);
    CU_ASSERT_STRING_EQUAL(m4, C_MSGID4);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    /* last call returns NULL, there are no more msgids */
    m5 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NULL(m5);

    /* check the returned msgids are all distinct */
    CU_ASSERT_PTR_NOT_EQUAL(m1, m2);
    CU_ASSERT_PTR_NOT_EQUAL(m1, m3);
    CU_ASSERT_PTR_NOT_EQUAL(m1, m4);
    CU_ASSERT_PTR_NOT_EQUAL(m2, m3);
    CU_ASSERT_PTR_NOT_EQUAL(m2, m4);
    CU_ASSERT_PTR_NOT_EQUAL(m3, m4);

    free(m1);
    free(m2);
    free(m3);
    free(m4);
#undef C_MSGID1
#undef C_MSGID2
#undef C_MSGID3
#undef C_MSGID4
}


/* multiple msgids separated by more interesting whitespace */
static void test_whitespace(void)
{
#define C_MSGID1    "<104.05.00006@example.com>"
#define C_MSGID2    "<107.08.09@gmail.com>"
#define C_MSGID3    "<1000A-0B-0000C@apple.com>"
#define C_MSGID4    "<110309849374836@fastmail.fm>"
    static const char C_MSGIDS[] =
        C_MSGID1 "     " C_MSGID2 "\t" C_MSGID3 "\t\r\n\t" C_MSGID4 "\r\n";
    char *s;
    char *m1;
    char *m2;
    char *m3;
    char *m4;
    char *m5;

    /* We checked in the "simple" test that buffers are unmolested,
     * so this time just pass message_iter_msgid() a const variable */
    s = (char *)C_MSGIDS;

    /* each call should returns a separate newly allocated string
     * which is the next msgid in the input */

    m1 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m1);
    CU_ASSERT_STRING_EQUAL(m1, C_MSGID1);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m2 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m2);
    CU_ASSERT_STRING_EQUAL(m2, C_MSGID2);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m3 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m3);
    CU_ASSERT_STRING_EQUAL(m3, C_MSGID3);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m4 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m4);
    CU_ASSERT_STRING_EQUAL(m4, C_MSGID4);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    /* last call returns NULL, there are no more msgids */
    m5 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NULL(m5);

    /* check the returned msgids are all distinct */
    CU_ASSERT_PTR_NOT_EQUAL(m1, m2);
    CU_ASSERT_PTR_NOT_EQUAL(m1, m3);
    CU_ASSERT_PTR_NOT_EQUAL(m1, m4);
    CU_ASSERT_PTR_NOT_EQUAL(m2, m3);
    CU_ASSERT_PTR_NOT_EQUAL(m2, m4);
    CU_ASSERT_PTR_NOT_EQUAL(m3, m4);

    free(m1);
    free(m2);
    free(m3);
    free(m4);
#undef C_MSGID1
#undef C_MSGID2
#undef C_MSGID3
#undef C_MSGID4
}



/* duplicate msgids are returned, not elided */
static void test_dups(void)
{
#define C_MSGID1    "<204.05.00006@example.com>"
#define C_MSGID2    "<207.08.09@gmail.com>"
    static const char C_MSGIDS[] =
        C_MSGID1 " " C_MSGID2 " " C_MSGID2 " " C_MSGID1;
    char *s;
    char *m1;
    char *m2;
    char *m3;
    char *m4;
    char *m5;

    /* We checked in the "simple" test that buffers are unmolested,
     * so this time just pass message_iter_msgid() a const variable */
    s = (char *)C_MSGIDS;

    /* each call should returns a separate newly allocated string
     * which is the next msgid in the input */

    m1 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m1);
    CU_ASSERT_STRING_EQUAL(m1, C_MSGID1);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m2 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m2);
    CU_ASSERT_STRING_EQUAL(m2, C_MSGID2);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m3 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m3);
    CU_ASSERT_STRING_EQUAL(m3, C_MSGID2);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m4 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m4);
    CU_ASSERT_STRING_EQUAL(m4, C_MSGID1);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    /* last call returns NULL, there are no more msgids */
    m5 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NULL(m5);

    /* check the returned msgids are all distinct */
    CU_ASSERT_PTR_NOT_EQUAL(m1, m2);
    CU_ASSERT_PTR_NOT_EQUAL(m1, m3);
    CU_ASSERT_PTR_NOT_EQUAL(m1, m4);
    CU_ASSERT_PTR_NOT_EQUAL(m2, m3);
    CU_ASSERT_PTR_NOT_EQUAL(m2, m4);
    CU_ASSERT_PTR_NOT_EQUAL(m3, m4);

    free(m1);
    free(m2);
    free(m3);
    free(m4);
#undef C_MSGID1
#undef C_MSGID2
}


/* a CRLF followed by a non WS marks the end of the header */
static void test_eol(void)
{
#define C_MSGID1    "<304.05.00006@example.com>"
#define C_MSGID2    "<307.08.09@gmail.com>"
#define C_MSGID3    "<3000A-0B-0000C@apple.com>"
#define C_MSGID4    "<310309849374836@fastmail.fm>"
    static const char C_MSGIDS[] =
        C_MSGID1 "\r\n"
        " " C_MSGID2 "\r\n"
        "In-Reply-To: " C_MSGID3 "\r\n"
        "Message-Id: " C_MSGID4 "\r\n"
        "\r\n";
    char *s;
    char *m1;
    char *m2;
    char *m3;

    /* We checked in the "simple" test that buffers are unmolested,
     * so this time just pass message_iter_msgid() a const variable */
    s = (char *)C_MSGIDS;

    /* each call should returns a separate newly allocated string
     * which is the next msgid in the input */

    m1 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m1);
    CU_ASSERT_STRING_EQUAL(m1, C_MSGID1);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m2 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m2);
    CU_ASSERT_STRING_EQUAL(m2, C_MSGID2);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    /* we stop seeing msgids after the end of the first header */
    m3 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NULL(m3);

    /* check the returned msgids are all distinct */
    CU_ASSERT_PTR_NOT_EQUAL(m1, m2);

    free(m1);
    free(m2);
#undef C_MSGID1
#undef C_MSGID2
#undef C_MSGID3
#undef C_MSGID4
}


/* accept a malformed msgid with no @ sign */
static void test_noatsign(void)
{
#define C_MSGID1    "<404.05.00006@example.com>"
#define C_MSGID2    "<407.08.09-no-at-here-gmail.com>"
#define C_MSGID3    "<4000A-0B-0000C@apple.com>"
    static const char C_MSGIDS[] =
        C_MSGID1 " " C_MSGID2 " " C_MSGID3;
    char *s;
    char *m1;
    char *m2;
    char *m3;
    char *m4;

    /* We checked in the "simple" test that buffers are unmolested,
     * so this time just pass message_iter_msgid() a const variable */
    s = (char *)C_MSGIDS;

    /* each call should returns a separate newly allocated string
     * which is the next msgid in the input */

    m1 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m1);
    CU_ASSERT_STRING_EQUAL(m1, C_MSGID1);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m2 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m2);
    CU_ASSERT_STRING_EQUAL(m2, C_MSGID2);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m3 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m3);
    CU_ASSERT_STRING_EQUAL(m3, C_MSGID3);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    /* we stop seeing msgids after 3rd msgid */
    m4 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NULL(m4);

    /* check the returned msgids are all distinct */
    CU_ASSERT_PTR_NOT_EQUAL(m1, m2);
    CU_ASSERT_PTR_NOT_EQUAL(m2, m3);

    free(m1);
    free(m2);
    free(m3);
#undef C_MSGID1
#undef C_MSGID2
#undef C_MSGID3
}


/* handle a msgid with a quoted localpart */
static void test_quoted_localpart(void)
{
#define C_MSGID1    "<504.05.00006@example.com>"
#define C_MSGID2    "<\"507>0 8@09\"@gmail.com>"
#define C_MSGID2ret "<507>0 8@09@gmail.com>"
#define C_MSGID3    "<5000A-0B-0000C@apple.com>"
    static const char C_MSGIDS[] =
        C_MSGID1 " " C_MSGID2 " " C_MSGID3;
    char *s;
    char *m1;
    char *m2;
    char *m3;
    char *m4;

    /* We checked in the "simple" test that buffers are unmolested,
     * so this time just pass message_iter_msgid() a const variable */
    s = (char *)C_MSGIDS;

    /* each call should returns a separate newly allocated string
     * which is the next msgid in the input */

    m1 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m1);
    CU_ASSERT_STRING_EQUAL(m1, C_MSGID1);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m2 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m2);
    CU_ASSERT_STRING_EQUAL(m2, C_MSGID2ret);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m3 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m3);
    CU_ASSERT_STRING_EQUAL(m3, C_MSGID3);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    /* we stop seeing msgids after 2nd msgid */
    m4 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NULL(m4);

    /* check the returned msgids are all distinct */
    CU_ASSERT_PTR_NOT_EQUAL(m1, m2);
    CU_ASSERT_PTR_NOT_EQUAL(m1, m3);
    CU_ASSERT_PTR_NOT_EQUAL(m2, m3);

    free(m1);
    free(m2);
    free(m3);
#undef C_MSGID1
#undef C_MSGID2
#undef C_MSGID2ret
#undef C_MSGID3
}


/* handle a msgid with a quoted localpart containing an escaped quote */
static void test_escaped_quoted_localpart(void)
{
#define C_MSGID1    "<504.05.00006@example.com>"
#define C_MSGID2    "<\"507>0\\\"8@09\"@gmail.com>"
#define C_MSGID2ret "<507>0\"8@09@gmail.com>"
#define C_MSGID3    "<5000A-0B-0000C@apple.com>"
    static const char C_MSGIDS[] =
        C_MSGID1 " " C_MSGID2 " " C_MSGID3;
    char *s;
    char *m1;
    char *m2;
    char *m3;
    char *m4;

    /* We checked in the "simple" test that buffers are unmolested,
     * so this time just pass message_iter_msgid() a const variable */
    s = (char *)C_MSGIDS;

    /* each call should returns a separate newly allocated string
     * which is the next msgid in the input */

    m1 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m1);
    CU_ASSERT_STRING_EQUAL(m1, C_MSGID1);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m2 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m2);
    CU_ASSERT_STRING_EQUAL(m2, C_MSGID2ret);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    m3 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NOT_NULL(m3);
    CU_ASSERT_STRING_EQUAL(m3, C_MSGID3);
    CU_ASSERT(s >= C_MSGIDS && s <= C_MSGIDS+sizeof(C_MSGIDS));

    /* we stop seeing msgids after 2nd msgid */
    m4 = message_iter_msgid(s, 0, &s);
    CU_ASSERT_PTR_NULL(m4);

    /* check the returned msgids are all distinct */
    CU_ASSERT_PTR_NOT_EQUAL(m1, m2);
    CU_ASSERT_PTR_NOT_EQUAL(m1, m3);
    CU_ASSERT_PTR_NOT_EQUAL(m2, m3);

    free(m1);
    free(m2);
    free(m3);
#undef C_MSGID1
#undef C_MSGID2
#undef C_MSGID2ret
#undef C_MSGID3
}


/* handle a msgid with malformed <> */
static void test_malformed_angles(void)
{
#define C_MSGID1    "<604.05.00006@example.com>"
#define C_MSGID2    "<607.08.09@gmail.com" // <- malformed
#define C_MSGID3    "<6000A-0B-0000C@apple.com>"

    const char *test_cases[] = {
        C_MSGID1 " " C_MSGID2 " " C_MSGID3, // malformed at 2nd place,
        C_MSGID2 " " C_MSGID1 " " C_MSGID3, // malformed at 1st place,
        C_MSGID1 " " C_MSGID3 " " C_MSGID2, // malformed at 3rd place,
    };

    for (size_t i = 0; i < sizeof(test_cases)/sizeof(test_cases[0]); i++) {
        const char *input = test_cases[i];

        /* We checked in the "simple" test that buffers are unmolested,
         * so this time just pass message_iter_msgid() a const variable */
        char *s = (char*) input;
        char *m1;
        char *m2;
        char *m3;

        /* each call should returns a separate newly allocated string
         * which is the next msgid in the input */

        m1 = message_iter_msgid(s, 0, &s);
        CU_ASSERT_PTR_NOT_NULL(m1);
        CU_ASSERT_STRING_EQUAL(m1, C_MSGID1);
        CU_ASSERT(s >= input && s <= input+strlen(input));

        m2 = message_iter_msgid(s, 0, &s);
        CU_ASSERT_PTR_NOT_NULL(m2);
        CU_ASSERT_STRING_EQUAL(m2, C_MSGID3);
        CU_ASSERT(s >= input && s <= input+strlen(input));

        /* we stop seeing msgids after 2nd msgid */
        m3 = message_iter_msgid(s, 0, &s);
        CU_ASSERT_PTR_NULL(m3);

        /* check the returned msgids are all distinct */
        CU_ASSERT_PTR_NOT_EQUAL(m1, m2);

        free(m1);
        free(m2);
    }

#undef C_MSGID1
#undef C_MSGID2
#undef C_MSGID3
}

static char *parse_single_msgid(const char *header_value)
{
    char *str = xstrdup(header_value);
    char *s = str;
    char *msgid = message_iter_msgid(s, 0, &s);
    CU_ASSERT(s >= str && s <= str + strlen(str));
    CU_ASSERT_PTR_NULL(message_iter_msgid(s, 0, &s));
    free(str);
    return msgid;
}

static void test_regression(void)
{
    struct test_case {
        const char *input;
        const char *expect; // if different from input
        enum { ACCEPT, REJECT } outcome;
    };

    struct test_case test_cases[] = {
        // Valid message-ids according to RFC 5322:
        {
            .input = "<foo.bar.baz@example.com>",
            .outcome = ACCEPT,
        },
        {
            .input = "<foo@bar>",
            .outcome = ACCEPT,
        },
        {
            .input = "<\"x>\"@example.com>",
            .expect = "<x>@example.com>", // produces invalid message-id!
            .outcome = ACCEPT,
        },
        {
            .input = "<\"x@\"@example.com>",
            .expect = "<x@@example.com>", // produces invalid message-id!
            .outcome = ACCEPT,
        },
        {
            .input = "<\"x\r\n\t\"@example.com>",
            .expect = "<x\r\n\t@example.com>", // produces invalid message-id!
            .outcome = ACCEPT,
        },

        // Valid message-ids that are rejected:
        {
            .input = "foo@[bar]",
            .outcome = REJECT,
        },

        // Invalid message-ids that are leniently accepted:
        {
            .input = "<hello@🌏>",
            .outcome = ACCEPT,
        },
        {
            .input = "<\"🐛\"@example.com>",
            .expect = "<🐛@example.com>",
            .outcome = ACCEPT,
        },
        {
            .input = "<foo\t@\tbar>",
            .outcome = ACCEPT,
        },
        {
            .input = "<foo>",
            .outcome = ACCEPT,
        },
        {
            .input = "<foo.bar>",
            .outcome = ACCEPT,
        },
        {
            .input = "<hello-world>",
            .outcome = ACCEPT,
        },

        // Invalid, unbracketed message-ids that are leniently accepted:
        {
            .input = "foobar",
            .expect = "<foobar>",
            .outcome = ACCEPT,
        },
        {
            .input = "foo@bar",
            .expect = "<foo@bar>",
            .outcome = ACCEPT,
        },
        {
            .input = "a.b.c",
            .expect = "<a.b.c>",
            .outcome = ACCEPT,
        },
        {
            .input = "a.b@c.d",
            .expect = "<a.b@c.d>",
            .outcome = ACCEPT,
        },
        {
            .input = "a!#$%&'*+-/=?^_`{|}~@c.d",
            .expect = "<a!#$%&'*+-/=?^_`{|}~@c.d>",
            .outcome = ACCEPT,
        },


        // Invalid message-ids that are rejected:
        {
            .input = "<foo@bar@baz>",
            .outcome = REJECT,
        },
        {
            .input = "foo@bar@baz",
            .outcome = REJECT,
        },
        {
            .input = "a..b",
            .outcome = REJECT,
        },
        {
            .input = "a@@b",
            .outcome = REJECT,
        },
        {
            .input = "#",
            .outcome = REJECT,
        },
        {
            .input = "<",
            .outcome = REJECT,
        },
        {
            .input = "<a",
            .outcome = REJECT,
        },
        {
            .input = "<a@b",
            .outcome = REJECT,
        },
        {
            .input = "<P[20",
            .outcome = REJECT,
        },
    };

    for (size_t i = 0; i < sizeof(test_cases) / sizeof(test_cases[0]); i++) {
        struct test_case *tc = &test_cases[i];
        char *msgid = parse_single_msgid(tc->input);
        switch (tc->outcome) {
            case ACCEPT:
                // Assert that message-id is parsed.
                CU_ASSERT_PTR_NOT_NULL(msgid);
                CU_ASSERT_STRING_EQUAL(
                    msgid, tc->expect ? tc->expect : tc->input);
                break;
            case REJECT:
                // Assert that message-id is rejected.
                CU_ASSERT_PTR_NULL(msgid);
                break;
        }
        free(msgid);
    }
}

/* vim: set ft=c: */
