Skip to content

Commit e7bec26

Browse files
Fix infinite loop in email folding logic (GH-12732)
As far as I can tell, this infinite loop would be triggered if: 1. The value being folded contains a single word (no spaces) longer than max_line_length 2. The max_line_length is shorter than the encoding's name + 9 characters. bpo-36564: https://bugs.python.org/issue36564 (cherry picked from commit f69d5c6) Co-authored-by: Paul Ganssle <pganssle@users.noreply.github.com>
1 parent 134f796 commit e7bec26

File tree

4 files changed

+35
-6
lines changed

4 files changed

+35
-6
lines changed

Lib/email/_header_value_parser.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2740,15 +2740,22 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset):
27402740
trailing_wsp = to_encode[-1]
27412741
to_encode = to_encode[:-1]
27422742
new_last_ew = len(lines[-1]) if last_ew is None else last_ew
2743+
2744+
encode_as = 'utf-8' if charset == 'us-ascii' else charset
2745+
2746+
# The RFC2047 chrome takes up 7 characters plus the length
2747+
# of the charset name.
2748+
chrome_len = len(encode_as) + 7
2749+
2750+
if (chrome_len + 1) >= maxlen:
2751+
raise errors.HeaderParseError(
2752+
"max_line_length is too small to fit an encoded word")
2753+
27432754
while to_encode:
27442755
remaining_space = maxlen - len(lines[-1])
2745-
# The RFC2047 chrome takes up 7 characters plus the length
2746-
# of the charset name.
2747-
encode_as = 'utf-8' if charset == 'us-ascii' else charset
2748-
text_space = remaining_space - len(encode_as) - 7
2756+
text_space = remaining_space - chrome_len
27492757
if text_space <= 0:
27502758
lines.append(' ')
2751-
# XXX We'll get an infinite loop here if maxlen is <= 7
27522759
continue
27532760

27542761
to_encode_word = to_encode[:text_space]

Lib/email/parser.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from email._policybase import compat32
1414

1515

16-
1716
class Parser:
1817
def __init__(self, _class=None, *, policy=compat32):
1918
"""Parser of RFC 2822 and MIME email messages.

Lib/test/test_email/test_policy.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import types
44
import textwrap
55
import unittest
6+
import email.errors
67
import email.policy
78
import email.parser
89
import email.generator
@@ -258,6 +259,25 @@ def test_non_ascii_chars_do_not_cause_inf_loop(self):
258259
'Subject: \n' +
259260
12 * ' =?utf-8?q?=C4=85?=\n')
260261

262+
def test_short_maxlen_error(self):
263+
# RFC 2047 chrome takes up 7 characters, plus the length of the charset
264+
# name, so folding should fail if maxlen is lower than the minimum
265+
# required length for a line.
266+
267+
# Note: This is only triggered when there is a single word longer than
268+
# max_line_length, hence the 1234567890 at the end of this whimsical
269+
# subject. This is because when we encounter a word longer than
270+
# max_line_length, it is broken down into encoded words to fit
271+
# max_line_length. If the max_line_length isn't large enough to even
272+
# contain the RFC 2047 chrome (`?=<charset>?q??=`), we fail.
273+
subject = "Melt away the pounds with this one simple trick! 1234567890"
274+
275+
for maxlen in [3, 7, 9]:
276+
with self.subTest(maxlen=maxlen):
277+
policy = email.policy.default.clone(max_line_length=maxlen)
278+
with self.assertRaises(email.errors.HeaderParseError):
279+
policy.fold("Subject", subject)
280+
261281
# XXX: Need subclassing tests.
262282
# For adding subclassed objects, make sure the usual rules apply (subclass
263283
# wins), but that the order still works (right overrides left).
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix infinite loop in email header folding logic that would be triggered when
2+
an email policy's max_line_length is not long enough to include the required
3+
markup and any values in the message. Patch by Paul Ganssle

0 commit comments

Comments
 (0)