From 0ffcae6adfe1be48124429484a39acdf32979549 Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 11:14:04 +0300
Subject: [PATCH 01/15] lib-mail: test-message-parser - Add another test for
 boundary matching

---
 src/lib-mail/test-message-parser.c | 71 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 71 insertions(+)

diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c
index 4f4d684083..529a36d59c 100644
--- a/src/lib-mail/test-message-parser.c
+++ b/src/lib-mail/test-message-parser.c
@@ -613,6 +613,76 @@ static const char input_msg[] =
 	test_end();
 }
 
+static void test_message_parser_continuing_mime_boundary_reverse(void)
+{
+static const char input_msg[] =
+"Content-Type: multipart/mixed; boundary=\"ab\"\n"
+"\n"
+"--ab\n"
+"Content-Type: multipart/mixed; boundary=\"a\"\n"
+"\n"
+"--a\n"
+"Content-Type: text/plain\n"
+"\n"
+"body\n"
+"--ab\n"
+"Content-Type: text/html\n"
+"\n"
+"body2\n";
+	struct message_parser_ctx *parser;
+	struct istream *input;
+	struct message_part *parts;
+	struct message_block block;
+	pool_t pool;
+	int ret;
+
+	test_begin("message parser continuing mime boundary reverse");
+	pool = pool_alloconly_create("message parser", 10240);
+	input = test_istream_create(input_msg);
+
+	parser = message_parser_init(pool, input, 0, 0);
+	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
+	test_assert(ret < 0);
+	message_parser_deinit(&parser, &parts);
+
+	test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(parts->header_size.lines == 2);
+	test_assert(parts->header_size.physical_size == 46);
+	test_assert(parts->header_size.virtual_size == 46+2);
+	test_assert(parts->body_size.lines == 11);
+	test_assert(parts->body_size.physical_size == 121);
+	test_assert(parts->body_size.virtual_size == 121+11);
+	test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(parts->children->physical_pos == 51);
+	test_assert(parts->children->header_size.lines == 2);
+	test_assert(parts->children->header_size.physical_size == 45);
+	test_assert(parts->children->header_size.virtual_size == 45+2);
+	test_assert(parts->children->body_size.lines == 3);
+	test_assert(parts->children->body_size.physical_size == 34);
+	test_assert(parts->children->body_size.virtual_size == 34+3);
+	test_assert(parts->children->children->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(parts->children->children->physical_pos == 100);
+	test_assert(parts->children->children->header_size.lines == 2);
+	test_assert(parts->children->children->header_size.physical_size == 26);
+	test_assert(parts->children->children->header_size.virtual_size == 26+2);
+	test_assert(parts->children->children->body_size.lines == 0);
+	test_assert(parts->children->children->body_size.physical_size == 4);
+	test_assert(parts->children->children->body_size.virtual_size == 4);
+	test_assert(parts->children->next->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(parts->children->next->physical_pos == 136);
+	test_assert(parts->children->next->header_size.lines == 2);
+	test_assert(parts->children->next->header_size.physical_size == 25);
+	test_assert(parts->children->next->header_size.virtual_size == 25+2);
+	test_assert(parts->children->next->body_size.lines == 1);
+	test_assert(parts->children->next->body_size.physical_size == 6);
+	test_assert(parts->children->next->body_size.virtual_size == 6+1);
+
+	test_parsed_parts(input, parts);
+	i_stream_unref(&input);
+	pool_unref(&pool);
+	test_end();
+}
+
 static void test_message_parser_no_eoh(void)
 {
 	static const char input_msg[] = "a:b\n";
@@ -653,6 +723,7 @@ int main(void)
 		test_message_parser_garbage_suffix_mime_boundary,
 		test_message_parser_continuing_mime_boundary,
 		test_message_parser_continuing_truncated_mime_boundary,
+		test_message_parser_continuing_mime_boundary_reverse,
 		test_message_parser_no_eoh,
 		NULL
 	};
-- 
2.11.0

From 6defb282bec6b17bd1c0e2f62a4d365b453c39ef Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 11:27:14 +0300
Subject: [PATCH 02/15] lib-mail: test-message-parser - Test that
 children_count is correct

---
 src/lib-mail/test-message-parser.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c
index 529a36d59c..c275707265 100644
--- a/src/lib-mail/test-message-parser.c
+++ b/src/lib-mail/test-message-parser.c
@@ -59,6 +59,7 @@ static bool msg_parts_cmp(struct message_part *p1, struct message_part *p2)
 		    p1->body_size.physical_size != p2->body_size.physical_size ||
 		    p1->body_size.virtual_size != p2->body_size.virtual_size ||
 		    p1->body_size.lines != p2->body_size.lines ||
+		    p1->children_count != p2->children_count ||
 		    p1->flags != p2->flags)
 			return FALSE;
 
@@ -195,6 +196,7 @@ static const char input_msg[] =
 	message_parser_deinit(&parser, &parts);
 
 	test_assert((parts->flags & MESSAGE_PART_FLAG_MULTIPART) != 0);
+	test_assert(parts->children_count == 4);
 	test_assert(parts->header_size.lines == 2);
 	test_assert(parts->header_size.physical_size == 48);
 	test_assert(parts->header_size.virtual_size == 48+2);
@@ -218,6 +220,7 @@ static const char input_msg[] =
 	test_assert(parts->children->next->next->next->header_size.virtual_size == 23);
 	test_assert(parts->children->next->next->next->header_size.lines == 0);
 	for (part = parts->children; part != NULL; part = part->next) {
+		test_assert(part->children_count == 0);
 		test_assert(part->body_size.physical_size == 0);
 		test_assert(part->body_size.virtual_size == 0);
 	}
@@ -258,6 +261,7 @@ static const char input_msg[] =
 	message_parser_deinit(&parser, &parts);
 
 	test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(parts->children_count == 2);
 	test_assert(parts->header_size.lines == 2);
 	test_assert(parts->header_size.physical_size == 46);
 	test_assert(parts->header_size.virtual_size == 46+2);
@@ -265,6 +269,7 @@ static const char input_msg[] =
 	test_assert(parts->body_size.physical_size == 86);
 	test_assert(parts->body_size.virtual_size == 86+8);
 
+	test_assert(parts->children->children_count == 0);
 	test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->children->physical_pos == 51);
 	test_assert(parts->children->header_size.lines == 1);
@@ -274,6 +279,7 @@ static const char input_msg[] =
 	test_assert(parts->children->body_size.physical_size == 0);
 	test_assert(parts->children->children == NULL);
 
+	test_assert(parts->children->next->children_count == 0);
 	test_assert(parts->children->next->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->children->next->physical_pos == 101);
 	test_assert(parts->children->next->header_size.lines == 2);
@@ -310,6 +316,7 @@ static const char input_msg[] =
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
 
+	test_assert(parts->children_count == 0);
 	test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->header_size.lines == 1);
 	test_assert(parts->header_size.physical_size == 45);
@@ -347,6 +354,7 @@ static const char input_msg[] =
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
 
+	test_assert(parts->children_count == 0);
 	test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->header_size.lines == 2);
 	test_assert(parts->header_size.physical_size == 46);
@@ -391,6 +399,7 @@ static const char input_msg[] =
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
 
+	test_assert(parts->children_count == 2);
 	test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->header_size.lines == 2);
 	test_assert(parts->header_size.physical_size == 45);
@@ -398,6 +407,7 @@ static const char input_msg[] =
 	test_assert(parts->body_size.lines == 7);
 	test_assert(parts->body_size.physical_size == 84);
 	test_assert(parts->body_size.virtual_size == 84+7);
+	test_assert(parts->children->children_count == 1);
 	test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->children->physical_pos == 49);
 	test_assert(parts->children->header_size.lines == 2);
@@ -406,6 +416,7 @@ static const char input_msg[] =
 	test_assert(parts->children->body_size.lines == 4);
 	test_assert(parts->children->body_size.physical_size == 35);
 	test_assert(parts->children->body_size.virtual_size == 35+4);
+	test_assert(parts->children->children->children_count == 0);
 	test_assert(parts->children->children->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->children->children->physical_pos == 98);
 	test_assert(parts->children->children->header_size.lines == 2);
@@ -449,6 +460,7 @@ static const char input_msg[] =
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
 
+	test_assert(parts->children_count == 2);
 	test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->header_size.lines == 2);
 	test_assert(parts->header_size.physical_size == 45);
@@ -456,6 +468,7 @@ static const char input_msg[] =
 	test_assert(parts->body_size.lines == 7);
 	test_assert(parts->body_size.physical_size == 86);
 	test_assert(parts->body_size.virtual_size == 86+7);
+	test_assert(parts->children->children_count == 1);
 	test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->children->physical_pos == 50);
 	test_assert(parts->children->header_size.lines == 2);
@@ -464,6 +477,7 @@ static const char input_msg[] =
 	test_assert(parts->children->body_size.lines == 4);
 	test_assert(parts->children->body_size.physical_size == 36);
 	test_assert(parts->children->body_size.virtual_size == 36+4);
+	test_assert(parts->children->children->children_count == 0);
 	test_assert(parts->children->children->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->children->children->physical_pos == 100);
 	test_assert(parts->children->children->header_size.lines == 2);
@@ -507,6 +521,7 @@ static const char input_msg[] =
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
 
+	test_assert(parts->children_count == 2);
 	test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->header_size.lines == 2);
 	test_assert(parts->header_size.physical_size == 45);
@@ -514,6 +529,7 @@ static const char input_msg[] =
 	test_assert(parts->body_size.lines == 7);
 	test_assert(parts->body_size.physical_size == 86);
 	test_assert(parts->body_size.virtual_size == 86+7);
+	test_assert(parts->children->children_count == 1);
 	test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->children->physical_pos == 49);
 	test_assert(parts->children->header_size.lines == 2);
@@ -522,6 +538,7 @@ static const char input_msg[] =
 	test_assert(parts->children->body_size.lines == 4);
 	test_assert(parts->children->body_size.physical_size == 36);
 	test_assert(parts->children->body_size.virtual_size == 36+4);
+	test_assert(parts->children->children->children_count == 0);
 	test_assert(parts->children->children->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->children->children->physical_pos == 100);
 	test_assert(parts->children->children->header_size.lines == 2);
@@ -567,6 +584,7 @@ static const char input_msg[] =
 	message_parser_deinit(&parser, &parts);
 
 	part = parts;
+	test_assert(part->children_count == 3);
 	test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(part->header_size.lines == 2);
 	test_assert(part->header_size.physical_size == 45);
@@ -576,6 +594,7 @@ static const char input_msg[] =
 	test_assert(part->body_size.virtual_size == 112+9);
 
 	part = parts->children;
+	test_assert(part->children_count == 0);
 	test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(part->physical_pos == 49);
 	test_assert(part->header_size.lines == 1);
@@ -589,6 +608,7 @@ static const char input_msg[] =
 	   we could make it, but it would complicate the message-parser even
 	   more. */
 	part = parts->children->next;
+	test_assert(part->children_count == 0);
 	test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(part->physical_pos == 117);
 	test_assert(part->header_size.lines == 1);
@@ -599,6 +619,7 @@ static const char input_msg[] =
 	test_assert(part->children == NULL);
 
 	part = parts->children->next->next;
+	test_assert(part->children_count == 0);
 	test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(part->header_size.lines == 0);
 	test_assert(part->header_size.physical_size == 0);
@@ -645,6 +666,7 @@ static const char input_msg[] =
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
 
+	test_assert(parts->children_count == 3);
 	test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->header_size.lines == 2);
 	test_assert(parts->header_size.physical_size == 46);
@@ -652,6 +674,7 @@ static const char input_msg[] =
 	test_assert(parts->body_size.lines == 11);
 	test_assert(parts->body_size.physical_size == 121);
 	test_assert(parts->body_size.virtual_size == 121+11);
+	test_assert(parts->children->children_count == 1);
 	test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->children->physical_pos == 51);
 	test_assert(parts->children->header_size.lines == 2);
@@ -660,6 +683,7 @@ static const char input_msg[] =
 	test_assert(parts->children->body_size.lines == 3);
 	test_assert(parts->children->body_size.physical_size == 34);
 	test_assert(parts->children->body_size.virtual_size == 34+3);
+	test_assert(parts->children->children->children_count == 0);
 	test_assert(parts->children->children->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->children->children->physical_pos == 100);
 	test_assert(parts->children->children->header_size.lines == 2);
@@ -668,6 +692,7 @@ static const char input_msg[] =
 	test_assert(parts->children->children->body_size.lines == 0);
 	test_assert(parts->children->children->body_size.physical_size == 4);
 	test_assert(parts->children->children->body_size.virtual_size == 4);
+	test_assert(parts->children->next->children_count == 0);
 	test_assert(parts->children->next->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME));
 	test_assert(parts->children->next->physical_pos == 136);
 	test_assert(parts->children->next->header_size.lines == 2);
-- 
2.11.0

From 354d5b43a93b6f2ba80c7fc507b75fb1dc4c8df5 Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 11:47:18 +0300
Subject: [PATCH 03/15] lib-mail: Move message_parser_init_from_parts()
 handling to its own file

This helps to see what code they have in common.
---
 src/lib-mail/Makefile.am                 |   4 +-
 src/lib-mail/message-parser-from-parts.c | 366 +++++++++++++++++++++++++++
 src/lib-mail/message-parser-private.h    |  55 +++++
 src/lib-mail/message-parser.c            | 410 +------------------------------
 4 files changed, 428 insertions(+), 407 deletions(-)
 create mode 100644 src/lib-mail/message-parser-from-parts.c
 create mode 100644 src/lib-mail/message-parser-private.h

diff --git a/src/lib-mail/Makefile.am b/src/lib-mail/Makefile.am
index 8fe43d69d0..57d9e2b8c4 100644
--- a/src/lib-mail/Makefile.am
+++ b/src/lib-mail/Makefile.am
@@ -28,6 +28,7 @@ libmail_la_SOURCES = \
 	message-header-parser.c \
 	message-id.c \
 	message-parser.c \
+	message-parser-from-parts.c \
 	message-part.c \
 	message-part-data.c \
 	message-part-serialize.c \
@@ -42,7 +43,8 @@ libmail_la_SOURCES = \
 	rfc822-parser.c
 
 noinst_HEADERS = \
-	html-entities.h
+	html-entities.h \
+	message-parser-private.h
 
 headers = \
 	istream-attachment-connector.h \
diff --git a/src/lib-mail/message-parser-from-parts.c b/src/lib-mail/message-parser-from-parts.c
new file mode 100644
index 0000000000..b23055ab9b
--- /dev/null
+++ b/src/lib-mail/message-parser-from-parts.c
@@ -0,0 +1,366 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "message-parser-private.h"
+
+static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx,
+					 struct message_block *block_r);
+static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx,
+					    struct message_block *block_r);
+
+static int preparsed_parse_eof(struct message_parser_ctx *ctx ATTR_UNUSED,
+			       struct message_block *block_r ATTR_UNUSED)
+{
+	return -1;
+}
+
+static void preparsed_skip_to_next(struct message_parser_ctx *ctx)
+{
+	ctx->parse_next_block = preparsed_parse_next_header_init;
+	while (ctx->part != NULL) {
+		if (ctx->part->next != NULL) {
+			ctx->part = ctx->part->next;
+			break;
+		}
+
+		/* parse epilogue of multipart parent if requested */
+		if (ctx->part->parent != NULL &&
+		    (ctx->part->parent->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
+		    (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0) {
+			/* check for presence of epilogue */
+			uoff_t part_end = ctx->part->physical_pos +
+				ctx->part->header_size.physical_size +
+				ctx->part->body_size.physical_size;
+			uoff_t parent_end = ctx->part->parent->physical_pos +
+				ctx->part->parent->header_size.physical_size +
+				ctx->part->parent->body_size.physical_size;
+
+			if (parent_end > part_end) {
+				ctx->parse_next_block = preparsed_parse_epilogue_init;
+				break;
+			}
+		}
+		ctx->part = ctx->part->parent;
+	}
+	if (ctx->part == NULL)
+		ctx->parse_next_block = preparsed_parse_eof;
+}
+
+static int preparsed_parse_body_finish(struct message_parser_ctx *ctx,
+				       struct message_block *block_r)
+{
+	i_stream_skip(ctx->input, ctx->skip);
+	ctx->skip = 0;
+
+	preparsed_skip_to_next(ctx);
+	return ctx->parse_next_block(ctx, block_r);
+}
+
+static int preparsed_parse_prologue_finish(struct message_parser_ctx *ctx,
+					   struct message_block *block_r)
+{
+	i_stream_skip(ctx->input, ctx->skip);
+	ctx->skip = 0;
+
+	ctx->parse_next_block = preparsed_parse_next_header_init;
+	ctx->part = ctx->part->children;
+	return ctx->parse_next_block(ctx, block_r);
+}
+
+static int preparsed_parse_body_more(struct message_parser_ctx *ctx,
+				     struct message_block *block_r)
+{
+	uoff_t end_offset = ctx->part->physical_pos +
+		ctx->part->header_size.physical_size +
+		ctx->part->body_size.physical_size;
+	bool full;
+	int ret;
+
+	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
+		return ret;
+
+	if (ctx->input->v_offset + block_r->size >= end_offset) {
+		block_r->size = end_offset - ctx->input->v_offset;
+		ctx->parse_next_block = preparsed_parse_body_finish;
+	}
+	ctx->skip = block_r->size;
+	return 1;
+}
+
+static int preparsed_parse_prologue_more(struct message_parser_ctx *ctx,
+					 struct message_block *block_r)
+{
+	uoff_t boundary_min_start, end_offset;
+	const unsigned char *cur;
+	bool full;
+	int ret;
+
+	i_assert(ctx->part->children != NULL);
+	end_offset = ctx->part->children->physical_pos;
+
+	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
+		return ret;
+
+	if (ctx->input->v_offset + block_r->size >= end_offset) {
+		/* we've got the full prologue: clip off the initial boundary */
+		block_r->size = end_offset - ctx->input->v_offset;
+		cur = block_r->data + block_r->size - 1;
+
+		/* [\r]\n--boundary[\r]\n */ 
+		if (block_r->size < 5 || *cur != '\n') {
+			ctx->broken_reason = "Prologue boundary end not at expected position";
+			return -1;
+		}
+		
+		cur--;
+		if (*cur == '\r') cur--;
+
+		/* find newline just before boundary */
+		for (; cur >= block_r->data; cur--) {
+			if (*cur == '\n') break;
+		}
+
+		if (cur[0] != '\n' || cur[1] != '-' || cur[2] != '-') {
+			ctx->broken_reason = "Prologue boundary beginning not at expected position";
+			return -1;
+		}
+
+		if (cur != block_r->data && cur[-1] == '\r') cur--;
+
+		/* clip boundary */
+		block_r->size = cur - block_r->data;			
+
+		ctx->parse_next_block = preparsed_parse_prologue_finish;
+		ctx->skip = block_r->size;
+		return 1;
+	}
+		
+	/* retain enough data in the stream buffer to contain initial boundary */
+	if (end_offset > BOUNDARY_END_MAX_LEN)
+		boundary_min_start = end_offset - BOUNDARY_END_MAX_LEN;
+	else
+		boundary_min_start = 0;
+
+	if (ctx->input->v_offset + block_r->size >= boundary_min_start) {
+		if (boundary_min_start <= ctx->input->v_offset)
+			return 0;
+		block_r->size = boundary_min_start - ctx->input->v_offset;
+	}
+	ctx->skip = block_r->size;
+	return 1;
+}
+
+static int preparsed_parse_epilogue_more(struct message_parser_ctx *ctx,
+					 struct message_block *block_r)
+{
+	uoff_t end_offset = ctx->part->physical_pos +
+		ctx->part->header_size.physical_size +
+		ctx->part->body_size.physical_size;
+	bool full;
+	int ret;
+
+	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
+		return ret;
+
+	if (ctx->input->v_offset + block_r->size >= end_offset) {
+		block_r->size = end_offset - ctx->input->v_offset;
+		ctx->parse_next_block = preparsed_parse_body_finish;
+	}
+	ctx->skip = block_r->size;
+	return 1;
+}
+
+static int preparsed_parse_epilogue_boundary(struct message_parser_ctx *ctx,
+					     struct message_block *block_r)
+{
+	uoff_t end_offset = ctx->part->physical_pos +
+		ctx->part->header_size.physical_size +
+		ctx->part->body_size.physical_size;
+	const unsigned char *data, *cur;
+	size_t size;
+	bool full;
+	int ret;
+
+	if (end_offset - ctx->input->v_offset < 7) {
+		ctx->broken_reason = "Epilogue position is wrong";
+		return -1;
+	}
+
+	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
+		return ret;
+
+	/* [\r]\n--boundary--[\r]\n */
+	if (block_r->size < 7) {
+		ctx->want_count = 7;
+		return 0;
+	}
+
+	data = block_r->data;
+	size = block_r->size;
+	cur = data;
+
+	if (*cur == '\r') cur++;
+
+	if (cur[0] != '\n' || cur[1] != '-' || data[2] != '-') {
+		ctx->broken_reason = "Epilogue boundary start not at expected position";
+		return -1;
+	}
+
+	/* find the end of the line */
+	cur += 3;
+	if ((cur = memchr(cur, '\n', size - (cur-data))) == NULL) {
+		if (end_offset < ctx->input->v_offset + size) {
+			ctx->broken_reason = "Epilogue boundary end not at expected position";
+			return -1;
+		} else if (ctx->input->v_offset + size < end_offset &&
+			   size < BOUNDARY_END_MAX_LEN &&
+			   !ctx->input->eof && !full) {
+			ctx->want_count = BOUNDARY_END_MAX_LEN;
+			return 0;
+		}
+	}
+
+	block_r->size = 0;
+	ctx->parse_next_block = preparsed_parse_epilogue_more;
+	ctx->skip = cur - data + 1;
+	return 0;
+}
+
+static int preparsed_parse_body_init(struct message_parser_ctx *ctx,
+				     struct message_block *block_r)
+{
+	uoff_t offset = ctx->part->physical_pos +
+		ctx->part->header_size.physical_size;
+
+	if (offset < ctx->input->v_offset) {
+		/* header was actually larger than the cached size suggested */
+		ctx->broken_reason = "Header larger than its cached size";
+		return -1;
+	}
+	i_stream_skip(ctx->input, offset - ctx->input->v_offset);
+
+	/* multipart messages may begin with --boundary--, which makes them
+	   not have any children. */
+	if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 ||
+	    ctx->part->children == NULL)
+		ctx->parse_next_block = preparsed_parse_body_more;
+	else
+		ctx->parse_next_block = preparsed_parse_prologue_more;
+	return ctx->parse_next_block(ctx, block_r);
+}
+
+static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx,
+					 struct message_block *block_r)
+{
+	uoff_t offset = ctx->part->physical_pos +
+		ctx->part->header_size.physical_size +
+		ctx->part->body_size.physical_size;
+
+	ctx->part = ctx->part->parent;
+
+	if (offset < ctx->input->v_offset) {
+		/* last child was actually larger than the cached size
+		   suggested */
+		ctx->broken_reason = "Part larger than its cached size";
+		return -1;
+	}
+	i_stream_skip(ctx->input, offset - ctx->input->v_offset);
+
+	ctx->parse_next_block = preparsed_parse_epilogue_boundary;
+	return ctx->parse_next_block(ctx, block_r);
+}
+
+static int preparsed_parse_finish_header(struct message_parser_ctx *ctx,
+					 struct message_block *block_r)
+{
+	if (ctx->part->children != NULL) {
+		if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
+		    (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0)
+			ctx->parse_next_block = preparsed_parse_body_init;
+		else {
+			ctx->parse_next_block = preparsed_parse_next_header_init;
+			ctx->part = ctx->part->children;
+		}
+	} else if ((ctx->flags & MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK) == 0) {
+		ctx->parse_next_block = preparsed_parse_body_init;
+	} else {
+		preparsed_skip_to_next(ctx);
+	}
+	return ctx->parse_next_block(ctx, block_r);
+}
+
+static int preparsed_parse_next_header(struct message_parser_ctx *ctx,
+				       struct message_block *block_r)
+{
+	struct message_header_line *hdr;
+	int ret;
+
+	ret = message_parse_header_next(ctx->hdr_parser_ctx, &hdr);
+	if (ret == 0 || (ret < 0 && ctx->input->stream_errno != 0)) {
+		ctx->want_count = i_stream_get_data_size(ctx->input) + 1;
+		return ret;
+	}
+
+	if (hdr != NULL) {
+		block_r->hdr = hdr;
+		block_r->size = 0;
+		return 1;
+	}
+	message_parse_header_deinit(&ctx->hdr_parser_ctx);
+
+	ctx->parse_next_block = preparsed_parse_finish_header;
+
+	/* return empty block as end of headers */
+	block_r->hdr = NULL;
+	block_r->size = 0;
+
+	i_assert(ctx->skip == 0);
+	if (ctx->input->v_offset != ctx->part->physical_pos +
+	    ctx->part->header_size.physical_size) {
+		ctx->broken_reason = "Cached header size mismatch";
+		return -1;
+	}
+	return 1;
+}
+
+static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx,
+					    struct message_block *block_r)
+{
+	struct istream *hdr_input;
+
+	i_assert(ctx->hdr_parser_ctx == NULL);
+
+	i_assert(ctx->part->physical_pos >= ctx->input->v_offset);
+	i_stream_skip(ctx->input, ctx->part->physical_pos -
+		      ctx->input->v_offset);
+
+	/* the header may become truncated by --boundaries. limit the header
+	   stream's size to what it's supposed to be to avoid duplicating (and
+	   keeping in sync!) all the same complicated logic as in
+	   parse_next_header(). */
+	hdr_input = i_stream_create_limit(ctx->input, ctx->part->header_size.physical_size);
+	ctx->hdr_parser_ctx =
+		message_parse_header_init(hdr_input, NULL, ctx->hdr_flags);
+	i_stream_unref(&hdr_input);
+
+	ctx->parse_next_block = preparsed_parse_next_header;
+	return preparsed_parse_next_header(ctx, block_r);
+}
+
+struct message_parser_ctx *
+message_parser_init_from_parts(struct message_part *parts,
+			       struct istream *input,
+			       enum message_header_parser_flags hdr_flags,
+			       enum message_parser_flags flags)
+{
+	struct message_parser_ctx *ctx;
+
+	i_assert(parts != NULL);
+
+	ctx = message_parser_init_int(input, hdr_flags, flags);
+	ctx->preparsed = TRUE;
+	ctx->parts = ctx->part = parts;
+	ctx->parse_next_block = preparsed_parse_next_header_init;
+	return ctx;
+}
diff --git a/src/lib-mail/message-parser-private.h b/src/lib-mail/message-parser-private.h
new file mode 100644
index 0000000000..98a576eda4
--- /dev/null
+++ b/src/lib-mail/message-parser-private.h
@@ -0,0 +1,55 @@
+#ifndef MESSAGE_PARSER_PRIVATE_H
+#define MESSAGE_PARSER_PRIVATE_H
+
+#include "message-parser.h"
+
+/* RFC-2046 requires boundaries are max. 70 chars + "--" prefix + "--" suffix.
+   We'll add a bit more just in case. */
+#define BOUNDARY_END_MAX_LEN (70 + 2 + 2 + 10)
+
+struct message_boundary {
+	struct message_boundary *next;
+
+	struct message_part *part;
+	const char *boundary;
+	size_t len;
+
+	bool epilogue_found:1;
+};
+
+struct message_parser_ctx {
+	pool_t parser_pool, part_pool;
+	struct istream *input;
+	struct message_part *parts, *part;
+	const char *broken_reason;
+
+	enum message_header_parser_flags hdr_flags;
+	enum message_parser_flags flags;
+
+	const char *last_boundary;
+	struct message_boundary *boundaries;
+
+	size_t skip;
+	char last_chr;
+	unsigned int want_count;
+
+	struct message_header_parser_ctx *hdr_parser_ctx;
+	unsigned int prev_hdr_newline_size;
+
+	int (*parse_next_block)(struct message_parser_ctx *ctx,
+				struct message_block *block_r);
+
+	bool part_seen_content_type:1;
+	bool multipart:1;
+	bool preparsed:1;
+	bool eof:1;
+};
+
+struct message_parser_ctx *
+message_parser_init_int(struct istream *input,
+			enum message_header_parser_flags hdr_flags,
+			enum message_parser_flags flags);
+int message_parser_read_more(struct message_parser_ctx *ctx,
+			     struct message_block *block_r, bool *full_r);
+
+#endif
diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index e7a4f4cc31..653f964118 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -6,49 +6,7 @@
 #include "istream.h"
 #include "rfc822-parser.h"
 #include "rfc2231-parser.h"
-#include "message-parser.h"
-
-/* RFC-2046 requires boundaries are max. 70 chars + "--" prefix + "--" suffix.
-   We'll add a bit more just in case. */
-#define BOUNDARY_END_MAX_LEN (70 + 2 + 2 + 10)
-
-struct message_boundary {
-	struct message_boundary *next;
-
-	struct message_part *part;
-	const char *boundary;
-	size_t len;
-
-	bool epilogue_found:1;
-};
-
-struct message_parser_ctx {
-	pool_t parser_pool, part_pool;
-	struct istream *input;
-	struct message_part *parts, *part;
-	const char *broken_reason;
-
-	enum message_header_parser_flags hdr_flags;
-	enum message_parser_flags flags;
-
-	const char *last_boundary;
-	struct message_boundary *boundaries;
-
-	size_t skip;
-	char last_chr;
-	unsigned int want_count;
-
-	struct message_header_parser_ctx *hdr_parser_ctx;
-	unsigned int prev_hdr_newline_size;
-
-	int (*parse_next_block)(struct message_parser_ctx *ctx,
-				struct message_block *block_r);
-
-	bool part_seen_content_type:1;
-	bool multipart:1;
-	bool preparsed:1;
-	bool eof:1;
-};
+#include "message-parser-private.h"
 
 message_part_header_callback_t *null_message_part_header_callback = NULL;
 
@@ -58,10 +16,6 @@ static int parse_next_body_to_boundary(struct message_parser_ctx *ctx,
 				       struct message_block *block_r);
 static int parse_next_body_to_eof(struct message_parser_ctx *ctx,
 				  struct message_block *block_r);
-static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx,
-					 struct message_block *block_r);
-static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx,
-					    struct message_block *block_r);
 
 static struct message_boundary *
 boundary_find(struct message_boundary *boundaries,
@@ -122,8 +76,8 @@ static void parse_body_add_block(struct message_parser_ctx *ctx,
 	ctx->part->body_size.virtual_size += block->size + missing_cr_count;
 }
 
-static int message_parser_read_more(struct message_parser_ctx *ctx,
-				    struct message_block *block_r, bool *full_r)
+int message_parser_read_more(struct message_parser_ctx *ctx,
+			     struct message_block *block_r, bool *full_r)
 {
 	int ret;
 
@@ -692,346 +646,7 @@ static int parse_next_header_init(struct message_parser_ctx *ctx,
 	return parse_next_header(ctx, block_r);
 }
 
-static int preparsed_parse_eof(struct message_parser_ctx *ctx ATTR_UNUSED,
-			       struct message_block *block_r ATTR_UNUSED)
-{
-	return -1;
-}
-
-static void preparsed_skip_to_next(struct message_parser_ctx *ctx)
-{
-	ctx->parse_next_block = preparsed_parse_next_header_init;
-	while (ctx->part != NULL) {
-		if (ctx->part->next != NULL) {
-			ctx->part = ctx->part->next;
-			break;
-		}
-
-		/* parse epilogue of multipart parent if requested */
-		if (ctx->part->parent != NULL &&
-		    (ctx->part->parent->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
-		    (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0) {
-			/* check for presence of epilogue */
-			uoff_t part_end = ctx->part->physical_pos +
-				ctx->part->header_size.physical_size +
-				ctx->part->body_size.physical_size;
-			uoff_t parent_end = ctx->part->parent->physical_pos +
-				ctx->part->parent->header_size.physical_size +
-				ctx->part->parent->body_size.physical_size;
-
-			if (parent_end > part_end) {
-				ctx->parse_next_block = preparsed_parse_epilogue_init;
-				break;
-			}
-		}
-		ctx->part = ctx->part->parent;
-	}
-	if (ctx->part == NULL)
-		ctx->parse_next_block = preparsed_parse_eof;
-}
-
-static int preparsed_parse_body_finish(struct message_parser_ctx *ctx,
-				       struct message_block *block_r)
-{
-	i_stream_skip(ctx->input, ctx->skip);
-	ctx->skip = 0;
-
-	preparsed_skip_to_next(ctx);
-	return ctx->parse_next_block(ctx, block_r);
-}
-
-static int preparsed_parse_prologue_finish(struct message_parser_ctx *ctx,
-					   struct message_block *block_r)
-{
-	i_stream_skip(ctx->input, ctx->skip);
-	ctx->skip = 0;
-
-	ctx->parse_next_block = preparsed_parse_next_header_init;
-	ctx->part = ctx->part->children;
-	return ctx->parse_next_block(ctx, block_r);
-}
-
-static int preparsed_parse_body_more(struct message_parser_ctx *ctx,
-				     struct message_block *block_r)
-{
-	uoff_t end_offset = ctx->part->physical_pos +
-		ctx->part->header_size.physical_size +
-		ctx->part->body_size.physical_size;
-	bool full;
-	int ret;
-
-	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
-		return ret;
-
-	if (ctx->input->v_offset + block_r->size >= end_offset) {
-		block_r->size = end_offset - ctx->input->v_offset;
-		ctx->parse_next_block = preparsed_parse_body_finish;
-	}
-	ctx->skip = block_r->size;
-	return 1;
-}
-
-static int preparsed_parse_prologue_more(struct message_parser_ctx *ctx,
-					 struct message_block *block_r)
-{
-	uoff_t boundary_min_start, end_offset;
-	const unsigned char *cur;
-	bool full;
-	int ret;
-
-	i_assert(ctx->part->children != NULL);
-	end_offset = ctx->part->children->physical_pos;
-
-	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
-		return ret;
-
-	if (ctx->input->v_offset + block_r->size >= end_offset) {
-		/* we've got the full prologue: clip off the initial boundary */
-		block_r->size = end_offset - ctx->input->v_offset;
-		cur = block_r->data + block_r->size - 1;
-
-		/* [\r]\n--boundary[\r]\n */ 
-		if (block_r->size < 5 || *cur != '\n') {
-			ctx->broken_reason = "Prologue boundary end not at expected position";
-			return -1;
-		}
-		
-		cur--;
-		if (*cur == '\r') cur--;
-
-		/* find newline just before boundary */
-		for (; cur >= block_r->data; cur--) {
-			if (*cur == '\n') break;
-		}
-
-		if (cur[0] != '\n' || cur[1] != '-' || cur[2] != '-') {
-			ctx->broken_reason = "Prologue boundary beginning not at expected position";
-			return -1;
-		}
-
-		if (cur != block_r->data && cur[-1] == '\r') cur--;
-
-		/* clip boundary */
-		block_r->size = cur - block_r->data;			
-
-		ctx->parse_next_block = preparsed_parse_prologue_finish;
-		ctx->skip = block_r->size;
-		return 1;
-	}
-		
-	/* retain enough data in the stream buffer to contain initial boundary */
-	if (end_offset > BOUNDARY_END_MAX_LEN)
-		boundary_min_start = end_offset - BOUNDARY_END_MAX_LEN;
-	else
-		boundary_min_start = 0;
-
-	if (ctx->input->v_offset + block_r->size >= boundary_min_start) {
-		if (boundary_min_start <= ctx->input->v_offset)
-			return 0;
-		block_r->size = boundary_min_start - ctx->input->v_offset;
-	}
-	ctx->skip = block_r->size;
-	return 1;
-}
-
-static int preparsed_parse_epilogue_more(struct message_parser_ctx *ctx,
-					 struct message_block *block_r)
-{
-	uoff_t end_offset = ctx->part->physical_pos +
-		ctx->part->header_size.physical_size +
-		ctx->part->body_size.physical_size;
-	bool full;
-	int ret;
-
-	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
-		return ret;
-
-	if (ctx->input->v_offset + block_r->size >= end_offset) {
-		block_r->size = end_offset - ctx->input->v_offset;
-		ctx->parse_next_block = preparsed_parse_body_finish;
-	}
-	ctx->skip = block_r->size;
-	return 1;
-}
-
-static int preparsed_parse_epilogue_boundary(struct message_parser_ctx *ctx,
-					     struct message_block *block_r)
-{
-	uoff_t end_offset = ctx->part->physical_pos +
-		ctx->part->header_size.physical_size +
-		ctx->part->body_size.physical_size;
-	const unsigned char *data, *cur;
-	size_t size;
-	bool full;
-	int ret;
-
-	if (end_offset - ctx->input->v_offset < 7) {
-		ctx->broken_reason = "Epilogue position is wrong";
-		return -1;
-	}
-
-	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
-		return ret;
-
-	/* [\r]\n--boundary--[\r]\n */
-	if (block_r->size < 7) {
-		ctx->want_count = 7;
-		return 0;
-	}
-
-	data = block_r->data;
-	size = block_r->size;
-	cur = data;
-
-	if (*cur == '\r') cur++;
-
-	if (cur[0] != '\n' || cur[1] != '-' || data[2] != '-') {
-		ctx->broken_reason = "Epilogue boundary start not at expected position";
-		return -1;
-	}
-
-	/* find the end of the line */
-	cur += 3;
-	if ((cur = memchr(cur, '\n', size - (cur-data))) == NULL) {
-		if (end_offset < ctx->input->v_offset + size) {
-			ctx->broken_reason = "Epilogue boundary end not at expected position";
-			return -1;
-		} else if (ctx->input->v_offset + size < end_offset &&
-			   size < BOUNDARY_END_MAX_LEN &&
-			   !ctx->input->eof && !full) {
-			ctx->want_count = BOUNDARY_END_MAX_LEN;
-			return 0;
-		}
-	}
-
-	block_r->size = 0;
-	ctx->parse_next_block = preparsed_parse_epilogue_more;
-	ctx->skip = cur - data + 1;
-	return 0;
-}
-
-static int preparsed_parse_body_init(struct message_parser_ctx *ctx,
-				     struct message_block *block_r)
-{
-	uoff_t offset = ctx->part->physical_pos +
-		ctx->part->header_size.physical_size;
-
-	if (offset < ctx->input->v_offset) {
-		/* header was actually larger than the cached size suggested */
-		ctx->broken_reason = "Header larger than its cached size";
-		return -1;
-	}
-	i_stream_skip(ctx->input, offset - ctx->input->v_offset);
-
-	/* multipart messages may begin with --boundary--, which makes them
-	   not have any children. */
-	if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 ||
-	    ctx->part->children == NULL)
-		ctx->parse_next_block = preparsed_parse_body_more;
-	else
-		ctx->parse_next_block = preparsed_parse_prologue_more;
-	return ctx->parse_next_block(ctx, block_r);
-}
-
-static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx,
-					 struct message_block *block_r)
-{
-	uoff_t offset = ctx->part->physical_pos +
-		ctx->part->header_size.physical_size +
-		ctx->part->body_size.physical_size;
-
-	ctx->part = ctx->part->parent;
-
-	if (offset < ctx->input->v_offset) {
-		/* last child was actually larger than the cached size
-		   suggested */
-		ctx->broken_reason = "Part larger than its cached size";
-		return -1;
-	}
-	i_stream_skip(ctx->input, offset - ctx->input->v_offset);
-
-	ctx->parse_next_block = preparsed_parse_epilogue_boundary;
-	return ctx->parse_next_block(ctx, block_r);
-}
-
-static int preparsed_parse_finish_header(struct message_parser_ctx *ctx,
-					 struct message_block *block_r)
-{
-	if (ctx->part->children != NULL) {
-		if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
-		    (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0)
-			ctx->parse_next_block = preparsed_parse_body_init;
-		else {
-			ctx->parse_next_block = preparsed_parse_next_header_init;
-			ctx->part = ctx->part->children;
-		}
-	} else if ((ctx->flags & MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK) == 0) {
-		ctx->parse_next_block = preparsed_parse_body_init;
-	} else {
-		preparsed_skip_to_next(ctx);
-	}
-	return ctx->parse_next_block(ctx, block_r);
-}
-
-static int preparsed_parse_next_header(struct message_parser_ctx *ctx,
-				       struct message_block *block_r)
-{
-	struct message_header_line *hdr;
-	int ret;
-
-	ret = message_parse_header_next(ctx->hdr_parser_ctx, &hdr);
-	if (ret == 0 || (ret < 0 && ctx->input->stream_errno != 0)) {
-		ctx->want_count = i_stream_get_data_size(ctx->input) + 1;
-		return ret;
-	}
-
-	if (hdr != NULL) {
-		block_r->hdr = hdr;
-		block_r->size = 0;
-		return 1;
-	}
-	message_parse_header_deinit(&ctx->hdr_parser_ctx);
-
-	ctx->parse_next_block = preparsed_parse_finish_header;
-
-	/* return empty block as end of headers */
-	block_r->hdr = NULL;
-	block_r->size = 0;
-
-	i_assert(ctx->skip == 0);
-	if (ctx->input->v_offset != ctx->part->physical_pos +
-	    ctx->part->header_size.physical_size) {
-		ctx->broken_reason = "Cached header size mismatch";
-		return -1;
-	}
-	return 1;
-}
-
-static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx,
-					    struct message_block *block_r)
-{
-	struct istream *hdr_input;
-
-	i_assert(ctx->hdr_parser_ctx == NULL);
-
-	i_assert(ctx->part->physical_pos >= ctx->input->v_offset);
-	i_stream_skip(ctx->input, ctx->part->physical_pos -
-		      ctx->input->v_offset);
-
-	/* the header may become truncated by --boundaries. limit the header
-	   stream's size to what it's supposed to be to avoid duplicating (and
-	   keeping in sync!) all the same complicated logic as in
-	   parse_next_header(). */
-	hdr_input = i_stream_create_limit(ctx->input, ctx->part->header_size.physical_size);
-	ctx->hdr_parser_ctx =
-		message_parse_header_init(hdr_input, NULL, ctx->hdr_flags);
-	i_stream_unref(&hdr_input);
-
-	ctx->parse_next_block = preparsed_parse_next_header;
-	return preparsed_parse_next_header(ctx, block_r);
-}
-
-static struct message_parser_ctx *
+struct message_parser_ctx *
 message_parser_init_int(struct istream *input,
 			enum message_header_parser_flags hdr_flags,
 			enum message_parser_flags flags)
@@ -1063,23 +678,6 @@ message_parser_init(pool_t part_pool, struct istream *input,
 	return ctx;
 }
 
-struct message_parser_ctx *
-message_parser_init_from_parts(struct message_part *parts,
-			       struct istream *input,
-			       enum message_header_parser_flags hdr_flags,
-			       enum message_parser_flags flags)
-{
-	struct message_parser_ctx *ctx;
-
-	i_assert(parts != NULL);
-
-	ctx = message_parser_init_int(input, hdr_flags, flags);
-	ctx->preparsed = TRUE;
-	ctx->parts = ctx->part = parts;
-	ctx->parse_next_block = preparsed_parse_next_header_init;
-	return ctx;
-}
-
 void message_parser_deinit(struct message_parser_ctx **_ctx,
 			  struct message_part **parts_r)
 {
-- 
2.11.0

From 1765e533f1172e4823413268493e8d4198004f37 Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 11:33:31 +0300
Subject: [PATCH 04/15] lib-mail: message-parser - Add a message_part_finish()
 helper function

---
 src/lib-mail/message-parser.c | 25 ++++++++++++-------------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index 653f964118..9a98cc83a3 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -150,6 +150,13 @@ message_part_append(pool_t pool, struct message_part *parent)
 	return part;
 }
 
+static void message_part_finish(struct message_parser_ctx *ctx)
+{
+	message_size_add(&ctx->part->parent->body_size, &ctx->part->body_size);
+	message_size_add(&ctx->part->parent->body_size, &ctx->part->header_size);
+	ctx->part = ctx->part->parent;
+}
+
 static void parse_next_body_multipart_init(struct message_parser_ctx *ctx)
 {
 	struct message_boundary *b;
@@ -267,19 +274,16 @@ static int parse_part_finish(struct message_parser_ctx *ctx,
 			     struct message_boundary *boundary,
 			     struct message_block *block_r, bool first_line)
 {
-	struct message_part *part;
 	size_t line_size;
 
 	i_assert(ctx->last_boundary == NULL);
 
 	/* get back to parent MIME part, summing the child MIME part sizes
 	   into parent's body sizes */
-	for (part = ctx->part; part != boundary->part; part = part->parent) {
-		message_size_add(&part->parent->body_size, &part->body_size);
-		message_size_add(&part->parent->body_size, &part->header_size);
+	while (ctx->part != boundary->part) {
+		message_part_finish(ctx);
+		i_assert(ctx->part != NULL);
 	}
-	i_assert(part != NULL);
-	ctx->part = part;
 
 	if (boundary->epilogue_found) {
 		/* this boundary isn't needed anymore */
@@ -734,13 +738,8 @@ int message_parser_parse_next_block(struct message_parser_ctx *ctx,
 		i_assert(ctx->input->eof || ctx->input->closed ||
 			 ctx->input->stream_errno != 0 ||
 			 ctx->broken_reason != NULL);
-		while (ctx->part->parent != NULL) {
-			message_size_add(&ctx->part->parent->body_size,
-					 &ctx->part->body_size);
-			message_size_add(&ctx->part->parent->body_size,
-					 &ctx->part->header_size);
-			ctx->part = ctx->part->parent;
-		}
+		while (ctx->part->parent != NULL)
+			message_part_finish(ctx);
 	}
 
 	if (block_r->size == 0) {
-- 
2.11.0

From 2dd9d73d7ec0e64c8a6fa055ce4bc382639d1826 Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 11:34:22 +0300
Subject: [PATCH 05/15] lib-mail: message-parser - Change message_part_append()
 to do all work internally

---
 src/lib-mail/message-parser.c | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index 9a98cc83a3..0edd4d267c 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -122,16 +122,17 @@ int message_parser_read_more(struct message_parser_ctx *ctx,
 	return 1;
 }
 
-static struct message_part *
-message_part_append(pool_t pool, struct message_part *parent)
+static void
+message_part_append(struct message_parser_ctx *ctx)
 {
+	struct message_part *parent = ctx->part;
 	struct message_part *p, *part, **list;
 
 	i_assert(parent != NULL);
 	i_assert((parent->flags & (MESSAGE_PART_FLAG_MULTIPART |
 				   MESSAGE_PART_FLAG_MESSAGE_RFC822)) != 0);
 
-	part = p_new(pool, struct message_part, 1);
+	part = p_new(ctx->part_pool, struct message_part, 1);
 	part->parent = parent;
 	for (p = parent; p != NULL; p = p->parent)
 		p->children_count++;
@@ -147,7 +148,7 @@ message_part_append(pool_t pool, struct message_part *parent)
 		list = &(*list)->next;
 
 	*list = part;
-	return part;
+	ctx->part = part;
 }
 
 static void message_part_finish(struct message_parser_ctx *ctx)
@@ -175,7 +176,7 @@ static void parse_next_body_multipart_init(struct message_parser_ctx *ctx)
 static int parse_next_body_message_rfc822_init(struct message_parser_ctx *ctx,
 					       struct message_block *block_r)
 {
-	ctx->part = message_part_append(ctx->part_pool, ctx->part);
+	message_part_append(ctx);
 	return parse_next_header_init(ctx, block_r);
 }
 
@@ -225,7 +226,7 @@ boundary_line_find(struct message_parser_ctx *ctx,
 static int parse_next_mime_header_init(struct message_parser_ctx *ctx,
 				       struct message_block *block_r)
 {
-	ctx->part = message_part_append(ctx->part_pool, ctx->part);
+	message_part_append(ctx);
 	ctx->part->flags |= MESSAGE_PART_FLAG_IS_MIME;
 
 	return parse_next_header_init(ctx, block_r);
-- 
2.11.0

From 9f565d94ed7962f6c982387c25d093c34edbb5f0 Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 11:36:48 +0300
Subject: [PATCH 06/15] lib-mail: message-parser - Optimize updating
 children_count

---
 src/lib-mail/message-parser.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index 0edd4d267c..f19759c257 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -126,7 +126,7 @@ static void
 message_part_append(struct message_parser_ctx *ctx)
 {
 	struct message_part *parent = ctx->part;
-	struct message_part *p, *part, **list;
+	struct message_part *part, **list;
 
 	i_assert(parent != NULL);
 	i_assert((parent->flags & (MESSAGE_PART_FLAG_MULTIPART |
@@ -134,8 +134,6 @@ message_part_append(struct message_parser_ctx *ctx)
 
 	part = p_new(ctx->part_pool, struct message_part, 1);
 	part->parent = parent;
-	for (p = parent; p != NULL; p = p->parent)
-		p->children_count++;
 
 	/* set child position */
 	part->physical_pos =
@@ -155,6 +153,7 @@ static void message_part_finish(struct message_parser_ctx *ctx)
 {
 	message_size_add(&ctx->part->parent->body_size, &ctx->part->body_size);
 	message_size_add(&ctx->part->parent->body_size, &ctx->part->header_size);
+	ctx->part->parent->children_count += 1 + ctx->part->children_count;
 	ctx->part = ctx->part->parent;
 }
 
-- 
2.11.0

From f543817877ce91e59386eb83144e00e288a19839 Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 12:00:38 +0300
Subject: [PATCH 07/15] lib-mail: message-parser - Optimize appending new part
 to linked list

---
 src/lib-mail/message-parser-private.h |  3 +++
 src/lib-mail/message-parser.c         | 24 ++++++++++++++++++------
 2 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/src/lib-mail/message-parser-private.h b/src/lib-mail/message-parser-private.h
index 98a576eda4..fd92a48776 100644
--- a/src/lib-mail/message-parser-private.h
+++ b/src/lib-mail/message-parser-private.h
@@ -29,6 +29,9 @@ struct message_parser_ctx {
 	const char *last_boundary;
 	struct message_boundary *boundaries;
 
+	struct message_part **next_part;
+	ARRAY(struct message_part **) next_part_stack;
+
 	size_t skip;
 	char last_chr;
 	unsigned int want_count;
diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index f19759c257..0f690ab689 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -1,7 +1,7 @@
 /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
-#include "buffer.h"
+#include "array.h"
 #include "str.h"
 #include "istream.h"
 #include "rfc822-parser.h"
@@ -126,7 +126,7 @@ static void
 message_part_append(struct message_parser_ctx *ctx)
 {
 	struct message_part *parent = ctx->part;
-	struct message_part *part, **list;
+	struct message_part *part;
 
 	i_assert(parent != NULL);
 	i_assert((parent->flags & (MESSAGE_PART_FLAG_MULTIPART |
@@ -141,16 +141,26 @@ message_part_append(struct message_parser_ctx *ctx)
 		parent->body_size.physical_size +
 		parent->header_size.physical_size;
 
-	list = &part->parent->children;
-	while (*list != NULL)
-		list = &(*list)->next;
+	/* add to parent's linked list */
+	*ctx->next_part = part;
+	/* update the parent's end-of-linked-list pointer */
+	struct message_part **next_part = &part->next;
+	array_push_back(&ctx->next_part_stack, &next_part);
+	/* This part is now the new parent for the next message_part_append()
+	   call. Its linked list begins with the children pointer. */
+	ctx->next_part = &part->children;
 
-	*list = part;
 	ctx->part = part;
 }
 
 static void message_part_finish(struct message_parser_ctx *ctx)
 {
+	struct message_part **const *parent_next_partp;
+
+	parent_next_partp = array_back(&ctx->next_part_stack);
+	array_pop_back(&ctx->next_part_stack);
+	ctx->next_part = *parent_next_partp;
+
 	message_size_add(&ctx->part->parent->body_size, &ctx->part->body_size);
 	message_size_add(&ctx->part->parent->body_size, &ctx->part->header_size);
 	ctx->part->parent->children_count += 1 + ctx->part->children_count;
@@ -678,7 +688,9 @@ message_parser_init(pool_t part_pool, struct istream *input,
 	ctx = message_parser_init_int(input, hdr_flags, flags);
 	ctx->part_pool = part_pool;
 	ctx->parts = ctx->part = p_new(part_pool, struct message_part, 1);
+	ctx->next_part = &ctx->part->children;
 	ctx->parse_next_block = parse_next_header_init;
+	p_array_init(&ctx->next_part_stack, ctx->parser_pool, 4);
 	return ctx;
 }
 
-- 
2.11.0

From f080cb2da2064005f35e1d16cd4efded21552491 Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 12:10:07 +0300
Subject: [PATCH 08/15] lib-mail: message-parser - Minor code cleanup to
 finding the end of boundary line

---
 src/lib-mail/message-parser.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index 0f690ab689..88c1b31564 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -211,17 +211,16 @@ boundary_line_find(struct message_parser_ctx *ctx,
 	}
 
 	/* need to find the end of line */
-	if (memchr(data + 2, '\n', size - 2) == NULL &&
-	    size < BOUNDARY_END_MAX_LEN &&
+	data += 2;
+	size -= 2;
+	if (memchr(data, '\n', size) == NULL &&
+	    size+2 < BOUNDARY_END_MAX_LEN &&
 	    !ctx->input->eof && !full) {
 		/* no LF found */
 		ctx->want_count = BOUNDARY_END_MAX_LEN;
 		return 0;
 	}
 
-	data += 2;
-	size -= 2;
-
 	*boundary_r = boundary_find(ctx->boundaries, data, size);
 	if (*boundary_r == NULL)
 		return -1;
-- 
2.11.0

From 6c530af237d5f0486d5fff68d114de905ced9b97 Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 12:53:12 +0300
Subject: [PATCH 09/15] lib-mail: message-parser - Truncate excessively long
 MIME boundaries

RFC 2046 requires that the boundaries are a maximum of 70 characters
(excluding the "--" prefix and suffix). We allow 80 characters for a bit of
extra safety. Anything longer than that is truncated and treated the same
as if it was just 80 characters.
---
 src/lib-mail/message-parser-private.h |  3 +-
 src/lib-mail/message-parser.c         |  4 +-
 src/lib-mail/test-message-parser.c    | 95 +++++++++++++++++++++++++++++++++++
 3 files changed, 100 insertions(+), 2 deletions(-)

diff --git a/src/lib-mail/message-parser-private.h b/src/lib-mail/message-parser-private.h
index fd92a48776..d8116259ad 100644
--- a/src/lib-mail/message-parser-private.h
+++ b/src/lib-mail/message-parser-private.h
@@ -5,7 +5,8 @@
 
 /* RFC-2046 requires boundaries are max. 70 chars + "--" prefix + "--" suffix.
    We'll add a bit more just in case. */
-#define BOUNDARY_END_MAX_LEN (70 + 2 + 2 + 10)
+#define BOUNDARY_STRING_MAX_LEN (70 + 10)
+#define BOUNDARY_END_MAX_LEN (BOUNDARY_STRING_MAX_LEN + 2 + 2)
 
 struct message_boundary {
 	struct message_boundary *next;
diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index 88c1b31564..43142491b2 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -477,8 +477,10 @@ static void parse_content_type(struct message_parser_ctx *ctx,
 	rfc2231_parse(&parser, &results);
 	for (; *results != NULL; results += 2) {
 		if (strcasecmp(results[0], "boundary") == 0) {
+			/* truncate excessively long boundaries */
 			ctx->last_boundary =
-				p_strdup(ctx->parser_pool, results[1]);
+				p_strndup(ctx->parser_pool, results[1],
+					  BOUNDARY_STRING_MAX_LEN);
 			break;
 		}
 	}
diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c
index c275707265..6bf1643e88 100644
--- a/src/lib-mail/test-message-parser.c
+++ b/src/lib-mail/test-message-parser.c
@@ -736,6 +736,100 @@ static void test_message_parser_no_eoh(void)
 	test_end();
 }
 
+static void test_message_parser_long_mime_boundary(void)
+{
+	/* Close the boundaries in wrong reverse order. But because all
+	   boundaries are actually truncated to the same size (..890) it
+	   works the same as if all of them were duplicate boundaries. */
+static const char input_msg[] =
+"Content-Type: multipart/mixed; boundary=\"1234567890123456789012345678901234567890123456789012345678901234567890123456789012\"\n"
+"\n"
+"--1234567890123456789012345678901234567890123456789012345678901234567890123456789012\n"
+"Content-Type: multipart/mixed; boundary=\"123456789012345678901234567890123456789012345678901234567890123456789012345678901\"\n"
+"\n"
+"--123456789012345678901234567890123456789012345678901234567890123456789012345678901\n"
+"Content-Type: multipart/mixed; boundary=\"12345678901234567890123456789012345678901234567890123456789012345678901234567890\"\n"
+"\n"
+"--12345678901234567890123456789012345678901234567890123456789012345678901234567890\n"
+"Content-Type: text/plain\n"
+"\n"
+"1\n"
+"--1234567890123456789012345678901234567890123456789012345678901234567890123456789012\n"
+"Content-Type: text/plain\n"
+"\n"
+"22\n"
+"--123456789012345678901234567890123456789012345678901234567890123456789012345678901\n"
+"Content-Type: text/plain\n"
+"\n"
+"333\n"
+"--12345678901234567890123456789012345678901234567890123456789012345678901234567890\n"
+"Content-Type: text/plain\n"
+"\n"
+"4444\n";
+	struct message_parser_ctx *parser;
+	struct istream *input;
+	struct message_part *parts, *part;
+	struct message_block block;
+	pool_t pool;
+	int ret;
+
+	test_begin("message parser long mime boundary");
+	pool = pool_alloconly_create("message parser", 10240);
+	input = test_istream_create(input_msg);
+
+	parser = message_parser_init(pool, input, 0, 0);
+	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
+	test_assert(ret < 0);
+	message_parser_deinit(&parser, &parts);
+
+	part = parts;
+	test_assert(part->children_count == 6);
+	test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(part->header_size.lines == 2);
+	test_assert(part->header_size.physical_size == 126);
+	test_assert(part->header_size.virtual_size == 126+2);
+	test_assert(part->body_size.lines == 22);
+	test_assert(part->body_size.physical_size == 871);
+	test_assert(part->body_size.virtual_size == 871+22);
+
+	part = parts->children;
+	test_assert(part->children_count == 5);
+	test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(part->header_size.lines == 2);
+	test_assert(part->header_size.physical_size == 125);
+	test_assert(part->header_size.virtual_size == 125+2);
+	test_assert(part->body_size.lines == 19);
+	test_assert(part->body_size.physical_size == 661);
+	test_assert(part->body_size.virtual_size == 661+19);
+
+	part = parts->children->children;
+	test_assert(part->children_count == 4);
+	test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(part->header_size.lines == 2);
+	test_assert(part->header_size.physical_size == 124);
+	test_assert(part->header_size.virtual_size == 124+2);
+	test_assert(part->body_size.lines == 16);
+	test_assert(part->body_size.physical_size == 453);
+	test_assert(part->body_size.virtual_size == 453+16);
+
+	part = parts->children->children->children;
+	for (unsigned int i = 1; i <= 3; i++, part = part->next) {
+		test_assert(part->children_count == 0);
+		test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME));
+		test_assert(part->header_size.lines == 2);
+		test_assert(part->header_size.physical_size == 26);
+		test_assert(part->header_size.virtual_size == 26+2);
+		test_assert(part->body_size.lines == 0);
+		test_assert(part->body_size.physical_size == i);
+		test_assert(part->body_size.virtual_size == i);
+	}
+
+	test_parsed_parts(input, parts);
+	i_stream_unref(&input);
+	pool_unref(&pool);
+	test_end();
+}
+
 int main(void)
 {
 	static void (*const test_functions[])(void) = {
@@ -749,6 +843,7 @@ int main(void)
 		test_message_parser_continuing_mime_boundary,
 		test_message_parser_continuing_truncated_mime_boundary,
 		test_message_parser_continuing_mime_boundary_reverse,
+		test_message_parser_long_mime_boundary,
 		test_message_parser_no_eoh,
 		NULL
 	};
-- 
2.11.0

From f6a31b782088d9f626f4718654cb91cf723f3f9c Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 13:06:02 +0300
Subject: [PATCH 10/15] lib-mail: message-parser - Optimize boundary lookups
 when exact boundary is found

When an exact boundary is found, there's no need to continue looking for
more boundaries.
---
 src/lib-mail/message-parser.c | 24 +++++++++++++++++++++---
 1 file changed, 21 insertions(+), 3 deletions(-)

diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index 43142491b2..f0a0cf41f0 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -31,8 +31,14 @@ boundary_find(struct message_boundary *boundaries,
 	while (boundaries != NULL) {
 		if (boundaries->len <= len &&
 		    memcmp(boundaries->boundary, data, boundaries->len) == 0 &&
-		    (best == NULL || best->len < boundaries->len))
+		    (best == NULL || best->len < boundaries->len)) {
 			best = boundaries;
+			if (best->len == len) {
+				/* This is exactly the wanted boundary. There
+				   can't be a better one. */
+				break;
+			}
+		}
 
 		boundaries = boundaries->next;
 	}
@@ -213,15 +219,27 @@ boundary_line_find(struct message_parser_ctx *ctx,
 	/* need to find the end of line */
 	data += 2;
 	size -= 2;
-	if (memchr(data, '\n', size) == NULL &&
+	const unsigned char *lf_pos = memchr(data, '\n', size);
+	if (lf_pos == NULL &&
 	    size+2 < BOUNDARY_END_MAX_LEN &&
 	    !ctx->input->eof && !full) {
 		/* no LF found */
 		ctx->want_count = BOUNDARY_END_MAX_LEN;
 		return 0;
 	}
+	size_t find_size = size;
+
+	if (lf_pos != NULL) {
+		find_size = lf_pos - data;
+		if (find_size > 0 && data[find_size-1] == '\r')
+			find_size--;
+		if (find_size > 2 && data[find_size-1] == '-' &&
+		    data[find_size-2] == '-')
+			find_size -= 2;
+	} else if (find_size > BOUNDARY_END_MAX_LEN)
+		find_size = BOUNDARY_END_MAX_LEN;
 
-	*boundary_r = boundary_find(ctx->boundaries, data, size);
+	*boundary_r = boundary_find(ctx->boundaries, data, find_size);
 	if (*boundary_r == NULL)
 		return -1;
 
-- 
2.11.0

From 86adf700cc31775744ed48ff5aebe62b97e52c51 Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 14:53:27 +0300
Subject: [PATCH 11/15] lib-mail: message-parser - Add boundary_remove_until()
 helper function

---
 src/lib-mail/message-parser.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index f0a0cf41f0..0fb0f9181a 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -173,6 +173,13 @@ static void message_part_finish(struct message_parser_ctx *ctx)
 	ctx->part = ctx->part->parent;
 }
 
+static void
+boundary_remove_until(struct message_parser_ctx *ctx,
+		      struct message_boundary *boundary)
+{
+	ctx->boundaries = boundary;
+}
+
 static void parse_next_body_multipart_init(struct message_parser_ctx *ctx)
 {
 	struct message_boundary *b;
@@ -314,10 +321,10 @@ static int parse_part_finish(struct message_parser_ctx *ctx,
 
 	if (boundary->epilogue_found) {
 		/* this boundary isn't needed anymore */
-		ctx->boundaries = boundary->next;
+		boundary_remove_until(ctx, boundary->next);
 	} else {
 		/* forget about the boundaries we possibly skipped */
-		ctx->boundaries = boundary;
+		boundary_remove_until(ctx, boundary);
 	}
 
 	/* the boundary itself should already be in buffer. add that. */
-- 
2.11.0

From a6f9ff266c1a2458a1762cdb1206b50497cacb09 Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 15:00:57 +0300
Subject: [PATCH 12/15] lib-mail: message-parser - Don't use memory pool for
 parser

This reduces memory usage when parsing many MIME parts where boundaries are
being added and removed constantly.
---
 src/lib-mail/message-parser-private.h |  6 ++---
 src/lib-mail/message-parser.c         | 41 ++++++++++++++++++++++++-----------
 2 files changed, 31 insertions(+), 16 deletions(-)

diff --git a/src/lib-mail/message-parser-private.h b/src/lib-mail/message-parser-private.h
index d8116259ad..fe106819e2 100644
--- a/src/lib-mail/message-parser-private.h
+++ b/src/lib-mail/message-parser-private.h
@@ -12,14 +12,14 @@ struct message_boundary {
 	struct message_boundary *next;
 
 	struct message_part *part;
-	const char *boundary;
+	char *boundary;
 	size_t len;
 
 	bool epilogue_found:1;
 };
 
 struct message_parser_ctx {
-	pool_t parser_pool, part_pool;
+	pool_t part_pool;
 	struct istream *input;
 	struct message_part *parts, *part;
 	const char *broken_reason;
@@ -27,7 +27,7 @@ struct message_parser_ctx {
 	enum message_header_parser_flags hdr_flags;
 	enum message_parser_flags flags;
 
-	const char *last_boundary;
+	char *last_boundary;
 	struct message_boundary *boundaries;
 
 	struct message_part **next_part;
diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index 0fb0f9181a..c9ff98576b 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -173,10 +173,24 @@ static void message_part_finish(struct message_parser_ctx *ctx)
 	ctx->part = ctx->part->parent;
 }
 
+static void message_boundary_free(struct message_boundary *b)
+{
+	i_free(b->boundary);
+	i_free(b);
+}
+
 static void
 boundary_remove_until(struct message_parser_ctx *ctx,
 		      struct message_boundary *boundary)
 {
+	while (ctx->boundaries != boundary) {
+		struct message_boundary *cur = ctx->boundaries;
+
+		i_assert(cur != NULL);
+		ctx->boundaries = cur->next;
+		message_boundary_free(cur);
+
+	}
 	ctx->boundaries = boundary;
 }
 
@@ -184,15 +198,14 @@ static void parse_next_body_multipart_init(struct message_parser_ctx *ctx)
 {
 	struct message_boundary *b;
 
-	b = p_new(ctx->parser_pool, struct message_boundary, 1);
+	b = i_new(struct message_boundary, 1);
 	b->part = ctx->part;
 	b->boundary = ctx->last_boundary;
+	ctx->last_boundary = NULL;
 	b->len = strlen(b->boundary);
 
 	b->next = ctx->boundaries;
 	ctx->boundaries = b;
-
-	ctx->last_boundary = NULL;
 }
 
 static int parse_next_body_message_rfc822_init(struct message_parser_ctx *ctx,
@@ -309,6 +322,8 @@ static int parse_part_finish(struct message_parser_ctx *ctx,
 			     struct message_block *block_r, bool first_line)
 {
 	size_t line_size;
+	size_t boundary_len = boundary->len;
+	bool boundary_epilogue_found = boundary->epilogue_found;
 
 	i_assert(ctx->last_boundary == NULL);
 
@@ -341,7 +356,7 @@ static int parse_part_finish(struct message_parser_ctx *ctx,
 		i_assert(block_r->data[0] == '\n');
 		line_size = 1;
 	}
-	line_size += 2 + boundary->len + (boundary->epilogue_found ? 2 : 0);
+	line_size += 2 + boundary_len + (boundary_epilogue_found ? 2 : 0);
 	i_assert(block_r->size >= ctx->skip + line_size);
 	block_r->size = line_size;
 	parse_body_add_block(ctx, block_r);
@@ -503,9 +518,9 @@ static void parse_content_type(struct message_parser_ctx *ctx,
 	for (; *results != NULL; results += 2) {
 		if (strcasecmp(results[0], "boundary") == 0) {
 			/* truncate excessively long boundaries */
+			i_free(ctx->last_boundary);
 			ctx->last_boundary =
-				p_strndup(ctx->parser_pool, results[1],
-					  BOUNDARY_STRING_MAX_LEN);
+				i_strndup(results[1], BOUNDARY_STRING_MAX_LEN);
 			break;
 		}
 	}
@@ -628,7 +643,7 @@ static int parse_next_header(struct message_parser_ctx *ctx,
 		i_assert(!ctx->multipart);
 		part->flags = 0;
 	}
-	ctx->last_boundary = NULL;
+	i_free(ctx->last_boundary);
 
 	if (!ctx->part_seen_content_type ||
 	    (part->flags & MESSAGE_PART_FLAG_IS_MIME) == 0) {
@@ -692,11 +707,8 @@ message_parser_init_int(struct istream *input,
 			enum message_parser_flags flags)
 {
 	struct message_parser_ctx *ctx;
-	pool_t pool;
 
-	pool = pool_alloconly_create("Message Parser", 1024);
-	ctx = p_new(pool, struct message_parser_ctx, 1);
-	ctx->parser_pool = pool;
+	ctx = i_new(struct message_parser_ctx, 1);
 	ctx->hdr_flags = hdr_flags;
 	ctx->flags = flags;
 	ctx->input = input;
@@ -716,7 +728,7 @@ message_parser_init(pool_t part_pool, struct istream *input,
 	ctx->parts = ctx->part = p_new(part_pool, struct message_part, 1);
 	ctx->next_part = &ctx->part->children;
 	ctx->parse_next_block = parse_next_header_init;
-	p_array_init(&ctx->next_part_stack, ctx->parser_pool, 4);
+	i_array_init(&ctx->next_part_stack, 4);
 	return ctx;
 }
 
@@ -743,8 +755,11 @@ int message_parser_deinit_from_parts(struct message_parser_ctx **_ctx,
 
 	if (ctx->hdr_parser_ctx != NULL)
 		message_parse_header_deinit(&ctx->hdr_parser_ctx);
+	boundary_remove_until(ctx, NULL);
 	i_stream_unref(&ctx->input);
-	pool_unref(&ctx->parser_pool);
+	array_free(&ctx->next_part_stack);
+	i_free(ctx->last_boundary);
+	i_free(ctx);
 	i_assert(ret < 0 || *parts_r != NULL);
 	return ret;
 }
-- 
2.11.0

From 45cd8d8fd39d301607b22ec6f4ac800331a30225 Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 16:50:56 +0300
Subject: [PATCH 13/15] lib-mail, global: message_parser_init*() - Convert
 flags to settings structure

---
 src/doveadm/doveadm-mail-fetch.c            |  7 +++--
 src/lib-imap/test-imap-bodystructure.c      | 10 +++---
 src/lib-imap/test-imap-envelope.c           | 10 +++---
 src/lib-mail/istream-attachment-extractor.c |  8 +++--
 src/lib-mail/istream-binary-converter.c     |  8 +++--
 src/lib-mail/message-parser-from-parts.c    |  5 ++-
 src/lib-mail/message-parser-private.h       |  3 +-
 src/lib-mail/message-parser.c               | 12 +++-----
 src/lib-mail/message-parser.h               | 11 ++++---
 src/lib-mail/message-search.c               |  9 +++---
 src/lib-mail/message-snippet.c              |  3 +-
 src/lib-mail/test-message-decoder.c         |  4 ++-
 src/lib-mail/test-message-parser.c          | 47 +++++++++++++++++------------
 src/lib-mail/test-message-part.c            |  3 +-
 src/lib-storage/index/index-mail-headers.c  | 22 ++++++--------
 src/plugins/fts/fts-build-mail.c            |  7 +++--
 16 files changed, 95 insertions(+), 74 deletions(-)

diff --git a/src/doveadm/doveadm-mail-fetch.c b/src/doveadm/doveadm-mail-fetch.c
index 75b69e4242..d8b396a199 100644
--- a/src/doveadm/doveadm-mail-fetch.c
+++ b/src/doveadm/doveadm-mail-fetch.c
@@ -265,6 +265,9 @@ static int fetch_text(struct fetch_cmd_context *ctx)
 
 static int fetch_text_utf8(struct fetch_cmd_context *ctx)
 {
+	const struct message_parser_settings parser_set = {
+		.hdr_flags = MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE,
+	};
 	struct istream *input;
 	struct message_parser_ctx *parser;
 	struct message_decoder_context *decoder;
@@ -275,9 +278,7 @@ static int fetch_text_utf8(struct fetch_cmd_context *ctx)
 	if (mail_get_stream(ctx->mail, NULL, NULL, &input) < 0)
 		return -1;
 
-	parser = message_parser_init(pool_datastack_create(), input,
-				     MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE,
-				     0);
+	parser = message_parser_init(pool_datastack_create(), input, &parser_set);
 	decoder = message_decoder_init(NULL, 0);
 
 	while ((ret = message_parser_parse_next_block(parser, &raw_block)) > 0) {
diff --git a/src/lib-imap/test-imap-bodystructure.c b/src/lib-imap/test-imap-bodystructure.c
index 6f456a4453..dfc9957488 100644
--- a/src/lib-imap/test-imap-bodystructure.c
+++ b/src/lib-imap/test-imap-bodystructure.c
@@ -381,6 +381,11 @@ static const unsigned int normalize_tests_count = N_ELEMENTS(normalize_tests);
 static struct message_part *
 msg_parse(pool_t pool, const char *message, bool parse_bodystructure)
 {
+	const struct message_parser_settings parser_set = {
+		.hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
+			MESSAGE_HEADER_PARSER_FLAG_DROP_CR,
+		.flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK,
+	};
 	struct message_parser_ctx *parser;
 	struct istream *input;
 	struct message_block block;
@@ -388,10 +393,7 @@ msg_parse(pool_t pool, const char *message, bool parse_bodystructure)
 	int ret;
 
 	input = i_stream_create_from_data(message, strlen(message));
-	parser = message_parser_init(pool, input,
-			MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
-			MESSAGE_HEADER_PARSER_FLAG_DROP_CR,
-			MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK);
+	parser = message_parser_init(pool, input, &parser_set);
 	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) {
 		if (parse_bodystructure) {
 			message_part_data_parse_from_header(pool, block.part,
diff --git a/src/lib-imap/test-imap-envelope.c b/src/lib-imap/test-imap-envelope.c
index 0d0891701b..1f295e58ba 100644
--- a/src/lib-imap/test-imap-envelope.c
+++ b/src/lib-imap/test-imap-envelope.c
@@ -118,6 +118,11 @@ static const unsigned int parse_tests_count = N_ELEMENTS(parse_tests);
 static struct message_part_envelope *
 msg_parse(pool_t pool, const char *message)
 {
+	const struct message_parser_settings parser_set = {
+		.hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
+			MESSAGE_HEADER_PARSER_FLAG_DROP_CR,
+		.flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK,
+	};
 	struct message_parser_ctx *parser;
 	struct message_part_envelope *envlp = NULL;
 	struct istream *input;
@@ -126,10 +131,7 @@ msg_parse(pool_t pool, const char *message)
 	int ret;
 
 	input = i_stream_create_from_data(message, strlen(message));
-	parser = message_parser_init(pool, input,
-			MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
-			MESSAGE_HEADER_PARSER_FLAG_DROP_CR,
-			MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK);
+	parser = message_parser_init(pool, input, &parser_set);
 	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) {
 		i_assert(block.part->parent == NULL);
 		message_part_envelope_parse_from_header(pool, &envlp, block.hdr);
diff --git a/src/lib-mail/istream-attachment-extractor.c b/src/lib-mail/istream-attachment-extractor.c
index e9655a5a67..7d4ac01072 100644
--- a/src/lib-mail/istream-attachment-extractor.c
+++ b/src/lib-mail/istream-attachment-extractor.c
@@ -696,6 +696,10 @@ i_stream_create_attachment_extractor(struct istream *input,
 				     struct istream_attachment_settings *set,
 				     void *context)
 {
+	const struct message_parser_settings parser_set = {
+		.flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS |
+			MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES,
+	};
 	struct attachment_istream *astream;
 
 	i_assert(set->min_size > 0);
@@ -722,9 +726,7 @@ i_stream_create_attachment_extractor(struct istream *input,
 	astream->istream.istream.seekable = FALSE;
 
 	astream->pool = pool_alloconly_create("istream attachment", 1024);
-	astream->parser = message_parser_init(astream->pool, input, 0,
-				MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS |
-				MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES);
+	astream->parser = message_parser_init(astream->pool, input, &parser_set);
 	return i_stream_create(&astream->istream, input,
 			       i_stream_get_fd(input), 0);
 }
diff --git a/src/lib-mail/istream-binary-converter.c b/src/lib-mail/istream-binary-converter.c
index 201a588152..856b854738 100644
--- a/src/lib-mail/istream-binary-converter.c
+++ b/src/lib-mail/istream-binary-converter.c
@@ -286,6 +286,10 @@ static void i_stream_binary_converter_close(struct iostream_private *stream,
 
 struct istream *i_stream_create_binary_converter(struct istream *input)
 {
+	const struct message_parser_settings parser_set = {
+		.flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS |
+			MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES,
+	};
 	struct binary_converter_istream *bstream;
 
 	bstream = i_new(struct binary_converter_istream, 1);
@@ -299,9 +303,7 @@ struct istream *i_stream_create_binary_converter(struct istream *input)
 	bstream->istream.istream.seekable = FALSE;
 
 	bstream->pool = pool_alloconly_create("istream binary converter", 128);
-	bstream->parser = message_parser_init(bstream->pool, input, 0,
-				MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS |
-				MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES);
+	bstream->parser = message_parser_init(bstream->pool, input, &parser_set);
 	return i_stream_create(&bstream->istream, input,
 			       i_stream_get_fd(input), 0);
 }
diff --git a/src/lib-mail/message-parser-from-parts.c b/src/lib-mail/message-parser-from-parts.c
index b23055ab9b..8e21ec8f18 100644
--- a/src/lib-mail/message-parser-from-parts.c
+++ b/src/lib-mail/message-parser-from-parts.c
@@ -351,14 +351,13 @@ static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx,
 struct message_parser_ctx *
 message_parser_init_from_parts(struct message_part *parts,
 			       struct istream *input,
-			       enum message_header_parser_flags hdr_flags,
-			       enum message_parser_flags flags)
+			       const struct message_parser_settings *set)
 {
 	struct message_parser_ctx *ctx;
 
 	i_assert(parts != NULL);
 
-	ctx = message_parser_init_int(input, hdr_flags, flags);
+	ctx = message_parser_init_int(input, set);
 	ctx->preparsed = TRUE;
 	ctx->parts = ctx->part = parts;
 	ctx->parse_next_block = preparsed_parse_next_header_init;
diff --git a/src/lib-mail/message-parser-private.h b/src/lib-mail/message-parser-private.h
index fe106819e2..dbf8464cfb 100644
--- a/src/lib-mail/message-parser-private.h
+++ b/src/lib-mail/message-parser-private.h
@@ -51,8 +51,7 @@ struct message_parser_ctx {
 
 struct message_parser_ctx *
 message_parser_init_int(struct istream *input,
-			enum message_header_parser_flags hdr_flags,
-			enum message_parser_flags flags);
+			const struct message_parser_settings *set);
 int message_parser_read_more(struct message_parser_ctx *ctx,
 			     struct message_block *block_r, bool *full_r);
 
diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index c9ff98576b..41b9ed133a 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -703,14 +703,13 @@ static int parse_next_header_init(struct message_parser_ctx *ctx,
 
 struct message_parser_ctx *
 message_parser_init_int(struct istream *input,
-			enum message_header_parser_flags hdr_flags,
-			enum message_parser_flags flags)
+			const struct message_parser_settings *set)
 {
 	struct message_parser_ctx *ctx;
 
 	ctx = i_new(struct message_parser_ctx, 1);
-	ctx->hdr_flags = hdr_flags;
-	ctx->flags = flags;
+	ctx->hdr_flags = set->hdr_flags;
+	ctx->flags = set->flags;
 	ctx->input = input;
 	i_stream_ref(input);
 	return ctx;
@@ -718,12 +717,11 @@ message_parser_init_int(struct istream *input,
 
 struct message_parser_ctx *
 message_parser_init(pool_t part_pool, struct istream *input,
-		    enum message_header_parser_flags hdr_flags,
-		    enum message_parser_flags flags)
+		    const struct message_parser_settings *set)
 {
 	struct message_parser_ctx *ctx;
 
-	ctx = message_parser_init_int(input, hdr_flags, flags);
+	ctx = message_parser_init_int(input, set);
 	ctx->part_pool = part_pool;
 	ctx->parts = ctx->part = p_new(part_pool, struct message_part, 1);
 	ctx->next_part = &ctx->part->children;
diff --git a/src/lib-mail/message-parser.h b/src/lib-mail/message-parser.h
index 3efd851862..d159b2607d 100644
--- a/src/lib-mail/message-parser.h
+++ b/src/lib-mail/message-parser.h
@@ -17,6 +17,11 @@ enum message_parser_flags {
 	MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES		= 0x08
 };
 
+struct message_parser_settings {
+	enum message_header_parser_flags hdr_flags;
+	enum message_parser_flags flags;
+};
+
 struct message_parser_ctx;
 
 struct message_block {
@@ -45,8 +50,7 @@ extern message_part_header_callback_t *null_message_part_header_callback;
    are allocated from. */
 struct message_parser_ctx *
 message_parser_init(pool_t part_pool, struct istream *input,
-		    enum message_header_parser_flags hdr_flags,
-		    enum message_parser_flags flags);
+		    const struct message_parser_settings *set);
 /* Deinitialize message parser. The ctx must NOT have been created by
    message_parser_init_from_parts(). */
 void message_parser_deinit(struct message_parser_ctx **ctx,
@@ -55,8 +59,7 @@ void message_parser_deinit(struct message_parser_ctx **ctx,
 struct message_parser_ctx *
 message_parser_init_from_parts(struct message_part *parts,
 			       struct istream *input,
-			       enum message_header_parser_flags hdr_flags,
-			       enum message_parser_flags flags);
+			       const struct message_parser_settings *set);
 /* Same as message_parser_deinit(), but return an error message describing
    why the preparsed parts didn't match the message. This can also safely be
    called even when preparsed parts weren't used - it'll always just return
diff --git a/src/lib-mail/message-search.c b/src/lib-mail/message-search.c
index 66c043c158..14d1a11470 100644
--- a/src/lib-mail/message-search.c
+++ b/src/lib-mail/message-search.c
@@ -196,8 +196,9 @@ message_search_msg_real(struct message_search_context *ctx,
 			struct istream *input, struct message_part *parts,
 			const char **error_r)
 {
-	const enum message_header_parser_flags hdr_parser_flags =
-		MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE;
+	const struct message_parser_settings parser_set = {
+		.hdr_flags = MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE,
+	};
 	struct message_parser_ctx *parser_ctx;
 	struct message_block raw_block;
 	struct message_part *new_parts;
@@ -207,10 +208,10 @@ message_search_msg_real(struct message_search_context *ctx,
 
 	if (parts != NULL) {
 		parser_ctx = message_parser_init_from_parts(parts,
-						input, hdr_parser_flags, 0);
+						input, &parser_set);
 	} else {
 		parser_ctx = message_parser_init(pool_datastack_create(),
-						 input, hdr_parser_flags, 0);
+						 input, &parser_set);
 	}
 
 	while ((ret = message_parser_parse_next_block(parser_ctx,
diff --git a/src/lib-mail/message-snippet.c b/src/lib-mail/message-snippet.c
index 2100b70554..e6965fd707 100644
--- a/src/lib-mail/message-snippet.c
+++ b/src/lib-mail/message-snippet.c
@@ -137,6 +137,7 @@ int message_snippet_generate(struct istream *input,
 			     unsigned int max_snippet_chars,
 			     string_t *snippet)
 {
+	const struct message_parser_settings parser_set = { .flags = 0 };
 	struct message_parser_ctx *parser;
 	struct message_part *parts;
 	struct message_decoder_context *decoder;
@@ -151,7 +152,7 @@ int message_snippet_generate(struct istream *input,
 	ctx.snippet.chars_left = max_snippet_chars;
 	ctx.quoted_snippet.snippet = str_new(pool, max_snippet_chars);
 	ctx.quoted_snippet.chars_left = max_snippet_chars - 1; /* -1 for '>' */
-	parser = message_parser_init(pool_datastack_create(), input, 0, 0);
+	parser = message_parser_init(pool_datastack_create(), input, &parser_set);
 	decoder = message_decoder_init(NULL, 0);
 	while ((ret = message_parser_parse_next_block(parser, &raw_block)) > 0) {
 		if (!message_decoder_decode_next_block(decoder, &raw_block, &block))
diff --git a/src/lib-mail/test-message-decoder.c b/src/lib-mail/test-message-decoder.c
index e1faca29b4..3007283cad 100644
--- a/src/lib-mail/test-message-decoder.c
+++ b/src/lib-mail/test-message-decoder.c
@@ -105,6 +105,7 @@ static void test_message_decoder_multipart(void)
 		"\n"
 		"?garbage\n"
 		"--foo--\n";
+	const struct message_parser_settings parser_set = { .flags = 0, };
 	struct message_parser_ctx *parser;
 	struct message_decoder_context *decoder;
 	struct message_part *parts;
@@ -116,7 +117,8 @@ static void test_message_decoder_multipart(void)
 	test_begin("message decoder multipart");
 
 	istream = test_istream_create(test_message_input);
-	parser = message_parser_init(pool_datastack_create(), istream, 0, 0);
+	parser = message_parser_init(pool_datastack_create(), istream,
+				     &parser_set);
 	decoder = message_decoder_init(NULL, 0);
 
 	test_istream_set_allow_eof(istream, FALSE);
diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c
index 6bf1643e88..5e496275fe 100644
--- a/src/lib-mail/test-message-parser.c
+++ b/src/lib-mail/test-message-parser.c
@@ -39,6 +39,8 @@ static const char test_msg[] =
 "\n";
 #define TEST_MSG_LEN (sizeof(test_msg)-1)
 
+static const struct message_parser_settings set_empty = { .flags = 0 };
+
 static bool msg_parts_cmp(struct message_part *p1, struct message_part *p2)
 {
 	while (p1 != NULL || p2 != NULL) {
@@ -71,6 +73,9 @@ static bool msg_parts_cmp(struct message_part *p1, struct message_part *p2)
 
 static void test_parsed_parts(struct istream *input, struct message_part *parts)
 {
+	const struct message_parser_settings parser_set = {
+		.flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK,
+	};
 	struct message_parser_ctx *parser;
 	struct message_block block;
 	struct message_part *parts2;
@@ -81,8 +86,7 @@ static void test_parsed_parts(struct istream *input, struct message_part *parts)
 	if (i_stream_get_size(input, TRUE, &input_size) < 0)
 		i_unreached();
 
-	parser = message_parser_init_from_parts(parts, input, 0,
-					MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK);
+	parser = message_parser_init_from_parts(parts, input, &parser_set);
 	for (i = 1; i <= input_size*2+1; i++) {
 		test_istream_set_size(input, i/2);
 		if (i > TEST_MSG_LEN*2)
@@ -111,9 +115,11 @@ static void test_message_parser_small_blocks(void)
 	output = t_str_new(128);
 
 	/* full parsing */
-	parser = message_parser_init(pool, input, 0,
-		MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS |
-		MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES);
+	const struct message_parser_settings full_parser_set = {
+		.flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS |
+			MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES,
+	};
+	parser = message_parser_init(pool, input, &full_parser_set);
 	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) {
 		if (block.hdr != NULL)
 			message_header_line_write(output, block.hdr);
@@ -129,7 +135,7 @@ static void test_message_parser_small_blocks(void)
 	i_stream_seek(input, 0);
 	test_istream_set_allow_eof(input, FALSE);
 
-	parser = message_parser_init(pool, input, 0, 0);
+	parser = message_parser_init(pool, input, &set_empty);
 	for (i = 1; i <= TEST_MSG_LEN*2+1; i++) {
 		test_istream_set_size(input, i/2);
 		if (i > TEST_MSG_LEN*2)
@@ -147,8 +153,11 @@ static void test_message_parser_small_blocks(void)
 	test_istream_set_allow_eof(input, FALSE);
 
 	end_of_headers_idx = (strstr(test_msg, "\n-----") - test_msg);
-	parser = message_parser_init_from_parts(parts, input, 0,
-					MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK);
+	const struct message_parser_settings preparsed_parser_set = {
+		.flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK,
+	};
+	parser = message_parser_init_from_parts(parts, input,
+						&preparsed_parser_set);
 	for (i = 1; i <= TEST_MSG_LEN*2+1; i++) {
 		test_istream_set_size(input, i/2);
 		if (i > TEST_MSG_LEN*2)
@@ -190,7 +199,7 @@ static const char input_msg[] =
 	pool = pool_alloconly_create("message parser", 10240);
 	input = test_istream_create(input_msg);
 
-	parser = message_parser_init(pool, input, 0, 0);
+	parser = message_parser_init(pool, input, &set_empty);
 	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
@@ -255,7 +264,7 @@ static const char input_msg[] =
 	pool = pool_alloconly_create("message parser", 10240);
 	input = test_istream_create(input_msg);
 
-	parser = message_parser_init(pool, input, 0, 0);
+	parser = message_parser_init(pool, input, &set_empty);
 	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
@@ -311,7 +320,7 @@ static const char input_msg[] =
 	pool = pool_alloconly_create("message parser", 10240);
 	input = test_istream_create(input_msg);
 
-	parser = message_parser_init(pool, input, 0, 0);
+	parser = message_parser_init(pool, input, &set_empty);
 	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
@@ -349,7 +358,7 @@ static const char input_msg[] =
 	pool = pool_alloconly_create("message parser", 10240);
 	input = test_istream_create(input_msg);
 
-	parser = message_parser_init(pool, input, 0, 0);
+	parser = message_parser_init(pool, input, &set_empty);
 	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
@@ -394,7 +403,7 @@ static const char input_msg[] =
 	pool = pool_alloconly_create("message parser", 10240);
 	input = test_istream_create(input_msg);
 
-	parser = message_parser_init(pool, input, 0, 0);
+	parser = message_parser_init(pool, input, &set_empty);
 	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
@@ -455,7 +464,7 @@ static const char input_msg[] =
 	pool = pool_alloconly_create("message parser", 10240);
 	input = test_istream_create(input_msg);
 
-	parser = message_parser_init(pool, input, 0, 0);
+	parser = message_parser_init(pool, input, &set_empty);
 	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
@@ -516,7 +525,7 @@ static const char input_msg[] =
 	pool = pool_alloconly_create("message parser", 10240);
 	input = test_istream_create(input_msg);
 
-	parser = message_parser_init(pool, input, 0, 0);
+	parser = message_parser_init(pool, input, &set_empty);
 	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
@@ -578,7 +587,7 @@ static const char input_msg[] =
 	pool = pool_alloconly_create("message parser", 10240);
 	input = test_istream_create(input_msg);
 
-	parser = message_parser_init(pool, input, 0, 0);
+	parser = message_parser_init(pool, input, &set_empty);
 	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
@@ -661,7 +670,7 @@ static const char input_msg[] =
 	pool = pool_alloconly_create("message parser", 10240);
 	input = test_istream_create(input_msg);
 
-	parser = message_parser_init(pool, input, 0, 0);
+	parser = message_parser_init(pool, input, &set_empty);
 	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
@@ -721,7 +730,7 @@ static void test_message_parser_no_eoh(void)
 	pool = pool_alloconly_create("message parser", 10240);
 	input = test_istream_create(input_msg);
 
-	parser = message_parser_init(pool, input, 0, 0);
+	parser = message_parser_init(pool, input, &set_empty);
 	test_assert(message_parser_parse_next_block(parser, &block) > 0 &&
 		    block.hdr != NULL && strcmp(block.hdr->name, "a") == 0 &&
 		    block.hdr->value_len == 1 && block.hdr->value[0] == 'b');
@@ -777,7 +786,7 @@ static const char input_msg[] =
 	pool = pool_alloconly_create("message parser", 10240);
 	input = test_istream_create(input_msg);
 
-	parser = message_parser_init(pool, input, 0, 0);
+	parser = message_parser_init(pool, input, &set_empty);
 	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
 	test_assert(ret < 0);
 	message_parser_deinit(&parser, &parts);
diff --git a/src/lib-mail/test-message-part.c b/src/lib-mail/test-message-part.c
index 6cad8ffbd3..4a51a39f99 100644
--- a/src/lib-mail/test-message-part.c
+++ b/src/lib-mail/test-message-part.c
@@ -65,6 +65,7 @@ static const char test_msg[] =
 
 static void test_message_part_idx(void)
 {
+	const struct message_parser_settings set = { .flags = 0 };
 	struct message_parser_ctx *parser;
 	struct istream *input;
 	struct message_part *parts, *part, *prev_part;
@@ -77,7 +78,7 @@ static void test_message_part_idx(void)
 	pool = pool_alloconly_create("message parser", 10240);
 	input = i_stream_create_from_data(test_msg, TEST_MSG_LEN);
 
-	parser = message_parser_init(pool, input, 0, 0);
+	parser = message_parser_init(pool, input, &set);
 	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) {
 		part_idx = message_part_to_idx(block.part);
 		test_assert(part_idx >= prev_idx);
diff --git a/src/lib-storage/index/index-mail-headers.c b/src/lib-storage/index/index-mail-headers.c
index 54a5883177..df21b9129e 100644
--- a/src/lib-storage/index/index-mail-headers.c
+++ b/src/lib-storage/index/index-mail-headers.c
@@ -16,11 +16,11 @@
 #include "index-storage.h"
 #include "index-mail.h"
 
-static const enum message_header_parser_flags hdr_parser_flags =
-	MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
-	MESSAGE_HEADER_PARSER_FLAG_DROP_CR;
-static const enum message_parser_flags msg_parser_flags =
-	MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK;
+static const struct message_parser_settings msg_parser_set = {
+	.hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
+		MESSAGE_HEADER_PARSER_FLAG_DROP_CR,
+	.flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK,
+};
 
 static int header_line_cmp(const struct index_mail_line *l1,
 			   const struct index_mail_line *l2)
@@ -399,7 +399,7 @@ index_mail_cache_parse_init(struct mail *_mail, struct istream *input)
 	mail->data.parser_input = input;
 	mail->data.parser_ctx =
 		message_parser_init(mail->mail.data_pool, input,
-				    hdr_parser_flags, msg_parser_flags);
+				    &msg_parser_set);
 	i_stream_unref(&input);
 	return input2;
 }
@@ -428,14 +428,12 @@ static void index_mail_init_parser(struct index_mail *mail)
 		data->parser_input = data->stream;
 		data->parser_ctx = message_parser_init(mail->mail.data_pool,
 						       data->stream,
-						       hdr_parser_flags,
-						       msg_parser_flags);
+						       &msg_parser_set);
 	} else {
 		data->parser_ctx =
 			message_parser_init_from_parts(data->parts,
 						       data->stream,
-						       hdr_parser_flags,
-						       msg_parser_flags);
+						       &msg_parser_set);
 	}
 }
 
@@ -468,7 +466,7 @@ int index_mail_parse_headers(struct index_mail *mail,
 		i_assert(!data->save_bodystructure_body ||
 			 data->parser_ctx != NULL);
 		message_parse_header(data->stream, &data->hdr_size,
-				     hdr_parser_flags,
+				     msg_parser_set.hdr_flags,
 				     index_mail_parse_header_cb, mail);
 	}
 	if (index_mail_stream_check_failure(mail) < 0)
@@ -526,7 +524,7 @@ int index_mail_headers_get_envelope(struct index_mail *mail)
 	if (mail->data.envelope == NULL) {
 		/* we got the headers from cache - parse them to get the
 		   envelope */
-		message_parse_header(stream, NULL, hdr_parser_flags,
+		message_parse_header(stream, NULL, msg_parser_set.hdr_flags,
 				     imap_envelope_parse_callback, mail);
 		if (stream->stream_errno != 0) {
 			index_mail_stream_log_failure_for(mail, stream);
diff --git a/src/plugins/fts/fts-build-mail.c b/src/plugins/fts/fts-build-mail.c
index 3cb4ea657f..e088e7397b 100644
--- a/src/plugins/fts/fts-build-mail.c
+++ b/src/plugins/fts/fts-build-mail.c
@@ -475,6 +475,9 @@ fts_build_mail_real(struct fts_backend_update_context *update_ctx,
 		    const char **retriable_err_msg_r,
 		    bool *may_need_retry_r)
 {
+	const struct message_parser_settings parser_set = {
+		.hdr_flags = MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE,
+	};
 	struct fts_mail_build_context ctx;
 	struct istream *input;
 	struct message_parser_ctx *parser;
@@ -503,9 +506,7 @@ fts_build_mail_real(struct fts_backend_update_context *update_ctx,
 		ctx.pending_input = buffer_create_dynamic(default_pool, 128);
 
 	prev_part = NULL;
-	parser = message_parser_init(pool_datastack_create(), input,
-				     MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE,
-				     0);
+	parser = message_parser_init(pool_datastack_create(), input, &parser_set);
 
 	decoder = message_decoder_init(update_ctx->normalizer, 0);
 	for (;;) {
-- 
2.11.0

From e6fe2ef5d5a8620308d42a7ed1b35e907c645f72 Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 16:59:40 +0300
Subject: [PATCH 14/15] lib-mail: message-parser - Support limiting max number
 of nested MIME parts

The default is to allow 100 nested MIME parts. When the limit is reached,
the innermost MIME part's body contains all the rest of the inner bodies
until a parent MIME part is reached.
---
 src/lib-mail/message-parser-private.h |   2 +
 src/lib-mail/message-parser.c         |  39 ++++++--
 src/lib-mail/message-parser.h         |   6 ++
 src/lib-mail/test-message-parser.c    | 163 ++++++++++++++++++++++++++++++++++
 4 files changed, 203 insertions(+), 7 deletions(-)

diff --git a/src/lib-mail/message-parser-private.h b/src/lib-mail/message-parser-private.h
index dbf8464cfb..4bb0c3dbfd 100644
--- a/src/lib-mail/message-parser-private.h
+++ b/src/lib-mail/message-parser-private.h
@@ -23,9 +23,11 @@ struct message_parser_ctx {
 	struct istream *input;
 	struct message_part *parts, *part;
 	const char *broken_reason;
+	unsigned int nested_parts_count;
 
 	enum message_header_parser_flags hdr_flags;
 	enum message_parser_flags flags;
+	unsigned int max_nested_mime_parts;
 
 	char *last_boundary;
 	struct message_boundary *boundaries;
diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index 41b9ed133a..ea0154d5ed 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -157,12 +157,17 @@ message_part_append(struct message_parser_ctx *ctx)
 	ctx->next_part = &part->children;
 
 	ctx->part = part;
+	ctx->nested_parts_count++;
+	i_assert(ctx->nested_parts_count < ctx->max_nested_mime_parts);
 }
 
 static void message_part_finish(struct message_parser_ctx *ctx)
 {
 	struct message_part **const *parent_next_partp;
 
+	i_assert(ctx->nested_parts_count > 0);
+	ctx->nested_parts_count--;
+
 	parent_next_partp = array_back(&ctx->next_part_stack);
 	array_pop_back(&ctx->next_part_stack);
 	ctx->next_part = *parent_next_partp;
@@ -542,6 +547,11 @@ static bool block_is_at_eoh(const struct message_block *block)
 	return FALSE;
 }
 
+static bool parse_too_many_nested_mime_parts(struct message_parser_ctx *ctx)
+{
+	return ctx->nested_parts_count+1 >= ctx->max_nested_mime_parts;
+}
+
 #define MUTEX_FLAGS \
 	(MESSAGE_PART_FLAG_MESSAGE_RFC822 | MESSAGE_PART_FLAG_MULTIPART)
 
@@ -566,8 +576,12 @@ static int parse_next_header(struct message_parser_ctx *ctx,
 		   "\n--boundary" belongs to us or to a previous boundary.
 		   this is a problem if the boundary prefixes are identical,
 		   because MIME requires only the prefix to match. */
-		parse_next_body_multipart_init(ctx);
-		ctx->multipart = TRUE;
+		if (!parse_too_many_nested_mime_parts(ctx)) {
+			parse_next_body_multipart_init(ctx);
+			ctx->multipart = TRUE;
+		} else {
+			part->flags &= ~MESSAGE_PART_FLAG_MULTIPART;
+		}
 	}
 
 	/* before parsing the header see if we can find a --boundary from here.
@@ -671,12 +685,16 @@ static int parse_next_header(struct message_parser_ctx *ctx,
 		i_assert(ctx->last_boundary == NULL);
 		ctx->multipart = FALSE;
 		ctx->parse_next_block = parse_next_body_to_boundary;
-	} else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0)
+	} else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0 &&
+		   !parse_too_many_nested_mime_parts(ctx)) {
 		ctx->parse_next_block = parse_next_body_message_rfc822_init;
-	else if (ctx->boundaries != NULL)
-		ctx->parse_next_block = parse_next_body_to_boundary;
-	else
-		ctx->parse_next_block = parse_next_body_to_eof;
+	} else {
+		part->flags &= ~MESSAGE_PART_FLAG_MESSAGE_RFC822;
+		if (ctx->boundaries != NULL)
+			ctx->parse_next_block = parse_next_body_to_boundary;
+		else
+			ctx->parse_next_block = parse_next_body_to_eof;
+	}
 
 	ctx->want_count = 1;
 
@@ -710,6 +728,9 @@ message_parser_init_int(struct istream *input,
 	ctx = i_new(struct message_parser_ctx, 1);
 	ctx->hdr_flags = set->hdr_flags;
 	ctx->flags = set->flags;
+	ctx->max_nested_mime_parts = set->max_nested_mime_parts != 0 ?
+		set->max_nested_mime_parts :
+		MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS;
 	ctx->input = input;
 	i_stream_ref(input);
 	return ctx;
@@ -754,6 +775,10 @@ int message_parser_deinit_from_parts(struct message_parser_ctx **_ctx,
 	if (ctx->hdr_parser_ctx != NULL)
 		message_parse_header_deinit(&ctx->hdr_parser_ctx);
 	boundary_remove_until(ctx, NULL);
+	/* caller might have stopped the parsing early */
+	i_assert(ctx->nested_parts_count == 0 ||
+		 i_stream_have_bytes_left(ctx->input));
+
 	i_stream_unref(&ctx->input);
 	array_free(&ctx->next_part_stack);
 	i_free(ctx->last_boundary);
diff --git a/src/lib-mail/message-parser.h b/src/lib-mail/message-parser.h
index d159b2607d..7f6ea04936 100644
--- a/src/lib-mail/message-parser.h
+++ b/src/lib-mail/message-parser.h
@@ -17,9 +17,15 @@ enum message_parser_flags {
 	MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES		= 0x08
 };
 
+#define MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS 100
+
 struct message_parser_settings {
 	enum message_header_parser_flags hdr_flags;
 	enum message_parser_flags flags;
+
+	/* Maximum nested MIME parts.
+	   0 = MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS. */
+	unsigned int max_nested_mime_parts;
 };
 
 struct message_parser_ctx;
diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c
index 5e496275fe..df2586eddd 100644
--- a/src/lib-mail/test-message-parser.c
+++ b/src/lib-mail/test-message-parser.c
@@ -175,6 +175,36 @@ static void test_message_parser_small_blocks(void)
 	test_end();
 }
 
+static void test_message_parser_stop_early(void)
+{
+	struct message_parser_ctx *parser;
+	struct istream *input;
+	struct message_part *parts;
+	struct message_block block;
+	unsigned int i;
+	pool_t pool;
+	int ret;
+
+	test_begin("message parser in stop early");
+	pool = pool_alloconly_create("message parser", 10240);
+	input = test_istream_create(test_msg);
+
+	test_istream_set_allow_eof(input, FALSE);
+	for (i = 1; i <= TEST_MSG_LEN+1; i++) {
+		i_stream_seek(input, 0);
+		test_istream_set_size(input, i);
+		parser = message_parser_init(pool, input, &set_empty);
+		while ((ret = message_parser_parse_next_block(parser,
+							      &block)) > 0) ;
+		test_assert(ret == 0);
+		message_parser_deinit(&parser, &parts);
+	}
+
+	i_stream_unref(&input);
+	pool_unref(&pool);
+	test_end();
+}
+
 static void test_message_parser_truncated_mime_headers(void)
 {
 static const char input_msg[] =
@@ -839,10 +869,141 @@ static const char input_msg[] =
 	test_end();
 }
 
+static void test_message_parser_mime_part_nested_limit(void)
+{
+static const char input_msg[] =
+"Content-Type: multipart/mixed; boundary=\"1\"\n"
+"\n"
+"--1\n"
+"Content-Type: multipart/mixed; boundary=\"2\"\n"
+"\n"
+"--2\n"
+"Content-Type: text/plain\n"
+"\n"
+"1\n"
+"--2\n"
+"Content-Type: text/plain\n"
+"\n"
+"22\n"
+"--1\n"
+"Content-Type: text/plain\n"
+"\n"
+"333\n";
+	const struct message_parser_settings parser_set = {
+		.max_nested_mime_parts = 2,
+	};
+	struct message_parser_ctx *parser;
+	struct istream *input;
+	struct message_part *parts, *part;
+	struct message_block block;
+	pool_t pool;
+	int ret;
+
+	test_begin("message parser mime part nested limit");
+	pool = pool_alloconly_create("message parser", 10240);
+	input = test_istream_create(input_msg);
+
+	parser = message_parser_init(pool, input, &parser_set);
+	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
+	test_assert(ret < 0);
+	message_parser_deinit(&parser, &parts);
+
+	part = parts;
+	test_assert(part->children_count == 2);
+	test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(part->header_size.lines == 2);
+	test_assert(part->header_size.physical_size == 45);
+	test_assert(part->header_size.virtual_size == 45+2);
+	test_assert(part->body_size.lines == 15);
+	test_assert(part->body_size.physical_size == 148);
+	test_assert(part->body_size.virtual_size == 148+15);
+
+	part = parts->children;
+	test_assert(part->children_count == 0);
+	test_assert(part->flags == MESSAGE_PART_FLAG_IS_MIME);
+	test_assert(part->header_size.lines == 2);
+	test_assert(part->header_size.physical_size == 45);
+	test_assert(part->header_size.virtual_size == 45+2);
+	test_assert(part->body_size.lines == 7);
+	test_assert(part->body_size.physical_size == 64);
+	test_assert(part->body_size.virtual_size == 64+7);
+
+	part = parts->children->next;
+	test_assert(part->children_count == 0);
+	test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(part->header_size.lines == 2);
+	test_assert(part->header_size.physical_size == 26);
+	test_assert(part->header_size.virtual_size == 26+2);
+	test_assert(part->body_size.lines == 1);
+	test_assert(part->body_size.physical_size == 4);
+	test_assert(part->body_size.virtual_size == 4+1);
+
+	test_parsed_parts(input, parts);
+	i_stream_unref(&input);
+	pool_unref(&pool);
+	test_end();
+}
+
+static void test_message_parser_mime_part_nested_limit_rfc822(void)
+{
+static const char input_msg[] =
+"Content-Type: message/rfc822\n"
+"\n"
+"Content-Type: message/rfc822\n"
+"\n"
+"Content-Type: text/plain\n"
+"\n"
+"1\n";
+	const struct message_parser_settings parser_set = {
+		.max_nested_mime_parts = 2,
+	};
+	struct message_parser_ctx *parser;
+	struct istream *input;
+	struct message_part *parts, *part;
+	struct message_block block;
+	pool_t pool;
+	int ret;
+
+	test_begin("message parser mime part nested limit rfc822");
+	pool = pool_alloconly_create("message parser", 10240);
+	input = test_istream_create(input_msg);
+
+	parser = message_parser_init(pool, input, &parser_set);
+	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
+	test_assert(ret < 0);
+	message_parser_deinit(&parser, &parts);
+
+	part = parts;
+	test_assert(part->children_count == 1);
+	test_assert(part->flags == (MESSAGE_PART_FLAG_MESSAGE_RFC822 | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(part->header_size.lines == 2);
+	test_assert(part->header_size.physical_size == 30);
+	test_assert(part->header_size.virtual_size == 30+2);
+	test_assert(part->body_size.lines == 5);
+	test_assert(part->body_size.physical_size == 58);
+	test_assert(part->body_size.virtual_size == 58+5);
+
+	part = parts->children;
+	test_assert(part->children_count == 0);
+	test_assert(part->flags == MESSAGE_PART_FLAG_IS_MIME);
+	test_assert(part->header_size.lines == 2);
+	test_assert(part->header_size.physical_size == 30);
+	test_assert(part->header_size.virtual_size == 30+2);
+	test_assert(part->body_size.lines == 3);
+	test_assert(part->body_size.physical_size == 28);
+	test_assert(part->body_size.virtual_size == 28+3);
+
+	test_parsed_parts(input, parts);
+	i_stream_unref(&input);
+	pool_unref(&pool);
+	test_end();
+}
+
 int main(void)
 {
 	static void (*const test_functions[])(void) = {
 		test_message_parser_small_blocks,
+		test_message_parser_stop_early,
 		test_message_parser_truncated_mime_headers,
 		test_message_parser_truncated_mime_headers2,
 		test_message_parser_truncated_mime_headers3,
@@ -854,6 +1015,8 @@ int main(void)
 		test_message_parser_continuing_mime_boundary_reverse,
 		test_message_parser_long_mime_boundary,
 		test_message_parser_no_eoh,
+		test_message_parser_mime_part_nested_limit,
+		test_message_parser_mime_part_nested_limit_rfc822,
 		NULL
 	};
 	return test_run(test_functions);
-- 
2.11.0

From 39e1ba38c2b3a5e3e567a35b7f6c414c7ed43769 Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 23 Apr 2020 17:09:33 +0300
Subject: [PATCH 15/15] lib-mail: message-parser - Support limiting max number
 of MIME parts

The default is to allow 10000 MIME parts. When it's reached, no more
MIME boundary lines will be recognized, so the rest of the mail belongs
to the last added MIME part.
---
 src/lib-mail/message-parser-private.h |  2 +
 src/lib-mail/message-parser.c         | 12 +++++
 src/lib-mail/message-parser.h         |  4 ++
 src/lib-mail/test-message-parser.c    | 86 +++++++++++++++++++++++++++++++++++
 4 files changed, 104 insertions(+)

diff --git a/src/lib-mail/message-parser-private.h b/src/lib-mail/message-parser-private.h
index 4bb0c3dbfd..1f9c66b827 100644
--- a/src/lib-mail/message-parser-private.h
+++ b/src/lib-mail/message-parser-private.h
@@ -24,10 +24,12 @@ struct message_parser_ctx {
 	struct message_part *parts, *part;
 	const char *broken_reason;
 	unsigned int nested_parts_count;
+	unsigned int total_parts_count;
 
 	enum message_header_parser_flags hdr_flags;
 	enum message_parser_flags flags;
 	unsigned int max_nested_mime_parts;
+	unsigned int max_total_mime_parts;
 
 	char *last_boundary;
 	struct message_boundary *boundaries;
diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index ea0154d5ed..6370a1bff7 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -158,7 +158,9 @@ message_part_append(struct message_parser_ctx *ctx)
 
 	ctx->part = part;
 	ctx->nested_parts_count++;
+	ctx->total_parts_count++;
 	i_assert(ctx->nested_parts_count < ctx->max_nested_mime_parts);
+	i_assert(ctx->total_parts_count <= ctx->max_total_mime_parts);
 }
 
 static void message_part_finish(struct message_parser_ctx *ctx)
@@ -241,6 +243,12 @@ boundary_line_find(struct message_parser_ctx *ctx,
 		return -1;
 	}
 
+	if (ctx->total_parts_count >= ctx->max_total_mime_parts) {
+		/* can't add any more MIME parts. just stop trying to find
+		   more boundaries. */
+		return -1;
+	}
+
 	/* need to find the end of line */
 	data += 2;
 	size -= 2;
@@ -731,6 +739,9 @@ message_parser_init_int(struct istream *input,
 	ctx->max_nested_mime_parts = set->max_nested_mime_parts != 0 ?
 		set->max_nested_mime_parts :
 		MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS;
+	ctx->max_total_mime_parts = set->max_total_mime_parts != 0 ?
+		set->max_total_mime_parts :
+		MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS;
 	ctx->input = input;
 	i_stream_ref(input);
 	return ctx;
@@ -747,6 +758,7 @@ message_parser_init(pool_t part_pool, struct istream *input,
 	ctx->parts = ctx->part = p_new(part_pool, struct message_part, 1);
 	ctx->next_part = &ctx->part->children;
 	ctx->parse_next_block = parse_next_header_init;
+	ctx->total_parts_count = 1;
 	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 7f6ea04936..f19e526284 100644
--- a/src/lib-mail/message-parser.h
+++ b/src/lib-mail/message-parser.h
@@ -18,6 +18,7 @@ enum message_parser_flags {
 };
 
 #define MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS 100
+#define MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS 10000
 
 struct message_parser_settings {
 	enum message_header_parser_flags hdr_flags;
@@ -26,6 +27,9 @@ struct message_parser_settings {
 	/* Maximum nested MIME parts.
 	   0 = MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS. */
 	unsigned int max_nested_mime_parts;
+	/* Maximum MIME parts in total.
+	   0 = MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS. */
+	unsigned int max_total_mime_parts;
 };
 
 struct message_parser_ctx;
diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c
index df2586eddd..0422b42265 100644
--- a/src/lib-mail/test-message-parser.c
+++ b/src/lib-mail/test-message-parser.c
@@ -999,6 +999,91 @@ static const char input_msg[] =
 	test_end();
 }
 
+static void test_message_parser_mime_part_limit(void)
+{
+static const char input_msg[] =
+"Content-Type: multipart/mixed; boundary=\"1\"\n"
+"\n"
+"--1\n"
+"Content-Type: multipart/mixed; boundary=\"2\"\n"
+"\n"
+"--2\n"
+"Content-Type: text/plain\n"
+"\n"
+"1\n"
+"--2\n"
+"Content-Type: text/plain\n"
+"\n"
+"22\n"
+"--1\n"
+"Content-Type: text/plain\n"
+"\n"
+"333\n";
+	const struct message_parser_settings parser_set = {
+		.max_total_mime_parts = 4,
+	};
+	struct message_parser_ctx *parser;
+	struct istream *input;
+	struct message_part *parts, *part;
+	struct message_block block;
+	pool_t pool;
+	int ret;
+
+	test_begin("message parser mime part limit");
+	pool = pool_alloconly_create("message parser", 10240);
+	input = test_istream_create(input_msg);
+
+	parser = message_parser_init(pool, input, &parser_set);
+	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
+	test_assert(ret < 0);
+	message_parser_deinit(&parser, &parts);
+
+	part = parts;
+	test_assert(part->children_count == 3);
+	test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(part->header_size.lines == 2);
+	test_assert(part->header_size.physical_size == 45);
+	test_assert(part->header_size.virtual_size == 45+2);
+	test_assert(part->body_size.lines == 15);
+	test_assert(part->body_size.physical_size == 148);
+	test_assert(part->body_size.virtual_size == 148+15);
+
+	part = parts->children;
+	test_assert(part->children_count == 2);
+	test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(part->header_size.lines == 2);
+	test_assert(part->header_size.physical_size == 45);
+	test_assert(part->header_size.virtual_size == 45+2);
+	test_assert(part->body_size.lines == 12);
+	test_assert(part->body_size.physical_size == 99);
+	test_assert(part->body_size.virtual_size == 99+12);
+
+	part = parts->children->children;
+	test_assert(part->children_count == 0);
+	test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(part->header_size.lines == 2);
+	test_assert(part->header_size.physical_size == 26);
+	test_assert(part->header_size.virtual_size == 26+2);
+	test_assert(part->body_size.lines == 0);
+	test_assert(part->body_size.physical_size == 1);
+	test_assert(part->body_size.virtual_size == 1);
+
+	part = parts->children->children->next;
+	test_assert(part->children_count == 0);
+	test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME));
+	test_assert(part->header_size.lines == 2);
+	test_assert(part->header_size.physical_size == 26);
+	test_assert(part->header_size.virtual_size == 26+2);
+	test_assert(part->body_size.lines == 5);
+	test_assert(part->body_size.physical_size == 37);
+	test_assert(part->body_size.virtual_size == 37+5);
+
+	test_parsed_parts(input, parts);
+	i_stream_unref(&input);
+	pool_unref(&pool);
+	test_end();
+}
+
 int main(void)
 {
 	static void (*const test_functions[])(void) = {
@@ -1017,6 +1102,7 @@ int main(void)
 		test_message_parser_no_eoh,
 		test_message_parser_mime_part_nested_limit,
 		test_message_parser_mime_part_nested_limit_rfc822,
+		test_message_parser_mime_part_limit,
 		NULL
 	};
 	return test_run(test_functions);
-- 
2.11.0

From 6001e4b48c5a23735eb9c4ca9a187a175fd1a1da Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Thu, 2 Jul 2020 17:31:19 +0300
Subject: [PATCH] lib-mail: Fix handling trailing "--" in MIME boundaries

Broken by 5b8ec27fae941d06516c30476dcf4820c6d200ab
---
 src/lib-mail/message-parser.c      | 14 ++++++++----
 src/lib-mail/test-message-parser.c | 46 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 56 insertions(+), 4 deletions(-)

diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index 6370a1bff7..011dea9050 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -19,7 +19,7 @@ static int parse_next_body_to_eof(struct message_parser_ctx *ctx,
 
 static struct message_boundary *
 boundary_find(struct message_boundary *boundaries,
-	      const unsigned char *data, size_t len)
+	      const unsigned char *data, size_t len, bool trailing_dashes)
 {
 	struct message_boundary *best = NULL;
 
@@ -33,7 +33,11 @@ boundary_find(struct message_boundary *boundaries,
 		    memcmp(boundaries->boundary, data, boundaries->len) == 0 &&
 		    (best == NULL || best->len < boundaries->len)) {
 			best = boundaries;
-			if (best->len == len) {
+			/* If we see "foo--", it could either mean that there
+			   is a boundary named "foo" that ends now or there's
+			   a boundary "foo--" which continues. */
+			if (best->len == len ||
+			    (best->len == len-2 && trailing_dashes)) {
 				/* This is exactly the wanted boundary. There
 				   can't be a better one. */
 				break;
@@ -261,6 +265,7 @@ boundary_line_find(struct message_parser_ctx *ctx,
 		return 0;
 	}
 	size_t find_size = size;
+	bool trailing_dashes = FALSE;
 
 	if (lf_pos != NULL) {
 		find_size = lf_pos - data;
@@ -268,11 +273,12 @@ boundary_line_find(struct message_parser_ctx *ctx,
 			find_size--;
 		if (find_size > 2 && data[find_size-1] == '-' &&
 		    data[find_size-2] == '-')
-			find_size -= 2;
+			trailing_dashes = TRUE;
 	} else if (find_size > BOUNDARY_END_MAX_LEN)
 		find_size = BOUNDARY_END_MAX_LEN;
 
-	*boundary_r = boundary_find(ctx->boundaries, data, find_size);
+	*boundary_r = boundary_find(ctx->boundaries, data, find_size,
+				    trailing_dashes);
 	if (*boundary_r == NULL)
 		return -1;
 
diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c
index 1b782f5df2..641edec4be 100644
--- a/src/lib-mail/test-message-parser.c
+++ b/src/lib-mail/test-message-parser.c
@@ -532,6 +532,51 @@ static const char input_msg[] =
 	test_end();
 }
 
+static void test_message_parser_trailing_dashes(void)
+{
+static const char input_msg[] =
+"Content-Type: multipart/mixed; boundary=\"a--\"\n"
+"\n"
+"--a--\n"
+"Content-Type: multipart/mixed; boundary=\"a----\"\n"
+"\n"
+"--a----\n"
+"Content-Type: text/plain\n"
+"\n"
+"body\n"
+"--a------\n"
+"Content-Type: text/html\n"
+"\n"
+"body2\n"
+"--a----";
+	struct message_parser_ctx *parser;
+	struct istream *input;
+	struct message_part *parts;
+	struct message_block block;
+	pool_t pool;
+	int ret;
+
+	test_begin("message parser trailing dashes");
+	pool = pool_alloconly_create("message parser", 10240);
+	input = test_istream_create(input_msg);
+
+	parser = message_parser_init(pool, input, &set_empty);
+	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ;
+	test_assert(ret < 0);
+	message_parser_deinit(&parser, &parts);
+
+	test_assert(parts->children_count == 2);
+	test_assert(parts->children->next == NULL);
+	test_assert(parts->children->children_count == 1);
+	test_assert(parts->children->children->next == NULL);
+	test_assert(parts->children->children->children_count == 0);
+
+	test_parsed_parts(input, parts);
+	i_stream_unref(&input);
+	pool_unref(&pool);
+	test_end();
+}
+
 static void test_message_parser_continuing_mime_boundary(void)
 {
 static const char input_msg[] =
@@ -1095,6 +1140,7 @@ int main(void)
 		test_message_parser_empty_multipart,
 		test_message_parser_duplicate_mime_boundary,
 		test_message_parser_garbage_suffix_mime_boundary,
+		test_message_parser_trailing_dashes,
 		test_message_parser_continuing_mime_boundary,
 		test_message_parser_continuing_truncated_mime_boundary,
 		test_message_parser_continuing_mime_boundary_reverse,
-- 
2.11.0

