diff --git a/Lib/email/generator.py b/Lib/email/generator.py index ab5bd0653e440c..53bacd6a8f237b 100644 --- a/Lib/email/generator.py +++ b/Lib/email/generator.py @@ -417,7 +417,11 @@ class BytesGenerator(Generator): """ def write(self, s): - self._fp.write(s.encode('ascii', 'surrogateescape')) + if getattr(self.policy, "utf8", False): + encoded = s.encode('utf-8', 'surrogateescape') + else: + encoded = s.encode('ascii', 'surrogateescape') + self._fp.write(encoded) def _new_buffer(self): return BytesIO() diff --git a/Lib/test/test_email/test_generator.py b/Lib/test/test_email/test_generator.py index c75a842c33578e..c2f359e095906d 100644 --- a/Lib/test/test_email/test_generator.py +++ b/Lib/test/test_email/test_generator.py @@ -472,6 +472,21 @@ def test_smtp_policy(self): g.flatten(msg) self.assertEqual(s.getvalue(), expected) + def test_utf8_round_trip_preserves_unicode_body(self): + msg = EmailMessage() + msg['From'] = "Páolo " + msg['To'] = 'Dinsdale' + msg['Subject'] = 'Nudge nudge, wink, wink \u1F609' + msg.set_content("oh là là, know what I mean, know what I mean?") + s = io.BytesIO() + g = BytesGenerator(s, policy=policy.SMTPUTF8) + g.flatten(msg) + out = s.getvalue() + parsed = message_from_bytes(out, policy=policy.default.clone(utf8=True)) + + self.assertEqual(parsed["Subject"], 'Nudge nudge, wink, wink \u1F609') + self.assertEqual(parsed.get_body().get_content(), "oh là là, know what I mean, know what I mean?\r\n") + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2025-07-19-22-54-48.gh-issue-136686.fc2Zl0.rst b/Misc/NEWS.d/next/Library/2025-07-19-22-54-48.gh-issue-136686.fc2Zl0.rst new file mode 100644 index 00000000000000..0f2510253b6b6c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-19-22-54-48.gh-issue-136686.fc2Zl0.rst @@ -0,0 +1,2 @@ +Fixed a bug where emails with ``multipart/related`` content did not round-trip +correctly through ``email.parser.parsebytes()`` and ``as_bytes()``.