Mercurial > dovecot > core-2.2
view src/lib-mail/test-message-parser.c @ 22588:041460202062
ostream-multiplex: Unreference stream parent
Otherwise it won't get free'd.
author | Aki Tuomi <aki.tuomi@dovecot.fi> |
---|---|
date | Mon, 09 Oct 2017 18:21:24 +0300 |
parents | 2e2563132d5f |
children | cb108f786fb4 |
line wrap: on
line source
/* Copyright (c) 2007-2017 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "istream.h" #include "message-parser.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 bool msg_parts_cmp(struct message_part *p1, struct message_part *p2) { while (p1 != NULL || p2 != NULL) { if ((p1 != NULL) != (p2 != NULL)) return FALSE; if ((p1->children != NULL) != (p2->children != NULL)) return FALSE; if (p1->children) { if (!msg_parts_cmp(p1->children, p2->children)) return FALSE; } if (p1->physical_pos != p2->physical_pos || p1->header_size.physical_size != p2->header_size.physical_size || p1->header_size.virtual_size != p2->header_size.virtual_size || p1->header_size.lines != p2->header_size.lines || 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->flags != p2->flags) return FALSE; p1 = p1->next; p2 = p2->next; } return TRUE; } static void test_parsed_parts(struct istream *input, struct message_part *parts) { struct message_parser_ctx *parser; struct message_block block; struct message_part *parts2; uoff_t i, input_size; const char *error; int ret; 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, 0, MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK); 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 ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; } test_assert(message_parser_deinit_from_parts(&parser, &parts2, &error) == 0); test_assert(msg_parts_cmp(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; 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 */ parser = message_parser_init(pool, input, 0, MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS | MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES); 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_n(output, block.data, block.size); } test_assert(ret < 0); test_assert(message_parser_deinit(&parser, &parts) == 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, 0, 0); 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)); } test_assert(message_parser_deinit(&parser, &parts2) == 0); test_assert(msg_parts_cmp(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); parser = message_parser_init_from_parts(parts, input, 0, MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK); 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(&parser, &parts2) == 0); test_assert(msg_parts_cmp(parts, parts2)); i_stream_unref(&input); pool_unref(&pool); 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->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_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->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 message_parser_ctx *parser; struct istream *input; struct message_part *parts; struct message_block block; pool_t pool; int ret; test_begin("message parser truncated mime headers 2"); 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 | 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 == 8); test_assert(parts->body_size.physical_size == 86); test_assert(parts->body_size.virtual_size == 86+8); 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->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 message_parser_ctx *parser; struct istream *input; struct message_part *parts; struct message_block block; pool_t pool; int ret; test_begin("message parser truncated mime headers 3"); 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 | 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 message_parser_ctx *parser; struct istream *input; struct message_part *parts; struct message_block block; pool_t pool; int ret; test_begin("message parser empty multipart"); 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 | 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 message_parser_ctx *parser; struct istream *input; struct message_part *parts; struct message_block block; pool_t pool; int ret; test_begin("message parser duplicate 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); test_assert(message_parser_deinit(&parser, &parts) == 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 == 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_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->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 message_parser_ctx *parser; struct istream *input; struct message_part *parts; struct message_block block; pool_t pool; int ret; test_begin("message parser garbage suffix 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); test_assert(message_parser_deinit(&parser, &parts) == 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 == 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->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->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_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 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"); 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 | 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->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->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 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 continuing truncated 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->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); part = parts->children; 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->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->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_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, 0, 0); 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); test_assert(message_parser_deinit(&parser, &parts) == 0); test_parsed_parts(input, parts); 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, 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_continuing_mime_boundary, test_message_parser_continuing_truncated_mime_boundary, test_message_parser_no_eoh, NULL }; return test_run(test_functions); }