Skip to content

Commit 6f2f475

Browse files
authored
bpo-40597: email: Use CTE if lines are longer than max_line_length consistently (gh-20038)
raw_data_manager (default for EmailPolicy, EmailMessage) does correct wrapping of 'text' parts as long as the message contains characters outside of 7bit US-ASCII set: base64 or qp Content-Transfer-Encoding is applied if the lines would be too long without it. It did not, however, do this for ascii-only text, which could result in lines that were longer than policy.max_line_length or even the rfc 998 maximum. This changeset fixes the heuristic so that if lines are longer than policy.max_line_length, it will always apply a content-transfer-encoding so that the lines are wrapped correctly.
1 parent 3d17c04 commit 6f2f475

File tree

3 files changed

+23
-7
lines changed

3 files changed

+23
-7
lines changed

Lib/email/contentmanager.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,13 @@ def embedded_body(lines): return linesep.join(lines) + linesep
146146
def normal_body(lines): return b'\n'.join(lines) + b'\n'
147147
if cte==None:
148148
# Use heuristics to decide on the "best" encoding.
149-
try:
150-
return '7bit', normal_body(lines).decode('ascii')
151-
except UnicodeDecodeError:
152-
pass
153-
if (policy.cte_type == '8bit' and
154-
max(len(x) for x in lines) <= policy.max_line_length):
155-
return '8bit', normal_body(lines).decode('ascii', 'surrogateescape')
149+
if max(len(x) for x in lines) <= policy.max_line_length:
150+
try:
151+
return '7bit', normal_body(lines).decode('ascii')
152+
except UnicodeDecodeError:
153+
pass
154+
if policy.cte_type == '8bit':
155+
return '8bit', normal_body(lines).decode('ascii', 'surrogateescape')
156156
sniff = embedded_body(lines[:10])
157157
sniff_qp = quoprimime.body_encode(sniff.decode('latin-1'),
158158
policy.max_line_length)

Lib/test/test_email/test_contentmanager.py

+15
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,21 @@ def test_set_text_charset_latin_1(self):
329329
self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
330330
self.assertEqual(m.get_content(), content)
331331

332+
def test_set_text_plain_long_line_heuristics(self):
333+
m = self._make_message()
334+
content = ("Simple but long message that is over 78 characters"
335+
" long to force transfer encoding.\n")
336+
raw_data_manager.set_content(m, content)
337+
self.assertEqual(str(m), textwrap.dedent("""\
338+
Content-Type: text/plain; charset="utf-8"
339+
Content-Transfer-Encoding: quoted-printable
340+
341+
Simple but long message that is over 78 characters long to =
342+
force transfer encoding.
343+
"""))
344+
self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
345+
self.assertEqual(m.get_content(), content)
346+
332347
def test_set_text_short_line_minimal_non_ascii_heuristics(self):
333348
m = self._make_message()
334349
content = "et là il est monté sur moi et il commence à m'éto.\n"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
If text content lines are longer than policy.max_line_length, always use a content-encoding to make sure they are wrapped.

0 commit comments

Comments
 (0)