Skip to content

Commit 17d1490

Browse files
gh-143935: Email preserve parens when folding comments (#143936)
Fix a bug in the folding of comments when flattening an email message using a modern email policy. Comments consisting of a very long sequence of non-foldable characters could trigger a forced line wrap that omitted the required leading space on the continuation line, causing the remainder of the comment to be interpreted as a new header field. This enabled header injection with carefully crafted inputs. Co-authored-by: Denis Ledoux <dle@odoo.com>
1 parent bb2b9ba commit 17d1490

File tree

3 files changed

+43
-1
lines changed

3 files changed

+43
-1
lines changed

Lib/email/_header_value_parser.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ def make_quoted_pairs(value):
101101
return str(value).replace('\\', '\\\\').replace('"', '\\"')
102102

103103

104+
def make_parenthesis_pairs(value):
105+
"""Escape parenthesis and backslash for use within a comment."""
106+
return str(value).replace('\\', '\\\\') \
107+
.replace('(', '\\(').replace(')', '\\)')
108+
109+
104110
def quote_string(value):
105111
escaped = make_quoted_pairs(value)
106112
return f'"{escaped}"'
@@ -943,7 +949,7 @@ def value(self):
943949
return ' '
944950

945951
def startswith_fws(self):
946-
return True
952+
return self and self[0] in WSP
947953

948954

949955
class ValueTerminal(Terminal):
@@ -2963,6 +2969,13 @@ def _refold_parse_tree(parse_tree, *, policy):
29632969
[ValueTerminal(make_quoted_pairs(p), 'ptext')
29642970
for p in newparts] +
29652971
[ValueTerminal('"', 'ptext')])
2972+
if part.token_type == 'comment':
2973+
newparts = (
2974+
[ValueTerminal('(', 'ptext')] +
2975+
[ValueTerminal(make_parenthesis_pairs(p), 'ptext')
2976+
if p.token_type == 'ptext' else p
2977+
for p in newparts] +
2978+
[ValueTerminal(')', 'ptext')])
29662979
if not part.as_ew_allowed:
29672980
wrap_as_ew_blocked += 1
29682981
newparts.append(end_ew_not_allowed)

Lib/test/test_email/test__header_value_parser.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3294,6 +3294,29 @@ def test_address_list_with_specials_in_long_quoted_string(self):
32943294
with self.subTest(to=to):
32953295
self._test(parser.get_address_list(to)[0], folded, policy=policy)
32963296

3297+
def test_address_list_with_long_unwrapable_comment(self):
3298+
policy = self.policy.clone(max_line_length=40)
3299+
cases = [
3300+
# (to, folded)
3301+
('(loremipsumdolorsitametconsecteturadipi)<spy@example.org>',
3302+
'(loremipsumdolorsitametconsecteturadipi)<spy@example.org>\n'),
3303+
('<spy@example.org>(loremipsumdolorsitametconsecteturadipi)',
3304+
'<spy@example.org>(loremipsumdolorsitametconsecteturadipi)\n'),
3305+
('(loremipsum dolorsitametconsecteturadipi)<spy@example.org>',
3306+
'(loremipsum dolorsitametconsecteturadipi)<spy@example.org>\n'),
3307+
('<spy@example.org>(loremipsum dolorsitametconsecteturadipi)',
3308+
'<spy@example.org>(loremipsum\n dolorsitametconsecteturadipi)\n'),
3309+
('(Escaped \\( \\) chars \\\\ in comments stay escaped)<spy@example.org>',
3310+
'(Escaped \\( \\) chars \\\\ in comments stay\n escaped)<spy@example.org>\n'),
3311+
('((loremipsum)(loremipsum)(loremipsum)(loremipsum))<spy@example.org>',
3312+
'((loremipsum)(loremipsum)(loremipsum)(loremipsum))<spy@example.org>\n'),
3313+
('((loremipsum)(loremipsum)(loremipsum) (loremipsum))<spy@example.org>',
3314+
'((loremipsum)(loremipsum)(loremipsum)\n (loremipsum))<spy@example.org>\n'),
3315+
]
3316+
for (to, folded) in cases:
3317+
with self.subTest(to=to):
3318+
self._test(parser.get_address_list(to)[0], folded, policy=policy)
3319+
32973320
# XXX Need tests with comments on various sides of a unicode token,
32983321
# and with unicode tokens in the comments. Spaces inside the quotes
32993322
# currently don't do the right thing.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Fixed a bug in the folding of comments when flattening an email message
2+
using a modern email policy. Comments consisting of a very long sequence of
3+
non-foldable characters could trigger a forced line wrap that omitted the
4+
required leading space on the continuation line, causing the remainder of
5+
the comment to be interpreted as a new header field. This enabled header
6+
injection with carefully crafted inputs.

0 commit comments

Comments
 (0)