diff --git a/src/lib-mail/message-parser-from-parts.c b/src/lib-mail/message-parser-from-parts.c index 7bb1a847fe1..ce83d466a5c 100644 --- a/src/lib-mail/message-parser-from-parts.c +++ b/src/lib-mail/message-parser-from-parts.c @@ -121,15 +121,26 @@ static int preparsed_parse_prologue_more(struct message_parser_ctx *ctx, 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) { + /* boundary is at the very start of the prologue, i.e. + there is no preamble before it (no preceding newline + to find). Don't read before block_r->data. */ + if (block_r->data[0] != '-' || block_r->data[1] != '-') { + ctx->broken_reason = "Prologue boundary beginning not at expected position"; + return -1; + } + block_r->size = 0; + } else { + 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--; + if (cur != block_r->data && cur[-1] == '\r') cur--; - /* clip boundary */ - block_r->size = cur - block_r->data; + /* clip boundary */ + block_r->size = cur - block_r->data; + } ctx->parse_next_block = preparsed_parse_prologue_finish; ctx->skip = block_r->size; diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c index 5c2372d3040..ee5d188dcdd 100644 --- a/src/lib-mail/test-message-parser.c +++ b/src/lib-mail/test-message-parser.c @@ -1521,6 +1521,50 @@ static void test_message_parser_too_many_header_bytes_100(void) test_end(); } +static void test_message_parser_preparsed_empty_preamble(void) +{ +static const char input_msg[] = +"Content-Type: multipart/mixed; boundary=\"b\"\r\n" +"\r\n" +"--b\r\n" +"Content-Type: text/plain\r\n" +"\r\n" +"hello\r\n" +"--b--\r\n"; + const struct message_parser_settings preparsed_set = { + .flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS, + }; + struct message_parser_ctx *parser; + struct istream *input; + struct message_part *parts, *parts2; + struct message_block block; + const char *error; + pool_t pool; + + test_begin("message parser preparsed empty preamble"); + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + test_istream_set_allow_eof(input, TRUE); + + test_assert(message_parse_stream(pool, input, &set_empty, FALSE, &parts) < 0); + + /* Re-parse from the cached parts, requesting multipart blocks so the + prologue is returned. The prologue region here is exactly the + boundary line ("--b\r\n") with no preamble before it. Before the fix + the backward newline scan walked past block start and clipped the + block to a (size_t)-underflowed length. */ + i_stream_seek(input, 0); + parser = message_parser_init_from_parts(parts, input, &preparsed_set); + while (message_parser_parse_next_block(parser, &block) > 0) + test_assert(block.size <= sizeof(input_msg)); + 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(); +} + int main(void) { static void (*const test_functions[])(void) = { @@ -1546,6 +1590,7 @@ int main(void) test_message_parser_mime_version_missing, test_message_parser_too_many_header_bytes_default, test_message_parser_too_many_header_bytes_100, + test_message_parser_preparsed_empty_preamble, NULL }; return test_run(test_functions);