Skip to content

Commit 79a47e2

Browse files
miss-islingtonpganssle
authored andcommitted
Fix infinite loop in email folding logic (GH-12732) (GH-14799)
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 <[email protected]>
1 parent 317c33e commit 79a47e2

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
@@ -2715,15 +2715,22 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset):
27152715
trailing_wsp = to_encode[-1]
27162716
to_encode = to_encode[:-1]
27172717
new_last_ew = len(lines[-1]) if last_ew is None else last_ew
2718+
2719+
encode_as = 'utf-8' if charset == 'us-ascii' else charset
2720+
2721+
# The RFC2047 chrome takes up 7 characters plus the length
2722+
# of the charset name.
2723+
chrome_len = len(encode_as) + 7
2724+
2725+
if (chrome_len + 1) >= maxlen:
2726+
raise errors.HeaderParseError(
2727+
"max_line_length is too small to fit an encoded word")
2728+
27182729
while to_encode:
27192730
remaining_space = maxlen - len(lines[-1])
2720-
# The RFC2047 chrome takes up 7 characters plus the length
2721-
# of the charset name.
2722-
encode_as = 'utf-8' if charset == 'us-ascii' else charset
2723-
text_space = remaining_space - len(encode_as) - 7
2731+
text_space = remaining_space - chrome_len
27242732
if text_space <= 0:
27252733
lines.append(' ')
2726-
# XXX We'll get an infinite loop here if maxlen is <= 7
27272734
continue
27282735

27292736
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
@@ -2,6 +2,7 @@
22
import types
33
import textwrap
44
import unittest
5+
import email.errors
56
import email.policy
67
import email.parser
78
import email.generator
@@ -245,6 +246,25 @@ def test_non_ascii_chars_do_not_cause_inf_loop(self):
245246
'Subject: \n' +
246247
12 * ' =?utf-8?q?=C4=85?=\n')
247248

249+
def test_short_maxlen_error(self):
250+
# RFC 2047 chrome takes up 7 characters, plus the length of the charset
251+
# name, so folding should fail if maxlen is lower than the minimum
252+
# required length for a line.
253+
254+
# Note: This is only triggered when there is a single word longer than
255+
# max_line_length, hence the 1234567890 at the end of this whimsical
256+
# subject. This is because when we encounter a word longer than
257+
# max_line_length, it is broken down into encoded words to fit
258+
# max_line_length. If the max_line_length isn't large enough to even
259+
# contain the RFC 2047 chrome (`?=<charset>?q??=`), we fail.
260+
subject = "Melt away the pounds with this one simple trick! 1234567890"
261+
262+
for maxlen in [3, 7, 9]:
263+
with self.subTest(maxlen=maxlen):
264+
policy = email.policy.default.clone(max_line_length=maxlen)
265+
with self.assertRaises(email.errors.HeaderParseError):
266+
policy.fold("Subject", subject)
267+
248268
# XXX: Need subclassing tests.
249269
# For adding subclassed objects, make sure the usual rules apply (subclass
250270
# 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)