summaryrefslogtreecommitdiffstats
path: root/src/lib-mail/test-message-parser.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-mail/test-message-parser.c1398
1 files changed, 1398 insertions, 0 deletions
diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c
new file mode 100644
index 0000000..663bfe8
--- /dev/null
+++ b/src/lib-mail/test-message-parser.c
@@ -0,0 +1,1398 @@
+/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "istream.h"
+#include "message-parser.h"
+#include "message-part-data.h"
+#include "message-size.h"
+#include "test-common.h"
+
+static const char test_msg[] =
+"Return-Path: <test@example.org>\n"
+"Subject: Hello world\n"
+"From: Test User <test@example.org>\n"
+"To: Another User <test2@example.org>\n"
+"Message-Id: <1.2.3.4@example>\n"
+"Mime-Version: 1.0\n"
+"Date: Sun, 23 May 2007 04:58:08 +0300\n"
+"Content-Type: multipart/signed; micalg=pgp-sha1;\n"
+" protocol=\"application/pgp-signature\";\n"
+" boundary=\"=-GNQXLhuj24Pl1aCkk4/d\"\n"
+"\n"
+"--=-GNQXLhuj24Pl1aCkk4/d\n"
+"Content-Type: text/plain\n"
+"Content-Transfer-Encoding: quoted-printable\n"
+"\n"
+"There was a day=20\n"
+"a happy=20day\n"
+"\n"
+"--=-GNQXLhuj24Pl1aCkk4/d\n"
+"Content-Type: application/pgp-signature; name=signature.asc\n"
+"\n"
+"-----BEGIN PGP SIGNATURE-----\n"
+"Version: GnuPG v1.2.4 (GNU/Linux)\n"
+"\n"
+"invalid\n"
+"-----END PGP SIGNATURE-----\n"
+"\n"
+"--=-GNQXLhuj24Pl1aCkk4/d--\n"
+"\n"
+"\n";
+#define TEST_MSG_LEN (sizeof(test_msg)-1)
+
+static const struct message_parser_settings set_empty = { .flags = 0 };
+
+static int message_parse_stream(pool_t pool, struct istream *input,
+ const struct message_parser_settings *set,
+ bool parse_data, struct message_part **parts_r)
+{
+ int ret;
+ struct message_parser_ctx *parser;
+ struct message_block block;
+
+ i_zero(&block);
+ parser = message_parser_init(pool, input, set);
+ while ((ret = message_parser_parse_next_block(parser, &block)) > 0)
+ if (parse_data)
+ message_part_data_parse_from_header(pool, block.part,
+ block.hdr);
+ message_parser_deinit(&parser, parts_r);
+ test_assert(input->stream_errno == 0);
+ return ret;
+}
+
+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;
+ uoff_t i, input_size;
+ const char *error;
+
+ i_stream_seek(input, 0);
+ if (i_stream_get_size(input, TRUE, &input_size) < 0)
+ i_unreached();
+
+ 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)
+ test_istream_set_allow_eof(input, TRUE);
+ while (message_parser_parse_next_block(parser, &block) > 0) ;
+ }
+ test_assert(message_parser_deinit_from_parts(&parser, &parts2, &error) == 0);
+ test_assert(message_part_is_equal(parts, parts2));
+}
+
+static void test_message_parser_small_blocks(void)
+{
+ struct message_parser_ctx *parser;
+ struct istream *input;
+ struct message_part *parts, *parts2;
+ struct message_block block;
+ unsigned int i, end_of_headers_idx;
+ string_t *output;
+ pool_t pool;
+ const char *error;
+ int ret;
+
+ test_begin("message parser in small blocks");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(test_msg);
+ output = t_str_new(128);
+
+ /* full parsing */
+ 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);
+ else if (block.size > 0)
+ str_append_data(output, block.data, block.size);
+ }
+
+ test_assert(ret < 0);
+ message_parser_deinit(&parser, &parts);
+ test_assert(input->stream_errno == 0);
+ test_assert(strcmp(test_msg, str_c(output)) == 0);
+
+ /* parsing in small blocks */
+ i_stream_seek(input, 0);
+ test_istream_set_allow_eof(input, FALSE);
+
+ 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)
+ test_istream_set_allow_eof(input, TRUE);
+ while ((ret = message_parser_parse_next_block(parser,
+ &block)) > 0) ;
+ test_assert((ret == 0 && i <= TEST_MSG_LEN*2) ||
+ (ret < 0 && i > TEST_MSG_LEN*2));
+ }
+ message_parser_deinit(&parser, &parts2);
+ test_assert(input->stream_errno == 0);
+ test_assert(message_part_is_equal(parts, parts2));
+
+ /* parsing in small blocks from preparsed parts */
+ i_stream_seek(input, 0);
+ test_istream_set_allow_eof(input, FALSE);
+
+ end_of_headers_idx = (strstr(test_msg, "\n-----") - test_msg);
+ 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)
+ test_istream_set_allow_eof(input, TRUE);
+ while ((ret = message_parser_parse_next_block(parser,
+ &block)) > 0) ;
+ test_assert((ret == 0 && i/2 <= end_of_headers_idx) ||
+ (ret < 0 && i/2 > end_of_headers_idx));
+ }
+ test_assert(message_parser_deinit_from_parts(&parser, &parts2, &error) == 0);
+ test_assert(message_part_is_equal(parts, parts2));
+
+ i_stream_unref(&input);
+ pool_unref(&pool);
+ test_end();
+}
+
+static void test_message_parser_stop_early(void)
+{
+ struct istream *input, *input2;
+ struct message_part *parts;
+ unsigned int i;
+ pool_t pool;
+
+ test_begin("message parser in stop early");
+ pool = pool_alloconly_create("message parser", 524288);
+ 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);
+
+ test_assert(message_parse_stream(pool, input, &set_empty, FALSE, &parts) == 0);
+
+ /* test preparsed - first re-parse everything with a stream
+ that sees EOF at this position */
+ input2 = i_stream_create_from_data(test_msg, i);
+ test_assert(message_parse_stream(pool, input2, &set_empty, FALSE, &parts) == -1);
+
+ /* now parse from the parts */
+ i_stream_seek(input2, 0);
+ test_assert(message_parse_stream(pool, input2, &set_empty, FALSE, &parts) == -1);
+
+ i_stream_unref(&input2);
+ }
+
+ i_stream_unref(&input);
+ pool_unref(&pool);
+ test_end();
+}
+
+static void test_message_parser_get_sizes(struct istream *input,
+ struct message_size *body_size_r,
+ struct message_size *header_size_r,
+ bool expect_has_nuls)
+{
+ bool has_nuls;
+ i_zero(body_size_r);
+ i_zero(header_size_r);
+
+ message_get_header_size(input, header_size_r, &has_nuls);
+ test_assert(has_nuls == expect_has_nuls);
+ message_get_body_size(input, body_size_r, &has_nuls);
+ test_assert(has_nuls == expect_has_nuls);
+}
+
+static void test_message_parser_assert_sizes(const struct message_part *part,
+ const struct message_size *body_size,
+ const struct message_size *header_size)
+{
+ test_assert(part->header_size.lines == header_size->lines);
+ test_assert(part->header_size.physical_size == header_size->physical_size);
+ test_assert(part->header_size.virtual_size == header_size->virtual_size);
+ test_assert(part->body_size.lines == body_size->lines);
+ test_assert(part->body_size.physical_size == body_size->physical_size);
+ test_assert(part->body_size.virtual_size == body_size->virtual_size);
+}
+
+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 istream *input;
+ struct message_part *parts, *part;
+ struct message_size body_size, header_size;
+ pool_t pool;
+
+ test_begin("message parser truncated mime headers");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &set_empty, FALSE, &parts) < 0);
+
+ i_stream_seek(input, 0);
+ test_message_parser_get_sizes(input, &body_size, &header_size, FALSE);
+
+ 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);
+ test_assert(parts->body_size.lines == 8);
+ test_assert(parts->body_size.physical_size == 112);
+ test_assert(parts->body_size.virtual_size == 112+7);
+ test_message_parser_assert_sizes(parts, &body_size, &header_size);
+
+ test_assert(parts->children->physical_pos == 55);
+ 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->physical_pos == 62);
+ 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->physical_pos == 94);
+ 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->physical_pos == 127);
+ 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->children_count == 0);
+ 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);
+
+ test_parsed_parts(input, parts);
+ i_stream_unref(&input);
+ pool_unref(&pool);
+ test_end();
+}
+
+static void test_message_parser_truncated_mime_headers2(void)
+{
+static const char input_msg[] =
+"Content-Type: multipart/mixed; boundary=\"ab\"\n"
+"\n"
+"--ab\n"
+"Content-Type: multipart/mixed; boundary=\"a\"\n"
+"\n"
+"--ab\n"
+"Content-Type: text/plain\n"
+"\n"
+"--a\n\n";
+ struct istream *input;
+ struct message_part *parts;
+ struct message_size body_size, header_size;
+ pool_t pool;
+
+ test_begin("message parser truncated mime headers 2");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &set_empty, FALSE, &parts) < 0);
+
+ i_stream_seek(input, 0);
+ test_message_parser_get_sizes(input, &body_size, &header_size, FALSE);
+
+ 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);
+ test_assert(parts->body_size.lines == 8);
+ test_assert(parts->body_size.physical_size == 86);
+ test_assert(parts->body_size.virtual_size == 86+8);
+ test_message_parser_assert_sizes(parts, &body_size, &header_size);
+
+ 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);
+ test_assert(parts->children->header_size.physical_size == 44);
+ test_assert(parts->children->header_size.virtual_size == 44+1);
+ test_assert(parts->children->body_size.lines == 0);
+ 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);
+ test_assert(parts->children->next->header_size.physical_size == 26);
+ test_assert(parts->children->next->header_size.virtual_size == 26+2);
+ test_assert(parts->children->next->body_size.lines == 2);
+ test_assert(parts->children->next->body_size.physical_size == 5);
+ test_assert(parts->children->next->body_size.virtual_size == 5+2);
+ test_assert(parts->children->next->children == NULL);
+
+ test_parsed_parts(input, parts);
+ i_stream_unref(&input);
+ pool_unref(&pool);
+ test_end();
+}
+
+static void test_message_parser_truncated_mime_headers3(void)
+{
+static const char input_msg[] =
+"Content-Type: multipart/mixed; boundary=\"ab\"\n";
+ struct istream *input;
+ struct message_part *parts;
+ pool_t pool;
+
+ test_begin("message parser truncated mime headers 3");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &set_empty, FALSE, &parts) < 0);
+
+ 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);
+ test_assert(parts->header_size.virtual_size == 45+1);
+ test_assert(parts->body_size.lines == 0);
+ test_assert(parts->body_size.physical_size == 0);
+
+ test_assert(parts->children == NULL);
+
+ test_parsed_parts(input, parts);
+ i_stream_unref(&input);
+ pool_unref(&pool);
+ test_end();
+}
+
+static void test_message_parser_empty_multipart(void)
+{
+static const char input_msg[] =
+"Content-Type: multipart/mixed; boundary=\"ab\"\n"
+"\n"
+"body\n";
+ struct istream *input;
+ struct message_part *parts;
+ pool_t pool;
+
+ test_begin("message parser empty multipart");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &set_empty, FALSE, &parts) < 0);
+
+ 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);
+ test_assert(parts->header_size.virtual_size == 46+2);
+ test_assert(parts->body_size.lines == 1);
+ test_assert(parts->body_size.physical_size == 5);
+ test_assert(parts->body_size.virtual_size == 5+1);
+
+ test_assert(parts->children == NULL);
+
+ test_parsed_parts(input, parts);
+ i_stream_unref(&input);
+ pool_unref(&pool);
+ test_end();
+}
+
+static void test_message_parser_duplicate_mime_boundary(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";
+ struct istream *input;
+ struct message_part *parts;
+ struct message_size body_size, header_size;
+ pool_t pool;
+
+ test_begin("message parser duplicate mime boundary");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &set_empty, FALSE, &parts) < 0);
+
+ i_stream_seek(input, 0);
+ test_message_parser_get_sizes(input, &body_size, &header_size, FALSE);
+
+ 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);
+ test_assert(parts->header_size.virtual_size == 45+2);
+ 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_message_parser_assert_sizes(parts, &body_size, &header_size);
+
+ 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);
+ 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 == 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);
+ 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 == 1);
+ test_assert(parts->children->children->body_size.physical_size == 5);
+ test_assert(parts->children->children->body_size.virtual_size == 5+1);
+
+ test_parsed_parts(input, parts);
+ i_stream_unref(&input);
+ pool_unref(&pool);
+ test_end();
+}
+
+static void test_message_parser_garbage_suffix_mime_boundary(void)
+{
+static const char input_msg[] =
+"Content-Type: multipart/mixed; boundary=\"a\"\n"
+"\n"
+"--ab\n"
+"Content-Type: multipart/mixed; boundary=\"a\"\n"
+"\n"
+"--ac\n"
+"Content-Type: text/plain\n"
+"\n"
+"body\n";
+ struct istream *input;
+ struct message_part *parts;
+ struct message_size body_size, header_size;
+ pool_t pool;
+
+ test_begin("message parser garbage suffix mime boundary");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &set_empty, FALSE, &parts) < 0);
+
+ i_stream_seek(input, 0);
+ test_message_parser_get_sizes(input, &body_size, &header_size, FALSE);
+
+ 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);
+ test_assert(parts->header_size.virtual_size == 45+2);
+ 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_message_parser_assert_sizes(parts, &body_size, &header_size);
+
+ 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);
+ 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 == 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);
+ 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 == 1);
+ test_assert(parts->children->children->body_size.physical_size == 5);
+ test_assert(parts->children->children->body_size.virtual_size == 5+1);
+
+ test_parsed_parts(input, parts);
+ i_stream_unref(&input);
+ pool_unref(&pool);
+ 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 istream *input;
+ struct message_part *parts;
+ pool_t pool;
+
+ test_begin("message parser trailing dashes");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &set_empty, FALSE, &parts) < 0);
+
+ 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[] =
+"Content-Type: multipart/mixed; boundary=\"a\"\n"
+"\n"
+"--a\n"
+"Content-Type: multipart/mixed; boundary=\"ab\"\n"
+"\n"
+"--ab\n"
+"Content-Type: text/plain\n"
+"\n"
+"body\n";
+ struct istream *input;
+ struct message_part *parts;
+ pool_t pool;
+
+ test_begin("message parser continuing mime boundary");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &set_empty, FALSE, &parts) < 0);
+
+ i_stream_seek(input, 0);
+
+ 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);
+ test_assert(parts->header_size.virtual_size == 45+2);
+ 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);
+ test_assert(parts->children->header_size.physical_size == 46);
+ test_assert(parts->children->header_size.virtual_size == 46+2);
+ 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);
+ 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 == 1);
+ test_assert(parts->children->children->body_size.physical_size == 5);
+ test_assert(parts->children->children->body_size.virtual_size == 5+1);
+
+ test_parsed_parts(input, parts);
+ i_stream_unref(&input);
+ pool_unref(&pool);
+ test_end();
+}
+
+static void test_message_parser_continuing_truncated_mime_boundary(void)
+{
+static const char input_msg[] =
+"Content-Type: multipart/mixed; boundary=\"a\"\n"
+"\n"
+"--a\n"
+"Content-Type: multipart/mixed; boundary=\"ab\"\n"
+"MIME-Version: 1.0\n"
+"--ab\n"
+"Content-Type: text/plain\n"
+"\n"
+"--ab--\n"
+"--a--\n\n";
+ struct istream *input;
+ struct message_part *parts, *part;
+ struct message_size body_size, header_size;
+ pool_t pool;
+
+ test_begin("message parser continuing truncated mime boundary");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &set_empty, FALSE, &parts) < 0);
+
+ i_stream_seek(input, 0);
+ test_message_parser_get_sizes(input, &body_size, &header_size, FALSE);
+
+ 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 == 9);
+ test_assert(part->body_size.physical_size == 112);
+ test_assert(part->body_size.virtual_size == 112+9);
+ test_message_parser_assert_sizes(part, &body_size, &header_size);
+
+ 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);
+ test_assert(part->header_size.physical_size == 45+17);
+ test_assert(part->header_size.virtual_size == 45+17+1);
+ test_assert(part->body_size.lines == 0);
+ test_assert(part->body_size.physical_size == 0);
+ test_assert(part->children == NULL);
+
+ /* this will not be a child, since the header was truncated. I guess
+ 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);
+ test_assert(part->header_size.physical_size == 25);
+ test_assert(part->header_size.virtual_size == 25+1);
+ test_assert(part->body_size.lines == 0);
+ test_assert(part->body_size.physical_size == 0);
+ 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);
+ test_assert(part->body_size.lines == 0);
+ test_assert(part->body_size.physical_size == 0);
+ test_assert(part->children == NULL);
+ test_assert(part->next == NULL);
+
+ test_parsed_parts(input, parts);
+ i_stream_unref(&input);
+ pool_unref(&pool);
+ 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 istream *input;
+ struct message_part *parts;
+ struct message_size body_size, header_size;
+ pool_t pool;
+
+ test_begin("message parser continuing mime boundary reverse");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &set_empty, FALSE, &parts) < 0);
+
+ i_stream_seek(input, 0);
+ test_message_parser_get_sizes(input, &body_size, &header_size, FALSE);
+
+ 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);
+ 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_message_parser_assert_sizes(parts, &body_size, &header_size);
+
+ 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);
+ 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->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);
+ 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->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);
+ 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";
+ struct message_parser_ctx *parser;
+ struct istream *input;
+ struct message_part *parts;
+ struct message_block block;
+ pool_t pool;
+
+ test_begin("message parser no EOH");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ 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');
+ test_assert(message_parser_parse_next_block(parser, &block) > 0 &&
+ block.hdr == NULL && block.size == 0);
+ test_assert(message_parser_parse_next_block(parser, &block) < 0);
+ message_parser_deinit(&parser, &parts);
+ test_assert(input->stream_errno == 0);
+
+ test_parsed_parts(input, parts);
+ i_stream_unref(&input);
+ pool_unref(&pool);
+ 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 istream *input;
+ struct message_part *parts, *part;
+ struct message_size body_size, header_size;
+ pool_t pool;
+
+ test_begin("message parser long mime boundary");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &set_empty, FALSE, &parts) < 0);
+
+ i_stream_seek(input, 0);
+ test_message_parser_get_sizes(input, &body_size, &header_size, FALSE);
+
+ 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);
+ test_message_parser_assert_sizes(part, &body_size, &header_size);
+
+ 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();
+}
+
+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 istream *input;
+ struct message_part *parts, *part;
+ struct message_size body_size, header_size;
+ pool_t pool;
+
+ test_begin("message parser mime part nested limit");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &parser_set, FALSE, &parts) < 0);
+
+ i_stream_seek(input, 0);
+ test_message_parser_get_sizes(input, &body_size, &header_size, FALSE);
+
+ 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);
+ test_message_parser_assert_sizes(part, &body_size, &header_size);
+
+ part = parts->children;
+ test_assert(part->children_count == 0);
+ test_assert(part->flags == (MESSAGE_PART_FLAG_IS_MIME |
+ MESSAGE_PART_FLAG_OVERFLOW));
+ 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 istream *input;
+ struct message_part *parts, *part;
+ struct message_size body_size, header_size;
+ pool_t pool;
+
+ test_begin("message parser mime part nested limit rfc822");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &parser_set, FALSE, &parts) < 0);
+
+ i_stream_seek(input, 0);
+ test_message_parser_get_sizes(input, &body_size, &header_size, FALSE);
+
+ 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);
+ test_message_parser_assert_sizes(part, &body_size, &header_size);
+
+ part = parts->children;
+ test_assert(part->children_count == 0);
+ test_assert(part->flags == (MESSAGE_PART_FLAG_IS_MIME |
+ MESSAGE_PART_FLAG_OVERFLOW));
+ 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();
+}
+
+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 istream *input;
+ struct message_part *parts, *part;
+ struct message_size body_size, header_size;
+ pool_t pool;
+
+ test_begin("message parser mime part limit");
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &parser_set, FALSE, &parts) < 0);
+
+ i_stream_seek(input, 0);
+ test_message_parser_get_sizes(input, &body_size, &header_size, FALSE);
+
+ 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);
+ test_message_parser_assert_sizes(part, &body_size, &header_size);
+
+ 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 |
+ MESSAGE_PART_FLAG_OVERFLOW));
+ 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();
+}
+
+static void test_message_parser_mime_part_limit_rfc822(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: message/rfc822\n"
+"\n"
+"Content-Type: text/plain\n"
+"\n"
+"1\n"
+"--2\n"
+"Content-Type: message/rfc822\n"
+"\n"
+"Content-Type: text/plain\n"
+"\n"
+"22\n"
+"--1\n"
+"Content-Type: message/rfc822\n"
+"\n"
+"Content-Type: text/plain\n"
+"\n"
+"333\n";
+ const struct message_parser_settings parser_set = {
+ .max_total_mime_parts = 3,
+ };
+ 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 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 == 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 == 21);
+ test_assert(part->body_size.physical_size == 238);
+ test_assert(part->body_size.virtual_size == 238+21);
+
+ part = parts->children;
+ test_assert(part->children_count == 1);
+ 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 == 18);
+ test_assert(part->body_size.physical_size == 189);
+ test_assert(part->body_size.virtual_size == 189+18);
+
+ part = parts->children->children;
+ test_assert(part->children_count == 0);
+ test_assert(part->flags == (MESSAGE_PART_FLAG_IS_MIME |
+ MESSAGE_PART_FLAG_OVERFLOW));
+ 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 == 15);
+ test_assert(part->body_size.physical_size == 155);
+ test_assert(part->body_size.virtual_size == 155+15);
+
+ test_parsed_parts(input, parts);
+ i_stream_unref(&input);
+ pool_unref(&pool);
+ test_end();
+}
+
+static void test_message_parser_mime_version(void)
+{
+ test_begin("message parser mime version");
+
+ /* Check that MIME version is accepted. */
+static const char *const input_msgs[] = {
+ /* valid mime header */
+"Content-Type: multipart/mixed; boundary=\"1\"\n"
+"MIME-Version: 1.0\n\n"
+"--1\n"
+"Content-Type: text/plain\n"
+"\n"
+"hello, world\n"
+"--1\n",
+ /* future mime header */
+"Content-Type: multipart/mixed; boundary=\"1\"\n"
+"MIME-Version: 2.0\n\n"
+"--1\n"
+"Content-Type: text/plain\n"
+"\n"
+"hello, world\n"
+"--1\n",
+ /* invalid value in mime header */
+"Content-Type: multipart/mixed; boundary=\"1\"\n"
+"MIME-Version: abc\n\n"
+"--1\n"
+"Content-Type: text/plain\n"
+"\n"
+"hello, world\n"
+"--1\n",
+ /* missing value in mime header */
+"Content-Type: multipart/mixed; boundary=\"1\"\n"
+"MIME-Version:\n\n"
+"--1\n"
+"Content-Type: text/plain\n"
+"\n"
+"hello, world\n"
+"--1\n"
+ };
+
+ const struct message_parser_settings parser_set = {
+ .max_total_mime_parts = 2,
+ .flags = MESSAGE_PARSER_FLAG_MIME_VERSION_STRICT,
+ };
+ struct istream *input;
+ struct message_part *parts, *part;
+ pool_t pool;
+
+ for (size_t i = 0; i < N_ELEMENTS(input_msgs); i++) {
+ ssize_t variance = (ssize_t)strlen(input_msgs[i]) - (ssize_t)strlen(input_msgs[0]);
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msgs[i]);
+
+ test_assert(message_parse_stream(pool, input, &parser_set, TRUE, &parts) < 0);
+ part = parts;
+
+ test_assert_idx(part->children_count == 1, i);
+ test_assert_idx(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME), i);
+ test_assert_idx(part->header_size.lines == 3, i);
+ test_assert_idx(part->header_size.physical_size == (size_t)(63 + variance), i);
+ test_assert_idx(part->header_size.virtual_size == (size_t)(66 + variance), i);
+ test_assert_idx(part->body_size.lines == 5, i);
+ test_assert_idx(part->body_size.physical_size == 47, i);
+ test_assert_idx(part->body_size.virtual_size == 52, i);
+ test_assert_strcmp_idx(part->data->content_type, "multipart", i);
+ test_assert_strcmp_idx(part->data->content_subtype, "mixed", i);
+ part = part->children;
+
+ test_assert_idx(part->children_count == 0, i);
+ test_assert_idx(part->flags == (MESSAGE_PART_FLAG_TEXT |
+ MESSAGE_PART_FLAG_IS_MIME |
+ MESSAGE_PART_FLAG_OVERFLOW), i);
+ test_assert_idx(part->header_size.lines == 2, i);
+ test_assert_idx(part->header_size.physical_size == 26, i);
+ test_assert_idx(part->header_size.virtual_size == 28, i);
+ test_assert_idx(part->body_size.lines == 2, i);
+ test_assert_idx(part->body_size.physical_size == 17, i);
+ test_assert_strcmp_idx(part->data->content_type, "text", i);
+ test_assert_strcmp_idx(part->data->content_subtype, "plain", i);
+
+ test_parsed_parts(input, parts);
+ i_stream_unref(&input);
+ pool_unref(&pool);
+ };
+
+ /* test for +10MB header */
+ const size_t test_hdr_size = 10*1024*1024UL;
+ const size_t test_msg_size = test_hdr_size + 1024UL;
+ /* add space for parser */
+ pool = pool_alloconly_create("10mb header", test_msg_size + 10240UL);
+ string_t *buffer = str_new(pool, test_msg_size + 1);
+
+ str_append(buffer, "MIME-Version: ");
+
+ /* @UNSAFE */
+ char *tmp = buffer_append_space_unsafe(buffer, test_hdr_size);
+ memset(tmp, 'a', test_hdr_size);
+
+ str_append_c(buffer, '\n');
+ str_append(buffer, "Content-Type: multipart/mixed; boundary=1\n\n--1--");
+
+ input = test_istream_create_data(buffer->data, buffer->used);
+ test_assert(message_parse_stream(pool, input, &parser_set, TRUE, &parts) < 0);
+
+ i_stream_unref(&input);
+ pool_unref(&pool);
+ test_end();
+}
+
+static void test_message_parser_mime_version_missing(void)
+{
+ test_begin("message parser mime version missing");
+
+static const char input_msg[] =
+"Content-Type: multipart/mixed; boundary=\"1\"\n\n"
+"--1\n"
+"Content-Type: text/plain\n"
+"\n"
+"hello, world\n"
+"--1\n";
+
+ const struct message_parser_settings parser_set = {
+ .max_total_mime_parts = 2,
+ .flags = MESSAGE_PARSER_FLAG_MIME_VERSION_STRICT,
+ };
+ struct istream *input;
+ struct message_part *parts, *part;
+ pool_t pool;
+
+ pool = pool_alloconly_create("message parser", 10240);
+ input = test_istream_create(input_msg);
+
+ test_assert(message_parse_stream(pool, input, &parser_set, TRUE, &parts) < 0);
+ part = parts;
+
+ /* non-MIME message should end up as plain text mail */
+
+ test_assert(part->children_count == 0);
+ test_assert(part->flags == MESSAGE_PART_FLAG_TEXT);
+ test_assert(part->header_size.lines == 2);
+ test_assert(part->header_size.physical_size == 45);
+ test_assert(part->header_size.virtual_size == 47);
+ test_assert(part->body_size.lines == 5);
+ test_assert(part->body_size.physical_size == 47);
+ test_assert(part->body_size.virtual_size == 52);
+ test_assert(part->children == NULL);
+ test_assert(part->data->content_type == NULL);
+ test_assert(part->data->content_subtype == NULL);
+
+ 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,
+ 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,
+ 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,
+ test_message_parser_mime_part_limit,
+ test_message_parser_mime_part_limit_rfc822,
+ test_message_parser_mime_version,
+ test_message_parser_mime_version_missing,
+ NULL
+ };
+ return test_run(test_functions);
+}