Skip to content

bpo-36564: Fix infinite loop in email folding logic #12732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 16, 2019
Merged
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
17 changes: 12 additions & 5 deletions Lib/email/_header_value_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2846,15 +2846,22 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset):
trailing_wsp = to_encode[-1]
to_encode = to_encode[:-1]
new_last_ew = len(lines[-1]) if last_ew is None else last_ew

encode_as = 'utf-8' if charset == 'us-ascii' else charset

# The RFC2047 chrome takes up 7 characters plus the length
# of the charset name.
chrome_len = len(encode_as) + 7

if (chrome_len + 1) >= maxlen:
raise errors.HeaderParseError(
"max_line_length is too small to fit an encoded word")

while to_encode:
remaining_space = maxlen - len(lines[-1])
# The RFC2047 chrome takes up 7 characters plus the length
# of the charset name.
encode_as = 'utf-8' if charset == 'us-ascii' else charset
text_space = remaining_space - len(encode_as) - 7
text_space = remaining_space - chrome_len
if text_space <= 0:
lines.append(' ')
# XXX We'll get an infinite loop here if maxlen is <= 7
continue

to_encode_word = to_encode[:text_space]
Expand Down
1 change: 0 additions & 1 deletion Lib/email/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from email._policybase import compat32



class Parser:
def __init__(self, _class=None, *, policy=compat32):
"""Parser of RFC 2822 and MIME email messages.
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_email/test_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import types
import textwrap
import unittest
import email.errors
import email.policy
import email.parser
import email.generator
Expand Down Expand Up @@ -257,6 +258,25 @@ def test_non_ascii_chars_do_not_cause_inf_loop(self):
'Subject: \n' +
12 * ' =?utf-8?q?=C4=85?=\n')

def test_short_maxlen_error(self):
# RFC 2047 chrome takes up 7 characters, plus the length of the charset
# name, so folding should fail if maxlen is lower than the minimum
# required length for a line.

# Note: This is only triggered when there is a single word longer than
# max_line_length, hence the 1234567890 at the end of this whimsical
# subject. This is because when we encounter a word longer than
# max_line_length, it is broken down into encoded words to fit
# max_line_length. If the max_line_length isn't large enough to even
# contain the RFC 2047 chrome (`?=<charset>?q??=`), we fail.
subject = "Melt away the pounds with this one simple trick! 1234567890"

for maxlen in [3, 7, 9]:
with self.subTest(maxlen=maxlen):
policy = email.policy.default.clone(max_line_length=maxlen)
with self.assertRaises(email.errors.HeaderParseError):
policy.fold("Subject", subject)

# XXX: Need subclassing tests.
# For adding subclassed objects, make sure the usual rules apply (subclass
# wins), but that the order still works (right overrides left).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix infinite loop in email header folding logic that would be triggered when
an email policy's max_line_length is not long enough to include the required
markup and any values in the message. Patch by Paul Ganssle