changeset 19515:74b03ecb79fd

lib-mail: message-parser didn't detect MIME part boundaries in the middle of MIME part headers. Instead the --boundary line was thought to be part of the header itself.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Tue, 15 Dec 2015 17:32:09 +0200
parents 9f3e9150b6a3
children c986b91115d7
files src/lib-mail/message-parser.c src/lib-mail/test-message-parser.c
diffstat 2 files changed, 92 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-mail/message-parser.c	Tue Dec 15 17:29:11 2015 +0200
+++ b/src/lib-mail/message-parser.c	Tue Dec 15 17:32:09 2015 +0200
@@ -1,6 +1,7 @@
 /* Copyright (c) 2002-2015 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "buffer.h"
 #include "str.h"
 #include "istream.h"
 #include "rfc822-parser.h"
@@ -37,6 +38,7 @@
 	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);
@@ -510,17 +512,42 @@
 {
 	struct message_part *part = ctx->part;
 	struct message_header_line *hdr;
+	struct message_boundary *boundary;
+	bool full;
 	int ret;
 
-	if (ctx->skip > 0) {
-		i_stream_skip(ctx->input, ctx->skip);
-		ctx->skip = 0;
-	}
+	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
+		return 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;
+	/* before parsing the header see if we can find a --boundary from here.
+	   we're guaranteed to be at the beginning of the line here. */
+	ret = ctx->boundaries == NULL ? -1 :
+		boundary_line_find(ctx, block_r->data,
+				   block_r->size, full, &boundary);
+	if (ret < 0) {
+		/* no boundary */
+		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;
+		}
+	} else if (ret == 0) {
+		/* need more data */
+		return 0;
+	} else {
+		/* boundary found. stop parsing headers here. The previous
+		   [CR]LF belongs to the MIME boundary though. */
+		if (ctx->prev_hdr_newline_size > 0) {
+			i_assert(ctx->part->header_size.lines > 0);
+			ctx->part->header_size.lines--;
+			ctx->part->header_size.physical_size -=
+				ctx->prev_hdr_newline_size;
+			ctx->part->header_size.virtual_size -=
+				ctx->prev_hdr_newline_size;
+			if (ctx->prev_hdr_newline_size == 1)
+				ctx->part->header_size.virtual_size--;
+		}
+		hdr = NULL;
 	}
 
 	if (hdr != NULL) {
@@ -543,6 +570,8 @@
 
 		block_r->hdr = hdr;
 		block_r->size = 0;
+		ctx->prev_hdr_newline_size = hdr->no_newline ? 0 :
+			(hdr->crlf_newline ? 2 : 1);
 		return 1;
 	}
 
@@ -608,6 +637,7 @@
 		message_parse_header_init(ctx->input, &ctx->part->header_size,
 					  ctx->hdr_flags);
 	ctx->part_seen_content_type = FALSE;
+	ctx->prev_hdr_newline_size = 0;
 
 	ctx->parse_next_block = parse_next_header;
 	return parse_next_header(ctx, block_r);
--- a/src/lib-mail/test-message-parser.c	Tue Dec 15 17:29:11 2015 +0200
+++ b/src/lib-mail/test-message-parser.c	Tue Dec 15 17:32:09 2015 +0200
@@ -128,10 +128,64 @@
 	test_end();
 }
 
+static void test_message_parser_truncated_mime_headers(void)
+{
+static const char input_msg[] =
+"Content-Type: multipart/mixed; boundary=\":foo\"\n"
+"\n"
+"--:foo\n"
+"--:foo\n"
+"Content-Type: text/plain\n"
+"--:foo\n"
+"Content-Type: text/plain\r\n"
+"--:foo\n"
+"Content-Type: text/html\n"
+"--:foo--\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 truncated mime headers");
+	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);
+	test_assert(message_parser_deinit(&parser, &parts) == 0);
+
+	test_assert((parts->flags & MESSAGE_PART_FLAG_MULTIPART) != 0);
+	test_assert(parts->children->header_size.physical_size == 0);
+	test_assert(parts->children->body_size.physical_size == 0);
+	test_assert(parts->children->body_size.lines == 0);
+	test_assert(parts->children->next->header_size.physical_size == 24);
+	test_assert(parts->children->next->header_size.virtual_size == 24);
+	test_assert(parts->children->next->header_size.lines == 0);
+	test_assert(parts->children->next->next->header_size.physical_size == 24);
+	test_assert(parts->children->next->next->header_size.virtual_size == 24);
+	test_assert(parts->children->next->next->header_size.lines == 0);
+	test_assert(parts->children->next->next->next->header_size.physical_size == 23);
+	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->body_size.physical_size == 0);
+		test_assert(part->body_size.virtual_size == 0);
+	}
+	test_assert(parts->children->next->next->next->next == NULL);
+
+	i_stream_unref(&input);
+	pool_unref(&pool);
+	test_end();
+}
+
 int main(void)
 {
 	static void (*test_functions[])(void) = {
 		test_message_parser_small_blocks,
+		test_message_parser_truncated_mime_headers,
 		NULL
 	};
 	return test_run(test_functions);