diff options
Diffstat (limited to 'src/lib-mail')
-rw-r--r-- | src/lib-mail/message-address.c | 43 | ||||
-rw-r--r-- | src/lib-mail/message-address.h | 12 | ||||
-rw-r--r-- | src/lib-mail/message-header-parser.c | 48 | ||||
-rw-r--r-- | src/lib-mail/message-header-parser.h | 10 | ||||
-rw-r--r-- | src/lib-mail/message-parser-private.h | 2 | ||||
-rw-r--r-- | src/lib-mail/message-parser.c | 15 | ||||
-rw-r--r-- | src/lib-mail/message-parser.h | 6 | ||||
-rw-r--r-- | src/lib-mail/message-part-data.c | 17 | ||||
-rw-r--r-- | src/lib-mail/message-part-data.h | 6 | ||||
-rw-r--r-- | src/lib-mail/test-message-address.c | 274 | ||||
-rw-r--r-- | src/lib-mail/test-message-header-parser.c | 67 | ||||
-rw-r--r-- | src/lib-mail/test-message-parser.c | 154 |
12 files changed, 495 insertions, 159 deletions
diff --git a/src/lib-mail/message-address.c b/src/lib-mail/message-address.c index fb06afa..ae37014 100644 --- a/src/lib-mail/message-address.c +++ b/src/lib-mail/message-address.c @@ -1,6 +1,7 @@ /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" +#include "llist.h" #include "str.h" #include "strescape.h" #include "smtp-address.h" @@ -12,7 +13,8 @@ struct message_address_parser_context { pool_t pool; struct rfc822_parser_context parser; - struct message_address *first_addr, *last_addr, addr; + struct message_address addr; + struct message_address_list addr_list; string_t *str; bool fill_missing, non_strict_dots; @@ -27,11 +29,7 @@ static void add_address(struct message_address_parser_context *ctx) memcpy(addr, &ctx->addr, sizeof(ctx->addr)); i_zero(&ctx->addr); - if (ctx->first_addr == NULL) - ctx->first_addr = addr; - else - ctx->last_addr->next = addr; - ctx->last_addr = addr; + DLLIST2_APPEND(&ctx->addr_list.head, &ctx->addr_list.tail, addr); } /* quote with "" and escape all '\', '"' and "'" characters if need */ @@ -442,10 +440,11 @@ static int parse_path(struct message_address_parser_context *ctx) return ret; } -static struct message_address * +static void message_address_parse_real(pool_t pool, const unsigned char *data, size_t size, unsigned int max_addresses, - enum message_address_parse_flags flags) + enum message_address_parse_flags flags, + struct message_address_list *list_r) { struct message_address_parser_context ctx; @@ -464,7 +463,7 @@ message_address_parse_real(pool_t pool, const unsigned char *data, size_t size, (void)parse_address_list(&ctx, max_addresses); } rfc822_parser_deinit(&ctx.parser); - return ctx.first_addr; + *list_r = ctx.addr_list; } static int @@ -484,7 +483,7 @@ message_address_parse_path_real(pool_t pool, const unsigned char *data, ret = parse_path(&ctx); rfc822_parser_deinit(&ctx.parser); - *addr_r = ctx.first_addr; + *addr_r = ctx.addr_list.head; return (ret < 0 ? -1 : 0); } @@ -493,17 +492,24 @@ message_address_parse(pool_t pool, const unsigned char *data, size_t size, unsigned int max_addresses, enum message_address_parse_flags flags) { - struct message_address *addr; + struct message_address_list list; + message_address_parse_full(pool, data, size, max_addresses, flags, + &list); + return list.head; +} +void message_address_parse_full(pool_t pool, const unsigned char *data, + size_t size, unsigned int max_addresses, + enum message_address_parse_flags flags, + struct message_address_list *list_r) +{ if (pool->datastack_pool) { - return message_address_parse_real(pool, data, size, - max_addresses, flags); - } - T_BEGIN { - addr = message_address_parse_real(pool, data, size, - max_addresses, flags); + message_address_parse_real(pool, data, size, + max_addresses, flags, list_r); + } else T_BEGIN { + message_address_parse_real(pool, data, size, + max_addresses, flags, list_r); } T_END; - return addr; } int message_address_parse_path(pool_t pool, const unsigned char *data, @@ -631,6 +637,7 @@ const char *message_address_first_to_string(const struct message_address *addr) struct message_address first_addr; first_addr = *addr; + first_addr.prev = NULL; first_addr.next = NULL; first_addr.route = NULL; return message_address_to_string(&first_addr); diff --git a/src/lib-mail/message-address.h b/src/lib-mail/message-address.h index 8370397..224f7a7 100644 --- a/src/lib-mail/message-address.h +++ b/src/lib-mail/message-address.h @@ -18,7 +18,7 @@ enum message_address_parse_flags { {name = NULL, NULL, "group", NULL}, ..., {NULL, NULL, NULL, NULL} */ struct message_address { - struct message_address *next; + struct message_address *prev, *next; /* display-name */ const char *name; @@ -31,12 +31,22 @@ struct message_address { bool invalid_syntax; }; +struct message_address_list { + struct message_address *head, *tail; +}; + /* Parse message addresses from given data. Note that giving an empty string will return NULL since there are no addresses. */ struct message_address * message_address_parse(pool_t pool, const unsigned char *data, size_t size, unsigned int max_addresses, enum message_address_parse_flags flags); +/* Same as message_address_parse(), but return message_address_list containing + both the first and the last address in the linked list. */ +void message_address_parse_full(pool_t pool, const unsigned char *data, + size_t size, unsigned int max_addresses, + enum message_address_parse_flags flags, + struct message_address_list *list_r); /* Parse RFC 5322 "path" (Return-Path header) from given data. Returns -1 if the path is invalid and 0 otherwise. diff --git a/src/lib-mail/message-header-parser.c b/src/lib-mail/message-header-parser.c index c5026f1..5e020bb 100644 --- a/src/lib-mail/message-header-parser.c +++ b/src/lib-mail/message-header-parser.c @@ -21,6 +21,9 @@ struct message_header_parser_ctx { string_t *name; buffer_t *value_buf; + size_t header_block_max_size; + size_t header_block_total_size; + enum message_header_parser_flags flags; bool skip_line:1; bool has_nuls:1; @@ -38,6 +41,7 @@ message_parse_header_init(struct istream *input, struct message_size *hdr_size, ctx->name = str_new(default_pool, 128); ctx->flags = flags; ctx->value_buf = buffer_create_dynamic(default_pool, 4096); + ctx->header_block_max_size = MESSAGE_HEADER_BLOCK_DEFAULT_MAX_SIZE; i_stream_ref(input); if (hdr_size != NULL) @@ -45,6 +49,21 @@ message_parse_header_init(struct istream *input, struct message_size *hdr_size, return ctx; } +void +message_parse_header_set_limit(struct message_header_parser_ctx *parser, + size_t header_block_max_size) +{ + parser->header_block_max_size = header_block_max_size; +} + +void +message_parse_header_lower_limit(struct message_header_parser_ctx *parser, + size_t header_block_max_size) +{ + if (header_block_max_size < parser->header_block_max_size) + message_parse_header_set_limit(parser, header_block_max_size); +} + void message_parse_header_deinit(struct message_header_parser_ctx **_ctx) { struct message_header_parser_ctx *ctx = *_ctx; @@ -77,6 +96,7 @@ int message_parse_header_next(struct message_header_parser_ctx *ctx, /* new header line */ line->name_offset = ctx->input->v_offset; colon_pos = UINT_MAX; + ctx->header_block_total_size += ctx->value_buf->used; buffer_set_used_size(ctx->value_buf, 0); } @@ -342,33 +362,39 @@ int message_parse_header_next(struct message_header_parser_ctx *ctx, } } + line->value_len = I_MIN(line->value_len, ctx->header_block_max_size); + size_t line_value_size = line->value_len; + size_t header_total_used = ctx->header_block_total_size + ctx->value_buf->used; + size_t line_available = ctx->header_block_max_size <= header_total_used ? 0 : + ctx->header_block_max_size - header_total_used; + line_value_size = I_MIN(line_value_size, line_available); + if (!line->continued) { /* first header line. make a copy of the line since we can't really trust input stream not to lose it. */ - buffer_append(ctx->value_buf, line->value, line->value_len); + buffer_append(ctx->value_buf, line->value, line_value_size); line->value = line->full_value = ctx->value_buf->data; - line->full_value_len = line->value_len; + line->full_value_len = line->value_len = line_value_size; } else if (line->use_full_value) { /* continue saving the full value. */ if (last_no_newline) { /* line is longer than fit into our buffer, so we were forced to break it into multiple message_header_lines */ - } else { - if (last_crlf) + } else if (line_value_size > 1) { + if (last_crlf && line_value_size > 2) buffer_append_c(ctx->value_buf, '\r'); buffer_append_c(ctx->value_buf, '\n'); } if ((ctx->flags & MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE) != 0 && line->value_len > 0 && line->value[0] != ' ' && - IS_LWSP(line->value[0])) { + IS_LWSP(line->value[0]) && + line_value_size > 0) { buffer_append_c(ctx->value_buf, ' '); - buffer_append(ctx->value_buf, - line->value + 1, line->value_len - 1); - } else { - buffer_append(ctx->value_buf, - line->value, line->value_len); - } + buffer_append(ctx->value_buf, line->value + 1, line_value_size - 1); + } else + buffer_append(ctx->value_buf, line->value, line_value_size); + line->full_value = ctx->value_buf->data; line->full_value_len = ctx->value_buf->used; } else { diff --git a/src/lib-mail/message-header-parser.h b/src/lib-mail/message-header-parser.h index ce0825c..43cf95e 100644 --- a/src/lib-mail/message-header-parser.h +++ b/src/lib-mail/message-header-parser.h @@ -1,6 +1,9 @@ #ifndef MESSAGE_HEADER_PARSER_H #define MESSAGE_HEADER_PARSER_H +/* This can be overridden by message_parse_header_set_limit() */ +#define MESSAGE_HEADER_BLOCK_DEFAULT_MAX_SIZE ((size_t) 10 * 1024*1024) + #define IS_LWSP(c) \ ((c) == ' ' || (c) == '\t') @@ -48,6 +51,13 @@ message_parse_header_init(struct istream *input, struct message_size *hdr_size, enum message_header_parser_flags flags) ATTR_NULL(2); void message_parse_header_deinit(struct message_header_parser_ctx **ctx); +void +message_parse_header_set_limit(struct message_header_parser_ctx *parser, + size_t header_block_max_size); +void +message_parse_header_lower_limit(struct message_header_parser_ctx *parser, + size_t header_block_max_size); + /* Read and return next header line. Returns 1 if header is returned, 0 if input stream is non-blocking and more data needs to be read, -1 when all is done or error occurred (see stream's error status). */ diff --git a/src/lib-mail/message-parser-private.h b/src/lib-mail/message-parser-private.h index 41c32da..8b362a9 100644 --- a/src/lib-mail/message-parser-private.h +++ b/src/lib-mail/message-parser-private.h @@ -30,6 +30,8 @@ struct message_parser_ctx { enum message_parser_flags flags; unsigned int max_nested_mime_parts; unsigned int max_total_mime_parts; + size_t all_headers_max_size; + size_t all_headers_total_size; char *last_boundary; struct message_boundary *boundaries; diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c index 9a9c9a3..c7e3b1e 100644 --- a/src/lib-mail/message-parser.c +++ b/src/lib-mail/message-parser.c @@ -617,7 +617,18 @@ static int parse_next_header(struct message_parser_ctx *ctx, } if (ret < 0) { /* no boundary */ + size_t headers_available = + ctx->all_headers_max_size > ctx->all_headers_total_size ? + ctx->all_headers_max_size - ctx->all_headers_total_size : 0; + message_parse_header_lower_limit(ctx->hdr_parser_ctx, headers_available); ret = message_parse_header_next(ctx->hdr_parser_ctx, &hdr); + if (ret > 0) { + if (!hdr->continues) { + ctx->all_headers_total_size += hdr->name_len; + ctx->all_headers_total_size += hdr->middle_len; + } + ctx->all_headers_total_size += hdr->value_len; + } if (ret == 0 || (ret < 0 && ctx->input->stream_errno != 0)) { ctx->want_count = i_stream_get_data_size(ctx->input) + 1; return ret; @@ -762,6 +773,9 @@ message_parser_init_int(struct istream *input, ctx->max_total_mime_parts = set->max_total_mime_parts != 0 ? set->max_total_mime_parts : MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS; + ctx->all_headers_max_size = set->all_headers_max_size != 0 ? + set->all_headers_max_size : + MESSAGE_PARSER_DEFAULT_ALL_HEADERS_MAX_SIZE; ctx->input = input; i_stream_ref(input); return ctx; @@ -779,6 +793,7 @@ message_parser_init(pool_t part_pool, struct istream *input, ctx->next_part = &ctx->part->children; ctx->parse_next_block = parse_next_header_init; ctx->total_parts_count = 1; + ctx->all_headers_total_size = 0; i_array_init(&ctx->next_part_stack, 4); return ctx; } diff --git a/src/lib-mail/message-parser.h b/src/lib-mail/message-parser.h index f19e526..8d70d73 100644 --- a/src/lib-mail/message-parser.h +++ b/src/lib-mail/message-parser.h @@ -19,6 +19,7 @@ enum message_parser_flags { #define MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS 100 #define MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS 10000 +#define MESSAGE_PARSER_DEFAULT_ALL_HEADERS_MAX_SIZE ((size_t) 50 * 1024*1024) struct message_parser_settings { enum message_header_parser_flags hdr_flags; @@ -30,6 +31,11 @@ struct message_parser_settings { /* Maximum MIME parts in total. 0 = MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS. */ unsigned int max_total_mime_parts; + + /* Maximum bytes fore headers in top header plus all + MIME sections headers + 0 = MESSAGE_PARSER_DEFAULT_ALL_HEADERS_MAX_SIZE */ + size_t all_headers_max_size; }; struct message_parser_ctx; diff --git a/src/lib-mail/message-part-data.c b/src/lib-mail/message-part-data.c index a5771f8..25019ab 100644 --- a/src/lib-mail/message-part-data.c +++ b/src/lib-mail/message-part-data.c @@ -4,6 +4,7 @@ #include "str.h" #include "wildcard-match.h" #include "array.h" +#include "llist.h" #include "rfc822-parser.h" #include "rfc2231-parser.h" #include "message-address.h" @@ -176,7 +177,7 @@ void message_part_envelope_parse_from_header(pool_t pool, { struct message_part_envelope *d; enum envelope_field field; - struct message_address **addr_p, *addr; + struct message_address_list *addr_p, new_addr; const char **str_p; if (*data == NULL) { @@ -234,18 +235,18 @@ void message_part_envelope_parse_from_header(pool_t pool, } if (addr_p != NULL) { - addr = message_address_parse(pool, hdr->full_value, - hdr->full_value_len, - UINT_MAX, - MESSAGE_ADDRESS_PARSE_FLAG_FILL_MISSING); + message_address_parse_full(pool, hdr->full_value, + hdr->full_value_len, + UINT_MAX, + MESSAGE_ADDRESS_PARSE_FLAG_FILL_MISSING, + &new_addr); /* Merge multiple headers the same as if they were comma separated in a single line. This is better from security point of view, because attacker could intentionally write addresses in a way that e.g. the first From header is validated while MUA only shows the second From header. */ - while (*addr_p != NULL) - addr_p = &(*addr_p)->next; - *addr_p = addr; + DLLIST2_JOIN(&addr_p->head, &addr_p->tail, + &new_addr.head, &new_addr.tail); } else if (str_p != NULL) { *str_p = message_header_strdup(pool, hdr->full_value, hdr->full_value_len); diff --git a/src/lib-mail/message-part-data.h b/src/lib-mail/message-part-data.h index 5ff9ffe..7ec878d 100644 --- a/src/lib-mail/message-part-data.h +++ b/src/lib-mail/message-part-data.h @@ -2,6 +2,7 @@ #define MESSAGE_PART_DATA_H #include "message-part.h" +#include "message-address.h" #define MESSAGE_PART_DEFAULT_CHARSET "us-ascii" @@ -14,8 +15,9 @@ struct message_part_param { struct message_part_envelope { const char *date, *subject; - struct message_address *from, *sender, *reply_to; - struct message_address *to, *cc, *bcc; + + struct message_address_list from, sender, reply_to; + struct message_address_list to, cc, bcc; const char *in_reply_to, *message_id; }; diff --git a/src/lib-mail/test-message-address.c b/src/lib-mail/test-message-address.c index e6204bb..54aa9a8 100644 --- a/src/lib-mail/test-message-address.c +++ b/src/lib-mail/test-message-address.c @@ -19,8 +19,9 @@ static bool cmp_addr(const struct message_address *a1, a1->invalid_syntax == a2->invalid_syntax; } -static const struct message_address * -test_parse_address(const char *input, bool fill_missing) +static void +test_parse_address_full(const char *input, bool fill_missing, + struct message_address_list *list_r) { const enum message_address_parse_flags flags = fill_missing ? MESSAGE_ADDRESS_PARSE_FLAG_FILL_MISSING : 0; @@ -28,11 +29,18 @@ test_parse_address(const char *input, bool fill_missing) if there's any out-of-bounds access */ size_t input_len = strlen(input); unsigned char *input_dup = i_memdup(input, input_len); - const struct message_address *addr = - message_address_parse(pool_datastack_create(), - input_dup, input_len, UINT_MAX, flags); + message_address_parse_full(pool_datastack_create(), + input_dup, input_len, UINT_MAX, flags, + list_r); i_free(input_dup); - return addr; +} + +static const struct message_address * +test_parse_address(const char *input, bool fill_missing) +{ + struct message_address_list list; + test_parse_address_full(input, fill_missing, &list); + return list.head; } static void test_message_address(void) @@ -47,174 +55,174 @@ static void test_message_address(void) } tests[] = { /* user@domain -> <user@domain> */ { "user@domain", "<user@domain>", NULL, - { NULL, NULL, NULL, "user", "domain", FALSE }, - { NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, { "\"user\"@domain", "<user@domain>", NULL, - { NULL, NULL, NULL, "user", "domain", FALSE }, - { NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, { "\"user name\"@domain", "<\"user name\"@domain>", NULL, - { NULL, NULL, NULL, "user name", "domain", FALSE }, - { NULL, NULL, NULL, "user name", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, NULL, "user name", "domain", FALSE }, + { NULL, NULL, NULL, NULL, "user name", "domain", FALSE }, 0 }, { "\"user@na\\\\me\"@domain", "<\"user@na\\\\me\"@domain>", NULL, - { NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, - { NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, + { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, 0 }, { "\"user\\\"name\"@domain", "<\"user\\\"name\"@domain>", NULL, - { NULL, NULL, NULL, "user\"name", "domain", FALSE }, - { NULL, NULL, NULL, "user\"name", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE }, + { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE }, 0 }, { "\"\"@domain", "<\"\"@domain>", NULL, - { NULL, NULL, NULL, "", "domain", FALSE }, - { NULL, NULL, NULL, "", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, NULL, "", "domain", FALSE }, + { NULL, NULL, NULL, NULL, "", "domain", FALSE }, 0 }, { "user", "<user>", "<user@MISSING_DOMAIN>", - { NULL, NULL, NULL, "user", "", TRUE }, - { NULL, NULL, NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, NULL, NULL, "user", "", TRUE }, + { NULL, NULL, NULL, NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, { "@domain", "<\"\"@domain>", "<MISSING_MAILBOX@domain>", - { NULL, NULL, NULL, "", "domain", TRUE }, - { NULL, NULL, NULL, "MISSING_MAILBOX", "domain", TRUE }, 0 }, + { NULL, NULL, NULL, NULL, "", "domain", TRUE }, + { NULL, NULL, NULL, NULL, "MISSING_MAILBOX", "domain", TRUE }, 0 }, /* Display Name -> Display Name */ { "Display Name", "\"Display Name\"", "\"Display Name\" <MISSING_MAILBOX@MISSING_DOMAIN>", - { NULL, "Display Name", NULL, "", "", TRUE }, - { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, "Display Name", NULL, "", "", TRUE }, + { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, { "\"Display Name\"", "\"Display Name\"", "\"Display Name\" <MISSING_MAILBOX@MISSING_DOMAIN>", - { NULL, "Display Name", NULL, "", "", TRUE }, - { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, "Display Name", NULL, "", "", TRUE }, + { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, { "Display \"Name\"", "\"Display Name\"", "\"Display Name\" <MISSING_MAILBOX@MISSING_DOMAIN>", - { NULL, "Display Name", NULL, "", "", TRUE }, - { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, "Display Name", NULL, "", "", TRUE }, + { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, { "\"Display\" \"Name\"", "\"Display Name\"", "\"Display Name\" <MISSING_MAILBOX@MISSING_DOMAIN>", - { NULL, "Display Name", NULL, "", "", TRUE }, - { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, "Display Name", NULL, "", "", TRUE }, + { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, { "\"\"", "", "<MISSING_MAILBOX@MISSING_DOMAIN>", - { NULL, "", NULL, "", "", TRUE }, - { NULL, "", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, "", NULL, "", "", TRUE }, + { NULL, NULL, "", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, /* <user@domain> -> <user@domain> */ { "<user@domain>", NULL, NULL, - { NULL, NULL, NULL, "user", "domain", FALSE }, - { NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, { "<\"user\"@domain>", "<user@domain>", NULL, - { NULL, NULL, NULL, "user", "domain", FALSE }, - { NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, { "<\"user name\"@domain>", NULL, NULL, - { NULL, NULL, NULL, "user name", "domain", FALSE }, - { NULL, NULL, NULL, "user name", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, NULL, "user name", "domain", FALSE }, + { NULL, NULL, NULL, NULL, "user name", "domain", FALSE }, 0 }, { "<\"user@na\\\\me\"@domain>", NULL, NULL, - { NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, - { NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, + { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, 0 }, { "<\"user\\\"name\"@domain>", NULL, NULL, - { NULL, NULL, NULL, "user\"name", "domain", FALSE }, - { NULL, NULL, NULL, "user\"name", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE }, + { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE }, 0 }, { "<\"\"@domain>", NULL, NULL, - { NULL, NULL, NULL, "", "domain", FALSE }, - { NULL, NULL, NULL, "", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, NULL, "", "domain", FALSE }, + { NULL, NULL, NULL, NULL, "", "domain", FALSE }, 0 }, { "<user>", NULL, "<user@MISSING_DOMAIN>", - { NULL, NULL, NULL, "user", "", TRUE }, - { NULL, NULL, NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, NULL, NULL, "user", "", TRUE }, + { NULL, NULL, NULL, NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, { "<@route>", "<@route:\"\">", "<INVALID_ROUTE:MISSING_MAILBOX@MISSING_DOMAIN>", - { NULL, NULL, "@route", "", "", TRUE }, - { NULL, NULL, "INVALID_ROUTE", "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, NULL, "@route", "", "", TRUE }, + { NULL, NULL, NULL, "INVALID_ROUTE", "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, /* user@domain (Display Name) -> "Display Name" <user@domain> */ { "user@domain (DisplayName)", "DisplayName <user@domain>", NULL, - { NULL, "DisplayName", NULL, "user", "domain", FALSE }, - { NULL, "DisplayName", NULL, "user", "domain", FALSE }, 0 }, + { NULL, NULL, "DisplayName", NULL, "user", "domain", FALSE }, + { NULL, NULL, "DisplayName", NULL, "user", "domain", FALSE }, 0 }, { "user@domain (Display Name)", "\"Display Name\" <user@domain>", NULL, - { NULL, "Display Name", NULL, "user", "domain", FALSE }, - { NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 }, + { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, + { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 }, { "user@domain (Display\"Name)", "\"Display\\\"Name\" <user@domain>", NULL, - { NULL, "Display\"Name", NULL, "user", "domain", FALSE }, - { NULL, "Display\"Name", NULL, "user", "domain", FALSE }, 0 }, + { NULL, NULL, "Display\"Name", NULL, "user", "domain", FALSE }, + { NULL, NULL, "Display\"Name", NULL, "user", "domain", FALSE }, 0 }, { "user (Display Name)", "\"Display Name\" <user>", "\"Display Name\" <user@MISSING_DOMAIN>", - { NULL, "Display Name", NULL, "user", "", TRUE }, - { NULL, "Display Name", NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, "Display Name", NULL, "user", "", TRUE }, + { NULL, NULL, "Display Name", NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, { "@domain (Display Name)", "\"Display Name\" <\"\"@domain>", "\"Display Name\" <MISSING_MAILBOX@domain>", - { NULL, "Display Name", NULL, "", "domain", TRUE }, - { NULL, "Display Name", NULL, "MISSING_MAILBOX", "domain", TRUE }, 0 }, + { NULL, NULL, "Display Name", NULL, "", "domain", TRUE }, + { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "domain", TRUE }, 0 }, { "user@domain ()", "<user@domain>", NULL, - { NULL, NULL, NULL, "user", "domain", FALSE }, - { NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, /* Display Name <user@domain> -> "Display Name" <user@domain> */ { "DisplayName <user@domain>", NULL, NULL, - { NULL, "DisplayName", NULL, "user", "domain", FALSE }, - { NULL, "DisplayName", NULL, "user", "domain", FALSE }, 0 }, + { NULL, NULL, "DisplayName", NULL, "user", "domain", FALSE }, + { NULL, NULL, "DisplayName", NULL, "user", "domain", FALSE }, 0 }, { "Display Name <user@domain>", "\"Display Name\" <user@domain>", NULL, - { NULL, "Display Name", NULL, "user", "domain", FALSE }, - { NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 }, + { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, + { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 }, { "\"Display Name\" <user@domain>", NULL, NULL, - { NULL, "Display Name", NULL, "user", "domain", FALSE }, - { NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 }, + { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, + { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 }, { "\"Display\\\"Name\" <user@domain>", NULL, NULL, - { NULL, "Display\"Name", NULL, "user", "domain", FALSE }, - { NULL, "Display\"Name", NULL, "user", "domain", FALSE }, 0 }, + { NULL, NULL, "Display\"Name", NULL, "user", "domain", FALSE }, + { NULL, NULL, "Display\"Name", NULL, "user", "domain", FALSE }, 0 }, { "Display Name <user>", "\"Display Name\" <user>", "\"Display Name\" <user@MISSING_DOMAIN>", - { NULL, "Display Name", NULL, "user", "", TRUE }, - { NULL, "Display Name", NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, "Display Name", NULL, "user", "", TRUE }, + { NULL, NULL, "Display Name", NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, { "\"\" <user@domain>", "<user@domain>", NULL, - { NULL, NULL, NULL, "user", "domain", FALSE }, - { NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, /* <@route:user@domain> -> <@route:user@domain> */ { "<@route:user@domain>", NULL, NULL, - { NULL, NULL, "@route", "user", "domain", FALSE }, - { NULL, NULL, "@route", "user", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, "@route", "user", "domain", FALSE }, + { NULL, NULL, NULL, "@route", "user", "domain", FALSE }, 0 }, { "<@route,@route2:user@domain>", NULL, NULL, - { NULL, NULL, "@route,@route2", "user", "domain", FALSE }, - { NULL, NULL, "@route,@route2", "user", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, "@route,@route2", "user", "domain", FALSE }, + { NULL, NULL, NULL, "@route,@route2", "user", "domain", FALSE }, 0 }, { "<@route@route2:user@domain>", "<@route,@route2:user@domain>", NULL, - { NULL, NULL, "@route,@route2", "user", "domain", FALSE }, - { NULL, NULL, "@route,@route2", "user", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, "@route,@route2", "user", "domain", FALSE }, + { NULL, NULL, NULL, "@route,@route2", "user", "domain", FALSE }, 0 }, { "<@route@route2:user>", "<@route,@route2:user>", "<@route,@route2:user@MISSING_DOMAIN>", - { NULL, NULL, "@route,@route2", "user", "", TRUE }, - { NULL, NULL, "@route,@route2", "user", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, NULL, "@route,@route2", "user", "", TRUE }, + { NULL, NULL, NULL, "@route,@route2", "user", "MISSING_DOMAIN", TRUE }, 0 }, { "<@route@route2:\"\"@domain>", "<@route,@route2:\"\"@domain>", NULL, - { NULL, NULL, "@route,@route2", "", "domain", FALSE }, - { NULL, NULL, "@route,@route2", "", "domain", FALSE }, 0 }, + { NULL, NULL, NULL, "@route,@route2", "", "domain", FALSE }, + { NULL, NULL, NULL, "@route,@route2", "", "domain", FALSE }, 0 }, /* Display Name <@route:user@domain> -> "Display Name" <@route:user@domain> */ { "Display Name <@route:user@domain>", "\"Display Name\" <@route:user@domain>", NULL, - { NULL, "Display Name", "@route", "user", "domain", FALSE }, - { NULL, "Display Name", "@route", "user", "domain", FALSE }, 0 }, + { NULL, NULL, "Display Name", "@route", "user", "domain", FALSE }, + { NULL, NULL, "Display Name", "@route", "user", "domain", FALSE }, 0 }, { "Display Name <@route,@route2:user@domain>", "\"Display Name\" <@route,@route2:user@domain>", NULL, - { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, - { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, 0 }, + { NULL, NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, + { NULL, NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, 0 }, { "Display Name <@route@route2:user@domain>", "\"Display Name\" <@route,@route2:user@domain>", NULL, - { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, - { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, 0 }, + { NULL, NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, + { NULL, NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, 0 }, { "Display Name <@route@route2:user>", "\"Display Name\" <@route,@route2:user>", "\"Display Name\" <@route,@route2:user@MISSING_DOMAIN>", - { NULL, "Display Name", "@route,@route2", "user", "", TRUE }, - { NULL, "Display Name", "@route,@route2", "user", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, "Display Name", "@route,@route2", "user", "", TRUE }, + { NULL, NULL, "Display Name", "@route,@route2", "user", "MISSING_DOMAIN", TRUE }, 0 }, { "Display Name <@route@route2:\"\"@domain>", "\"Display Name\" <@route,@route2:\"\"@domain>", NULL, - { NULL, "Display Name", "@route,@route2", "", "domain", FALSE }, - { NULL, "Display Name", "@route,@route2", "", "domain", FALSE }, 0 }, + { NULL, NULL, "Display Name", "@route,@route2", "", "domain", FALSE }, + { NULL, NULL, "Display Name", "@route,@route2", "", "domain", FALSE }, 0 }, /* other tests: */ { "\"foo: <a@b>;,\" <user@domain>", NULL, NULL, - { NULL, "foo: <a@b>;,", NULL, "user", "domain", FALSE }, - { NULL, "foo: <a@b>;,", NULL, "user", "domain", FALSE }, 0 }, + { NULL, NULL, "foo: <a@b>;,", NULL, "user", "domain", FALSE }, + { NULL, NULL, "foo: <a@b>;,", NULL, "user", "domain", FALSE }, 0 }, { "<>", "", "<MISSING_MAILBOX@MISSING_DOMAIN>", - { NULL, NULL, NULL, "", "", TRUE }, - { NULL, NULL, NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, NULL, NULL, "", "", TRUE }, + { NULL, NULL, NULL, NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, { "<@>", "", "<INVALID_ROUTE:MISSING_MAILBOX@MISSING_DOMAIN>", - { NULL, NULL, NULL, "", "", TRUE }, - { NULL, NULL, "INVALID_ROUTE", "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, NULL, NULL, "", "", TRUE }, + { NULL, NULL, NULL, "INVALID_ROUTE", "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, /* Test against a out-of-bounds read bug - keep these two tests together in this same order: */ { "aaaa@", "<aaaa>", "<aaaa@MISSING_DOMAIN>", - { NULL, NULL, NULL, "aaaa", "", TRUE }, - { NULL, NULL, NULL, "aaaa", "MISSING_DOMAIN", TRUE }, 0 }, + { NULL, NULL, NULL, NULL, "aaaa", "", TRUE }, + { NULL, NULL, NULL, NULL, "aaaa", "MISSING_DOMAIN", TRUE }, 0 }, { "a(aa", "", "<MISSING_MAILBOX@MISSING_DOMAIN>", - { NULL, NULL, NULL, "", "", TRUE }, - { NULL, NULL, NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, + { NULL, NULL, NULL, NULL, "", "", TRUE }, + { NULL, NULL, NULL, NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, TEST_MESSAGE_ADDRESS_FLAG_SKIP_LIST }, }; static struct message_address group_prefix = { - NULL, NULL, NULL, "group", NULL, FALSE + NULL, NULL, NULL, NULL, "group", NULL, FALSE }; static struct message_address group_suffix = { - NULL, NULL, NULL, NULL, NULL, FALSE + NULL, NULL, NULL, NULL, NULL, NULL, FALSE }; const struct message_address *addr; string_t *str, *group; @@ -322,12 +330,39 @@ static void test_message_address(void) test_end(); } +static void test_message_address_list(void) +{ + test_begin("message address list"); + + const char *test_input = + "user1@example1.com, user2@example2.com, user3@example3.com"; + const struct message_address wanted_addrs[] = { + { NULL, NULL, NULL, NULL, "user1", "example1.com", FALSE }, + { NULL, NULL, NULL, NULL, "user2", "example2.com", FALSE }, + { NULL, NULL, NULL, NULL, "user3", "example3.com", FALSE }, + }; + + struct message_address_list list; + struct message_address *addr, *scanned_last_addr; + test_parse_address_full(test_input, FALSE, &list); + addr = list.head; + for (unsigned int i = 0; i < N_ELEMENTS(wanted_addrs); i++) { + test_assert_idx(cmp_addr(addr, &wanted_addrs[i]), i); + scanned_last_addr = addr; + addr = addr->next; + } + test_assert(list.tail == scanned_last_addr); + test_assert(addr == NULL); + + test_end(); +} + static void test_message_address_nuls(void) { const unsigned char input[] = "\"user\0nuls\\\0-esc\"@[domain\0nuls\\\0-esc] (comment\0nuls\\\0-esc)"; const struct message_address output = { - NULL, "comment\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", NULL, + NULL, NULL, "comment\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", NULL, "user\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", "[domain\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc]", FALSE }; @@ -345,7 +380,7 @@ static void test_message_address_nuls_display_name(void) const unsigned char input[] = "\"displayname\0nuls\\\0-esc\" <\"user\0nuls\\\0-esc\"@[domain\0nuls\\\0-esc]>"; const struct message_address output = { - NULL, "displayname\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", NULL, + NULL, NULL, "displayname\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", NULL, "user\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", "[domain\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc]", FALSE }; @@ -369,7 +404,7 @@ static void test_message_address_non_strict_dots(void) }; const struct message_address *addr; struct message_address output = { - NULL, NULL, NULL, "local-part", + NULL, NULL, NULL, NULL, "local-part", "example.com", FALSE }; @@ -421,29 +456,29 @@ static void test_message_address_path(void) struct message_address addr; } tests[] = { { "<>", NULL, - { NULL, NULL, NULL, NULL, NULL, FALSE } }, + { NULL, NULL, NULL, NULL, NULL, NULL, FALSE } }, { " < > ", "<>", - { NULL, NULL, NULL, NULL, NULL, FALSE } }, + { NULL, NULL, NULL, NULL, NULL, NULL, FALSE } }, { "<user@domain>", NULL, - { NULL, NULL, NULL, "user", "domain", FALSE } }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE } }, { " <user@domain> ", "<user@domain>", - { NULL, NULL, NULL, "user", "domain", FALSE } }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE } }, { "user@domain", "<user@domain>", - { NULL, NULL, NULL, "user", "domain", FALSE } }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE } }, { " user@domain ", "<user@domain>", - { NULL, NULL, NULL, "user", "domain", FALSE } }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE } }, { "<\"user\"@domain>", "<user@domain>", - { NULL, NULL, NULL, "user", "domain", FALSE } }, + { NULL, NULL, NULL, NULL, "user", "domain", FALSE } }, { "<\"user name\"@domain>", NULL, - { NULL, NULL, NULL, "user name", "domain", FALSE } }, + { NULL, NULL, NULL, NULL, "user name", "domain", FALSE } }, { "<\"user@na\\\\me\"@domain>", NULL, - { NULL, NULL, NULL, "user@na\\me", "domain", FALSE } }, + { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE } }, { "<\"user\\\"name\"@domain>", NULL, - { NULL, NULL, NULL, "user\"name", "domain", FALSE } }, + { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE } }, { "<\"\"@domain>", NULL, - { NULL, NULL, NULL, "", "domain", FALSE } }, + { NULL, NULL, NULL, NULL, "", "domain", FALSE } }, { "<@source", "<>", - { NULL, NULL, NULL, NULL, NULL, TRUE } }, + { NULL, NULL, NULL, NULL, NULL, NULL, TRUE } }, }; const struct message_address *addr; string_t *str; @@ -521,6 +556,7 @@ int main(void) { static void (*const test_functions[])(void) = { test_message_address, + test_message_address_list, test_message_address_nuls, test_message_address_nuls_display_name, test_message_address_non_strict_dots, diff --git a/src/lib-mail/test-message-header-parser.c b/src/lib-mail/test-message-header-parser.c index 700d341..93d8842 100644 --- a/src/lib-mail/test-message-header-parser.c +++ b/src/lib-mail/test-message-header-parser.c @@ -463,6 +463,71 @@ static void test_message_header_parser_extra_crlf_in_name(void) test_end(); } +#define assert_parsed_field(line, expected, actual, len) STMT_START { \ + test_assert_idx(memcmp(expected, actual, strlen(expected)) == 0, line); \ + test_assert_cmp_idx(strlen(expected), ==, len, line); \ +} STMT_END + +/* NOTE: implicit variables: (parser, hdr) */ +#define assert_parse_line(line, exp_name, exp_value, exp_full) STMT_START { \ + test_assert_idx(message_parse_header_next(parser, &hdr) > 0, line); \ + assert_parsed_field(line, exp_name, hdr->name, hdr->name_len); \ + assert_parsed_field(line, exp_value, hdr->value, hdr->value_len); \ + assert_parsed_field(line, exp_full, hdr->full_value, hdr->full_value_len); \ + if (hdr->continues) hdr->use_full_value = TRUE; \ +} STMT_END + +static const unsigned char test_message_header_truncation_input[] = + /*01*/ "header1: this is short\n" + /*02*/ "header2: this is multiline\n" + /*03*/ " and long 343638404244464850525456586062\n" + /*04*/ " 64666870727476788082848688909294969800\n" + /*05*/ " 02040608101214161820222426283032343638\n" + /*06*/ "header3: I should not appear at all\n" + /*07*/ "\n"; + +static void test_message_header_truncation_clean_oneline(void) +{ + test_begin("message header parser truncate + CLEAN_ONELINE flag"); + struct message_header_line *hdr = NULL; + struct istream *input = test_istream_create_data(test_message_header_truncation_input, sizeof(test_message_header_truncation_input)); + struct message_header_parser_ctx *parser = message_parse_header_init(input, NULL, MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE); + message_parse_header_set_limit(parser, 96); + + assert_parse_line( 1, "header1", "this is short", "this is short"); + assert_parse_line( 2, "header2", "this is multiline", "this is multiline"); + assert_parse_line( 3, "header2", " and long 343638404244464850525456586062", "this is multiline and long 343638404244464850525456586062"); + assert_parse_line( 4, "header2", " 64666870727476788082848688909294969800", "this is multiline and long 343638404244464850525456586062 6466687072747678808284868"); + assert_parse_line( 5, "header2", " 02040608101214161820222426283032343638", "this is multiline and long 343638404244464850525456586062 6466687072747678808284868"); + assert_parse_line( 6, "header3", "", ""); + test_assert(message_parse_header_next(parser, &hdr) > 0 && hdr->eoh); + + message_parse_header_deinit(&parser); + i_stream_unref(&input); + test_end(); +} + +static void test_message_header_truncation_flag0(void) +{ + test_begin("message header parser truncate + NO flags"); + struct message_header_line *hdr = NULL; + struct istream *input = test_istream_create_data(test_message_header_truncation_input, sizeof(test_message_header_truncation_input)); + struct message_header_parser_ctx *parser = message_parse_header_init(input, NULL, 0); + message_parse_header_set_limit(parser, 96); + + assert_parse_line( 1, "header1", "this is short", "this is short"); + assert_parse_line( 2, "header2", "this is multiline", "this is multiline"); + assert_parse_line( 3, "header2", " and long 343638404244464850525456586062", "this is multiline\n and long 343638404244464850525456586062"); + assert_parse_line( 4, "header2", " 64666870727476788082848688909294969800", "this is multiline\n and long 343638404244464850525456586062\n 646668707274767880828486"); + assert_parse_line( 5, "header2", " 02040608101214161820222426283032343638", "this is multiline\n and long 343638404244464850525456586062\n 646668707274767880828486"); + assert_parse_line( 6, "header3", "", ""); + test_assert(message_parse_header_next(parser, &hdr) > 0 && hdr->eoh); + + message_parse_header_deinit(&parser); + i_stream_unref(&input); + test_end(); +} + int main(void) { static void (*const test_functions[])(void) = { @@ -473,6 +538,8 @@ int main(void) test_message_header_parser_no_eoh, test_message_header_parser_nul, test_message_header_parser_extra_crlf_in_name, + test_message_header_truncation_flag0, + test_message_header_truncation_clean_oneline, NULL }; return test_run(test_functions); diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c index 663bfe8..b6bada2 100644 --- a/src/lib-mail/test-message-parser.c +++ b/src/lib-mail/test-message-parser.c @@ -1369,6 +1369,158 @@ static const char input_msg[] = test_end(); } +#define test_assert_virtual_size(part) \ + test_assert((part).virtual_size == (part).lines + (part).physical_size) + +#define test_assert_part(part, flags_, children, h_lines, h_size, b_lines, b_size ) \ +STMT_START { \ + test_assert((part)->flags == (flags_)); \ + test_assert((part)->children_count == children); \ + test_assert((part)->header_size.lines == h_lines); \ + test_assert((part)->header_size.physical_size == h_size); \ + test_assert((part)->body_size.lines == b_lines); \ + test_assert((part)->body_size.physical_size == b_size); \ + test_assert_virtual_size((part)->header_size); \ + test_assert_virtual_size((part)->body_size); \ +} STMT_END + +static const enum message_part_flags FLAGS_MULTIPART = + MESSAGE_PART_FLAG_IS_MIME | MESSAGE_PART_FLAG_MULTIPART; +static const enum message_part_flags FLAGS_RFC822 = + MESSAGE_PART_FLAG_IS_MIME | MESSAGE_PART_FLAG_MESSAGE_RFC822; +static const enum message_part_flags FLAGS_TEXT = + MESSAGE_PART_FLAG_IS_MIME | MESSAGE_PART_FLAG_TEXT; + +static const char too_many_header_bytes_input_msg[] = + "Content-Type: multipart/mixed; boundary=\"1\"\n\n" + "--1\n" + "Content-Type: multipart/mixed; boundary=\"2\"\n\n" + "--2\n" + "Content-Type: message/rfc822\n\n" + "Content-Type: text/plain\n\n1-rfc822\n" + "--2\n" + "Content-Type: message/rfc822\n\n" + "Content-Type: text/plain\n\n2-rfc822\n" + "--1\n" + "Content-Type: message/rfc822\n\n" + "Content-Type: text/plain\n\n3-rfc822\n"; + +static void test_message_parser_too_many_header_bytes_run( + const struct message_parser_settings *parser_set, + pool_t *pool_r, struct istream **input_r, + struct message_part **parts_r) +{ + *pool_r = pool_alloconly_create("message parser", 10240); + *input_r = test_istream_create(too_many_header_bytes_input_msg); + struct message_parser_ctx *parser = message_parser_init(*pool_r, *input_r, parser_set); + + int ret; + struct message_block block ATTR_UNUSED; + while ((ret = message_parser_parse_next_block(parser, &block)) > 0); + test_assert(ret < 0); + + message_parser_deinit(&parser, parts_r); +} + +static void test_message_parser_too_many_header_bytes_default(void) +{ + test_begin("message parser too many header bytes default"); + + pool_t pool; + struct istream *input; + struct message_part *part_root; + const struct message_parser_settings parser_set = { .all_headers_max_size = 0 }; + + test_message_parser_too_many_header_bytes_run(&parser_set, &pool, &input, &part_root); + + // test_assert_part(part, flags_, children, h_lines, h_size, b_lines, b_size ) + + test_assert_part(part_root, FLAGS_MULTIPART, 7, 2, 45, 21, 256); + test_assert(part_root->parent == NULL); + + struct message_part *part_1 = part_root->children; + test_assert_part(part_1, FLAGS_MULTIPART, 4, 2, 45, 11, 137); + + struct message_part *part_1_1 = part_1->children; + test_assert_part(part_1_1, FLAGS_RFC822, 1, 2, 30, 2, 34); + + struct message_part *part_1_1_1 = part_1_1->children; + test_assert_part(part_1_1_1, FLAGS_TEXT, 0, 2, 26, 0, 8); + + test_assert(part_1_1_1->next == NULL); + + struct message_part *part_1_2 = part_1_1->next; + test_assert_part(part_1_2, FLAGS_RFC822, 1, 2, 30, 2, 34); + + struct message_part *part_1_2_1 = part_1_2->children; + test_assert_part(part_1_2_1, FLAGS_TEXT, 0, 2, 26, 0, 8); + + test_assert(part_1_2_1->next == NULL); + + test_assert(part_1_2->next == NULL); + + struct message_part *part_2 = part_1->next; + test_assert_part(part_2, FLAGS_RFC822, 1, 2, 30, 3, 35); + + struct message_part *part_2_1 = part_2->children; + test_assert_part(part_2_1, FLAGS_TEXT, 0, 2, 26, 1, 9); + test_assert(part_2_1->next == NULL); + + test_assert(part_2->next == NULL); + + test_assert(part_root->next == NULL); + + test_parsed_parts(input, part_root); + i_stream_unref(&input); + pool_unref(&pool); + test_end(); +} + +static void test_message_parser_too_many_header_bytes_100(void) +{ + test_begin("message parser too many header bytes 100"); + + pool_t pool; + struct istream *input; + struct message_part *part_root; + const struct message_parser_settings parser_set = { .all_headers_max_size = 100 }; + + test_message_parser_too_many_header_bytes_run(&parser_set, &pool, &input, &part_root); + + // test_assert_part(part, flags_, children, h_lines, h_size, b_lines, b_size ) + + test_assert_part(part_root, FLAGS_MULTIPART, 5, 2, 45, 21, 256); + test_assert(part_root->parent == NULL); + + struct message_part *part_1 = part_root->children; + test_assert_part(part_1, FLAGS_MULTIPART, 3, 2, 45, 11, 137); + + struct message_part *part_1_1 = part_1->children; + test_assert_part(part_1_1, FLAGS_RFC822, 1, 2, 30, 2, 34); + + struct message_part *part_1_1_1 = part_1_1->children; + test_assert_part(part_1_1_1, MESSAGE_PART_FLAG_IS_MIME, 0, 2, 26, 0, 8); + + test_assert(part_1_1_1->next == NULL); + + struct message_part *part_1_2 = part_1_1->next; + test_assert_part(part_1_2, MESSAGE_PART_FLAG_IS_MIME, 0, 2, 30, 2, 34); + + test_assert(part_1_2->next == NULL); + + struct message_part *part_2 = part_1->next; + test_assert_part(part_2, MESSAGE_PART_FLAG_IS_MIME, 0, 2, 30, 3, 35); + + test_assert(part_2->next == NULL); + + test_assert(part_root->next == NULL); + + test_parsed_parts(input, part_root); + i_stream_unref(&input); + pool_unref(&pool); + test_end(); +} + int main(void) { static void (*const test_functions[])(void) = { @@ -1392,6 +1544,8 @@ int main(void) test_message_parser_mime_part_limit_rfc822, test_message_parser_mime_version, test_message_parser_mime_version_missing, + test_message_parser_too_many_header_bytes_default, + test_message_parser_too_many_header_bytes_100, NULL }; return test_run(test_functions); |