diff --git a/Lib/email/message.py b/Lib/email/message.py index 093b9012f31666..125266649621f1 100644 --- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -1211,6 +1211,11 @@ def clear_content(self): class EmailMessage(MIMEPart): + def _make_multipart(self, subtype, disallowed_subtypes, boundary): + super()._make_multipart(subtype, disallowed_subtypes, boundary) + if 'MIME-Version' not in self: + self['MIME-Version'] = '1.0' + def set_content(self, *args, **kw): super().set_content(*args, **kw) if 'MIME-Version' not in self: diff --git a/Lib/test/test_email/test_message.py b/Lib/test/test_email/test_message.py index 974b0ceaafbde4..0f5d85aca6a7ae 100644 --- a/Lib/test/test_email/test_message.py +++ b/Lib/test/test_email/test_message.py @@ -610,6 +610,8 @@ def _check_make_multipart(self, m, msg_headers, payload): if not name.lower().startswith('content-'): self.assertEqual(m[name], value) count += 1 + if isinstance(m, EmailMessage): + count += 1 # MIME-Version added when making multipart. self.assertEqual(len(m), count+1) # +1 for new Content-Type part = next(m.iter_parts()) count = 0 @@ -631,9 +633,11 @@ def subtype_as_make(self, method, subtype, outcome): self.assertEqual(m.get_content_subtype(), method) if subtype == 'no_content': self.assertEqual(len(m.get_payload()), 0) - self.assertEqual(m.items(), - msg_headers + [('Content-Type', - 'multipart/'+method)]) + expected_headers = msg_headers + [('Content-Type', + 'multipart/'+method)] + if isinstance(m, EmailMessage): + expected_headers.append(('MIME-Version', '1.0')) + self.assertEqual(m.items(), expected_headers) else: self.assertEqual(len(m.get_payload()), 1) self._check_make_multipart(m, msg_headers, payload) @@ -781,6 +785,22 @@ def get_payload_surrogate_as_gh_94606(self, msg, expected): class TestEmailMessage(TestEmailMessageBase, TestEmailBase): message = EmailMessage + def test_make_multipart_MIME_Version(self): + for subtype in ('related', 'alternative', 'mixed'): + for version in (None, '1.0'): + with self.subTest(subtype=subtype, version=version): + m = self.message() + if version is not None: + m['MIME-Version'] = version + getattr(m, 'make_' + subtype)() + self.assertEqual(m.get_all('MIME-Version'), ['1.0']) + + def test_add_related_adds_MIME_Version(self): + m = self.message() + m.add_related(b'image', 'image', 'png', cid='image') + self.assertEqual(m.get_all('MIME-Version'), ['1.0']) + self.assertEqual(m.get_content_type(), 'multipart/related') + def test_set_content_adds_MIME_Version(self): m = self._str_msg('') cm = self._TestContentManager() @@ -1116,6 +1136,13 @@ class TestMIMEPart(TestEmailMessageBase, TestEmailBase): # the full tests so that any future drift doesn't introduce bugs. message = MIMEPart + def test_make_multipart_does_not_add_MIME_Version(self): + for subtype in ('related', 'alternative', 'mixed'): + with self.subTest(subtype=subtype): + m = self.message() + getattr(m, 'make_' + subtype)() + self.assertNotIn('MIME-Version', m) + def test_set_content_does_not_add_MIME_Version(self): m = self._str_msg('') cm = self._TestContentManager() diff --git a/Misc/NEWS.d/next/Library/2026-10-20-04-00-00.gh-issue-120946.G8vD4r.rst b/Misc/NEWS.d/next/Library/2026-10-20-04-00-00.gh-issue-120946.G8vD4r.rst new file mode 100644 index 00000000000000..70b7885f92d524 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-20-04-00-00.gh-issue-120946.G8vD4r.rst @@ -0,0 +1,2 @@ +Add the required ``MIME-Version`` header when converting an +:class:`email.message.EmailMessage` to a multipart message.