#include "config.h"
#include "cunit/unit.h"
#include "rfc822tok.h"

#define WORD0   "lorem"
#define WORD1   "ipsum"
#define WORD2   "dolor"
#define WORD2A  "do"
#define WORD2B  "lor"
#define WORD3   "sit"
#define WORD4   "amet"

#define BADPOINTER  ((void *)0xdeadbeef)

static void test_simple(void)
{
    static const char PHRASE[] = WORD0" "WORD1" "WORD2" "WORD3" "WORD4;
    rfc822tok_t tok = RFC822TOK_INITIALIZER;
    char *p;
    int t;

    rfc822tok_init(&tok, PHRASE, sizeof(PHRASE)-1, 0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD1);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD2);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD3);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD4);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, EOF);
    CU_ASSERT_PTR_NULL(p);

    rfc822tok_fini(&tok);
}

/* test that ceasing to call rfc822tok_next() before the end
 * of the tokens does not leak memory as long as rfc822tok_fini()
 * is called; we rely on Valgrind to find memleaks */
static void test_stopearly(void)
{
    static const char PHRASE[] = WORD0" "WORD1" "WORD2" "WORD3" "WORD4;
    rfc822tok_t tok = RFC822TOK_INITIALIZER;
    char *p;
    int t;

    rfc822tok_init(&tok, PHRASE, sizeof(PHRASE)-1, 0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD0);

    rfc822tok_fini(&tok);
}

/* tokenizing an empty string just returns EOF */
static void test_blank(void)
{
    static const char PHRASE[] = "";
    rfc822tok_t tok = RFC822TOK_INITIALIZER;
    char *p;
    int t;

    rfc822tok_init(&tok, PHRASE, sizeof(PHRASE)-1, 0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, EOF);
    CU_ASSERT_PTR_NULL(p);

    rfc822tok_fini(&tok);
}

/* tokenizing a NULL string just returns EOF */
static void test_null(void)
{
    rfc822tok_t tok = RFC822TOK_INITIALIZER;
    char *p;
    int t;

    rfc822tok_init(&tok, NULL, 0, 0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, EOF);
    CU_ASSERT_PTR_NULL(p);

    rfc822tok_fini(&tok);
}

/* non-trivial whitespace between atoms */
static void test_whitespace(void)
{
    static const char PHRASE[] = WORD0"    "WORD1"\t"WORD2" \t \v \t "WORD3"\r\n\t"WORD4;
    rfc822tok_t tok = RFC822TOK_INITIALIZER;
    char *p;
    int t;

    rfc822tok_init(&tok, PHRASE, sizeof(PHRASE)-1, 0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD1);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD2);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD3);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD4);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, EOF);
    CU_ASSERT_PTR_NULL(p);

    rfc822tok_fini(&tok);
}

/* whitespace and non-trivial comments between atoms */
static void test_comments(void)
{
    static const char PHRASE[] = WORD0"()"
                                 WORD1"( hello world )"
                                 WORD2" ( he\\l\\lo \\) world\r\n and friends) "
                                 WORD3" (hell(o( (w)or)ld))"
                                 WORD4;
    rfc822tok_t tok = RFC822TOK_INITIALIZER;
    char *p;
    int t;

    rfc822tok_init(&tok, PHRASE, sizeof(PHRASE)-1, 0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD1);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD2);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD3);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD4);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, EOF);
    CU_ASSERT_PTR_NULL(p);

    rfc822tok_fini(&tok);
}

static void test_quoted_strings(void)
{
    static const char PHRASE[] = "\""WORD0"\""
                                 "\""WORD1"\\x\" "
                                 "\""WORD2A"\\\""WORD2B"\" "
                                 "\""WORD3"  \r\n\t   \t"WORD4"\"";
    rfc822tok_t tok = RFC822TOK_INITIALIZER;
    char *p;
    int t;

    rfc822tok_init(&tok, PHRASE, sizeof(PHRASE)-1, 0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_QSTRING);
    CU_ASSERT_STRING_EQUAL(p, WORD0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_QSTRING);
    CU_ASSERT_STRING_EQUAL(p, WORD1"x");

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_QSTRING);
    CU_ASSERT_STRING_EQUAL(p, WORD2A"\""WORD2B);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_QSTRING);
    CU_ASSERT_STRING_EQUAL(p, WORD3"  \t   \t"WORD4);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, EOF);
    CU_ASSERT_PTR_NULL(p);

    rfc822tok_fini(&tok);
}

static void test_special_dot(void)
{
    static const char PHRASE[] = WORD0" "
                                 WORD1"."WORD2"."WORD3" "
                                 WORD4;
    rfc822tok_t tok = RFC822TOK_INITIALIZER;
    char *p;
    int t;

    /* by default '.' are not special */
    rfc822tok_init(&tok, PHRASE, sizeof(PHRASE)-1, 0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD1"."WORD2"."WORD3);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD4);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, EOF);
    CU_ASSERT_PTR_NULL(p);

    rfc822tok_fini(&tok);

    /* with the flag, '.' are special */
    rfc822tok_init(&tok, PHRASE, sizeof(PHRASE)-1, RFC822_SPECIAL_DOT);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD1);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, '.');
    CU_ASSERT_PTR_NULL(p);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD2);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, '.');
    CU_ASSERT_PTR_NULL(p);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD3);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD4);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, EOF);
    CU_ASSERT_PTR_NULL(p);

    rfc822tok_fini(&tok);
}

static void test_special_equal(void)
{
    static const char PHRASE[] = WORD0" "
                                 WORD1"="WORD2"="WORD3" "
                                 WORD4;
    rfc822tok_t tok = RFC822TOK_INITIALIZER;
    char *p;
    int t;

    /* by default '=' are not special */
    rfc822tok_init(&tok, PHRASE, sizeof(PHRASE)-1, 0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD1"="WORD2"="WORD3);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD4);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, EOF);
    CU_ASSERT_PTR_NULL(p);

    rfc822tok_fini(&tok);

    /* with the flag, '=' are special */
    rfc822tok_init(&tok, PHRASE, sizeof(PHRASE)-1, RFC822_SPECIAL_EQUAL);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD0);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD1);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, '=');
    CU_ASSERT_PTR_NULL(p);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD2);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, '=');
    CU_ASSERT_PTR_NULL(p);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD3);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, RFC822_ATOM);
    CU_ASSERT_STRING_EQUAL(p, WORD4);

    p = BADPOINTER;
    t = rfc822tok_next(&tok, &p);
    CU_ASSERT_EQUAL(t, EOF);
    CU_ASSERT_PTR_NULL(p);

    rfc822tok_fini(&tok);
}

struct token
{
    int token;
    const char *text;
};

#define TESTCASE(in, inlen, exp) \
    do { \
        const struct token *_exp = (exp); \
        rfc822tok_t _tok = RFC822TOK_INITIALIZER; \
        rfc822tok_init(&_tok, (in), (inlen), RFC822_SPECIAL_EQUAL); \
        for (;; _exp++) { \
            char *_p = BADPOINTER; \
            int _t = rfc822tok_next(&_tok, &_p); \
            CU_ASSERT_EQUAL(_t, _exp->token); \
            CU_ASSERT_PTR_NOT_EQUAL(_p, BADPOINTER); \
            if (_t > 255) { \
                CU_ASSERT_STRING_EQUAL(_p, _exp->text); \
            } \
            else { \
                CU_ASSERT_PTR_NULL(_p); \
            } \
            if (_exp->token == EOF) break; \
        } \
        rfc822tok_fini(&_tok); \
    } while(0)

/* a real-world example */
static void test_content_type(void)
{
    /* Examples from RFC2045 */
    {
        static const char PHRASE[] = "Content-Type: text/plain; charset=us-ascii\r\n";
        static const struct token EXP[] = {
            { RFC822_ATOM, "Content-Type" },
            { ':', NULL },
            { RFC822_ATOM, "text/plain" },
            { ';', NULL },
            { RFC822_ATOM, "charset" },
            { '=', NULL },
            { RFC822_ATOM, "us-ascii" },
            { EOF, NULL }};
        TESTCASE(PHRASE, sizeof(PHRASE)-1, EXP);
    }
    {
        static const char PHRASE[] = "Content-Type: text/plain; charset=us-ascii (Plain text)\r\n";
        static const struct token EXP[] = {
            { RFC822_ATOM, "Content-Type" },
            { ':', NULL },
            { RFC822_ATOM, "text/plain" },
            { ';', NULL },
            { RFC822_ATOM, "charset" },
            { '=', NULL },
            { RFC822_ATOM, "us-ascii" },
            { EOF, NULL }};
        TESTCASE(PHRASE, sizeof(PHRASE)-1, EXP);
    }
    {
        static const char PHRASE[] = "Content-Type: text/plain; charset=\"us-ascii\"\r\n";
        static const struct token EXP[] = {
            { RFC822_ATOM, "Content-Type" },
            { ':', NULL },
            { RFC822_ATOM, "text/plain" },
            { ';', NULL },
            { RFC822_ATOM, "charset" },
            { '=', NULL },
            { RFC822_QSTRING, "us-ascii" },
            { EOF, NULL }};
        TESTCASE(PHRASE, sizeof(PHRASE)-1, EXP);
    }
    {
        static const char PHRASE[] = "Content-Type: \"text/plain\"; charset=\"us-ascii\"\r\n";
        static const struct token EXP[] = {
            { RFC822_ATOM, "Content-Type" },
            { ':', NULL },
            { RFC822_QSTRING, "text/plain" },
            { ';', NULL },
            { RFC822_ATOM, "charset" },
            { '=', NULL },
            { RFC822_QSTRING, "us-ascii" },
            { EOF, NULL }};
        TESTCASE(PHRASE, sizeof(PHRASE)-1, EXP);
    }
    /* some made-up examples */
    {
        static const char PHRASE[] = "Content-Type  : text/plain ; charset = \"us-ascii\" \r\n";
        static const struct token EXP[] = {
            { RFC822_ATOM, "Content-Type" },
            { ':', NULL },
            { RFC822_ATOM, "text/plain" },
            { ';', NULL },
            { RFC822_ATOM, "charset" },
            { '=', NULL },
            { RFC822_QSTRING, "us-ascii" },
            { EOF, NULL }};
        TESTCASE(PHRASE, sizeof(PHRASE)-1, EXP);
    }
    {
        static const char PHRASE[] = "Content-Type: text/plain\r\n ; charset = us-ascii \r\n";
        static const struct token EXP[] = {
            { RFC822_ATOM, "Content-Type" },
            { ':', NULL },
            { RFC822_ATOM, "text/plain" },
            { ';', NULL },
            { RFC822_ATOM, "charset" },
            { '=', NULL },
            { RFC822_ATOM, "us-ascii" },
            { EOF, NULL }};
        TESTCASE(PHRASE, sizeof(PHRASE)-1, EXP);
    }
}

/* vim: set ft=c: */
