Fix infinite loop in email folding logic (GH-12732) · python/cpython@f69d5c6 (original) (raw)

4 files changed

lines changed

Original file line number Diff line number Diff line change
@@ -2846,15 +2846,22 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset):
2846 2846 trailing_wsp = to_encode[-1]
2847 2847 to_encode = to_encode[:-1]
2848 2848 new_last_ew = len(lines[-1]) if last_ew is None else last_ew
2849 +
2850 +encode_as = 'utf-8' if charset == 'us-ascii' else charset
2851 +
2852 +# The RFC2047 chrome takes up 7 characters plus the length
2853 +# of the charset name.
2854 +chrome_len = len(encode_as) + 7
2855 +
2856 +if (chrome_len + 1) >= maxlen:
2857 +raise errors.HeaderParseError(
2858 +"max_line_length is too small to fit an encoded word")
2859 +
2849 2860 while to_encode:
2850 2861 remaining_space = maxlen - len(lines[-1])
2851 -# The RFC2047 chrome takes up 7 characters plus the length
2852 -# of the charset name.
2853 -encode_as = 'utf-8' if charset == 'us-ascii' else charset
2854 -text_space = remaining_space - len(encode_as) - 7
2862 +text_space = remaining_space - chrome_len
2855 2863 if text_space <= 0:
2856 2864 lines.append(' ')
2857 -# XXX We'll get an infinite loop here if maxlen is <= 7
2858 2865 continue
2859 2866
2860 2867 to_encode_word = to_encode[:text_space]
Original file line number Diff line number Diff line change
@@ -13,7 +13,6 @@
13 13 from email._policybase import compat32
14 14
15 15
16 -
17 16 class Parser:
18 17 def __init__(self, _class=None, *, policy=compat32):
19 18 """Parser of RFC 2822 and MIME email messages.
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
2 2 import types
3 3 import textwrap
4 4 import unittest
5 +import email.errors
5 6 import email.policy
6 7 import email.parser
7 8 import email.generator
@@ -257,6 +258,25 @@ def test_non_ascii_chars_do_not_cause_inf_loop(self):
257 258 'Subject: \n' +
258 259 12 * ' =?utf-8?q?=C4=85?=\n')
259 260
261 +def test_short_maxlen_error(self):
262 +# RFC 2047 chrome takes up 7 characters, plus the length of the charset
263 +# name, so folding should fail if maxlen is lower than the minimum
264 +# required length for a line.
265 +
266 +# Note: This is only triggered when there is a single word longer than
267 +# max_line_length, hence the 1234567890 at the end of this whimsical
268 +# subject. This is because when we encounter a word longer than
269 +# max_line_length, it is broken down into encoded words to fit
270 +# max_line_length. If the max_line_length isn't large enough to even
271 +# contain the RFC 2047 chrome (`?=?q??=`), we fail.
272 +subject = "Melt away the pounds with this one simple trick! 1234567890"
273 +
274 +for maxlen in [3, 7, 9]:
275 +with self.subTest(maxlen=maxlen):
276 +policy = email.policy.default.clone(max_line_length=maxlen)
277 +with self.assertRaises(email.errors.HeaderParseError):
278 +policy.fold("Subject", subject)
279 +
260 280 # XXX: Need subclassing tests.
261 281 # For adding subclassed objects, make sure the usual rules apply (subclass
262 282 # wins), but that the order still works (right overrides left).
Original file line number Diff line number Diff 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