Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Lib/email/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ def formataddr(pair, charset='utf-8', *, strict=True):
from email.charset import Charset
charset = Charset(charset)
encoded_name = charset.header_encode(name)
if specialsre.search(encoded_name):
# Not RFC 2047-encoded, so quote it like the ASCII branch
# below to keep specials from leaking into the header.
encoded_name = '"' + escapesre.sub(r'\\\g<0>', encoded_name) + '"'
return "%s <%s>" % (encoded_name, address)
else:
quotes = ''
Expand Down
26 changes: 26 additions & 0 deletions Lib/test/test_email/test_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -3308,6 +3308,32 @@ def test_name_with_dot(self):
# formataddr() quotes the name if there's a dot in it
self.assertEqual(utils.formataddr((a, b)), y)

def test_formataddr_non_ascii_name_with_specials(self):
# gh-100900: when the charset is configured not to RFC 2047-encode the
# display name, formataddr() must still quote a non-ASCII name that
# contains specials, the same way its ASCII branch does, so the result
# round-trips through getaddresses() instead of splitting on the comma.
from email import charset as _charset
sentinel = object()
previous = _charset.CHARSETS.get('utf-8', sentinel)
def restore():
if previous is sentinel:
_charset.CHARSETS.pop('utf-8', None)
else:
_charset.CHARSETS['utf-8'] = previous
self.addCleanup(restore)
_charset.add_charset('utf-8', None) # do not RFC 2047-encode the name
formatted = utils.formataddr(('Fôo, Bar', 'a@b.com'))
self.assertEqual(formatted, '"Fôo, Bar" <a@b.com>')
self.assertEqual(utils.getaddresses([formatted]),
[('Fôo, Bar', 'a@b.com')])
# A non-ASCII name without specials is still emitted unquoted.
self.assertEqual(utils.formataddr(('Fôo Bar', 'a@b.com')),
'Fôo Bar <a@b.com>')
# The ASCII branch is unchanged.
self.assertEqual(utils.formataddr(('Foo, Bar', 'a@b.com')),
'"Foo, Bar" <a@b.com>')

def test_parseaddr_preserves_quoted_pairs_in_addresses(self):
# issue 10005. Note that in the third test the second pair of
# backslashes is not actually a quoted pair because it is not inside a
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix :func:`email.utils.formataddr` failing to quote a non-ASCII display name
that contains special characters when the charset is configured not to
RFC 2047-encode it, which broke round-tripping through
:func:`~email.utils.getaddresses`. Patch by Nikolaus Schuetz.
Loading