Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a31dea1

Browse filesBrowse files
authored
gh-106669: Revert "gh-102988: Detect email address parsing errors ... (#105127)" (#106733)
This reverts commit 18dfbd0. Adds a regression test from the issue. See #106669.
1 parent d81b4f8 commit a31dea1
Copy full SHA for a31dea1

File tree

5 files changed

+30
-171
lines changed
Filter options

5 files changed

+30
-171
lines changed

‎Doc/library/email.utils.rst

Copy file name to clipboardExpand all lines: Doc/library/email.utils.rst
+1-25Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,6 @@ of the new API.
6565
*email address* parts. Returns a tuple of that information, unless the parse
6666
fails, in which case a 2-tuple of ``('', '')`` is returned.
6767

68-
.. versionchanged:: 3.12
69-
For security reasons, addresses that were ambiguous and could parse into
70-
multiple different addresses now cause ``('', '')`` to be returned
71-
instead of only one of the *potential* addresses.
72-
7368

7469
.. function:: formataddr(pair, charset='utf-8')
7570

@@ -92,7 +87,7 @@ of the new API.
9287
This method returns a list of 2-tuples of the form returned by ``parseaddr()``.
9388
*fieldvalues* is a sequence of header field values as might be returned by
9489
:meth:`Message.get_all <email.message.Message.get_all>`. Here's a simple
95-
example that gets all the recipients of a message:
90+
example that gets all the recipients of a message::
9691

9792
from email.utils import getaddresses
9893

@@ -102,25 +97,6 @@ of the new API.
10297
resent_ccs = msg.get_all('resent-cc', [])
10398
all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs)
10499

105-
When parsing fails for a single fieldvalue, a 2-tuple of ``('', '')``
106-
is returned in its place. Other errors in parsing the list of
107-
addresses such as a fieldvalue seemingly parsing into multiple
108-
addresses may result in a list containing a single empty 2-tuple
109-
``[('', '')]`` being returned rather than returning potentially
110-
invalid output.
111-
112-
Example malformed input parsing:
113-
114-
.. doctest::
115-
116-
>>> from email.utils import getaddresses
117-
>>> getaddresses(['alice@example.com <bob@example.com>', 'me@example.com'])
118-
[('', '')]
119-
120-
.. versionchanged:: 3.12
121-
The 2-tuple of ``('', '')`` in the returned values when parsing
122-
fails were added as to address a security issue.
123-
124100

125101
.. function:: parsedate(date)
126102

‎Doc/whatsnew/3.12.rst

Copy file name to clipboardExpand all lines: Doc/whatsnew/3.12.rst
-8Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -570,14 +570,6 @@ dis
570570
:data:`~dis.hasarg` collection instead.
571571
(Contributed by Irit Katriel in :gh:`94216`.)
572572

573-
email
574-
-----
575-
576-
* :func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now return
577-
``('', '')`` 2-tuples in more situations where invalid email addresses are
578-
encountered instead of potentially inaccurate values.
579-
(Contributed by Thomas Dwyer for :gh:`102988` to ameliorate CVE-2023-27043.)
580-
581573
fractions
582574
---------
583575

‎Lib/email/utils.py

Copy file name to clipboardExpand all lines: Lib/email/utils.py
+6-57Lines changed: 6 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -106,54 +106,12 @@ 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-
131109

132110
def getaddresses(fieldvalues):
133-
"""Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue.
134-
135-
When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in
136-
its place.
137-
138-
If the resulting list of parsed address is not the same as the number of
139-
fieldvalues in the input list a parsing error has occurred. A list
140-
containing a single empty 2-tuple [('', '')] is returned in its place.
141-
This is done to avoid invalid output.
142-
"""
143-
fieldvalues = [str(v) for v in fieldvalues]
144-
fieldvalues = _pre_parse_validation(fieldvalues)
145-
all = COMMASPACE.join(v for v in fieldvalues)
111+
"""Return a list of (REALNAME, EMAIL) for each fieldvalue."""
112+
all = COMMASPACE.join(str(v) for v in fieldvalues)
146113
a = _AddressList(all)
147-
result = _post_parse_validation(a.addresslist)
148-
149-
n = 0
150-
for v in fieldvalues:
151-
n += v.count(',') + 1
152-
153-
if len(result) != n:
154-
return [('', '')]
155-
156-
return result
114+
return a.addresslist
157115

158116

159117
def _format_timetuple_and_zone(timetuple, zone):
@@ -254,18 +212,9 @@ def parseaddr(addr):
254212
Return a tuple of realname and email address, unless the parse fails, in
255213
which case return a 2-tuple of ('', '').
256214
"""
257-
if isinstance(addr, list):
258-
addr = addr[0]
259-
260-
if not isinstance(addr, str):
261-
return ('', '')
262-
263-
addr = _pre_parse_validation([addr])[0]
264-
addrs = _post_parse_validation(_AddressList(addr).addresslist)
265-
266-
if not addrs or len(addrs) > 1:
267-
return ('', '')
268-
215+
addrs = _AddressList(addr).addresslist
216+
if not addrs:
217+
return '', ''
269218
return addrs[0]
270219

271220

‎Lib/test/test_email/test_email.py

Copy file name to clipboardExpand all lines: Lib/test/test_email/test_email.py
+19-77Lines changed: 19 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -3319,90 +3319,32 @@ def test_getaddresses(self):
33193319
[('Al Person', 'aperson@dom.ain'),
33203320
('Bud Person', 'bperson@dom.ain')])
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(['alice@example.org(<bob@example.com>']),
3326-
[('', '')])
3327-
eq(utils.getaddresses(['alice@example.org)<bob@example.com>']),
3328-
[('', '')])
3329-
eq(utils.getaddresses(['alice@example.org<<bob@example.com>']),
3330-
[('', '')])
3331-
eq(utils.getaddresses(['alice@example.org><bob@example.com>']),
3332-
[('', '')])
3333-
eq(utils.getaddresses(['alice@example.org@<bob@example.com>']),
3334-
[('', '')])
3335-
eq(utils.getaddresses(['alice@example.org,<bob@example.com>']),
3336-
[('', 'alice@example.org'), ('', 'bob@example.com')])
3337-
eq(utils.getaddresses(['alice@example.org;<bob@example.com>']),
3338-
[('', '')])
3339-
eq(utils.getaddresses(['alice@example.org:<bob@example.com>']),
3340-
[('', '')])
3341-
eq(utils.getaddresses(['alice@example.org.<bob@example.com>']),
3342-
[('', '')])
3343-
eq(utils.getaddresses(['alice@example.org"<bob@example.com>']),
3344-
[('', '')])
3345-
eq(utils.getaddresses(['alice@example.org[<bob@example.com>']),
3346-
[('', '')])
3347-
eq(utils.getaddresses(['alice@example.org]<bob@example.com>']),
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(['alice@example.org(<bob@example.com>']),
3354-
('', ''))
3355-
eq(utils.parseaddr(['alice@example.org)<bob@example.com>']),
3356-
('', ''))
3357-
eq(utils.parseaddr(['alice@example.org<<bob@example.com>']),
3358-
('', ''))
3359-
eq(utils.parseaddr(['alice@example.org><bob@example.com>']),
3360-
('', ''))
3361-
eq(utils.parseaddr(['alice@example.org@<bob@example.com>']),
3362-
('', ''))
3363-
eq(utils.parseaddr(['alice@example.org,<bob@example.com>']),
3364-
('', ''))
3365-
eq(utils.parseaddr(['alice@example.org;<bob@example.com>']),
3366-
('', ''))
3367-
eq(utils.parseaddr(['alice@example.org:<bob@example.com>']),
3368-
('', ''))
3369-
eq(utils.parseaddr(['alice@example.org.<bob@example.com>']),
3370-
('', ''))
3371-
eq(utils.parseaddr(['alice@example.org"<bob@example.com>']),
3372-
('', ''))
3373-
eq(utils.parseaddr(['alice@example.org[<bob@example.com>']),
3374-
('', ''))
3375-
eq(utils.parseaddr(['alice@example.org]<bob@example.com>']),
3376-
('', ''))
3322+
def test_getaddresses_comma_in_name(self):
3323+
"""GH-106669 regression test."""
3324+
self.assertEqual(
3325+
utils.getaddresses(
3326+
[
3327+
'"Bud, Person" <bperson@dom.ain>',
3328+
'aperson@dom.ain (Al Person)',
3329+
'"Mariusz Felisiak" <to@example.com>',
3330+
]
3331+
),
3332+
[
3333+
('Bud, Person', 'bperson@dom.ain'),
3334+
('Al Person', 'aperson@dom.ain'),
3335+
('Mariusz Felisiak', 'to@example.com'),
3336+
],
3337+
)
33773338

33783339
def test_getaddresses_nasty(self):
33793340
eq = self.assertEqual
33803341
eq(utils.getaddresses(['foo: ;']), [('', '')])
3381-
eq(utils.getaddresses(['[]*-- =~$']), [('', '')])
3342+
eq(utils.getaddresses(
3343+
['[]*-- =~$']),
3344+
[('', ''), ('', ''), ('', '*--')])
33823345
eq(utils.getaddresses(
33833346
['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
33843347
[('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
3385-
eq(utils.getaddresses(
3386-
[r'Pete(A nice \) chap) <pete(his account)@silly.test(his host)>']),
3387-
[('Pete (A nice ) chap his account his host)', 'pete@silly.test')])
3388-
eq(utils.getaddresses(
3389-
['(Empty list)(start)Undisclosed recipients :(nobody(I know))']),
3390-
[('', '')])
3391-
eq(utils.getaddresses(
3392-
['Mary <@machine.tld:mary@example.net>, , jdoe@test . example']),
3393-
[('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')])
3394-
eq(utils.getaddresses(
3395-
['John Doe <jdoe@machine(comment). example>']),
3396-
[('John Doe (comment)', 'jdoe@machine.example')])
3397-
eq(utils.getaddresses(
3398-
['"Mary Smith: Personal Account" <smith@home.example>']),
3399-
[('Mary Smith: Personal Account', 'smith@home.example')])
3400-
eq(utils.getaddresses(
3401-
['Undisclosed recipients:;']),
3402-
[('', '')])
3403-
eq(utils.getaddresses(
3404-
[r'<boss@nil.test>, "Giant; \"Big\" Box" <bob@example.net>']),
3405-
[('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')])
34063348

34073349
def test_getaddresses_embedded_comment(self):
34083350
"""Test proper handling of a nested comment"""
+4-4Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CVE-2023-27043: Prevent :func:`email.utils.parseaddr`
2-
and :func:`email.utils.getaddresses` from returning the realname portion of an
3-
invalid RFC2822 email header in the email address portion of the 2-tuple
4-
returned after being parsed by :class:`email._parseaddr.AddressList`.
1+
Reverted the :mod:`email.utils` security improvement change released in
2+
3.12beta4 that unintentionally caused :mod:`email.utils.getaddresses` to fail
3+
to parse email addresses with a comma in the quoted name field.
4+
See :gh:`106669`.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.