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

4 files changed

lines changed

Original file line numberDiff line numberDiff line change

@@ -2846,15 +2846,22 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset):

28462846

trailing_wsp = to_encode[-1]

28472847

to_encode = to_encode[:-1]

28482848

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+
28492860

while to_encode:

28502861

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

28552863

if text_space <= 0:

28562864

lines.append(' ')

2857-

# XXX We'll get an infinite loop here if maxlen is <= 7

28582865

continue

28592866
28602867

to_encode_word = to_encode[:text_space]

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.

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

@@ -257,6 +258,25 @@ def test_non_ascii_chars_do_not_cause_inf_loop(self):

257258

'Subject: \n' +

258259

12 * ' =?utf-8?q?=C4=85?=\n')

259260
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 (`?=<charset>?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+
260280

# XXX: Need subclassing tests.

261281

# For adding subclassed objects, make sure the usual rules apply (subclass

262282

# wins), but that the order still works (right overrides left).

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