Skip to content

Commit 1aa8bbe

Browse files
qsserhiy-storchaka
andauthored
bpo-40944: Fix IndexError when parse emails with truncated Message-ID, address, routes, etc (GH-20790)
Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 147cd05 commit 1aa8bbe

File tree

3 files changed

+51
-5
lines changed

3 files changed

+51
-5
lines changed

Lib/email/_header_value_parser.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,7 +1213,7 @@ def get_bare_quoted_string(value):
12131213
value is the text between the quote marks, with whitespace
12141214
preserved and quoted pairs decoded.
12151215
"""
1216-
if value[0] != '"':
1216+
if not value or value[0] != '"':
12171217
raise errors.HeaderParseError(
12181218
"expected '\"' but found '{}'".format(value))
12191219
bare_quoted_string = BareQuotedString()
@@ -1454,7 +1454,7 @@ def get_local_part(value):
14541454
"""
14551455
local_part = LocalPart()
14561456
leader = None
1457-
if value[0] in CFWS_LEADER:
1457+
if value and value[0] in CFWS_LEADER:
14581458
leader, value = get_cfws(value)
14591459
if not value:
14601460
raise errors.HeaderParseError(
@@ -1613,7 +1613,7 @@ def get_domain(value):
16131613
"""
16141614
domain = Domain()
16151615
leader = None
1616-
if value[0] in CFWS_LEADER:
1616+
if value and value[0] in CFWS_LEADER:
16171617
leader, value = get_cfws(value)
16181618
if not value:
16191619
raise errors.HeaderParseError(
@@ -1689,6 +1689,8 @@ def get_obs_route(value):
16891689
if value[0] in CFWS_LEADER:
16901690
token, value = get_cfws(value)
16911691
obs_route.append(token)
1692+
if not value:
1693+
break
16921694
if value[0] == '@':
16931695
obs_route.append(RouteComponentMarker)
16941696
token, value = get_domain(value[1:])
@@ -1707,7 +1709,7 @@ def get_angle_addr(value):
17071709
17081710
"""
17091711
angle_addr = AngleAddr()
1710-
if value[0] in CFWS_LEADER:
1712+
if value and value[0] in CFWS_LEADER:
17111713
token, value = get_cfws(value)
17121714
angle_addr.append(token)
17131715
if not value or value[0] != '<':
@@ -1717,7 +1719,7 @@ def get_angle_addr(value):
17171719
value = value[1:]
17181720
# Although it is not legal per RFC5322, SMTP uses '<>' in certain
17191721
# circumstances.
1720-
if value[0] == '>':
1722+
if value and value[0] == '>':
17211723
angle_addr.append(ValueTerminal('>', 'angle-addr-end'))
17221724
angle_addr.defects.append(errors.InvalidHeaderDefect(
17231725
"null addr-spec in angle-addr"))
@@ -1769,6 +1771,9 @@ def get_name_addr(value):
17691771
name_addr = NameAddr()
17701772
# Both the optional display name and the angle-addr can start with cfws.
17711773
leader = None
1774+
if not value:
1775+
raise errors.HeaderParseError(
1776+
"expected name-addr but found '{}'".format(value))
17721777
if value[0] in CFWS_LEADER:
17731778
leader, value = get_cfws(value)
17741779
if not value:

Lib/test/test_email/test__header_value_parser.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,10 @@ def test_get_quoted_string_header_ends_in_qcontent(self):
801801
self.assertEqual(qs.content, 'bob')
802802
self.assertEqual(qs.quoted_value, ' "bob"')
803803

804+
def test_get_quoted_string_cfws_only_raises(self):
805+
with self.assertRaises(errors.HeaderParseError):
806+
parser.get_quoted_string(' (foo) ')
807+
804808
def test_get_quoted_string_no_quoted_string(self):
805809
with self.assertRaises(errors.HeaderParseError):
806810
parser.get_quoted_string(' (ab) xyz')
@@ -1135,6 +1139,10 @@ def test_get_local_part_complex_obsolete_invalid(self):
11351139
'@python.org')
11361140
self.assertEqual(local_part.local_part, 'Fred.A.Johnson and dogs')
11371141

1142+
def test_get_local_part_empty_raises(self):
1143+
with self.assertRaises(errors.HeaderParseError):
1144+
parser.get_local_part('')
1145+
11381146
def test_get_local_part_no_part_raises(self):
11391147
with self.assertRaises(errors.HeaderParseError):
11401148
parser.get_local_part(' (foo) ')
@@ -1387,6 +1395,10 @@ def test_get_domain_obsolete(self):
13871395
'')
13881396
self.assertEqual(domain.domain, 'example.com')
13891397

1398+
def test_get_domain_empty_raises(self):
1399+
with self.assertRaises(errors.HeaderParseError):
1400+
parser.get_domain("")
1401+
13901402
def test_get_domain_no_non_cfws_raises(self):
13911403
with self.assertRaises(errors.HeaderParseError):
13921404
parser.get_domain(" (foo)\t")
@@ -1512,6 +1524,10 @@ def test_get_obs_route_no_route_before_end_raises(self):
15121524
with self.assertRaises(errors.HeaderParseError):
15131525
parser.get_obs_route('(foo) @example.com,')
15141526

1527+
def test_get_obs_route_no_route_before_end_raises2(self):
1528+
with self.assertRaises(errors.HeaderParseError):
1529+
parser.get_obs_route('(foo) @example.com, (foo) ')
1530+
15151531
def test_get_obs_route_no_route_before_special_raises(self):
15161532
with self.assertRaises(errors.HeaderParseError):
15171533
parser.get_obs_route('(foo) [abc],')
@@ -1520,6 +1536,14 @@ def test_get_obs_route_no_route_before_special_raises2(self):
15201536
with self.assertRaises(errors.HeaderParseError):
15211537
parser.get_obs_route('(foo) @example.com [abc],')
15221538

1539+
def test_get_obs_route_no_domain_after_at_raises(self):
1540+
with self.assertRaises(errors.HeaderParseError):
1541+
parser.get_obs_route('@')
1542+
1543+
def test_get_obs_route_no_domain_after_at_raises2(self):
1544+
with self.assertRaises(errors.HeaderParseError):
1545+
parser.get_obs_route('@example.com, @')
1546+
15231547
# get_angle_addr
15241548

15251549
def test_get_angle_addr_simple(self):
@@ -1646,6 +1670,14 @@ def test_get_angle_addr_ends_at_special(self):
16461670
self.assertIsNone(angle_addr.route)
16471671
self.assertEqual(angle_addr.addr_spec, '[email protected]')
16481672

1673+
def test_get_angle_addr_empty_raise(self):
1674+
with self.assertRaises(errors.HeaderParseError):
1675+
parser.get_angle_addr('')
1676+
1677+
def test_get_angle_addr_left_angle_only_raise(self):
1678+
with self.assertRaises(errors.HeaderParseError):
1679+
parser.get_angle_addr('<')
1680+
16491681
def test_get_angle_addr_no_angle_raise(self):
16501682
with self.assertRaises(errors.HeaderParseError):
16511683
parser.get_angle_addr('(foo) ')
@@ -1857,6 +1889,10 @@ def test_get_name_addr_ends_at_special(self):
18571889
self.assertIsNone(name_addr.route)
18581890
self.assertEqual(name_addr.addr_spec, '[email protected]')
18591891

1892+
def test_get_name_addr_empty_raises(self):
1893+
with self.assertRaises(errors.HeaderParseError):
1894+
parser.get_name_addr('')
1895+
18601896
def test_get_name_addr_no_content_raises(self):
18611897
with self.assertRaises(errors.HeaderParseError):
18621898
parser.get_name_addr(' (foo) ')
@@ -2732,6 +2768,10 @@ def test_get_msg_id_empty_id_right(self):
27322768
with self.assertRaises(errors.HeaderParseError):
27332769
parser.get_msg_id("<simplelocal@>")
27342770

2771+
def test_get_msg_id_no_id_right(self):
2772+
with self.assertRaises(errors.HeaderParseError):
2773+
parser.get_msg_id("<simplelocal@")
2774+
27352775
def test_get_msg_id_with_brackets(self):
27362776
# Microsof Outlook generates non-standard one-off addresses:
27372777
# https://learn.microsoft.com/en-us/office/client-developer/outlook/mapi/one-off-addresses
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix several IndexError when parse emails with truncated Message-ID, address, routes, etc, e.g. ``example@``.

0 commit comments

Comments
 (0)