Skip to content

[3.6] bpo-33529, email: Fix infinite loop in email header encoding (GH-12020) #14162

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
Jun 18, 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
23 changes: 13 additions & 10 deletions Lib/email/_header_value_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2725,16 +2725,19 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset):
lines.append(' ')
# XXX We'll get an infinite loop here if maxlen is <= 7
continue
first_part = to_encode[:text_space]
ew = _ew.encode(first_part, charset=encode_as)
excess = len(ew) - remaining_space
if excess > 0:
# encode always chooses the shortest encoding, so this
# is guaranteed to fit at this point.
first_part = first_part[:-excess]
ew = _ew.encode(first_part)
lines[-1] += ew
to_encode = to_encode[len(first_part):]

to_encode_word = to_encode[:text_space]
encoded_word = _ew.encode(to_encode_word, charset=encode_as)
excess = len(encoded_word) - remaining_space
while excess > 0:
# Since the chunk to encode is guaranteed to fit into less than 100 characters,
# shrinking it by one at a time shouldn't take long.
to_encode_word = to_encode_word[:-1]
encoded_word = _ew.encode(to_encode_word, charset=encode_as)
excess = len(encoded_word) - remaining_space
lines[-1] += encoded_word
to_encode = to_encode[len(to_encode_word):]

if to_encode:
lines.append(' ')
new_last_ew = len(lines[-1])
Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test_email/test_headerregistry.py
Original file line number Diff line number Diff line change
Expand Up @@ -1643,10 +1643,10 @@ def test_fold_overlong_words_using_RFC2047(self):
self.assertEqual(
h.fold(policy=policy.default),
'X-Report-Abuse: =?utf-8?q?=3Chttps=3A//www=2Emailitapp=2E'
'com/report=5F?=\n'
' =?utf-8?q?abuse=2Ephp=3Fmid=3Dxxx-xxx-xxxx'
'xxxxxxxxxxxxxxxxxxxx=3D=3D-xxx-?=\n'
' =?utf-8?q?xx-xx=3E?=\n')
'com/report=5Fabuse?=\n'
' =?utf-8?q?=2Ephp=3Fmid=3Dxxx-xxx-xxxx'
'xxxxxxxxxxxxxxxxxxxx=3D=3D-xxx-xx-xx?=\n'
' =?utf-8?q?=3E?=\n')


if __name__ == '__main__':
Expand Down
8 changes: 8 additions & 0 deletions Lib/test/test_email/test_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,14 @@ def test_adding_default_policies_preserves_default_factory(self):
email.policy.EmailPolicy.header_factory)
self.assertEqual(newpolicy.__dict__, {'raise_on_defect': True})

def test_non_ascii_chars_do_not_cause_inf_loop(self):
policy = email.policy.default.clone(max_line_length=20)
actual = policy.fold('Subject', 'ą' * 12)
self.assertEqual(
actual,
'Subject: \n' +
12 * ' =?utf-8?q?=C4=85?=\n')

# 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,2 @@
Prevent fold function used in email header encoding from entering infinite
loop when there are too many non-ASCII characters in a header.