From 4af46b4ab5af49d8df034320a9a70fcbb062f7cf Mon Sep 17 00:00:00 2001 From: Henry Jones <44321887+henryivesjones@users.noreply.github.com> Date: Wed, 15 Apr 2026 01:10:08 +1200 Subject: [PATCH] gh-148192: Fix Generator._make_boundary behavior with CRLF line endings. (#148193) The Generator._make_boundary regex did 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 | 37 +++++++++++++++++++ ...-04-07-14-13-40.gh-issue-148192.34AUYQ.rst | 3 ++ 3 files changed, 41 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 cebbc416087f..ba11d63fba60 100644 --- a/Lib/email/generator.py +++ b/Lib/email/generator.py @@ -392,7 +392,7 @@ class Generator: 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 c2d7d09d591e..3c9a86f3e8cf 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,36 @@ class TestGeneratorBase: 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) + # Generator checks the message content for the string it is about + # to use as a boundary ('token' in this test) and when it finds it + # in our attachment appends .0 to make the boundary it uses unique. + 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 000000000000..87a568b50c17 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-07-14-13-40.gh-issue-148192.34AUYQ.rst @@ -0,0 +1,3 @@ +``email.generator.Generator._make_boundary`` could fail to detect a duplicate +boundary string if linesep was not \n. It now correctly detects boundary +strings when linesep is \r\n as well. -- 2.47.3