]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-mail: message_skip_virtual() - Fix handling plain CRs in message body
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Mon, 13 Jan 2025 13:58:29 +0000 (15:58 +0200)
committerTimo Sirainen <timo.sirainen@open-xchange.com>
Mon, 13 Jan 2025 13:58:29 +0000 (15:58 +0200)
The plain CRs should be handled like any other character, not returning
last_cr_r=TRUE.

This fixes partial IMAP FETCHes where body contains e.g. "\rfoo" and
doing a partial FETCH BODY[TEXT]<1> to skip over the CR.

Fixes errors like:
Deleting corrupted cache record: Broken virtual size in mailbox .. FETCH BODY[]<1> got too little data: 2 vs 3

src/lib-mail/message-size.c
src/lib-mail/message-size.h
src/lib-mail/test-message-size.c

index 472abeb7c2a6e08b15a94c73e7c1da90474c333c..309d0b4428a531fb906ef8b226408f2406fd75b8 100644 (file)
@@ -128,14 +128,14 @@ void message_size_add(struct message_size *dest,
 }
 
 int message_skip_virtual(struct istream *input, uoff_t virtual_skip,
-                        bool *last_cr_r)
+                        bool *last_virtual_cr_r)
 {
        const unsigned char *msg;
        size_t i, size;
        bool cr_skipped = FALSE;
        int ret;
 
-       *last_cr_r = FALSE;
+       *last_virtual_cr_r = FALSE;
        if (virtual_skip == 0)
                return 0;
 
@@ -143,17 +143,13 @@ int message_skip_virtual(struct istream *input, uoff_t virtual_skip,
                for (i = 0; i < size && virtual_skip > 0; i++) {
                        virtual_skip--;
 
-                       if (msg[i] == '\r') {
-                               /* CR */
-                               if (virtual_skip == 0)
-                                       *last_cr_r = TRUE;
-                       } else if (msg[i] == '\n') {
+                       if (msg[i] == '\n') {
                                /* LF */
                                if ((i == 0 && !cr_skipped) ||
                                    (i > 0 && msg[i-1] != '\r')) {
                                        if (virtual_skip == 0) {
                                                /* CR/LF boundary */
-                                               *last_cr_r = TRUE;
+                                               *last_virtual_cr_r = TRUE;
                                                break;
                                        }
 
index 5fe6b8a9d58427793c3d1f55078f45ccdc944269..c5be99b8d4cbce199edc0c7a851aa285f6a8eb7b 100644 (file)
@@ -19,10 +19,12 @@ int message_get_body_size(struct istream *input, struct message_size *body,
 void message_size_add(struct message_size *dest,
                      const struct message_size *src);
 
-/* Skip number of virtual bytes from buffer. last_cr_r is set to TRUE if the
-   last character we skipped was '\r', meaning that the next character should
-   be '\n', which shouldn't be treated as "\r\n". */
+/* Skip a number of bytes in the input stream, counting LFs as CRLFs.
+   last_virtual_cr_r is set to TRUE if the last character we skipped was a
+   virtual (nonexistent in istream) '\r', meaning that the next character in
+   the input stream is "\n", which means be treated as plain "\n",
+   not "\r\n". */
 int message_skip_virtual(struct istream *input, uoff_t virtual_skip,
-                        bool *last_cr_r);
+                        bool *last_virtual_cr_r);
 
 #endif
index 4342c27a28eadf2ad3cecc533e7c20358d6de38a..cd49056363dbed5043195beb424b6efa76c4af67 100644 (file)
@@ -149,10 +149,58 @@ static void test_message_size(void)
        }
 }
 
+static void test_message_skip_virtual(void)
+{
+       struct {
+               const char *input;
+               uoff_t virtual_skip;
+
+               const char *output;
+               bool last_virtual_cr;
+       } tests[] = {
+               { "\r\nfoo", 1, "\nfoo", FALSE },
+               { "\r\nfoo", 2, "foo", FALSE },
+               { "foo\r\nbar", 3, "\r\nbar", FALSE },
+               { "foo\r\nbar", 4, "\nbar", FALSE },
+               { "foo\r\nbar", 5, "bar", FALSE },
+               { "foo\r\n", 5, "", FALSE },
+               { "foo\r\nbar\r\nbaz", 8, "\r\nbaz", FALSE },
+               { "foo\r\nbar\r\nbaz", 9, "\nbaz", FALSE },
+               { "foo\r\nbar\r\nbaz", 10, "baz", FALSE },
+
+               { "\nfoo", 1, "\nfoo", TRUE },
+               { "\nfoo", 2, "foo", FALSE },
+               { "foo\nbar", 3, "\nbar", FALSE },
+               { "foo\nbar", 4, "\nbar", TRUE },
+               { "foo\nbar", 5, "bar", FALSE },
+               { "foo\n", 5, "", FALSE },
+               { "foo\nbar\nbaz", 8, "\nbaz", FALSE },
+               { "foo\nbar\nbaz", 9, "\nbaz", TRUE },
+               { "foo\nbar\nbaz", 10, "baz", FALSE },
+       };
+       test_begin("message_skip_virtual()");
+       for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
+               struct istream *input =
+                       i_stream_create_from_data(tests[i].input,
+                                                 strlen(tests[i].input));
+               bool last_virtual_cr;
+               test_assert_idx(message_skip_virtual(input, tests[i].virtual_skip,
+                                                    &last_virtual_cr) == 0, i);
+               size_t size;
+               const unsigned char *output =
+                       i_stream_get_data(input, &size);
+               test_assert_strcmp_idx(t_strndup(output, size), tests[i].output, i);
+               test_assert_idx(last_virtual_cr == tests[i].last_virtual_cr, i);
+               i_stream_unref(&input);
+       }
+       test_end();
+}
+
 int main(void)
 {
        static void (*const test_functions[])(void) = {
                test_message_size,
+               test_message_skip_virtual,
                NULL
        };
        return test_run(test_functions);