From 124907f3614a27c6c65e65c733f4da29d0bdb4f6 Mon Sep 17 00:00:00 2001 From: Julian Prolejko <39591181+jul3x@users.noreply.github.com> Date: Thu, 15 Oct 2026 23:29:36 +0200 Subject: [PATCH] gh-159304: Fix AttributeError in email get_body() for nested malformed multipart (GH-159306) MIMEPart._find_body() incorrectly checked self.is_multipart() instead of part.is_multipart() when recursing. (cherry picked from commit fdec4df45ab69fc25019565992d95b26b4e37523) Co-authored-by: Julian Prolejko <39591181+jul3x@users.noreply.github.com> --- Lib/email/message.py | 2 +- Lib/test/test_email/test_message.py | 19 +++++++++++++++++++ ...-10-13-12-00-00.gh-issue-159304.mFbYq2.rst | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-13-12-00-00.gh-issue-159304.mFbYq2.rst diff --git a/Lib/email/message.py b/Lib/email/message.py index 64e6c1e23cfb47..093b9012f31666 100644 --- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -1026,7 +1026,7 @@ def _find_body(self, part, preferencelist): if subtype in preferencelist: yield (preferencelist.index(subtype), part) return - if maintype != 'multipart' or not self.is_multipart(): + if maintype != 'multipart' or not part.is_multipart(): return if subtype != 'related': for subpart in part.iter_parts(): diff --git a/Lib/test/test_email/test_message.py b/Lib/test/test_email/test_message.py index 56ad446694d1f8..974b0ceaafbde4 100644 --- a/Lib/test/test_email/test_message.py +++ b/Lib/test/test_email/test_message.py @@ -1081,6 +1081,25 @@ def test_get_body_malformed(self): # AttributeError: 'str' object has no attribute 'is_attachment' m.get_body() + def test_get_body_malformed_nested_multipart(self): + # gh-159304: a nested multipart part without a boundary has a + # string payload, which get_body() must not iterate over. + msg = textwrap.dedent("""\ + From: sender@example.com + To: receiver@example.com + Subject: Malformed + Content-Type: multipart/related; boundary=OUTER + + --OUTER + Content-Type: multipart/related + + raw body no boundary + --OUTER-- + """) + m = self._str_msg(msg) + self.assertIsNone(m.get_body(('plain', 'html'))) + self.assertIs(m.get_body(('related',)), m) + def test_get_bytes_payload_with_quoted_printable_encoding(self): # We use a memoryview to avoid directly changing the private payload # and to prevent using the dedicated paths for string or bytes objects. diff --git a/Misc/NEWS.d/next/Library/2026-10-13-12-00-00.gh-issue-159304.mFbYq2.rst b/Misc/NEWS.d/next/Library/2026-10-13-12-00-00.gh-issue-159304.mFbYq2.rst new file mode 100644 index 00000000000000..8f5b61efe03959 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-13-12-00-00.gh-issue-159304.mFbYq2.rst @@ -0,0 +1,2 @@ +Fix :exc:`AttributeError` in :meth:`email.message.EmailMessage.get_body` when a +nested ``multipart`` part is malformed and its payload is a string.