diff -r efd692c86429 Lib/email/generator.py --- a/Lib/email/generator.py Fri Sep 09 20:09:43 2016 -0400 +++ b/Lib/email/generator.py Fri Sep 09 23:06:06 2016 -0400 @@ -85,6 +85,16 @@ from the policy associated with the msg. """ + # If this is a newly constructed message (there are no parser-derived + # headers) and we have at least one content- header, add the + # MIME-Version header. + if ('MIME-Version' not in msg + and all(hasattr(x, 'name') for x in msg.values()) + and any(x.lower().startswith('content-') for x in msg)): + msg['MIME-Version'] = '1.0' + return self._flatten(msg, unixfrom, linesep) + + def _flatten(self, msg, unixfrom=False, linesep=None): # We use the _XXX constants for operating on data that comes directly # from the msg, and _encoded_XXX constants for operating on data that # has already been converted (to bytes in the BytesGenerator) and @@ -265,7 +275,7 @@ for part in subparts: s = self._new_buffer() g = self.clone(s) - g.flatten(part, unixfrom=False, linesep=self._NL) + g._flatten(part, unixfrom=False, linesep=self._NL) msgtexts.append(s.getvalue()) # BAW: What about boundaries that are wrapped in double-quotes? boundary = msg.get_boundary() @@ -324,7 +334,7 @@ for part in msg.get_payload(): s = self._new_buffer() g = self.clone(s) - g.flatten(part, unixfrom=False, linesep=self._NL) + g._flatten(part, unixfrom=False, linesep=self._NL) text = s.getvalue() lines = text.split(self._encoded_NL) # Strip off the unnecessary trailing empty line @@ -351,7 +361,7 @@ # in that case we just emit the string body. payload = msg._payload if isinstance(payload, list): - g.flatten(msg.get_payload(0), unixfrom=False, linesep=self._NL) + g._flatten(msg.get_payload(0), unixfrom=False, linesep=self._NL) payload = s.getvalue() else: payload = self._encode(payload) diff -r efd692c86429 Lib/email/message.py --- a/Lib/email/message.py Fri Sep 09 20:09:43 2016 -0400 +++ b/Lib/email/message.py Fri Sep 09 23:06:06 2016 -0400 @@ -942,7 +942,7 @@ from email.iterators import walk -class MIMEPart(Message): +class EmailMessage(Message): def __init__(self, policy=None): if policy is None: @@ -1156,12 +1156,8 @@ self._payload = None -class EmailMessage(MIMEPart): - - def set_content(self, *args, **kw): - super().set_content(*args, **kw) - if 'MIME-Version' not in self: - self['MIME-Version'] = '1.0' +# Backward compatibility with the provisional version. +MIMEPart = EmailMessage # Set message_factory on Policy here to avoid a circular import. Policy.message_factory = Message diff -r efd692c86429 Lib/test/test_email/test_contentmanager.py --- a/Lib/test/test_email/test_contentmanager.py Fri Sep 09 20:09:43 2016 -0400 +++ b/Lib/test/test_email/test_contentmanager.py Fri Sep 09 23:06:06 2016 -0400 @@ -297,6 +297,7 @@ self.assertEqual(str(m), textwrap.dedent("""\ Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit + MIME-Version: 1.0 Simple message. """)) @@ -310,6 +311,7 @@ self.assertEqual(str(m), textwrap.dedent("""\ Content-Type: text/html; charset="utf-8" Content-Transfer-Encoding: 7bit + MIME-Version: 1.0

Simple message.

""")) @@ -323,6 +325,7 @@ self.assertEqual(str(m), textwrap.dedent("""\ Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: 7bit + MIME-Version: 1.0 Simple message. """)) @@ -336,6 +339,7 @@ self.assertEqual(bytes(m), textwrap.dedent("""\ Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit + MIME-Version: 1.0 et là il est monté sur moi et il commence à m'éto. """).encode('utf-8')) @@ -351,6 +355,7 @@ self.assertEqual(bytes(m), textwrap.dedent("""\ Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable + MIME-Version: 1.0 j'ai un probl=C3=A8me de python. il est sorti de son vivari= um. et l=C3=A0 il est mont=C3=A9 sur moi et il commence = @@ -369,6 +374,7 @@ self.assertEqual(bytes(m), textwrap.dedent("""\ Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable + MIME-Version: 1.0 """ + '\n'*10 + """ j'ai un probl=C3=A8me de python. il est sorti de son vivari= um. et l=C3=A0 il est mont=C3=A9 sur moi et il commence = @@ -384,6 +390,7 @@ self.assertEqual(bytes(m), textwrap.dedent("""\ Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit + MIME-Version: 1.0 áàäéèęöő. """).encode('utf-8')) @@ -397,6 +404,7 @@ self.assertEqual(bytes(m), textwrap.dedent("""\ Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit + MIME-Version: 1.0 """ + '\n'*10 + """ áàäéèęöő. """).encode('utf-8')) @@ -412,6 +420,7 @@ self.assertEqual(bytes(m), textwrap.dedent("""\ Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: base64 + MIME-Version: 1.0 w6HDoMOkw6nDqMSZw7bFkcOhw6DDpMOpw6jEmcO2xZHDocOgw6TDqcOoxJnD tsWRw6HDoMOkw6nDqMSZw7bFkcOhw6DDpMOpw6jEmcO2xZHDocOgw6TDqcOo @@ -437,6 +446,7 @@ self.assertEqual(bytes(m), textwrap.dedent("""\ Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable + MIME-Version: 1.0 """ + '\n'*10 + """ =C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3= =A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4= @@ -481,13 +491,13 @@ Subject: Forwarded message Content-Type: message/rfc822 Content-Transfer-Encoding: 8bit + MIME-Version: 1.0 To: python@vivarium.org From: police@monty.org Subject: get back in your box Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit - MIME-Version: 1.0 Or face the comfy chair. """)) @@ -511,13 +521,13 @@ Subject: Escape report Content-Type: message/rfc822 Content-Transfer-Encoding: 8bit + MIME-Version: 1.0 To: police@monty.org From: victim@monty.org Subject: Help Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit - MIME-Version: 1.0 j'ai un problème de python. il est sorti de son vivarium. """).encode('utf-8')) @@ -531,13 +541,13 @@ Subject: Escape report Content-Type: message/rfc822 Content-Transfer-Encoding: 8bit + MIME-Version: 1.0 To: police@monty.org From: victim@monty.org Subject: Help Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: base64 - MIME-Version: 1.0 aidhaSB1biBwcm9ibMOobWUgZGUgcHl0aG9uLiBpbCBlc3Qgc29ydGkgZGUgc29uIHZpdmFyaXVt Lgo= @@ -575,6 +585,7 @@ self.assertEqual(str(m), textwrap.dedent("""\ Content-Type: image/jpeg Content-Transfer-Encoding: base64 + MIME-Version: 1.0 Ym9ndXMgY29udGVudA== """)) @@ -651,6 +662,7 @@ From: foo@example.com Subject: I'm talking to myself. Content-Transfer-Encoding: 7bit + MIME-Version: 1.0 Simple message. """)) @@ -666,6 +678,7 @@ X-Foo-Header: foo X-Bar-Header: bar Content-Transfer-Encoding: 7bit + MIME-Version: 1.0 Simple message. """)) diff -r efd692c86429 Lib/test/test_email/test_generator.py --- a/Lib/test/test_email/test_generator.py Fri Sep 09 20:09:43 2016 -0400 +++ b/Lib/test/test_email/test_generator.py Fri Sep 09 23:06:06 2016 -0400 @@ -12,6 +12,7 @@ class TestGeneratorBase: policy = policy.default + message = EmailMessage def msgmaker(self, msg, policy=None): policy = self.policy if policy is None else policy @@ -162,6 +163,40 @@ g.flatten(msg) self.assertEqual(s.getvalue(), self.typ(expected)) + def test_mime_version_added_to_mime_message(self): + msg = self._make_message() + msg['To'] = 'foo@example.com' + msg.add_related('This is a test') + self.assertNotIn('MIME-Version', msg) + serialized = self.serialfunc(msg) + self.assertIn('MIME-Version', msg) + self.assertEqual(serialized, self.typ(textwrap.dedent("""\ + To: foo@example.com + Content-Type: multipart/related; boundary="{boundary}" + MIME-Version: 1.0 + + --{boundary} + Content-Type: text/plain; charset="utf-8" + Content-Transfer-Encoding: 7bit + Content-Disposition: inline + + This is a test + + --{boundary}-- + """).format(boundary=msg.get_boundary()))) + + def test_no_mime_version_in_non_mime_message(self): + msg = self._make_message() + msg['To'] = 'foo@example.com' + msg.set_payload('This is a test\n') + serialized = self.serialfunc(msg) + self.assertNotIn('MIME-Version', msg) + self.assertEqual(serialized, self.typ(textwrap.dedent("""\ + To: foo@example.com + + This is a test + """))) + class TestGenerator(TestGeneratorBase, TestEmailBase): @@ -169,6 +204,7 @@ genclass = Generator ioclass = io.StringIO typ = str + serialfunc = str class TestBytesGenerator(TestGeneratorBase, TestEmailBase): @@ -177,6 +213,7 @@ genclass = BytesGenerator ioclass = io.BytesIO typ = lambda self, x: x.encode('ascii') + serialfunc = bytes def test_cte_type_7bit_handles_unknown_8bit(self): source = ("Subject: Maintenant je vous présente mon " diff -r efd692c86429 Lib/test/test_email/test_message.py --- a/Lib/test/test_email/test_message.py Fri Sep 09 20:09:43 2016 -0400 +++ b/Lib/test/test_email/test_message.py Fri Sep 09 23:06:06 2016 -0400 @@ -36,8 +36,8 @@ @parameterize -class TestEmailMessageBase: - +class TestEmailMessage(TestEmailBase): + message = EmailMessage policy = policy.default # The first argument is a triple (related, html, plain) of indices into the @@ -742,33 +742,11 @@ self.assertEqual(len(list(m.iter_attachments())), 2) self.assertEqual(m.get_payload(), orig) - -class TestEmailMessage(TestEmailMessageBase, TestEmailBase): - message = EmailMessage - - def test_set_content_adds_MIME_Version(self): - m = self._str_msg('') - cm = self._TestContentManager() - self.assertNotIn('MIME-Version', m) - m.set_content(content_manager=cm) - self.assertEqual(m['MIME-Version'], '1.0') - - class _MIME_Version_adding_CM: - def set_content(self, msg, *args, **kw): - msg['MIME-Version'] = '1.0' - - def test_set_content_does_not_duplicate_MIME_Version(self): - m = self._str_msg('') - cm = self._MIME_Version_adding_CM() - self.assertNotIn('MIME-Version', m) - m.set_content(content_manager=cm) - self.assertEqual(m['MIME-Version'], '1.0') - - def test_as_string_uses_max_header_length_by_default(self): + def test_asstring_uses_max_header_length_by_default(self): m = self._str_msg('Subject: long line' + ' ab'*50 + '\n\n') self.assertEqual(len(m.as_string().strip().splitlines()), 3) - def test_as_string_allows_maxheaderlen(self): + def test_asstring_allows_maxheaderlen(self): m = self._str_msg('Subject: long line' + ' ab'*50 + '\n\n') self.assertEqual(len(m.as_string(maxheaderlen=0).strip().splitlines()), 1) @@ -785,19 +763,5 @@ self.assertEqual(str(m), 'Subject: unicöde\n\n') -class TestMIMEPart(TestEmailMessageBase, TestEmailBase): - # Doing the full test run here may seem a bit redundant, since the two - # classes are almost identical. But what if they drift apart? So we do - # the full tests so that any future drift doesn't introduce bugs. - message = MIMEPart - - def test_set_content_does_not_add_MIME_Version(self): - m = self._str_msg('') - cm = self._TestContentManager() - self.assertNotIn('MIME-Version', m) - m.set_content(content_manager=cm) - self.assertNotIn('MIME-Version', m) - - if __name__ == '__main__': unittest.main()