Skip to content

Commit 81098ed

Browse files
committed
pythongh-102988: Detect email address parsing errors and return empty tuple to indicate the parsing error (old API)
1 parent 7be667d commit 81098ed

File tree

2 files changed

+132
-9
lines changed

2 files changed

+132
-9
lines changed

Lib/email/utils.py

+54-6
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,51 @@ def formataddr(pair, charset='utf-8'):
106106
return address
107107

108108

109+
def _pre_parse_validation(email_header_fields):
110+
accepted_values = []
111+
for v in email_header_fields:
112+
s = v.replace('\\(', '').replace('\\)', '')
113+
if s.count('(') != s.count(')'):
114+
v = "('', '')"
115+
accepted_values.append(v)
116+
117+
return accepted_values
118+
119+
120+
def _post_parse_validation(parsed_email_header_tuples):
121+
accepted_values = []
122+
# The parser would have parsed a correctly formatted domain-literal
123+
# The existence of an [ after parsing indicates a parsing failure
124+
for v in parsed_email_header_tuples:
125+
if '[' in v[1]:
126+
v = ('', '')
127+
accepted_values.append(v)
128+
129+
return accepted_values
130+
109131

110132
def getaddresses(fieldvalues):
111-
"""Return a list of (REALNAME, EMAIL) for each fieldvalue."""
112-
all = COMMASPACE.join(str(v) for v in fieldvalues)
133+
"""Return a list of (REALNAME, EMAIL) for each fieldvalue,
134+
unless the parse fails, in which case return a 2-tuple of ('', '')
135+
will be returned in it's place.
136+
137+
If the resulting list is greater than number of items in the fieldvalues
138+
list, a list containing a single empty 2-tuple [('', '')] will be returned.
139+
"""
140+
fieldvalues = [str(v) for v in fieldvalues]
141+
fieldvalues = _pre_parse_validation(fieldvalues)
142+
all = COMMASPACE.join(v for v in fieldvalues)
113143
a = _AddressList(all)
114-
return a.addresslist
144+
result = _post_parse_validation(a.addresslist)
145+
146+
n = 0
147+
for v in fieldvalues:
148+
n += v.count(',') + 1
149+
150+
if len(result) != n:
151+
return [('', '')]
152+
153+
return result
115154

116155

117156
def _format_timetuple_and_zone(timetuple, zone):
@@ -212,9 +251,18 @@ def parseaddr(addr):
212251
Return a tuple of realname and email address, unless the parse fails, in
213252
which case return a 2-tuple of ('', '').
214253
"""
215-
addrs = _AddressList(addr).addresslist
216-
if not addrs:
217-
return '', ''
254+
if isinstance(addr, list):
255+
addr = addr[0]
256+
257+
if not isinstance(addr, str):
258+
return ('', '')
259+
260+
addr = _pre_parse_validation([addr])[0]
261+
addrs = _post_parse_validation(_AddressList(addr).addresslist)
262+
263+
if not addrs or len(addrs) > 1:
264+
return ('', '')
265+
218266
return addrs[0]
219267

220268

Lib/test/test_email/test_email.py

+78-3
Original file line numberDiff line numberDiff line change
@@ -3319,15 +3319,90 @@ def test_getaddresses(self):
33193319
[('Al Person', '[email protected]'),
33203320
('Bud Person', '[email protected]')])
33213321

3322+
def test_getaddresses_parsing_errors(self):
3323+
"""Test for parsing errors from CVE-2023-27043"""
3324+
eq = self.assertEqual
3325+
eq(utils.getaddresses(['[email protected](<[email protected]>']),
3326+
[('', '')])
3327+
eq(utils.getaddresses(['[email protected])<[email protected]>']),
3328+
[('', '')])
3329+
eq(utils.getaddresses(['[email protected]<<[email protected]>']),
3330+
[('', '')])
3331+
eq(utils.getaddresses(['[email protected]><[email protected]>']),
3332+
[('', '')])
3333+
eq(utils.getaddresses(['[email protected]@<[email protected]>']),
3334+
[('', '')])
3335+
eq(utils.getaddresses(['[email protected],<[email protected]>']),
3336+
3337+
eq(utils.getaddresses(['[email protected];<[email protected]>']),
3338+
[('', '')])
3339+
eq(utils.getaddresses(['[email protected]:<[email protected]>']),
3340+
[('', '')])
3341+
eq(utils.getaddresses(['[email protected].<[email protected]>']),
3342+
[('', '')])
3343+
eq(utils.getaddresses(['[email protected]"<[email protected]>']),
3344+
[('', '')])
3345+
eq(utils.getaddresses(['[email protected][<[email protected]>']),
3346+
[('', '')])
3347+
eq(utils.getaddresses(['[email protected]]<[email protected]>']),
3348+
[('', '')])
3349+
3350+
def test_parseaddr_parsing_errors(self):
3351+
"""Test for parsing errors from CVE-2023-27043"""
3352+
eq = self.assertEqual
3353+
eq(utils.parseaddr(['[email protected](<[email protected]>']),
3354+
('', ''))
3355+
eq(utils.parseaddr(['[email protected])<[email protected]>']),
3356+
('', ''))
3357+
eq(utils.parseaddr(['[email protected]<<[email protected]>']),
3358+
('', ''))
3359+
eq(utils.parseaddr(['[email protected]><[email protected]>']),
3360+
('', ''))
3361+
eq(utils.parseaddr(['[email protected]@<[email protected]>']),
3362+
('', ''))
3363+
eq(utils.parseaddr(['[email protected],<[email protected]>']),
3364+
('', ''))
3365+
eq(utils.parseaddr(['[email protected];<[email protected]>']),
3366+
('', ''))
3367+
eq(utils.parseaddr(['[email protected]:<[email protected]>']),
3368+
('', ''))
3369+
eq(utils.parseaddr(['[email protected].<[email protected]>']),
3370+
('', ''))
3371+
eq(utils.parseaddr(['[email protected]"<[email protected]>']),
3372+
('', ''))
3373+
eq(utils.parseaddr(['[email protected][<[email protected]>']),
3374+
('', ''))
3375+
eq(utils.parseaddr(['[email protected]]<[email protected]>']),
3376+
('', ''))
3377+
33223378
def test_getaddresses_nasty(self):
33233379
eq = self.assertEqual
33243380
eq(utils.getaddresses(['foo: ;']), [('', '')])
3325-
eq(utils.getaddresses(
3326-
['[]*-- =~$']),
3327-
[('', ''), ('', ''), ('', '*--')])
3381+
eq(utils.getaddresses(['[]*-- =~$']), [('', '')])
33283382
eq(utils.getaddresses(
33293383
['foo: ;', '"Jason R. Mastaler" <[email protected]>']),
33303384
[('', ''), ('Jason R. Mastaler', '[email protected]')])
3385+
eq(utils.getaddresses(
3386+
['Pete(A nice \) chap) <pete(his account)@silly.test(his host)>']),
3387+
[('Pete (A nice ) chap his account his host)', '[email protected]')])
3388+
eq(utils.getaddresses(
3389+
['(Empty list)(start)Undisclosed recipients :(nobody(I know))']),
3390+
[('', '')])
3391+
eq(utils.getaddresses(
3392+
['Mary <@machine.tld:[email protected]>, , jdoe@test . example']),
3393+
[('Mary', '[email protected]'), ('', ''), ('', '[email protected]')])
3394+
eq(utils.getaddresses(
3395+
['John Doe <jdoe@machine(comment). example>']),
3396+
[('John Doe (comment)', '[email protected]')])
3397+
eq(utils.getaddresses(
3398+
['"Mary Smith: Personal Account" <[email protected]>']),
3399+
[('Mary Smith: Personal Account', '[email protected]')])
3400+
eq(utils.getaddresses(
3401+
['Undisclosed recipients:;']),
3402+
[('', '')])
3403+
eq(utils.getaddresses(
3404+
['<[email protected]>, "Giant; \"Big\" Box" <[email protected]>']),
3405+
[('', '[email protected]'), ('Giant; Big Box', '[email protected]')])
33313406

33323407
def test_getaddresses_embedded_comment(self):
33333408
"""Test proper handling of a nested comment"""

0 commit comments

Comments
 (0)