diff --git a/src/lib-mail/rfc822-parser.c b/src/lib-mail/rfc822-parser.c index 2f7e77da9c..886707549a 100644 --- a/src/lib-mail/rfc822-parser.c +++ b/src/lib-mail/rfc822-parser.c @@ -312,6 +312,7 @@ static int rfc822_parse_domain_literal(struct rfc822_parser_context *ctx, string_t *str) { const unsigned char *start; + size_t len; /* domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] @@ -324,15 +325,38 @@ rfc822_parse_domain_literal(struct rfc822_parser_context *ctx, string_t *str) i_assert(ctx->data < ctx->end); i_assert(*ctx->data == '['); - for (start = ctx->data; ctx->data < ctx->end; ctx->data++) { - if (*ctx->data == '\\') { + for (start = ctx->data++; ctx->data < ctx->end; ctx->data++) { + switch (*ctx->data) { + case '[': + /* not allowed */ + return -1; + case ']': + str_append_data(str, start, ctx->data - start + 1); + ctx->data++; + return rfc822_skip_lwsp(ctx); + case '\n': + /* folding whitespace, remove the (CR)LF */ + len = ctx->data - start; + if (len > 0 && start[len-1] == '\r') + len--; + str_append_data(str, start, len); + start = ctx->data + 1; + break; + case '\\': + /* note: the '\' is preserved in the output */ ctx->data++; if (ctx->data >= ctx->end) + return -1; + + if (*ctx->data == '\r' || *ctx->data == '\n') { + /* quoted-pair doesn't allow CR/LF. + They are part of the obs-qp though, so don't + return them as error. */ + str_append_data(str, start, ctx->data - start); + start = ctx->data; + ctx->data--; break; - } else if (*ctx->data == ']') { - ctx->data++; - str_append_data(str, start, ctx->data - start); - return rfc822_skip_lwsp(ctx); + } } } diff --git a/src/lib-mail/test-rfc822-parser.c b/src/lib-mail/test-rfc822-parser.c index 8f8d4fb66b..3b0aaae640 100644 --- a/src/lib-mail/test-rfc822-parser.c +++ b/src/lib-mail/test-rfc822-parser.c @@ -41,6 +41,39 @@ static void test_rfc822_parse_quoted_string(void) 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(); +} + static void test_rfc822_parse_content_param(void) { const char *input = @@ -76,6 +109,7 @@ int main(void) { static void (*const test_functions[])(void) = { test_rfc822_parse_quoted_string, + test_rfc822_parse_domain_literal, test_rfc822_parse_content_param, NULL };