Add a 5th CRLF bug fix (mu_header_size), a malformed-mail robustness
Debian Trixie package bundle / packages (push) Successful in 22m27s
Debian Trixie package bundle / packages (push) Successful in 22m27s
test suite, and fix a missed symbols-file entry
mu_header_size() (libmailutils/mailbox/header.c) hard-coded a 1-byte
blank-line separator ("+1") regardless of what was actually parsed;
for a CRLF-terminated message the real separator is 2 bytes, so every
non-multipart message's body picked up the blank line's own trailing
LF as a spurious leading byte. This is a different code path from the
four multipart-specific bugs already fixed here -- it's used by every
plain message, not just multipart ones. Fixed by having header_parse()
record the actual separator width it found (a new eol_size field) and
mu_header_size() use that instead of a hard-coded constant, falling
back to the original "+1" only when parsing never found a blank line
at all, to leave that untested edge case's behavior unchanged. New
regression tests (hdrsize.c/.at) confirmed to fail without the fix and
pass with it.
Also adds a permanent malformed-mail robustness test suite
(mimerobust.c/.at, 13 cases: missing/empty boundary, a boundary that
never appears, truncated input, no closing boundary, nested
multipart, boundary values with regex-special characters, and more)
verifying mu_message_is_multipart()/get_num_parts()/get_part() handle
all of them with a clean error or a sensible part count -- never a
crash or a hang -- so that stays true as the parser evolves, requested
explicitly rather than left as a one-off ad hoc check.
Separately: 0004's mu_message_default_envelope_recipient() export was
never added to debian/libmailutils9t64.symbols, the same class of
oversight already fixed once for the body.c/envelope-recipient
symbols. Caught by re-running the real dpkg-buildpackage verification
after this patch, which failed with the identical dh_makeshlibs error
as before. Fixed in 0002-debian-trixie.patch alongside the existing
entries.
Full stack (0001, 0003, 0004, extended 0005) re-verified clean: 777/777
libmailutils tests (Gentoo distfile), 756/756 (Debian 3.19, different
upstream baseline), 116/116 and 114/114 sieve tests respectively, zero
regressions, via both the cached Gentoo distfile and a genuine
dpkg-buildpackage build of Debian's source in a fresh Trixie container.
Deployed live, bongo.service active, SIEVE-04 passing.
This commit is contained in:
@@ -13,9 +13,9 @@ index e01e7a1..4bfb44e 100644
|
||||
mailutils (1:3.19-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
diff --git a/debian/libmailutils9t64.symbols b/debian/libmailutils9t64.symbols
|
||||
--- a/debian/libmailutils9t64.symbols 2026-08-01 14:44:36.767753030 +0200
|
||||
+++ b/debian/libmailutils9t64.symbols 2026-08-01 14:44:36.768444316 +0200
|
||||
diff -uNr a/debian/libmailutils9t64.symbols b/debian/libmailutils9t64.symbols
|
||||
--- a/debian/libmailutils9t64.symbols 2026-08-01 20:04:58.507681247 +0200
|
||||
+++ b/debian/libmailutils9t64.symbols 2026-08-01 20:04:58.508435596 +0200
|
||||
@@ -503,16 +503,20 @@
|
||||
mu_dot_filter@Base 1:3.17
|
||||
mu_dq_filter@Base 1:3.17
|
||||
@@ -37,7 +37,15 @@ diff --git a/debian/libmailutils9t64.symbols b/debian/libmailutils9t64.symbols
|
||||
mu_envelope_sget_sender@Base 1:3.17
|
||||
mu_errname@Base 1:3.17
|
||||
mu_error@Base 1:3.17
|
||||
@@ -2071,6 +2075,8 @@
|
||||
@@ -978,6 +982,7 @@
|
||||
mu_message_create@Base 1:3.17
|
||||
mu_message_create_attachment@Base 1:3.17
|
||||
mu_message_create_copy@Base 1:3.17
|
||||
+ mu_message_default_envelope_recipient@Base 1:3.19-1+bongo1
|
||||
mu_message_destroy@Base 1:3.17
|
||||
mu_message_encapsulate@Base 1:3.17
|
||||
mu_message_from_stream_with_envelope@Base 1:3.17
|
||||
@@ -2071,6 +2076,8 @@
|
||||
mod_upperfirst@Base 1:3.17
|
||||
mu_cli_capa_sieve@Base 1:3.17
|
||||
mu_i_sv_2nrealloc@Base 1:3.17
|
||||
@@ -46,7 +54,7 @@ diff --git a/debian/libmailutils9t64.symbols b/debian/libmailutils9t64.symbols
|
||||
mu_i_sv_code@Base 1:3.17
|
||||
mu_i_sv_code_action@Base 1:3.17
|
||||
mu_i_sv_code_test@Base 1:3.17
|
||||
@@ -2234,6 +2240,7 @@
|
||||
@@ -2234,6 +2241,7 @@
|
||||
sieve_get_address_part@Base 1:3.17
|
||||
sieve_run@Base 1:3.17
|
||||
sieve_test_address@Base 1:3.17
|
||||
|
||||
@@ -1,6 +1,77 @@
|
||||
diff -uNr mailutils-3.21.orig/include/mailutils/sys/header.h mailutils-3.21/include/mailutils/sys/header.h
|
||||
--- mailutils-3.21.orig/include/mailutils/sys/header.h 2025-01-02 14:36:28.000000000 +0100
|
||||
+++ mailutils-3.21/include/mailutils/sys/header.h 2026-08-01 19:43:55.816935615 +0200
|
||||
@@ -56,7 +56,14 @@
|
||||
size_t numhdr;
|
||||
size_t numlines;
|
||||
size_t size;
|
||||
-
|
||||
+ /* Width in bytes of the blank line separating headers from body, as
|
||||
+ actually found while parsing ("\n" -> 1, "\r\n" -> 2), or 0 if
|
||||
+ parsing never found one (e.g. a header-only message). Lets
|
||||
+ mu_header_size() add the real separator width instead of
|
||||
+ hard-coding 1, which corrupted the body offset of every
|
||||
+ CRLF-terminated message by one byte. */
|
||||
+ size_t eol_size;
|
||||
+
|
||||
/* Temporary storage */
|
||||
mu_stream_t mstream;
|
||||
|
||||
diff -uNr mailutils-3.21.orig/libmailutils/mailbox/header.c mailutils-3.21/libmailutils/mailbox/header.c
|
||||
--- mailutils-3.21.orig/libmailutils/mailbox/header.c 2025-01-02 14:36:31.000000000 +0100
|
||||
+++ mailutils-3.21/libmailutils/mailbox/header.c 2026-08-01 19:43:55.816307754 +0200
|
||||
@@ -322,6 +322,7 @@
|
||||
return 0;
|
||||
|
||||
header->flags |= HEADER_INVALIDATE;
|
||||
+ header->eol_size = 0;
|
||||
mu_hdrent_free_list (header);
|
||||
|
||||
/* Get a header, a header is:
|
||||
@@ -335,9 +336,23 @@
|
||||
const char *fn, *fn_end, *fv, *fv_end;
|
||||
struct mu_hdrent *ent;
|
||||
|
||||
+ /* A "\r\n" blank line previously fell through to the "no colon
|
||||
+ found" malformed-header fallback below instead of being
|
||||
+ recognized here directly -- it still stopped parsing (by
|
||||
+ accident, not by design), but never recorded how wide the
|
||||
+ real separator was, which is what the fix below needs. */
|
||||
+ if (header_start[0] == '\n')
|
||||
+ {
|
||||
+ header->eol_size = 1;
|
||||
+ break;
|
||||
+ }
|
||||
+ if (header_start[0] == '\r' && len > 1 && header_start[1] == '\n')
|
||||
+ {
|
||||
+ header->eol_size = 2;
|
||||
+ break;
|
||||
+ }
|
||||
if (header_start[0] == ' '
|
||||
- || header_start[0] == '\t'
|
||||
- || header_start[0] == '\n')
|
||||
+ || header_start[0] == '\t')
|
||||
break;
|
||||
|
||||
/* Second loop extract one header field. */
|
||||
@@ -980,7 +995,13 @@
|
||||
size_t size;
|
||||
size_t lines;
|
||||
mu_hdrent_count (header, &count, &size, &lines);
|
||||
- *psize = size + 1;
|
||||
+ /* header->eol_size is the real width of the blank line found
|
||||
+ while parsing ("\n" -> 1, "\r\n" -> 2); hard-coding 1
|
||||
+ corrupted the body offset of every CRLF-terminated message by
|
||||
+ one byte. Fall back to the original "+1" when parsing never
|
||||
+ found a blank line at all (e.g. a header-only message), to
|
||||
+ keep that edge case's existing behavior unchanged. */
|
||||
+ *psize = size + (header->eol_size ? header->eol_size : 1);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
diff -uNr mailutils-3.21.orig/libmailutils/mime/mime.c mailutils-3.21/libmailutils/mime/mime.c
|
||||
--- mailutils-3.21.orig/libmailutils/mime/mime.c 2026-08-01 17:13:57.198386442 +0200
|
||||
+++ mailutils-3.21/libmailutils/mime/mime.c 2026-08-01 17:13:57.199012148 +0200
|
||||
--- mailutils-3.21.orig/libmailutils/mime/mime.c 2025-01-02 14:36:32.000000000 +0100
|
||||
+++ mailutils-3.21/libmailutils/mime/mime.c 2026-08-01 19:43:43.338641164 +0200
|
||||
@@ -263,6 +263,14 @@
|
||||
{
|
||||
if (line[llen-1] == '\n')
|
||||
@@ -72,8 +143,8 @@ diff -uNr mailutils-3.21.orig/libmailutils/mime/mime.c mailutils-3.21/libmailuti
|
||||
}
|
||||
}
|
||||
diff -uNr mailutils-3.21.orig/libmailutils/string/unfold.c mailutils-3.21/libmailutils/string/unfold.c
|
||||
--- mailutils-3.21.orig/libmailutils/string/unfold.c 2026-08-01 17:13:57.197250734 +0200
|
||||
+++ mailutils-3.21/libmailutils/string/unfold.c 2026-08-01 17:13:57.197872137 +0200
|
||||
--- mailutils-3.21.orig/libmailutils/string/unfold.c 2025-01-02 14:36:33.000000000 +0100
|
||||
+++ mailutils-3.21/libmailutils/string/unfold.c 2026-08-01 19:43:43.338729163 +0200
|
||||
@@ -39,6 +39,20 @@
|
||||
case uf_init:
|
||||
if (*q == '\n')
|
||||
@@ -95,9 +166,166 @@ diff -uNr mailutils-3.21.orig/libmailutils/string/unfold.c mailutils-3.21/libmai
|
||||
else
|
||||
*p++ = *q;
|
||||
break;
|
||||
diff -uNr mailutils-3.21.orig/libmailutils/tests/hdrsize.at mailutils-3.21/libmailutils/tests/hdrsize.at
|
||||
--- mailutils-3.21.orig/libmailutils/tests/hdrsize.at 1970-01-01 01:00:00.000000000 +0100
|
||||
+++ mailutils-3.21/libmailutils/tests/hdrsize.at 2026-08-01 19:43:55.820094939 +0200
|
||||
@@ -0,0 +1,55 @@
|
||||
+# This file is part of GNU Mailutils. -*- Autotest -*-
|
||||
+# Copyright (C) 2026 Mario Fetka.
|
||||
+#
|
||||
+# GNU Mailutils is free software; you can redistribute it and/or
|
||||
+# modify it under the terms of the GNU General Public License as
|
||||
+# published by the Free Software Foundation; either version 3, or (at
|
||||
+# your option) any later version.
|
||||
+#
|
||||
+# GNU Mailutils is distributed in the hope that it will be useful, but
|
||||
+# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
+# General Public License for more details.
|
||||
+#
|
||||
+# You should have received a copy of the GNU General Public License
|
||||
+# along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
|
||||
+
|
||||
+AT_BANNER([Body offset from header size])
|
||||
+
|
||||
+# mu_header_size() hard-coded a 1-byte blank-line separator ("+1")
|
||||
+# regardless of what was actually parsed; for a CRLF-terminated
|
||||
+# message (i.e. any real SMTP message) the real separator is 2 bytes,
|
||||
+# so the body's offset into the raw message started one byte too
|
||||
+# early, picking up the blank line's own trailing LF as a spurious
|
||||
+# leading byte of body content. Only affects non-multipart messages --
|
||||
+# multipart body offsets go through a completely separate code path in
|
||||
+# libmailutils/mime/mime.c (see mimeparts.at).
|
||||
+
|
||||
+AT_SETUP([non-multipart body, CRLF terminated])
|
||||
+AT_KEYWORDS([hdrsize unfold crlf])
|
||||
+AT_CHECK([
|
||||
+tocrlf <<EOT >input
|
||||
+Content-Type: text/plain
|
||||
+
|
||||
+just a normal message
|
||||
+EOT
|
||||
+hdrsize < input
|
||||
+],
|
||||
+[0],
|
||||
+[just a normal message\x0d\x0a
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+AT_SETUP([non-multipart body, LF terminated])
|
||||
+AT_KEYWORDS([hdrsize unfold])
|
||||
+AT_CHECK([
|
||||
+AT_DATA([input],[Content-Type: text/plain
|
||||
+
|
||||
+just a normal message
|
||||
+])
|
||||
+hdrsize < input
|
||||
+],
|
||||
+[0],
|
||||
+[just a normal message\x0a
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
diff -uNr mailutils-3.21.orig/libmailutils/tests/hdrsize.c mailutils-3.21/libmailutils/tests/hdrsize.c
|
||||
--- mailutils-3.21.orig/libmailutils/tests/hdrsize.c 1970-01-01 01:00:00.000000000 +0100
|
||||
+++ mailutils-3.21/libmailutils/tests/hdrsize.c 2026-08-01 19:43:55.819127126 +0200
|
||||
@@ -0,0 +1,94 @@
|
||||
+/* GNU Mailutils -- a suite of utilities for electronic mail
|
||||
+ Copyright (C) 2026 Mario Fetka.
|
||||
+
|
||||
+ GNU Mailutils is free software; you can redistribute it and/or modify
|
||||
+ it under the terms of the GNU General Public License as published by
|
||||
+ the Free Software Foundation; either version 3, or (at your option)
|
||||
+ any later version.
|
||||
+
|
||||
+ GNU Mailutils is distributed in the hope that it will be useful,
|
||||
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
+ GNU General Public License for more details.
|
||||
+
|
||||
+ You should have received a copy of the GNU General Public License
|
||||
+ along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
|
||||
+
|
||||
+/* mu_message_get_body() test: builds a message the same minimal way
|
||||
+ an application embedding libmailutils typically does, then prints
|
||||
+ its body with any byte outside printable ASCII shown as \xHH, so a
|
||||
+ stray or missing separator byte is visible in test output instead
|
||||
+ of silently vanishing into a terminal. Exists to catch a regression
|
||||
+ of mu_header_size()'s blank-line width (1 byte for "\n", 2 for
|
||||
+ "\r\n") being hard-coded again -- that previously corrupted the
|
||||
+ body of every CRLF-terminated, non-multipart message with a
|
||||
+ spurious leading byte. */
|
||||
+
|
||||
+#ifdef HAVE_CONFIG_H
|
||||
+# include <config.h>
|
||||
+#endif
|
||||
+#include <mailutils/mailutils.h>
|
||||
+
|
||||
+int
|
||||
+main (void)
|
||||
+{
|
||||
+ mu_message_t msg;
|
||||
+ mu_stream_t stream, memstream;
|
||||
+ mu_body_t body;
|
||||
+ mu_stream_t bstream;
|
||||
+ mu_off_t size;
|
||||
+ char *buf;
|
||||
+ size_t n = 0, i;
|
||||
+
|
||||
+ mu_set_program_name ("hdrsize");
|
||||
+ mu_stdstream_setup (MU_STDSTREAM_RESET_NONE);
|
||||
+
|
||||
+ MU_ASSERT (mu_memory_stream_create (&memstream, MU_STREAM_RDWR));
|
||||
+ MU_ASSERT (mu_stream_copy (memstream, mu_strin, 0, NULL));
|
||||
+ MU_ASSERT (mu_stream_size (memstream, &size));
|
||||
+
|
||||
+ buf = malloc (size ? size : 1);
|
||||
+ if (size)
|
||||
+ {
|
||||
+ MU_ASSERT (mu_stream_seek (memstream, 0, MU_SEEK_SET, NULL));
|
||||
+ MU_ASSERT (mu_stream_read (memstream, buf, size, &n));
|
||||
+ }
|
||||
+ MU_ASSERT (mu_static_memory_stream_create (&stream, buf, n));
|
||||
+ MU_ASSERT (mu_message_create (&msg, NULL));
|
||||
+ MU_ASSERT (mu_message_set_stream (msg, stream, NULL));
|
||||
+
|
||||
+ {
|
||||
+ mu_header_t header;
|
||||
+ /* mu_message_get_body() relies on msg->orig_header_size, which is
|
||||
+ only computed as a side effect of mu_message_get_header()'s
|
||||
+ lazy header parse -- it must run first. */
|
||||
+ MU_ASSERT (mu_message_get_header (msg, &header));
|
||||
+ }
|
||||
+ MU_ASSERT (mu_message_get_body (msg, &body));
|
||||
+ MU_ASSERT (mu_body_get_streamref (body, &bstream));
|
||||
+
|
||||
+ {
|
||||
+ mu_stream_t out;
|
||||
+ char *value;
|
||||
+ MU_ASSERT (mu_memory_stream_create (&out, MU_STREAM_RDWR));
|
||||
+ MU_ASSERT (mu_stream_copy (out, bstream, 0, NULL));
|
||||
+ MU_ASSERT (mu_stream_size (out, &size));
|
||||
+ value = malloc (size + 1);
|
||||
+ MU_ASSERT (mu_stream_seek (out, 0, MU_SEEK_SET, NULL));
|
||||
+ MU_ASSERT (mu_stream_read (out, value, size, NULL));
|
||||
+ value[size] = 0;
|
||||
+
|
||||
+ for (i = 0; value[i]; i++)
|
||||
+ {
|
||||
+ unsigned char c = (unsigned char) value[i];
|
||||
+ if (c >= 32 && c < 127)
|
||||
+ mu_printf ("%c", c);
|
||||
+ else
|
||||
+ mu_printf ("\\x%02x", c);
|
||||
+ }
|
||||
+ mu_printf ("\n");
|
||||
+ free (value);
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
diff -uNr mailutils-3.21.orig/libmailutils/tests/hdrunfold.at mailutils-3.21/libmailutils/tests/hdrunfold.at
|
||||
--- mailutils-3.21.orig/libmailutils/tests/hdrunfold.at 1970-01-01 01:00:00.000000000 +0100
|
||||
+++ mailutils-3.21/libmailutils/tests/hdrunfold.at 2026-08-01 17:13:57.200238203 +0200
|
||||
+++ mailutils-3.21/libmailutils/tests/hdrunfold.at 2026-08-01 19:43:43.338768881 +0200
|
||||
@@ -0,0 +1,76 @@
|
||||
+# This file is part of GNU Mailutils. -*- Autotest -*-
|
||||
+# Copyright (C) 2026 Mario Fetka.
|
||||
@@ -177,7 +405,7 @@ diff -uNr mailutils-3.21.orig/libmailutils/tests/hdrunfold.at mailutils-3.21/lib
|
||||
+AT_CLEANUP
|
||||
diff -uNr mailutils-3.21.orig/libmailutils/tests/hdrunfold.c mailutils-3.21/libmailutils/tests/hdrunfold.c
|
||||
--- mailutils-3.21.orig/libmailutils/tests/hdrunfold.c 1970-01-01 01:00:00.000000000 +0100
|
||||
+++ mailutils-3.21/libmailutils/tests/hdrunfold.c 2026-08-01 17:13:57.199610353 +0200
|
||||
+++ mailutils-3.21/libmailutils/tests/hdrunfold.c 2026-08-01 19:43:43.338812402 +0200
|
||||
@@ -0,0 +1,74 @@
|
||||
+/* GNU Mailutils -- a suite of utilities for electronic mail
|
||||
+ Copyright (C) 2026 Mario Fetka.
|
||||
@@ -254,43 +482,47 @@ diff -uNr mailutils-3.21.orig/libmailutils/tests/hdrunfold.c mailutils-3.21/libm
|
||||
+ return 0;
|
||||
+}
|
||||
diff -uNr mailutils-3.21.orig/libmailutils/tests/Makefile.am mailutils-3.21/libmailutils/tests/Makefile.am
|
||||
--- mailutils-3.21.orig/libmailutils/tests/Makefile.am 2026-08-01 17:13:57.312124568 +0200
|
||||
+++ mailutils-3.21/libmailutils/tests/Makefile.am 2026-08-01 17:13:57.201787328 +0200
|
||||
@@ -43,6 +43,7 @@
|
||||
--- mailutils-3.21.orig/libmailutils/tests/Makefile.am 2025-09-17 17:55:23.000000000 +0200
|
||||
+++ mailutils-3.21/libmailutils/tests/Makefile.am 2026-08-01 19:43:55.821070055 +0200
|
||||
@@ -43,6 +43,8 @@
|
||||
fsfolder\
|
||||
globtest\
|
||||
hdrcpy\
|
||||
+ hdrsize\
|
||||
+ hdrunfold\
|
||||
imapio\
|
||||
lck\
|
||||
listop\
|
||||
@@ -53,6 +54,7 @@
|
||||
@@ -53,6 +55,8 @@
|
||||
mcf\
|
||||
mimehdr\
|
||||
mimeitr\
|
||||
+ mimeparts\
|
||||
+ mimerobust\
|
||||
modtofsaf\
|
||||
msgset\
|
||||
modmesg\
|
||||
@@ -116,6 +118,7 @@
|
||||
@@ -116,6 +120,8 @@
|
||||
fsfolder03.at\
|
||||
fsfolder04.at\
|
||||
hdrcpy.at\
|
||||
+ hdrunfold.at\
|
||||
+ hdrsize.at\
|
||||
hdrflt.at\
|
||||
htmlent.at\
|
||||
globtest.at\
|
||||
@@ -128,6 +131,7 @@
|
||||
@@ -128,6 +134,8 @@
|
||||
logstr.at\
|
||||
mailcap.at\
|
||||
mimehdr.at\
|
||||
+ mimeparts.at\
|
||||
+ mimerobust.at\
|
||||
modmesg.at\
|
||||
modtofsaf.at\
|
||||
msgset.at\
|
||||
diff -uNr mailutils-3.21.orig/libmailutils/tests/mimeparts.at mailutils-3.21/libmailutils/tests/mimeparts.at
|
||||
--- mailutils-3.21.orig/libmailutils/tests/mimeparts.at 1970-01-01 01:00:00.000000000 +0100
|
||||
+++ mailutils-3.21/libmailutils/tests/mimeparts.at 2026-08-01 17:13:57.201295704 +0200
|
||||
+++ mailutils-3.21/libmailutils/tests/mimeparts.at 2026-08-01 19:43:43.338864049 +0200
|
||||
@@ -0,0 +1,55 @@
|
||||
+# This file is part of GNU Mailutils. -*- Autotest -*-
|
||||
+# Copyright (C) 2026 Mario Fetka.
|
||||
@@ -349,7 +581,7 @@ diff -uNr mailutils-3.21.orig/libmailutils/tests/mimeparts.at mailutils-3.21/lib
|
||||
+AT_CLEANUP
|
||||
diff -uNr mailutils-3.21.orig/libmailutils/tests/mimeparts.c mailutils-3.21/libmailutils/tests/mimeparts.c
|
||||
--- mailutils-3.21.orig/libmailutils/tests/mimeparts.c 1970-01-01 01:00:00.000000000 +0100
|
||||
+++ mailutils-3.21/libmailutils/tests/mimeparts.c 2026-08-01 17:13:57.200762999 +0200
|
||||
+++ mailutils-3.21/libmailutils/tests/mimeparts.c 2026-08-01 19:43:43.338904830 +0200
|
||||
@@ -0,0 +1,96 @@
|
||||
+/* GNU Mailutils -- a suite of utilities for electronic mail
|
||||
+ Copyright (C) 2026 Mario Fetka.
|
||||
@@ -447,22 +679,498 @@ diff -uNr mailutils-3.21.orig/libmailutils/tests/mimeparts.c mailutils-3.21/libm
|
||||
+ mu_message_unref (msg);
|
||||
+ return 0;
|
||||
+}
|
||||
diff -uNr mailutils-3.21.orig/libmailutils/tests/mimerobust.at mailutils-3.21/libmailutils/tests/mimerobust.at
|
||||
--- mailutils-3.21.orig/libmailutils/tests/mimerobust.at 1970-01-01 01:00:00.000000000 +0100
|
||||
+++ mailutils-3.21/libmailutils/tests/mimerobust.at 2026-08-01 19:43:55.818182225 +0200
|
||||
@@ -0,0 +1,283 @@
|
||||
+# This file is part of GNU Mailutils. -*- Autotest -*-
|
||||
+# Copyright (C) 2026 Mario Fetka.
|
||||
+#
|
||||
+# GNU Mailutils is free software; you can redistribute it and/or
|
||||
+# modify it under the terms of the GNU General Public License as
|
||||
+# published by the Free Software Foundation; either version 3, or (at
|
||||
+# your option) any later version.
|
||||
+#
|
||||
+# GNU Mailutils is distributed in the hope that it will be useful, but
|
||||
+# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
+# General Public License for more details.
|
||||
+#
|
||||
+# You should have received a copy of the GNU General Public License
|
||||
+# along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
|
||||
+
|
||||
+AT_BANNER([Multipart robustness against malformed input])
|
||||
+
|
||||
+dnl Each case here is a deliberately malformed multipart message --
|
||||
+dnl missing/empty boundary, a boundary that never appears, truncated
|
||||
+dnl input, no closing boundary, nested multipart, and so on. None of
|
||||
+dnl these demonstrate a fix the way mimeparts.at's CRLF cases do; the
|
||||
+dnl point is only that mu_message_is_multipart()/get_num_parts()/
|
||||
+dnl get_part() handle every one of them by returning a clean error or
|
||||
+dnl a sensible part count -- never a crash or a hang -- and that stays
|
||||
+dnl true as the parser evolves.
|
||||
+
|
||||
+AT_SETUP([missing boundary parameter])
|
||||
+AT_KEYWORDS([mimerobust multipart])
|
||||
+AT_CHECK([
|
||||
+tocrlf <<EOT >input
|
||||
+Content-Type: multipart/mixed
|
||||
+
|
||||
+--XYZ
|
||||
+Content-Type: text/plain
|
||||
+
|
||||
+part one
|
||||
+--XYZ--
|
||||
+EOT
|
||||
+mimerobust < input
|
||||
+],
|
||||
+[0],
|
||||
+[content-type=multipart/mixed
|
||||
+get_num_parts rc=4137
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+AT_SETUP([empty boundary value])
|
||||
+AT_KEYWORDS([mimerobust multipart])
|
||||
+AT_CHECK([
|
||||
+tocrlf <<EOT >input
|
||||
+Content-Type: multipart/mixed; boundary=""
|
||||
+
|
||||
+part one, no real boundary anywhere
|
||||
+EOT
|
||||
+mimerobust < input
|
||||
+],
|
||||
+[0],
|
||||
+[content-type=multipart/mixed; boundary=""
|
||||
+get_num_parts rc=4137
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+AT_SETUP([boundary never appears in body])
|
||||
+AT_KEYWORDS([mimerobust multipart])
|
||||
+AT_CHECK([
|
||||
+tocrlf <<EOT >input
|
||||
+Content-Type: multipart/mixed; boundary=NOPE
|
||||
+
|
||||
+This body never contains the boundary string at all,
|
||||
+just plain text going on and on.
|
||||
+EOT
|
||||
+mimerobust < input
|
||||
+],
|
||||
+[0],
|
||||
+[content-type=multipart/mixed; boundary=NOPE
|
||||
+nparts=0
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+AT_SETUP([no closing boundary])
|
||||
+AT_KEYWORDS([mimerobust multipart])
|
||||
+AT_CHECK([
|
||||
+tocrlf <<EOT >input
|
||||
+Content-Type: multipart/mixed; boundary=XYZ
|
||||
+
|
||||
+--XYZ
|
||||
+Content-Type: text/plain
|
||||
+
|
||||
+part one, never terminated with --XYZ--
|
||||
+EOT
|
||||
+mimerobust < input
|
||||
+],
|
||||
+[0],
|
||||
+[content-type=multipart/mixed; boundary=XYZ
|
||||
+nparts=1
|
||||
+ content-type=text/plain
|
||||
+ body: [[]]
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+AT_SETUP([truncated mid-headers])
|
||||
+AT_KEYWORDS([mimerobust multipart])
|
||||
+AT_CHECK([
|
||||
+printf 'Content-Type: multipart/mixed; boundary=XYZ\r\n\r\n--XYZ\r\nContent-Type: text/pla' > input
|
||||
+mimerobust < input
|
||||
+],
|
||||
+[0],
|
||||
+[content-type=multipart/mixed; boundary=XYZ
|
||||
+nparts=1
|
||||
+ content-type=text/pla
|
||||
+ body: [[]]
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+AT_SETUP([truncated right after boundary])
|
||||
+AT_KEYWORDS([mimerobust multipart])
|
||||
+AT_CHECK([
|
||||
+printf 'Content-Type: multipart/mixed; boundary=XYZ\r\n\r\n--XYZ' > input
|
||||
+mimerobust < input
|
||||
+],
|
||||
+[0],
|
||||
+[content-type=multipart/mixed; boundary=XYZ
|
||||
+nparts=0
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+AT_SETUP([empty body after headers])
|
||||
+AT_KEYWORDS([mimerobust multipart])
|
||||
+AT_CHECK([
|
||||
+printf 'Content-Type: multipart/mixed; boundary=XYZ\r\n\r\n' > input
|
||||
+mimerobust < input
|
||||
+],
|
||||
+[0],
|
||||
+[content-type=multipart/mixed; boundary=XYZ
|
||||
+nparts=0
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+AT_SETUP([header-only, no blank line at all])
|
||||
+AT_KEYWORDS([mimerobust multipart])
|
||||
+AT_CHECK([
|
||||
+printf 'Content-Type: multipart/mixed; boundary=XYZ' > input
|
||||
+mimerobust < input
|
||||
+],
|
||||
+[0],
|
||||
+[content-type=multipart/mixed; boundary=XYZ
|
||||
+nparts=0
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+AT_SETUP([nested multipart])
|
||||
+AT_KEYWORDS([mimerobust multipart])
|
||||
+AT_CHECK([
|
||||
+tocrlf <<EOT >input
|
||||
+Content-Type: multipart/mixed; boundary=OUTER
|
||||
+
|
||||
+--OUTER
|
||||
+Content-Type: multipart/alternative; boundary=INNER
|
||||
+
|
||||
+--INNER
|
||||
+Content-Type: text/plain
|
||||
+
|
||||
+inner plain part
|
||||
+--INNER
|
||||
+Content-Type: text/html
|
||||
+
|
||||
+<p>inner html part</p>
|
||||
+--INNER--
|
||||
+--OUTER
|
||||
+Content-Type: text/plain
|
||||
+
|
||||
+outer second part
|
||||
+--OUTER--
|
||||
+EOT
|
||||
+mimerobust < input
|
||||
+],
|
||||
+[0],
|
||||
+[content-type=multipart/mixed; boundary=OUTER
|
||||
+nparts=2
|
||||
+ content-type=multipart/alternative; boundary=INNER
|
||||
+ nparts=2
|
||||
+ content-type=text/plain
|
||||
+ body: [[inner plain part]]
|
||||
+ content-type=text/html
|
||||
+ body: [[<p>inner html part</p>]]
|
||||
+ content-type=text/plain
|
||||
+ body: [[outer second part]]
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+AT_SETUP([multipart with only a closing boundary, no parts])
|
||||
+AT_KEYWORDS([mimerobust multipart])
|
||||
+AT_CHECK([
|
||||
+tocrlf <<EOT >input
|
||||
+Content-Type: multipart/mixed; boundary=X
|
||||
+
|
||||
+--X--
|
||||
+EOT
|
||||
+mimerobust < input
|
||||
+],
|
||||
+[0],
|
||||
+[content-type=multipart/mixed; boundary=X
|
||||
+nparts=0
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+AT_SETUP([boundary value with regex-special characters])
|
||||
+AT_KEYWORDS([mimerobust multipart])
|
||||
+AT_CHECK([
|
||||
+tocrlf <<EOT >input
|
||||
+Content-Type: multipart/mixed; boundary="a.b*c+d"
|
||||
+
|
||||
+--a.b*c+d
|
||||
+Content-Type: text/plain
|
||||
+
|
||||
+content
|
||||
+--a.b*c+d--
|
||||
+EOT
|
||||
+mimerobust < input
|
||||
+],
|
||||
+[0],
|
||||
+[content-type=multipart/mixed; boundary="a.b*c+d"
|
||||
+nparts=1
|
||||
+ content-type=text/plain
|
||||
+ body: [[content]]
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+AT_SETUP([many empty parts])
|
||||
+AT_KEYWORDS([mimerobust multipart])
|
||||
+AT_CHECK([
|
||||
+tocrlf <<EOT >input
|
||||
+Content-Type: multipart/mixed; boundary=X
|
||||
+
|
||||
+--X
|
||||
+
|
||||
+--X
|
||||
+
|
||||
+--X
|
||||
+
|
||||
+--X
|
||||
+
|
||||
+--X
|
||||
+
|
||||
+--X--
|
||||
+EOT
|
||||
+mimerobust < input
|
||||
+],
|
||||
+[0],
|
||||
+[content-type=multipart/mixed; boundary=X
|
||||
+nparts=5
|
||||
+ content-type=text/plain
|
||||
+ body: [[]]
|
||||
+ content-type=text/plain
|
||||
+ body: [[]]
|
||||
+ content-type=text/plain
|
||||
+ body: [[]]
|
||||
+ content-type=text/plain
|
||||
+ body: [[]]
|
||||
+ content-type=text/plain
|
||||
+ body: [[]]
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+dnl Plain LF input here deliberately: this case is about
|
||||
+dnl mu_message_is_multipart() correctly reporting false and the body
|
||||
+dnl still being readable, not CRLF handling (covered separately by
|
||||
+dnl mimeparts.at), so exact line-ending preservation through this
|
||||
+dnl generated test isn't the point.
|
||||
+AT_SETUP([claims multipart, is not])
|
||||
+AT_KEYWORDS([mimerobust multipart])
|
||||
+AT_DATA([input],[Content-Type: text/plain
|
||||
+
|
||||
+just a normal message
|
||||
+])
|
||||
+AT_CHECK([mimerobust < input],
|
||||
+[0],
|
||||
+[content-type=text/plain
|
||||
+body: [[just a normal message
|
||||
+]]
|
||||
+])
|
||||
+AT_CLEANUP
|
||||
diff -uNr mailutils-3.21.orig/libmailutils/tests/mimerobust.c mailutils-3.21/libmailutils/tests/mimerobust.c
|
||||
--- mailutils-3.21.orig/libmailutils/tests/mimerobust.c 1970-01-01 01:00:00.000000000 +0100
|
||||
+++ mailutils-3.21/libmailutils/tests/mimerobust.c 2026-08-01 19:43:55.817463561 +0200
|
||||
@@ -0,0 +1,183 @@
|
||||
+/* GNU Mailutils -- a suite of utilities for electronic mail
|
||||
+ Copyright (C) 2026 Mario Fetka.
|
||||
+
|
||||
+ GNU Mailutils is free software; you can redistribute it and/or modify
|
||||
+ it under the terms of the GNU General Public License as published by
|
||||
+ the Free Software Foundation; either version 3, or (at your option)
|
||||
+ any later version.
|
||||
+
|
||||
+ GNU Mailutils is distributed in the hope that it will be useful,
|
||||
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
+ GNU General Public License for more details.
|
||||
+
|
||||
+ You should have received a copy of the GNU General Public License
|
||||
+ along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
|
||||
+
|
||||
+/* Robustness companion to mimeparts.c: builds a message the same
|
||||
+ minimal way an application embedding libmailutils typically does,
|
||||
+ then walks it with mu_message_is_multipart()/mu_message_get_num_
|
||||
+ parts()/mu_message_get_part(), reporting every return code and
|
||||
+ part explicitly instead of using MU_ASSERT. A malformed message
|
||||
+ (missing/empty boundary, truncated input, a boundary that never
|
||||
+ appears, etc.) returning a clean error is an expected, valid
|
||||
+ outcome here, not a failure -- only a crash or hang would be a
|
||||
+ real bug, and this file exists to make either of those loud rather
|
||||
+ than silent if they ever regress. */
|
||||
+
|
||||
+#ifdef HAVE_CONFIG_H
|
||||
+# include <config.h>
|
||||
+#endif
|
||||
+#include <mailutils/mailutils.h>
|
||||
+
|
||||
+static void
|
||||
+dump_part (mu_message_t msg, unsigned depth)
|
||||
+{
|
||||
+ mu_header_t header;
|
||||
+ char *ctype = NULL;
|
||||
+ int ismime = 0;
|
||||
+ int rc;
|
||||
+
|
||||
+ rc = mu_message_get_header (msg, &header);
|
||||
+ if (rc)
|
||||
+ {
|
||||
+ mu_printf ("%*sget_header rc=%d\n", depth * 2, "", rc);
|
||||
+ return;
|
||||
+ }
|
||||
+ mu_header_aget_value_unfold (header, MU_HEADER_CONTENT_TYPE, &ctype);
|
||||
+ mu_printf ("%*scontent-type=%s\n", depth * 2, "",
|
||||
+ ctype ? ctype : "(none)");
|
||||
+ free (ctype);
|
||||
+
|
||||
+ rc = mu_message_is_multipart (msg, &ismime);
|
||||
+ if (rc)
|
||||
+ {
|
||||
+ mu_printf ("%*sis_multipart rc=%d\n", depth * 2, "", rc);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ if (ismime)
|
||||
+ {
|
||||
+ size_t nparts, i;
|
||||
+ rc = mu_message_get_num_parts (msg, &nparts);
|
||||
+ if (rc)
|
||||
+ {
|
||||
+ mu_printf ("%*sget_num_parts rc=%d\n", depth * 2, "", rc);
|
||||
+ return;
|
||||
+ }
|
||||
+ mu_printf ("%*snparts=%lu\n", depth * 2, "", (unsigned long) nparts);
|
||||
+ for (i = 1; i <= nparts; i++)
|
||||
+ {
|
||||
+ mu_message_t part;
|
||||
+ int prc = mu_message_get_part (msg, i, &part);
|
||||
+ if (prc)
|
||||
+ {
|
||||
+ mu_printf ("%*sget_part(%lu) rc=%d\n", depth * 2, "",
|
||||
+ (unsigned long) i, prc);
|
||||
+ continue;
|
||||
+ }
|
||||
+ dump_part (part, depth + 1);
|
||||
+ }
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ {
|
||||
+ mu_body_t body;
|
||||
+ mu_stream_t stream;
|
||||
+
|
||||
+ rc = mu_message_get_body (msg, &body);
|
||||
+ if (rc)
|
||||
+ {
|
||||
+ mu_printf ("%*sget_body rc=%d\n", depth * 2, "", rc);
|
||||
+ return;
|
||||
+ }
|
||||
+ rc = mu_body_get_streamref (body, &stream);
|
||||
+ if (rc)
|
||||
+ {
|
||||
+ mu_printf ("%*sget_streamref rc=%d\n", depth * 2, "", rc);
|
||||
+ return;
|
||||
+ }
|
||||
+ mu_printf ("%*sbody: [", depth * 2, "");
|
||||
+ mu_stream_copy (mu_strout, stream, 0, NULL);
|
||||
+ mu_printf ("]\n");
|
||||
+ mu_stream_unref (stream);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+int
|
||||
+main (void)
|
||||
+{
|
||||
+ mu_message_t msg;
|
||||
+ mu_stream_t stream, memstream;
|
||||
+ mu_off_t size;
|
||||
+ int rc;
|
||||
+
|
||||
+ mu_set_program_name ("mimerobust");
|
||||
+ mu_stdstream_setup (MU_STDSTREAM_RESET_NONE);
|
||||
+
|
||||
+ rc = mu_memory_stream_create (&memstream, MU_STREAM_RDWR);
|
||||
+ if (rc)
|
||||
+ {
|
||||
+ mu_printf ("SETUP FAILED (memstream) rc=%d\n", rc);
|
||||
+ return 2;
|
||||
+ }
|
||||
+ rc = mu_stream_copy (memstream, mu_strin, 0, NULL);
|
||||
+ if (rc)
|
||||
+ {
|
||||
+ mu_printf ("SETUP FAILED (copy stdin) rc=%d\n", rc);
|
||||
+ return 2;
|
||||
+ }
|
||||
+ rc = mu_stream_size (memstream, &size);
|
||||
+ if (rc)
|
||||
+ {
|
||||
+ mu_printf ("SETUP FAILED (size) rc=%d\n", rc);
|
||||
+ return 2;
|
||||
+ }
|
||||
+
|
||||
+ {
|
||||
+ char *buf = malloc (size ? size : 1);
|
||||
+ size_t n = 0;
|
||||
+ if (!buf)
|
||||
+ {
|
||||
+ mu_printf ("SETUP FAILED (malloc)\n");
|
||||
+ return 2;
|
||||
+ }
|
||||
+ if (size)
|
||||
+ {
|
||||
+ rc = mu_stream_seek (memstream, 0, MU_SEEK_SET, NULL);
|
||||
+ if (rc == 0)
|
||||
+ rc = mu_stream_read (memstream, buf, size, &n);
|
||||
+ if (rc)
|
||||
+ {
|
||||
+ mu_printf ("SETUP FAILED (read) rc=%d\n", rc);
|
||||
+ free (buf);
|
||||
+ return 2;
|
||||
+ }
|
||||
+ }
|
||||
+ rc = mu_static_memory_stream_create (&stream, buf, n);
|
||||
+ if (rc)
|
||||
+ {
|
||||
+ mu_printf ("SETUP FAILED (static stream) rc=%d\n", rc);
|
||||
+ free (buf);
|
||||
+ return 2;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ rc = mu_message_create (&msg, NULL);
|
||||
+ if (rc)
|
||||
+ {
|
||||
+ mu_printf ("SETUP FAILED (message_create) rc=%d\n", rc);
|
||||
+ return 2;
|
||||
+ }
|
||||
+ rc = mu_message_set_stream (msg, stream, NULL);
|
||||
+ if (rc)
|
||||
+ {
|
||||
+ mu_printf ("SETUP FAILED (set_stream) rc=%d\n", rc);
|
||||
+ return 2;
|
||||
+ }
|
||||
+
|
||||
+ dump_part (msg, 0);
|
||||
+
|
||||
+ mu_message_unref (msg);
|
||||
+ return 0;
|
||||
+}
|
||||
diff -uNr mailutils-3.21.orig/libmailutils/tests/testsuite.at mailutils-3.21/libmailutils/tests/testsuite.at
|
||||
--- mailutils-3.21.orig/libmailutils/tests/testsuite.at 2026-08-01 17:13:57.312840761 +0200
|
||||
+++ mailutils-3.21/libmailutils/tests/testsuite.at 2026-08-01 17:13:57.202432027 +0200
|
||||
@@ -225,6 +225,7 @@
|
||||
--- mailutils-3.21.orig/libmailutils/tests/testsuite.at 2025-01-02 14:36:35.000000000 +0100
|
||||
+++ mailutils-3.21/libmailutils/tests/testsuite.at 2026-08-01 19:43:55.822164434 +0200
|
||||
@@ -225,6 +225,8 @@
|
||||
m4_include([inline-comment.at])
|
||||
m4_include([hdrflt.at])
|
||||
m4_include([hdrcpy.at])
|
||||
+m4_include([hdrunfold.at])
|
||||
+m4_include([hdrsize.at])
|
||||
m4_include([linecon.at])
|
||||
m4_include([htmlent.at])
|
||||
m4_include([xml.at])
|
||||
@@ -252,6 +253,7 @@
|
||||
@@ -252,6 +254,8 @@
|
||||
m4_include([modtofsaf.at])
|
||||
|
||||
m4_include([mimehdr.at])
|
||||
+m4_include([mimeparts.at])
|
||||
+m4_include([mimerobust.at])
|
||||
m4_include([content-type.at])
|
||||
|
||||
m4_include([msgset.at])
|
||||
|
||||
Reference in New Issue
Block a user