]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-mail: rfc822-parser - Fix content-type parser to accept only valid values
authorAki Tuomi <aki.tuomi@open-xchange.com>
Mon, 16 Nov 2020 14:20:30 +0000 (16:20 +0200)
committertimo.sirainen <timo.sirainen@open-xchange.com>
Tue, 1 Dec 2020 14:33:32 +0000 (14:33 +0000)
src/lib-mail/rfc822-parser.c
src/lib-mail/test-rfc822-parser.c

index f87866f4428f28ad995228f699bb4f5809211c37..c8595b4187b5ee377b046a7699ddec365429a100 100644 (file)
@@ -434,23 +434,39 @@ int rfc822_parse_domain(struct rfc822_parser_context *ctx, string_t *str)
 
 int rfc822_parse_content_type(struct rfc822_parser_context *ctx, string_t *str)
 {
+       size_t str_pos_0 = str->used;
        if (rfc822_skip_lwsp(ctx) <= 0)
                return -1;
 
-       /* get main type */
-       if (rfc822_parse_mime_token(ctx, str) <= 0)
+       /* get main type, require at least one byte */
+       if (rfc822_parse_mime_token(ctx, str) <= 0 ||
+           str->used == str_pos_0)
                return -1;
 
        /* skip over "/" */
-       if (*ctx->data != '/')
+       if (*ctx->data != '/') {
+               str_truncate(str, str_pos_0);
                return -1;
+       }
        ctx->data++;
-       if (rfc822_skip_lwsp(ctx) <= 0)
+       if (rfc822_skip_lwsp(ctx) <= 0) {
+               str_truncate(str, str_pos_0);
                return -1;
+       }
        str_append_c(str, '/');
 
-       /* get subtype */
-       return rfc822_parse_mime_token(ctx, str);
+       size_t str_pos = str->used;
+       /* get subtype, require at least one byte,
+          and check the next separator to avoid accepting
+          invalid values. */
+       int ret;
+       if ((ret = rfc822_parse_mime_token(ctx, str)) < 0 ||
+           str->used == str_pos ||
+           (ctx->data != ctx->end && *ctx->data != ';')) {
+               str_truncate(str, str_pos_0);
+               return -1;
+       }
+       return ret;
 }
 
 int rfc822_parse_content_param(struct rfc822_parser_context *ctx,
index 55018b99aa6596de6751655ebdddf9662d4f8ef1..e5cf405f69a615229729a28144170aebbda39f16 100644 (file)
@@ -198,6 +198,98 @@ static void test_rfc822_parse_domain_literal(void)
        test_end();
 }
 
+#undef TEST_STRING
+#define TEST_STRING(a) .input = (const unsigned char*)a, .input_len = sizeof(a)-1
+
+static void test_rfc822_parse_content_type(void)
+{
+       const struct {
+               const unsigned char *input;
+               size_t input_len;
+               int ret;
+               const char *output;
+       } test_cases[] = {
+               { TEST_STRING(""), -1, "" },
+               { TEST_STRING(";charset=us-ascii"), -1, "" },
+               { TEST_STRING(" ;charset=us-ascii"), -1, "" },
+               { TEST_STRING("/"), -1, "" },
+               { TEST_STRING("/;charset=us-ascii"), -1, "" },
+               { TEST_STRING("/ ;charset=us-ascii"), -1, "" },
+               { TEST_STRING("text/"), -1, "" },
+               { TEST_STRING("text/;charset=us-ascii"), -1, "" },
+               { TEST_STRING("text/ ;charset=us-ascii"), -1, "" },
+               { TEST_STRING("/plain"), -1, "" },
+               { TEST_STRING("/plain;charset=us-ascii"), -1, "" },
+               { TEST_STRING("/plain ;charset=us-ascii"), -1, "" },
+               { TEST_STRING("text/plain"), 0, "text/plain" },
+               { TEST_STRING("text/plain;charset=us-ascii"), 1, "text/plain" },
+               { TEST_STRING("text/plain ;charset=us-ascii"), 1, "text/plain" },
+               { TEST_STRING("text/plain/format"), -1, "" },
+               { TEST_STRING("text/plain/format;charset=us-ascii"), -1, "" },
+               { TEST_STRING("text/plain/format ;charset=us-ascii"), -1, "" },
+               { TEST_STRING("\xe5\x90\xab\xe9\x87\x8f/\xe7\xa8\xae\xe9\xa1\x9e"),
+                 0, "\xe5\x90\xab\xe9\x87\x8f/\xe7\xa8\xae\xe9\xa1\x9e" },
+               { TEST_STRING("\xe5\x90\xab\xe9\x87\x8f/\xe7\xa8\xae\xe9\xa1\x9e;charset=utf-8"),
+                 1, "\xe5\x90\xab\xe9\x87\x8f/\xe7\xa8\xae\xe9\xa1\x9e" },
+               { TEST_STRING("\xe5\x90\xab\xe9\x87\x8f/\xe7\xa8\xae\xe9\xa1\x9e ;charset=utf-8"),
+                 1, "\xe5\x90\xab\xe9\x87\x8f/\xe7\xa8\xae\xe9\xa1\x9e" },
+               { TEST_STRING("application/ld+json"), 0, "application/ld+json" },
+               { TEST_STRING("application/ld+json;charset=us-ascii"),
+                 1, "application/ld+json" },
+               { TEST_STRING("application/ld+json ;charset=us-ascii"),
+                 1, "application/ld+json" },
+               { TEST_STRING("application/x-magic-cap-package-1.0"),
+                 0, "application/x-magic-cap-package-1.0" },
+               { TEST_STRING("application/x-magic-cap-package-1.0;charset=us-ascii"),
+                 1, "application/x-magic-cap-package-1.0" },
+               { TEST_STRING("application/x-magic-cap-package-1.0 ;charset=us-ascii"),
+                 1, "application/x-magic-cap-package-1.0" },
+               { TEST_STRING("application/pro_eng"), 0, "application/pro_eng" },
+               { TEST_STRING("application/pro_eng;charset=us-ascii"),
+                 1, "application/pro_eng" },
+               { TEST_STRING("application/pro_eng ;charset=us-ascii"),
+                 1, "application/pro_eng" },
+               { TEST_STRING("application/wordperfect6.1"),
+                 0, "application/wordperfect6.1" },
+               { TEST_STRING("application/wordperfect6.1;charset=us-ascii"),
+                 1, "application/wordperfect6.1" },
+               { TEST_STRING("application/wordperfect6.1 ;charset=us-ascii"),
+                 1, "application/wordperfect6.1" },
+               { TEST_STRING("application/vnd.openxmlformats-officedocument.wordprocessingml.template"),
+                 0, "application/vnd.openxmlformats-officedocument.wordprocessingml.template" },
+               { TEST_STRING("application/vnd.openxmlformats-officedocument.wordprocessingml.template;charset=us-ascii"),
+                 1, "application/vnd.openxmlformats-officedocument.wordprocessingml.template" },
+               { TEST_STRING("application/vnd.openxmlformats-officedocument.wordprocessingml.template ;charset=us-asii"),
+                 1, "application/vnd.openxmlformats-officedocument.wordprocessingml.template" },
+               { TEST_STRING("(hello) text (plain) / (world) plain (eod)"),
+                 0, "text/plain" },
+               { TEST_STRING("(hello) text (plain) / (world) plain (eod);charset=us-ascii"),
+                 1, "text/plain" },
+               { TEST_STRING("(hello) text (plain) / (world) plain (eod); charset=us-ascii"),
+                 1, "text/plain" },
+               { TEST_STRING("message/rfc822\r\n"), 0, "message/rfc822" },
+               { TEST_STRING(" \t\r message/rfc822 \t\r\n"),
+                 0, "message/rfc822" },
+               { TEST_STRING(" \t\r message/rfc822 \t ;charset=us-ascii\r\n"),
+                 1, "message/rfc822" },
+               { TEST_STRING(" \t\r message/rfc822 \t ; charset=us-ascii\r\n"),
+                 1, "message/rfc822" },
+               { TEST_STRING("test\0/ty\0pe"), -1, "" },
+       };
+
+       for (size_t i = 0; i < N_ELEMENTS(test_cases); i++) T_BEGIN {
+               string_t *value = t_str_new(64);
+               struct rfc822_parser_context parser;
+
+               rfc822_parser_init(&parser, test_cases[i].input,
+                                  test_cases[i].input_len, NULL);
+               test_assert_idx(rfc822_parse_content_type(&parser, value) ==
+                               test_cases[i].ret, i);
+               test_assert_strcmp_idx(test_cases[i].output, str_c(value), i);
+               rfc822_parser_deinit(&parser);
+       } T_END;
+}
+
 static void test_rfc822_parse_content_param(void)
 {
        const char *input =
@@ -237,6 +329,7 @@ int main(void)
                test_rfc822_parse_quoted_string,
                test_rfc822_parse_dot_atom,
                test_rfc822_parse_domain_literal,
+               test_rfc822_parse_content_type,
                test_rfc822_parse_content_param,
                NULL
        };