diff options
Diffstat (limited to 'src/lib-mail/test-rfc822-parser.c')
-rw-r--r-- | src/lib-mail/test-rfc822-parser.c | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/src/lib-mail/test-rfc822-parser.c b/src/lib-mail/test-rfc822-parser.c new file mode 100644 index 0000000..a0e7ad0 --- /dev/null +++ b/src/lib-mail/test-rfc822-parser.c @@ -0,0 +1,445 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "rfc822-parser.h" +#include "test-common.h" + +static void test_rfc822_parse_comment(void) +{ + static const struct { + const char *input, *output; + int ret; + } tests[] = { + { "(", "", -1 }, + { "(()", "", -1 }, + + { "()", "", 0 }, + { "(())", "()", 0 }, + { "(foo ( bar ) baz)", "foo ( bar ) baz", 0 }, + { "(foo\t\tbar)", "foo\t\tbar", 0 }, + { "(foo\\(bar)", "foo(bar", 0 }, + { "(foo\\\\bar)", "foo\\bar", 0 }, + { "(foo\\\\\\\\)", "foo\\\\", 0 }, + { "(foo\\)bar)", "foo)bar", 0 }, + { "(foo\"flop\"\"bar)", "foo\"flop\"\"bar", 0 }, + + { "(foo\n bar)", "foo bar", 0 }, + { "(foo\n\t\t bar)", "foo\t\t bar", 0 }, + { "(foo\\\n bar)", "foo\\ bar", 0 }, + { "(foo\\\r\n bar)", "foo\\ bar", 0 }, + }; + struct rfc822_parser_context parser, parser2; + string_t *str = t_str_new(64); + unsigned int i = 0; + + test_begin("rfc822 parse comment"); + for (i = 0; i < N_ELEMENTS(tests); i++) { + rfc822_parser_init(&parser, (const void *)tests[i].input, + strlen(tests[i].input), str); + rfc822_parser_init(&parser2, (const void *)tests[i].input, + strlen(tests[i].input), NULL); + test_assert_idx(rfc822_skip_comment(&parser) == tests[i].ret, i); + test_assert_idx(rfc822_skip_comment(&parser2) == tests[i].ret, i); + test_assert_idx(tests[i].ret < 0 || + strcmp(tests[i].output, str_c(str)) == 0, i); + rfc822_parser_deinit(&parser); + rfc822_parser_deinit(&parser2); + str_truncate(str, 0); + } + test_end(); +} + +static void test_rfc822_parse_comment_nuls(void) +{ + const unsigned char input[] = "(\000a\000\000b\\\000c(\000d)\000)"; + const char output[] = "!a!!b\\!c(!d)!"; + struct rfc822_parser_context parser; + string_t *str = t_str_new(64); + + test_begin("rfc822 parse comment with NULs"); + + rfc822_parser_init(&parser, input, sizeof(input)-1, str); + test_assert(rfc822_skip_comment(&parser) == 0); + /* should be same as input, except the outer () removed */ + test_assert(str_len(str) == sizeof(input)-1-2 && + memcmp(input+1, str_data(str), str_len(str)) == 0); + rfc822_parser_deinit(&parser); + + str_truncate(str, 0); + rfc822_parser_init(&parser, input, sizeof(input)-1, str); + parser.nul_replacement_str = "!"; + test_assert(rfc822_skip_comment(&parser) == 0); + test_assert(strcmp(str_c(str), output) == 0); + rfc822_parser_deinit(&parser); + + test_end(); +} + +static void test_rfc822_parse_quoted_string(void) +{ + static const struct { + const char *input, *output; + int ret; + } tests[] = { + { "\"", "", -1 }, + { "\"\"", "", 0 }, + { "\"foo\"", "foo", 0 }, + { "\"\"foo", "", 1 }, + { "\"\"\"", "", 1 }, + { "\"\\\"\"", "\"", 0 }, + { "\"\\\\\"", "\\", 0 }, + { "\"\\\\foo\\\\foo\\\\\"", "\\foo\\foo\\", 0 }, + { "\"foo\n bar\"", "foo bar", 0 }, + { "\"foo\n\t\t bar\"", "foo\t\t bar", 0 }, + { "\"foo\\\n bar\"", "foo\\ bar", 0 }, + { "\"foo\\\r\n bar\"", "foo\\ bar", 0 }, + }; + struct rfc822_parser_context parser; + string_t *str = t_str_new(64); + unsigned int i = 0; + + test_begin("rfc822 parse quoted string"); + for (i = 0; i < N_ELEMENTS(tests); i++) { + rfc822_parser_init(&parser, (const void *)tests[i].input, + strlen(tests[i].input), NULL); + test_assert_idx(rfc822_parse_quoted_string(&parser, str) == tests[i].ret, i); + test_assert_idx(tests[i].ret < 0 || + strcmp(tests[i].output, str_c(str)) == 0, i); + rfc822_parser_deinit(&parser); + str_truncate(str, 0); + } + test_end(); +} + +static void test_rfc822_parse_dot_atom(void) +{ + static const struct { + const char *input, *output; + int ret; + } tests[] = { + { "foo", "foo", 0 }, + { "foo.bar", "foo.bar", 0 }, + { "foo.bar.baz", "foo.bar.baz", 0 }, + { "foo . \tbar (comments) . (...) baz\t ", "foo.bar.baz", 0 }, + + { ".", "", -1 }, + { "..", "", -1 }, + { ".foo", "", -1 }, + { "foo.", "foo.", -1 }, + { "foo..bar", "foo.", -1 }, + { "foo. .bar", "foo.", -1 }, + { "foo.(middle).bar", "foo.", -1 }, + { "foo. ", "foo.", -1 }, + { "foo.\t", "foo.", -1 }, + { "foo.(ending)", "foo.", -1 }, + }; + struct rfc822_parser_context parser; + string_t *str = t_str_new(64); + string_t *input2 = t_str_new(64); + unsigned int i = 0; + + test_begin("rfc822 parse dot-atom"); + for (i = 0; i < N_ELEMENTS(tests); i++) { + rfc822_parser_init(&parser, (const void *)tests[i].input, + strlen(tests[i].input), NULL); + test_assert_idx(rfc822_parse_dot_atom(&parser, str) == tests[i].ret, i); + test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i); + rfc822_parser_deinit(&parser); + str_truncate(str, 0); + + /* same input but with "," appended should return 1 on success, + and -1 still on error. */ + int expected_ret = tests[i].ret == -1 ? -1 : 1; + str_append(input2, tests[i].input); + str_append_c(input2, ','); + rfc822_parser_init(&parser, str_data(input2), + str_len(input2), NULL); + test_assert_idx(rfc822_parse_dot_atom(&parser, str) == expected_ret, i); + test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i); + rfc822_parser_deinit(&parser); + + str_truncate(str, 0); + str_truncate(input2, 0); + } + test_end(); +} + +static void test_rfc822_parse_domain_literal(void) +{ + static const struct { + const char *input, *output; + int ret; + } tests[] = { + { "@[", "", -1 }, + { "@[foo", "", -1 }, + { "@[foo[]", "", -1 }, + { "@[foo[]]", "", -1 }, + { "@[]", "[]", 0 }, + { "@[foo bar]", "[foo bar]", 0 }, + { "@[foo\n bar]", "[foo bar]", 0 }, + { "@[foo\n\t\t bar]", "[foo\t\t bar]", 0 }, + { "@[foo\\\n bar]", "[foo\\ bar]", 0 }, + }; + struct rfc822_parser_context parser; + string_t *str = t_str_new(64); + unsigned int i = 0; + + test_begin("rfc822 parse domain literal"); + for (i = 0; i < N_ELEMENTS(tests); i++) { + rfc822_parser_init(&parser, (const void *)tests[i].input, + strlen(tests[i].input), NULL); + test_assert_idx(rfc822_parse_domain(&parser, str) == tests[i].ret, i); + test_assert_idx(tests[i].ret < 0 || + strcmp(tests[i].output, str_c(str)) == 0, i); + rfc822_parser_deinit(&parser); + str_truncate(str, 0); + } + test_end(); +} + +#undef TEST_STRING +#define TEST_STRING(a) .input = (const unsigned char*)a, .input_len = sizeof(a)-1 + +static void test_rfc822_parse_content_type(void) +{ + const struct { + const unsigned char *input; + size_t input_len; + int ret; + const char *output; + } test_cases[] = { + { TEST_STRING(""), -1, "" }, + { TEST_STRING(";charset=us-ascii"), -1, "" }, + { TEST_STRING(" ;charset=us-ascii"), -1, "" }, + { TEST_STRING("/"), -1, "" }, + { TEST_STRING("/;charset=us-ascii"), -1, "" }, + { TEST_STRING("/ ;charset=us-ascii"), -1, "" }, + { TEST_STRING("text/"), -1, "" }, + { TEST_STRING("text/;charset=us-ascii"), -1, "" }, + { TEST_STRING("text/ ;charset=us-ascii"), -1, "" }, + { TEST_STRING("/plain"), -1, "" }, + { TEST_STRING("/plain;charset=us-ascii"), -1, "" }, + { TEST_STRING("/plain ;charset=us-ascii"), -1, "" }, + { TEST_STRING("text/plain"), 0, "text/plain" }, + { TEST_STRING("text/plain;charset=us-ascii"), 1, "text/plain" }, + { TEST_STRING("text/plain ;charset=us-ascii"), 1, "text/plain" }, + { TEST_STRING("text/plain/format"), -1, "" }, + { TEST_STRING("text/plain/format;charset=us-ascii"), -1, "" }, + { TEST_STRING("text/plain/format ;charset=us-ascii"), -1, "" }, + { TEST_STRING("\xe5\x90\xab\xe9\x87\x8f/\xe7\xa8\xae\xe9\xa1\x9e"), + 0, "\xe5\x90\xab\xe9\x87\x8f/\xe7\xa8\xae\xe9\xa1\x9e" }, + { TEST_STRING("\xe5\x90\xab\xe9\x87\x8f/\xe7\xa8\xae\xe9\xa1\x9e;charset=utf-8"), + 1, "\xe5\x90\xab\xe9\x87\x8f/\xe7\xa8\xae\xe9\xa1\x9e" }, + { TEST_STRING("\xe5\x90\xab\xe9\x87\x8f/\xe7\xa8\xae\xe9\xa1\x9e ;charset=utf-8"), + 1, "\xe5\x90\xab\xe9\x87\x8f/\xe7\xa8\xae\xe9\xa1\x9e" }, + { TEST_STRING("application/ld+json"), 0, "application/ld+json" }, + { TEST_STRING("application/ld+json;charset=us-ascii"), + 1, "application/ld+json" }, + { TEST_STRING("application/ld+json ;charset=us-ascii"), + 1, "application/ld+json" }, + { TEST_STRING("application/x-magic-cap-package-1.0"), + 0, "application/x-magic-cap-package-1.0" }, + { TEST_STRING("application/x-magic-cap-package-1.0;charset=us-ascii"), + 1, "application/x-magic-cap-package-1.0" }, + { TEST_STRING("application/x-magic-cap-package-1.0 ;charset=us-ascii"), + 1, "application/x-magic-cap-package-1.0" }, + { TEST_STRING("application/pro_eng"), 0, "application/pro_eng" }, + { TEST_STRING("application/pro_eng;charset=us-ascii"), + 1, "application/pro_eng" }, + { TEST_STRING("application/pro_eng ;charset=us-ascii"), + 1, "application/pro_eng" }, + { TEST_STRING("application/wordperfect6.1"), + 0, "application/wordperfect6.1" }, + { TEST_STRING("application/wordperfect6.1;charset=us-ascii"), + 1, "application/wordperfect6.1" }, + { TEST_STRING("application/wordperfect6.1 ;charset=us-ascii"), + 1, "application/wordperfect6.1" }, + { TEST_STRING("application/vnd.openxmlformats-officedocument.wordprocessingml.template"), + 0, "application/vnd.openxmlformats-officedocument.wordprocessingml.template" }, + { TEST_STRING("application/vnd.openxmlformats-officedocument.wordprocessingml.template;charset=us-ascii"), + 1, "application/vnd.openxmlformats-officedocument.wordprocessingml.template" }, + { TEST_STRING("application/vnd.openxmlformats-officedocument.wordprocessingml.template ;charset=us-asii"), + 1, "application/vnd.openxmlformats-officedocument.wordprocessingml.template" }, + { TEST_STRING("(hello) text (plain) / (world) plain (eod)"), + 0, "text/plain" }, + { TEST_STRING("(hello) text (plain) / (world) plain (eod);charset=us-ascii"), + 1, "text/plain" }, + { TEST_STRING("(hello) text (plain) / (world) plain (eod); charset=us-ascii"), + 1, "text/plain" }, + { TEST_STRING("message/rfc822\r\n"), 0, "message/rfc822" }, + { TEST_STRING(" \t\r message/rfc822 \t\r\n"), + 0, "message/rfc822" }, + { TEST_STRING(" \t\r message/rfc822 \t ;charset=us-ascii\r\n"), + 1, "message/rfc822" }, + { TEST_STRING(" \t\r message/rfc822 \t ; charset=us-ascii\r\n"), + 1, "message/rfc822" }, + { TEST_STRING("test\0/ty\0pe"), -1, "" }, + }; + + for (size_t i = 0; i < N_ELEMENTS(test_cases); i++) T_BEGIN { + string_t *value = t_str_new(64); + struct rfc822_parser_context parser; + + rfc822_parser_init(&parser, test_cases[i].input, + test_cases[i].input_len, NULL); + test_assert_idx(rfc822_parse_content_type(&parser, value) == + test_cases[i].ret, i); + test_assert_strcmp_idx(test_cases[i].output, str_c(value), i); + rfc822_parser_deinit(&parser); + } T_END; +} + +static void test_rfc822_parse_content_param(void) +{ + const char *input = + "; key1=value1#$!%&'*+-.^_`{|}~" + "; key2=\" \\\"(),/:;<=>?@[\\\\]\""; + const struct { + const char *key, *value; + } output[] = { + { "key1", "value1#$!%&'*+-.^_`{|}~" }, + { "key2", " \"(),/:;<=>?@[\\]" } + }; + struct rfc822_parser_context parser; + const char *key; + string_t *value = t_str_new(64); + unsigned int i = 0; + int ret; + + test_begin("rfc822 parse content param"); + rfc822_parser_init(&parser, (const void *)input, strlen(input), NULL); + while ((ret = rfc822_parse_content_param(&parser, &key, value)) > 0 && + i < N_ELEMENTS(output)) { + test_assert_idx(strcmp(output[i].key, key) == 0, i); + test_assert_idx(strcmp(output[i].value, str_c(value)) == 0, i); + i++; + } + rfc822_parser_deinit(&parser); + test_assert(ret == 0); + test_assert(i == N_ELEMENTS(output)); + test_end(); +} + +struct param { + const char *key, *value; +}; + +static void parse_content_type_param(const void *input, size_t input_len, + const char *content_type, + const struct param *params, size_t param_count, + bool expect_content_type, int expect_ret, + int idx) +{ + struct rfc822_parser_context parser; + const char *key; + string_t *value = t_str_new(64); + unsigned int i = 0; + int ret; + + i_assert(params != NULL || param_count == 0); + + rfc822_parser_init(&parser, input, input_len, NULL); + ret = rfc822_parse_content_type(&parser, value); + test_assert_idx((expect_content_type && ret == 1) || + (!expect_content_type && ret == -1), idx); + test_assert_strcmp_idx(content_type, str_c(value), idx); + + /* parse content type first */ + while ((ret = rfc822_parse_content_param(&parser, &key, value)) > 0) { + if (i < param_count) { + test_assert_strcmp_idx(params[i].key, key, idx); + test_assert_strcmp_idx(params[i].value, str_c(value), idx); + } + i++; + } + + test_assert_idx(expect_ret == ret, idx); + test_assert_idx(i == param_count, idx); + rfc822_parser_deinit(&parser); +} + +#undef TEST_STRING +#define TEST_STRING(a) (a), sizeof((a))-1 + +#define X10(a) a a a a a a a a a a + +static void test_rfc822_parse_content_type_param(void) +{ + const char *input = + "(hello) text/plain ; (should we skip;comments=\"yes\")" + " param=value" + " ; param2=value2 (with comments (with comment) with;comment=\"yes\") " + " ; param3=\"value3 (with no comment ; or=value)\"" + " ; param4=\"\xe7\xa8\xae\xe9\xa1\x9e\"" + " ; \xe5\x90\xab\xe9\x87\x8f=\"\xe7\xa8\xae\xe9\xa1\x9e\"" + " ; "X10(X10(X10("a")))"="X10(X10(X10("a"))) + " ; "X10(X10(X10(X10("a"))))"="X10(X10(X10(X10("a")))) + " ; (comment) param7 (comment2) = (comment3) value7 (comment4) " + ; + + const struct param output[] = { + { "param", "value" }, + { "param2", "value2" }, + { "param3", "value3 (with no comment ; or=value)" }, + { "param4", "\xe7\xa8\xae\xe9\xa1\x9e" }, + { "\xe5\x90\xab\xe9\x87\x8f", "\xe7\xa8\xae\xe9\xa1\x9e" }, + { X10(X10(X10("a"))), X10(X10(X10("a"))) }, + { X10(X10(X10(X10("a")))), X10(X10(X10(X10("a")))) }, + { "param7", "value7" }, + }; + const struct param output5[] = { + { "charset", "" }, + }; + + test_begin("rfc822 parse content type with params"); + + int idx = 0; + parse_content_type_param(input, strlen(input), "text/plain", + output, N_ELEMENTS(output), TRUE, 0, idx++); + parse_content_type_param(TEST_STRING("text/"), "", NULL, 0, FALSE, 0, idx++); + parse_content_type_param( + TEST_STRING("text/\0plain ;"), "", NULL, 0, FALSE, -1, idx++); + parse_content_type_param( + TEST_STRING("text/plain\0;charset=us-ascii"), "", NULL, 0, FALSE, -1, idx++); + parse_content_type_param( + TEST_STRING("text/plain;charset\0=us-ascii"), "text/plain", NULL, 0, TRUE, -1, idx++); + parse_content_type_param( + TEST_STRING("text/plain;charset="), "text/plain", + output5, N_ELEMENTS(output5), TRUE, 0, idx++); + parse_content_type_param( + TEST_STRING("text/plain ; ; charset=us-ascii"), "text/plain", NULL, 0, TRUE, -1, idx++); + /* build a large one */ + ARRAY(struct param) output2; + t_array_init(&output2, 1000); + string_t *large = t_str_new(10000); + str_append(large, "text/plain"); + for (unsigned int i = 0; i < 1000; i++) { + str_printfa(large, " ; param%u=\"value%u\"", i, i); + struct param *param = array_append_space(&output2); + param->key = t_strdup_printf("param%u", i); + param->value = t_strdup_printf("value%u", i); + } + parse_content_type_param(large->data, large->used, + "text/plain", + array_idx(&output2, 0), array_count(&output2), + TRUE, 0, idx++); + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_rfc822_parse_comment, + test_rfc822_parse_comment_nuls, + test_rfc822_parse_quoted_string, + test_rfc822_parse_dot_atom, + test_rfc822_parse_domain_literal, + test_rfc822_parse_content_type, + test_rfc822_parse_content_param, + test_rfc822_parse_content_type_param, + NULL + }; + return test_run(test_functions); +} |