From 6ff7da47e22cfcd39df83765a3ce0aac0128d025 Mon Sep 17 00:00:00 2001 From: Abhilash Raj Date: Sat, 15 Jun 2019 14:40:21 -0400 Subject: [PATCH 1/4] bpo-33972: Fix EmailMessage.iter_attachments raising AttributeError. When certain malformed messages have content-type set to 'mulitpart/*' but still have a single part body, iter_attachments can raise AttributeError. This patch fixes it by returning a None value instead when the body is single part. --- Lib/email/message.py | 8 +++++++- Lib/test/test_email/test_message.py | 9 +++++++++ .../Library/2019-06-15-14-39-50.bpo-33972.XxnNPw.rst | 2 ++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2019-06-15-14-39-50.bpo-33972.XxnNPw.rst diff --git a/Lib/email/message.py b/Lib/email/message.py index b6512f2198af43f..5e8532c64aaba30 100644 --- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -1041,7 +1041,13 @@ def iter_attachments(self): maintype, subtype = self.get_content_type().split('/') if maintype != 'multipart' or subtype == 'alternative': return - parts = self.get_payload().copy() + payload = self.get_payload() + # Certain malformed messages can have content type set to `multipart/*` + # but still have single part body, in which case payload.copy() can + # fail with AttributeError. + if not isinstance(payload, list): + return + parts = payload.copy() if maintype == 'multipart' and subtype == 'related': # For related, we treat everything but the root as an attachment. # The root may be indicated by 'start'; if there's no start or we diff --git a/Lib/test/test_email/test_message.py b/Lib/test/test_email/test_message.py index 5dc46e1b812c5d8..fab97d91882b8b4 100644 --- a/Lib/test/test_email/test_message.py +++ b/Lib/test/test_email/test_message.py @@ -929,6 +929,15 @@ def test_set_content_does_not_add_MIME_Version(self): m.set_content(content_manager=cm) self.assertNotIn('MIME-Version', m) + def test_string_payload_with_multipart_content_type(self): + msg = message_from_string(textwrap.dedent("""\ + Content-Type: multipart/mixed; charset="utf-8" + + sample text + """), policy=policy.default) + attachments = msg.iter_attachments() + self.assertEqual(list(attachments), []) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2019-06-15-14-39-50.bpo-33972.XxnNPw.rst b/Misc/NEWS.d/next/Library/2019-06-15-14-39-50.bpo-33972.XxnNPw.rst new file mode 100644 index 000000000000000..97fd4aa29224b64 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-06-15-14-39-50.bpo-33972.XxnNPw.rst @@ -0,0 +1,2 @@ +Email with single part but content-type set to 'multipart/*' doesn't raise +AttributeError anymore. From 46ef23deb6cb51a6af5ab98d7560c3288bcc0ec9 Mon Sep 17 00:00:00 2001 From: Abhilash Raj Date: Sat, 15 Jun 2019 15:32:56 -0400 Subject: [PATCH 2/4] Fix docs building. --- .../next/Library/2019-06-15-14-39-50.bpo-33972.XxnNPw.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2019-06-15-14-39-50.bpo-33972.XxnNPw.rst b/Misc/NEWS.d/next/Library/2019-06-15-14-39-50.bpo-33972.XxnNPw.rst index 97fd4aa29224b64..ded1570b308fb88 100644 --- a/Misc/NEWS.d/next/Library/2019-06-15-14-39-50.bpo-33972.XxnNPw.rst +++ b/Misc/NEWS.d/next/Library/2019-06-15-14-39-50.bpo-33972.XxnNPw.rst @@ -1,2 +1,2 @@ -Email with single part but content-type set to 'multipart/*' doesn't raise +Email with single part but content-type set to ``multipart/*`` doesn't raise AttributeError anymore. From 502e4b239d223ca73549d6fc8314ad6e2a72cff6 Mon Sep 17 00:00:00 2001 From: Abhilash Raj Date: Mon, 24 Jun 2019 10:48:05 -0700 Subject: [PATCH 3/4] Catch AttributeError instead of instance check. --- Lib/email/message.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/email/message.py b/Lib/email/message.py index 5e8532c64aaba30..bb77947c8758991 100644 --- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -1045,9 +1045,12 @@ def iter_attachments(self): # Certain malformed messages can have content type set to `multipart/*` # but still have single part body, in which case payload.copy() can # fail with AttributeError. - if not isinstance(payload, list): + try: + parts = payload.copy() + except AttributeError: + # payload is not a list, it is most probably a string. return - parts = payload.copy() + if maintype == 'multipart' and subtype == 'related': # For related, we treat everything but the root as an attachment. # The root may be indicated by 'start'; if there's no start or we From 52ea26d6b6475d137834d24eb18bab86ae762200 Mon Sep 17 00:00:00 2001 From: Abhilash Raj Date: Tue, 25 Jun 2019 09:33:26 -0700 Subject: [PATCH 4/4] Remove whitespace. --- Lib/email/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/email/message.py b/Lib/email/message.py index bb77947c8758991..12626026179608e 100644 --- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -1050,7 +1050,7 @@ def iter_attachments(self): except AttributeError: # payload is not a list, it is most probably a string. return - + if maintype == 'multipart' and subtype == 'related': # For related, we treat everything but the root as an attachment. # The root may be indicated by 'start'; if there's no start or we