Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Lib/email/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
35 changes: 35 additions & 0 deletions Lib/test/test_email/test_generator.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's be verbose and enhance this comment a bit to make things clearer. How about "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):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
``email.generator.Generator._make_boundary`` now correctly finds the
boundary when using CRLF linesep.
Copy link
Copy Markdown
Member

@bitdancer bitdancer Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This description more about the bugfix implementation than the bug. How about "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."

And, having written that, it occurs to me we could have the same bug if linesep were set to something exotic. Which shouldn't be a problem in practice, but then, neither is this bug ;) I'm sure non-standard linesep would reveal lots of other bugs in the code in that regard as well, though, so let's just ignore that potential issue.

Loading