#include <config.h>

#include "cunit/cyrunit.h"
#include "xmalloc.h"
#include "vparse.h"

static void test_double_end(void)
{
    char card[] = "BEGIN:VCARD\nEND:VCARD\nEND:VCARD";
    struct vparse_state vparser;
    memset(&vparser, 0, sizeof(struct vparse_state));
    vparser.base = card;
    int vr = vparse_parse(&vparser, 0);
    CU_ASSERT_EQUAL(vr, PE_MISMATCHED_CARD);
    vparse_free(&vparser);
}

static void test_wrap_onechar(void)
{
    char card[] = "BEGIN:VCARD\n"
                  "PRODID:+//IDN bitfire.at//DAVdroid/1.2.2-gplay vcard4android ez-vcard/0.9.1\n"
                  " 0\n"
                  "UID:abc\n"
                  "END:VCARD";
    char wantbuf[] = "BEGIN:VCARD\r\n"
                  "PRODID:+//IDN bitfire.at//DAVdroid/1.2.2-gplay vcard4android ez-vcard/0.9.10\r\n"
                  "UID:abc\r\n"
                  "END:VCARD\r\n";
    struct vparse_state vparser;
    memset(&vparser, 0, sizeof(struct vparse_state));
    vparser.base = card;
    int vr = vparse_parse(&vparser, 0);
    CU_ASSERT_EQUAL(vr, 0);
    struct buf *buf = buf_new();
    vparse_tobuf(vparser.card, buf);
    CU_ASSERT_STRING_EQUAL(wantbuf, buf_cstring(buf));
    vparse_free(&vparser);

    // XXX test value
    buf_destroy(buf);
}

static void test_repair_version(void)
{
    char card[] = "BEGIN:VCARD\n"
                  "VERSION: 3.0 \r\n"
                  "UID:abc\n"
                  "END:VCARD\r\n";

    char wantbuf[] = "BEGIN:VCARD\r\n"
                  "VERSION:3.0\r\n"
                  "UID:abc\r\n"
                  "END:VCARD\r\n";
    struct vparse_state vparser;
    memset(&vparser, 0, sizeof(struct vparse_state));
    vparser.base = card;
    int vr = vparse_parse(&vparser, 0);
    CU_ASSERT_EQUAL(vr, 0);
    struct buf *buf = buf_new();
    vparse_tobuf(vparser.card, buf);
    CU_ASSERT_STRING_EQUAL(wantbuf, buf_cstring(buf));
    vparse_free(&vparser);
    buf_destroy(buf);
}

static void test_repair_control_chars(void)
{
#define TESTCASE(in, wanterr, wantout) \
    { \
        struct vparse_state vparser; \
        memset(&vparser, 0, sizeof(struct vparse_state)); \
        vparse_set_multival(&vparser, "adr", ';'); \
        vparser.base = (in); \
        int vr = vparse_parse(&vparser, 0); \
        CU_ASSERT_EQUAL(vr, (wanterr)); \
        if (wantout != NULL) { \
            struct buf *buf = buf_new(); \
            vparse_tobuf(vparser.card, buf); \
            CU_ASSERT_STRING_EQUAL(wantout, buf_cstring(buf)); \
            buf_destroy(buf); \
        } \
        vparse_free(&vparser); \
    }

    struct testcase {
        const char *in;
        int wanterr;
        const char *out;
    } tests[] = {{
        /* Control in property name */
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "N\bOTE:Weird control chars\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:Weird control chars\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n",
    }, {
        /* Control in property value */
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:Weird control\b chars\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:Weird control chars\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n"
    }, {
        /* Newline forces parser to switch to property name state */
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:Weird control\n\bchars\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n",
        PE_NAME_EOL,
        NULL
    }, {
        /* Multivalue field, separated by semicolon */
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "ADR:;;123 Main Street;Any Town;CA;91921-1234;U.S.\x01\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "ADR:;;123 Main Street;Any Town;CA;91921-1234;U.S.\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n"
    }, {
        /* Control in parameter name */
        "BEGIN:VCARD\r\n"
        "VERSION:4.0\r\n"
        "EMAIL;\x01TYPE\x03=work:foo@local\r\n"
        "END:VCARD\r\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:4.0\r\n"
        "EMAIL;TYPE=work:foo@local\r\n"
        "END:VCARD\r\n"
    }, {
        /* Control in parameter value */
        "BEGIN:VCARD\r\n"
        "VERSION:4.0\r\n"
        "EMAIL;TYPE=w\x1bork:foo@local\r\n"
        "END:VCARD\r\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:4.0\r\n"
        "EMAIL;TYPE=work:foo@local\r\n"
        "END:VCARD\r\n"
    }, {
        /* Control char in quoted parameter value */
        "BEGIN:VCARD\r\n"
        "VERSION:4.0\r\n"
        "FOO;BAR=\"x\x1b,y\":0\r\n"
        "END:VCARD\r\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:4.0\r\n"
        "FOO;BAR=\"x,y\":0\r\n"
        "END:VCARD\r\n",
    }, {
        /* End with CRLF */
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:All lines end on CRLF\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:All lines end on CRLF\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n"
    }, {
        /* End with LF */
        "BEGIN:VCARD\n"
        "VERSION:3.0\n"
        "NOTE:All lines end on LF\n"
        "REV:2008-04-24T19:52:43Z\n"
        "END:VCARD\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:All lines end on LF\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n"
    }, {
        /* End with CR */
        "BEGIN:VCARD\r"
        "VERSION:3.0\r"
        "NOTE:All lines end on CR\r"
        "REV:2008-04-24T19:52:43Z\r"
        "END:VCARD\r",
        PE_FINISHED_EARLY,
        NULL
    }, {
        /* End with either CR or CRLF */
        "BEGIN:VCARD\n"
        "VERSION:3.0\n"
        "NOTE:Some lines end on LF and some on CRLF\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:Some lines end on LF and some on CRLF\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n"
    }, {
        /* Extra CR before CRLF */
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:An extra CR before CRLF\r\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:An extra CR before CRLF\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n"
    }, {
        /* Two LF make one empty line */
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:Two LF make one empty line\n\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:Two LF make one empty line\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n"
    }, {
        /* One lonely CR in the middle of text */
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:One lonely \r in the middle of text\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:One lonely  in the middle of text\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n"
    }, {
        /* One lonely CR in a multi-value property */
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "ADR:;;123\r\\nMain Street;Any Town;CA;91921-1234;U.S.A\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "ADR:;;123\\nMain Street;Any Town;CA;91921-1234;U.S.A\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n"
    }, {
        /* CR in key */
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE\r:CR in key\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "NOTE:CR in key\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n"
    }, {
        /* CR in param name */
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "EMAIL;TYPE\r=work:foo@local\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "EMAIL;TYPE=work:foo@local\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n"
    }, {
        /* CR in param name */
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "EMAIL;TYPE=work\r:foo@local\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n",
        PE_OK,
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "EMAIL;TYPE=work:foo@local\r\n"
        "REV:2008-04-24T19:52:43Z\r\n"
        "END:VCARD\r\n"
    }, {
        /* End of tests */
        NULL, 0, NULL
    }};

    struct testcase *t;
    for (t = tests; t->in; t++) {
        TESTCASE(t->in, t->wanterr, t->out);
    }

#undef TESTCASE
}

#ifdef USE_HTTPD
#include "imap/vcard_support.h"

static void test_multiparam_type(void)
{
#define TESTCASE(card, wantbuf) \
    { \
        struct vparse_card *vcard = vcard_parse_string(card); \
        CU_ASSERT_PTR_NOT_NULL(vcard); \
        struct buf *buf = vcard_as_buf(vcard); \
        CU_ASSERT_STRING_EQUAL(wantbuf, buf_cstring(buf)); \
        vparse_free_card(vcard); \
        buf_destroy(buf); \
    }

    TESTCASE(
        // card
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "UID:0dc2973f-5f46-49b9-8ba9-12a4cee6eeac\r\n"
        "N:Test;John;;;\r\n"
        "FN:John Test\r\n"
        "EMAIL;TYPE=HOME,PREF:home@mail.com\r\n"
        "EMAIL;TYPE=WORK:work@mail.com\r\n"
        "TEL;TYPE=HOME,VOICE:040-xx\r\n"
        "TEL;TYPE=CELL:06-xx\r\n"
        "PRODID:-//MailClient.Contact/7.0.30068.0\r\n"
        "END:VCARD\r\n",
        // wantbuf

        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "UID:0dc2973f-5f46-49b9-8ba9-12a4cee6eeac\r\n"
        "N:Test;John;;;\r\n"
        "FN:John Test\r\n"
        "EMAIL;TYPE=HOME;TYPE=PREF:home@mail.com\r\n"
        "EMAIL;TYPE=WORK:work@mail.com\r\n"
        "TEL;TYPE=HOME;TYPE=VOICE:040-xx\r\n"
        "TEL;TYPE=CELL:06-xx\r\n"
        "PRODID:-//MailClient.Contact/7.0.30068.0\r\n"
        "END:VCARD\r\n"
    );

    TESTCASE(
        // card
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "UID:0dc2973f-5f46-49b9-8ba9-12a4cee6eeac\r\n"
        "N:Test;John;;;\r\n"
        "FN:John Test\r\n"
        "X-SOCIAL-PROFILE;TYPE=Github,PREF;X-USER=\"foo,bar\":\r\n"
        "PRODID:-//MailClient.Contact/7.0.30068.0\r\n"
        "END:VCARD\r\n",
        // wantbuf
        "BEGIN:VCARD\r\n"
        "VERSION:3.0\r\n"
        "UID:0dc2973f-5f46-49b9-8ba9-12a4cee6eeac\r\n"
        "N:Test;John;;;\r\n"
        "FN:John Test\r\n"
        "X-SOCIAL-PROFILE;TYPE=Github;TYPE=PREF;X-USER=\"foo,bar\":\r\n"
        "PRODID:-//MailClient.Contact/7.0.30068.0\r\n"
        "END:VCARD\r\n"
    );

#undef TESTCASE
}
#else
static void test_multiparam_type(void) { }
#endif /* USE_HTTPD */
