From 2dfdcb644d42e17f2a3e571dfb69250a1662ac1f Mon Sep 17 00:00:00 2001 From: Henry Jones Date: Tue, 7 Apr 2026 14:17:13 +1200 Subject: [PATCH 1/2] Fix Generator._make_boundary behavior with CRLF line endings. The Generator._make_boundary regex does not match on boundary phrases correctly when using CRLF line endings due to re.MULTILINE not considering \r\n as a line ending. --- Lib/email/generator.py | 2 +- Lib/test/test_email/test_generator.py | 35 +++++++++++++++++++ ...-04-07-14-13-40.gh-issue-148192.34AUYQ.rst | 2 ++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-04-07-14-13-40.gh-issue-148192.34AUYQ.rst diff --git a/Lib/email/generator.py b/Lib/email/generator.py index cebbc416087fee..ba11d63fba600a 100644 --- a/Lib/email/generator.py +++ b/Lib/email/generator.py @@ -392,7 +392,7 @@ def _make_boundary(cls, text=None): b = boundary counter = 0 while True: - cre = cls._compile_re('^--' + re.escape(b) + '(--)?$', re.MULTILINE) + cre = cls._compile_re('^--' + re.escape(b) + '(--)?\r?$', re.MULTILINE) if not cre.search(text): break b = boundary + '.' + str(counter) diff --git a/Lib/test/test_email/test_generator.py b/Lib/test/test_email/test_generator.py index c2d7d09d591e86..febafe181b9c9e 100644 --- a/Lib/test/test_email/test_generator.py +++ b/Lib/test/test_email/test_generator.py @@ -1,13 +1,20 @@ import io import textwrap import unittest +import random +import sys from email import message_from_string, message_from_bytes from email.message import EmailMessage +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText from email.generator import Generator, BytesGenerator +import email.generator from email.headerregistry import Address from email import policy import email.errors from test.test_email import TestEmailBase, parameterize +import test.support + @parameterize @@ -288,6 +295,34 @@ def test_keep_long_encoded_newlines(self): g.flatten(msg) self.assertEqual(s.getvalue(), self.typ(expected)) + def _test_boundary_detection(self, linesep): + # Generate a boundary token in the same way as _make_boundary + token = random.randrange(sys.maxsize) + + def _patch_random_randrange(*args, **kwargs): + return token + + with test.support.swap_attr( + random, "randrange", _patch_random_randrange + ): + boundary = self.genclass._make_boundary(text=None) + boundary_in_part = ( + "this goes before the boundary\n--" + + boundary + + "\nthis goes after\n" + ) + msg = MIMEMultipart() + msg.attach(MIMEText(boundary_in_part)) + self.genclass(self.ioclass()).flatten(msg, linesep=linesep) + # .0 is appended if the boundary was found. + self.assertEqual(msg.get_boundary(), boundary + ".0") + + def test_lf_boundary_detection(self): + self._test_boundary_detection("\n") + + def test_crlf_boundary_detection(self): + self._test_boundary_detection("\r\n") + class TestGenerator(TestGeneratorBase, TestEmailBase): diff --git a/Misc/NEWS.d/next/Library/2026-04-07-14-13-40.gh-issue-148192.34AUYQ.rst b/Misc/NEWS.d/next/Library/2026-04-07-14-13-40.gh-issue-148192.34AUYQ.rst new file mode 100644 index 00000000000000..c736dfd311b2fd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-07-14-13-40.gh-issue-148192.34AUYQ.rst @@ -0,0 +1,2 @@ +:func:`email.generator.Generator._make_boundary` now correctly finds the +boundary when using CRLF linesep. From 540b8d91fc2ec5c54dc9af9f8aab3ed1c2532ae5 Mon Sep 17 00:00:00 2001 From: Henry Jones Date: Tue, 7 Apr 2026 14:26:53 +1200 Subject: [PATCH 2/2] remove :func: on blurb --- .../next/Library/2026-04-07-14-13-40.gh-issue-148192.34AUYQ.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2026-04-07-14-13-40.gh-issue-148192.34AUYQ.rst b/Misc/NEWS.d/next/Library/2026-04-07-14-13-40.gh-issue-148192.34AUYQ.rst index c736dfd311b2fd..7c5f4ad37ad6a2 100644 --- a/Misc/NEWS.d/next/Library/2026-04-07-14-13-40.gh-issue-148192.34AUYQ.rst +++ b/Misc/NEWS.d/next/Library/2026-04-07-14-13-40.gh-issue-148192.34AUYQ.rst @@ -1,2 +1,2 @@ -:func:`email.generator.Generator._make_boundary` now correctly finds the +``email.generator.Generator._make_boundary`` now correctly finds the boundary when using CRLF linesep.