]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-imap: Fix writing BODYSTRUCTURE for truncated message/rfc822 part
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Wed, 18 Nov 2020 16:55:34 +0000 (18:55 +0200)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Wed, 2 Dec 2020 07:31:44 +0000 (07:31 +0000)
If the max nesting limit is reached, write the last part out as
application/octet-stream instead of dummy message/rfc822.

Fixes error while parsing BODYSTRUCTURE:
message_part message/rfc822 flag doesn't match BODYSTRUCTURE

src/lib-imap/imap-bodystructure.c
src/lib-imap/test-imap-bodystructure.c

index e3da1090b4f2cbaa2bbacefde06720da53b6d358..ab422c00d21d2bda58176e4461b82d818a12af28 100644 (file)
@@ -142,31 +142,42 @@ static void part_write_body_multipart(const struct message_part *part,
        part_write_bodystructure_common(data, str);
 }
 
+static bool part_is_truncated(const struct message_part *part)
+{
+       const struct message_part_data *data = part->data;
+
+       i_assert((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0);
+
+       if (data->content_type != NULL) {
+               if (strcasecmp(data->content_type, "message") == 0 &&
+                   strcasecmp(data->content_subtype, "rfc822") == 0) {
+                       /* It's message/rfc822, but without
+                          MESSAGE_PART_FLAG_MESSAGE_RFC822. */
+                       return TRUE;
+               }
+       }
+       return FALSE;
+}
+
 static void part_write_body(const struct message_part *part,
                            string_t *str, bool extended)
 {
        const struct message_part_data *data = part->data;
-       bool text, message_rfc822;
+       bool text;
 
        i_assert(part->data != NULL);
 
-       if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0)
-               message_rfc822 = TRUE;
-       else if (data->content_type != NULL &&
-                strcasecmp(data->content_type, "message") == 0 &&
-                strcasecmp(data->content_subtype, "rfc822") == 0) {
-               /* It's message/rfc822, but without
-                  MESSAGE_PART_FLAG_MESSAGE_RFC822. That likely means maximum
-                  MIME part count was reached while parsing the mail. Write
-                  the missing child mail's ENVELOPE and BODY as empty dummy
-                  values. */
-               message_rfc822 = TRUE;
-       } else
-               message_rfc822 = FALSE;
-
-       if (message_rfc822) {
+       if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) {
                str_append(str, "\"message\" \"rfc822\"");
                text = FALSE;
+       } else if (part_is_truncated(part)) {
+               /* Maximum MIME part count was reached while parsing the mail.
+                  Write this part out as application/octet-stream instead.
+                  We're not using text/plain, because it would require
+                  message-parser to use MESSAGE_PART_FLAG_TEXT for this part
+                  to avoid losing line count in message_part serialization. */
+               str_append(str, "\"application\" \"octet-stream\"");
+               text = FALSE;
        } else {
                /* "content type" "subtype" */
                if (data->content_type == NULL) {
@@ -214,17 +225,6 @@ static void part_write_body(const struct message_part *part,
 
                part_write_bodystructure_siblings(part->children, str, extended);
                str_printfa(str, " %u", part->body_size.lines);
-       } else if (message_rfc822) {
-               /* truncated MIME part - write out dummy values */
-               i_assert(part->children == NULL);
-
-               str_append(str, " (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) ");
-
-               if (!extended)
-                       str_append(str, EMPTY_BODY);
-               else
-                       str_append(str, EMPTY_BODYSTRUCTURE);
-               str_printfa(str, " %u", part->body_size.lines);
        }
 
        if (!extended)
index dfc9957488582983143d84c9f630aae905220671..6cb699e12694243114a17c07d948ebc65b8b54c2 100644 (file)
@@ -4,6 +4,7 @@
 #include "istream.h"
 #include "str.h"
 #include "message-part-data.h"
+#include "message-part-serialize.h"
 #include "message-parser.h"
 #include "imap-bodystructure.h"
 #include "test-common.h"
@@ -379,12 +380,14 @@ struct normalize_test normalize_tests[] = {
 static const unsigned int normalize_tests_count = N_ELEMENTS(normalize_tests);
 
 static struct message_part *
-msg_parse(pool_t pool, const char *message, bool parse_bodystructure)
+msg_parse(pool_t pool, const char *message, unsigned int max_nested_mime_parts,
+         bool parse_bodystructure)
 {
        const struct message_parser_settings parser_set = {
                .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
                        MESSAGE_HEADER_PARSER_FLAG_DROP_CR,
                .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK,
+               .max_nested_mime_parts = max_nested_mime_parts,
        };
        struct message_parser_ctx *parser;
        struct istream *input;
@@ -418,7 +421,7 @@ static void test_imap_bodystructure_write(void)
                pool_t pool = pool_alloconly_create("imap bodystructure write", 1024);
 
                test_begin(t_strdup_printf("imap bodystructure write [%u]", i));
-               parts = msg_parse(pool, test->message, TRUE);
+               parts = msg_parse(pool, test->message, 0, TRUE);
 
                imap_bodystructure_write(parts, str, TRUE);
                test_assert(strcmp(str_c(str), test->bodystructure) == 0);
@@ -445,7 +448,7 @@ static void test_imap_bodystructure_parse(void)
                pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024);
 
                test_begin(t_strdup_printf("imap bodystructure parser [%u]", i));
-               parts = msg_parse(pool, test->message, FALSE);
+               parts = msg_parse(pool, test->message, 0, FALSE);
 
                test_assert(imap_body_parse_from_bodystructure(test->bodystructure,
                                                                     str, &error) == 0);
@@ -512,7 +515,7 @@ static void test_imap_bodystructure_normalize(void)
                pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024);
 
                test_begin(t_strdup_printf("imap bodystructure normalize [%u]", i));
-               parts = msg_parse(pool, test->message, FALSE);
+               parts = msg_parse(pool, test->message, 0, FALSE);
 
                ret = imap_bodystructure_parse(test->input,
                                                           pool, parts, &error);
@@ -531,6 +534,67 @@ static void test_imap_bodystructure_normalize(void)
        } T_END;
 }
 
+static const struct {
+       const char *input;
+       const char *bodystructure;
+       unsigned int max_depth;
+} truncation_tests[] = {
+       {
+               .input = "Content-Type: message/rfc822\n"
+                       "\n"
+                       "Content-Type: message/rfc822\n"
+                       "Header2: value2\n"
+                       "\n"
+                       "Subject: hello world\n"
+                       "Header2: value2\n"
+                       "Header3: value3\n"
+                       "\n"
+                       "body line 1\n"
+                       "body line 2\n"
+                       "body line 4\n"
+                       "body line 3\n",
+               .bodystructure = "\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 159 (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) (\"application\" \"octet-stream\" NIL NIL NIL \"7bit\" 110 NIL NIL NIL NIL) 11 NIL NIL NIL NIL",
+               .max_depth = 2,
+       },
+};
+
+static void test_imap_bodystructure_truncation(void)
+{
+       struct message_part *parts;
+       const char *error;
+       string_t *str_body = t_str_new(128);
+       string_t *str_parts = t_str_new(128);
+       pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024);
+
+       test_begin("imap bodystructure truncation");
+
+       for (unsigned int i = 0; i < N_ELEMENTS(truncation_tests); i++) {
+               p_clear(pool);
+               str_truncate(str_body, 0);
+               str_truncate(str_parts, 0);
+
+               parts = msg_parse(pool, truncation_tests[i].input,
+                                 truncation_tests[i].max_depth,
+                                 TRUE);
+
+               /* write out BODYSTRUCTURE and serialize message_parts */
+               imap_bodystructure_write(parts, str_body, TRUE);
+               message_part_serialize(parts, str_parts);
+
+               /* now deserialize message_parts and make sure they can be used
+                  to parse BODYSTRUCTURE */
+               parts = message_part_deserialize(pool, str_data(str_parts),
+                                                str_len(str_parts), &error);
+               test_assert(parts != NULL);
+               test_assert(imap_bodystructure_parse(str_c(str_body), pool,
+                                                    parts, &error) == 0);
+               test_assert_strcmp(str_c(str_body),
+                                  truncation_tests[i].bodystructure);
+       }
+       pool_unref(&pool);
+       test_end();
+}
+
 int main(void)
 {
        static void (*const test_functions[])(void) = {
@@ -538,6 +602,7 @@ int main(void)
                test_imap_bodystructure_parse,
                test_imap_bodystructure_normalize,
                test_imap_bodystructure_parse_full,
+               test_imap_bodystructure_truncation,
                NULL
        };
        return test_run(test_functions);