summaryrefslogtreecommitdiffstats
path: root/src/lib-mail
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-08-26 10:32:01 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-08-26 10:32:01 +0000
commit21103af131d3308ef39ad30c66e0fa0ea87b4525 (patch)
tree1f5669b5b37a04d2d2a6becc2056c5d341d4ab16 /src/lib-mail
parentAdding upstream version 1:2.3.21+dfsg1. (diff)
downloaddovecot-21103af131d3308ef39ad30c66e0fa0ea87b4525.tar.xz
dovecot-21103af131d3308ef39ad30c66e0fa0ea87b4525.zip
Adding upstream version 1:2.3.21.1+dfsg1.upstream/1%2.3.21.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/lib-mail/message-address.c43
-rw-r--r--src/lib-mail/message-address.h12
-rw-r--r--src/lib-mail/message-header-parser.c48
-rw-r--r--src/lib-mail/message-header-parser.h10
-rw-r--r--src/lib-mail/message-parser-private.h2
-rw-r--r--src/lib-mail/message-parser.c15
-rw-r--r--src/lib-mail/message-parser.h6
-rw-r--r--src/lib-mail/message-part-data.c17
-rw-r--r--src/lib-mail/message-part-data.h6
-rw-r--r--src/lib-mail/test-message-address.c274
-rw-r--r--src/lib-mail/test-message-header-parser.c67
-rw-r--r--src/lib-mail/test-message-parser.c154
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);