struct message_block *block_r);
unsigned int part_seen_content_type:1;
+ unsigned int multipart:1;
unsigned int eof:1;
};
}
}
+static bool block_is_at_eoh(const struct message_block *block)
+{
+ if (block->size < 1)
+ return FALSE;
+ if (block->data[0] == '\n')
+ return TRUE;
+ if (block->data[0] == '\r') {
+ if (block->size < 2)
+ return FALSE;
+ if (block->data[1] == '\n')
+ return TRUE;
+ }
+ return FALSE;
+}
+
#define MUTEX_FLAGS \
(MESSAGE_PART_FLAG_MESSAGE_RFC822 | MESSAGE_PART_FLAG_MULTIPART)
if ((ret = message_parser_read_more(ctx, block_r, &full)) == 0)
return ret;
+ if (ret > 0 && block_is_at_eoh(block_r) &&
+ ctx->last_boundary != NULL &&
+ (part->flags & MESSAGE_PART_FLAG_IS_MIME) != 0) {
+ /* we are at the end of headers and we've determined that we're
+ going to start a multipart. add the boundary already here
+ at this point so we can reliably determine whether the
+ "\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;
+ }
+
/* 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. */
if (ret > 0) {
ret = ctx->boundaries == NULL ? -1 :
boundary_line_find(ctx, block_r->data,
block_r->size, full, &boundary);
+ if (ret > 0 && boundary->part == ctx->part) {
+ /* our own body begins with our own --boundary.
+ we don't want to handle that yet. */
+ ret = -1;
+ }
}
if (ret < 0) {
/* no boundary */
}
/* end of headers */
- if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
- ctx->last_boundary == NULL) {
- /* multipart type but no message boundary */
- part->flags = 0;
- }
if ((part->flags & MESSAGE_PART_FLAG_IS_MIME) == 0) {
/* It's not MIME. Reset everything we found from
Content-Type. */
+ i_assert(!ctx->multipart);
part->flags = 0;
ctx->last_boundary = NULL;
}
i_assert((part->flags & MUTEX_FLAGS) != MUTEX_FLAGS);
ctx->last_chr = '\n';
- if (ctx->last_boundary != NULL) {
- parse_next_body_multipart_init(ctx);
+ if (ctx->multipart) {
+ 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)
ctx->parse_next_block = parse_next_body_message_rfc822_init;
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);
+
+ 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);
+
+ 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";
static void (*test_functions[])(void) = {
test_message_parser_small_blocks,
test_message_parser_truncated_mime_headers,
+ test_message_parser_duplicate_mime_boundary,
+ test_message_parser_continuing_mime_boundary,
test_message_parser_no_eoh,
NULL
};