From: Timo Sirainen Date: Mon, 13 Jan 2025 13:58:29 +0000 (+0200) Subject: lib-mail: message_skip_virtual() - Fix handling plain CRs in message body X-Git-Tag: 2.4.0~1361 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fc88314aa45ca751d7d83d1307d46e11cfbe263c;p=thirdparty%2Fdovecot%2Fcore.git lib-mail: message_skip_virtual() - Fix handling plain CRs in message body 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 --- diff --git a/src/lib-mail/message-size.c b/src/lib-mail/message-size.c index 472abeb7c2..309d0b4428 100644 --- a/src/lib-mail/message-size.c +++ b/src/lib-mail/message-size.c @@ -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; } diff --git a/src/lib-mail/message-size.h b/src/lib-mail/message-size.h index 5fe6b8a9d5..c5be99b8d4 100644 --- a/src/lib-mail/message-size.h +++ b/src/lib-mail/message-size.h @@ -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 diff --git a/src/lib-mail/test-message-size.c b/src/lib-mail/test-message-size.c index 4342c27a28..cd49056363 100644 --- a/src/lib-mail/test-message-size.c +++ b/src/lib-mail/test-message-size.c @@ -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);