diff --git a/Include/patchlevel.h b/Include/patchlevel.h index 95c1ac9f755477..4b830a2abea6a3 100644 --- a/Include/patchlevel.h +++ b/Include/patchlevel.h @@ -27,7 +27,7 @@ #define PY_RELEASE_SERIAL 0 /* Version as a string */ -#define PY_VERSION "2.7.18.12" +#define PY_VERSION "2.7.18.13" /*--end constants--*/ /* Subversion Revision number of this file (not of the repository). Empty diff --git a/Lib/email/test/test_email.py b/Lib/email/test/test_email.py index 801b31cc05985c..024caa06bb375b 100644 --- a/Lib/email/test/test_email.py +++ b/Lib/email/test/test_email.py @@ -2320,6 +2320,25 @@ def test_parseaddr_multiple_domains(self): ('', '') ) + def test_parseaddr_unicode(self): + """Test parseaddr with unicode strings""" + test_cases = [ + (u'user@example.com', ('', u'user@example.com')), + (u'Test User ', (u'Test User', u'user@example.com')), + (u'"Test User" ', (u'Test User', u'user@example.com')), + ] + + for addr, expected in test_cases: + result = Utils.parseaddr(addr, strict=True) + self.assertEqual(result, expected) + if result[0]: + self.assertIsInstance(result[0], unicode) + if result[1]: + self.assertIsInstance(result[1], unicode) + + result_non_strict = Utils.parseaddr(addr, strict=False) + self.assertEqual(result, result_non_strict) + def test_noquote_dump(self): self.assertEqual( Utils.formataddr(('A Silly Person', 'person@dom.ain')), @@ -2432,6 +2451,24 @@ def test_getaddresses_embedded_comment(self): addrs = Utils.getaddresses(['User ((nested comment)) ']) eq(addrs[0][1], 'foo@bar.com') + def test_getaddresses_unicode(self): + """Test getaddresses with unicode strings""" + test_cases = [ + ([u'user@example.com'], [('', u'user@example.com')]), + ([u'Test User '], [(u'Test User', u'user@example.com')]), + ([u'"Test User" '], [(u'Test User', u'user@example.com')]), + ([u'user1@example.com', u'user2@example.com'], [('', u'user1@example.com'), ('', u'user2@example.com')]), + ] + + for addrs, expected in test_cases: + result = Utils.getaddresses(addrs) + self.assertEqual(result, expected) + for realname, email in result: + if realname: + self.assertIsInstance(realname, unicode) + if email: + self.assertIsInstance(email, unicode) + def test_make_msgid_collisions(self): # Test make_msgid uniqueness, even with multiple threads class MsgidsThread(Thread): diff --git a/Lib/email/test/test_email_renamed.py b/Lib/email/test/test_email_renamed.py index e3c9af9f7e2be1..f8d6d930c8afb3 100644 --- a/Lib/email/test/test_email_renamed.py +++ b/Lib/email/test/test_email_renamed.py @@ -2199,6 +2199,25 @@ def test_parseaddr_empty(self): self.assertEqual(utils.parseaddr('<>'), ('', '')) self.assertEqual(utils.formataddr(utils.parseaddr('<>')), '') + def test_parseaddr_unicode(self): + """Test parseaddr with unicode strings""" + test_cases = [ + (u'user@example.com', ('', u'user@example.com')), + (u'Test User ', (u'Test User', u'user@example.com')), + (u'"Test User" ', (u'Test User', u'user@example.com')), + ] + + for addr, expected in test_cases: + result = utils.parseaddr(addr, strict=True) + self.assertEqual(result, expected) + if result[0]: + self.assertIsInstance(result[0], unicode) + if result[1]: + self.assertIsInstance(result[1], unicode) + + result_non_strict = utils.parseaddr(addr, strict=False) + self.assertEqual(result, result_non_strict) + def test_noquote_dump(self): self.assertEqual( utils.formataddr(('A Silly Person', 'person@dom.ain')), @@ -2293,7 +2312,25 @@ def test_getaddresses_embedded_comment(self): addrs = utils.getaddresses(['User ((nested comment)) ']) eq(addrs[0][1], 'foo@bar.com') - def test_utils_quote_unquote(self): + def test_getaddresses_unicode(self): + """Test getaddresses with unicode strings""" + test_cases = [ + ([u'user@example.com'], [('', u'user@example.com')]), + ([u'Test User '], [(u'Test User', u'user@example.com')]), + ([u'"Test User" '], [(u'Test User', u'user@example.com')]), + ([u'user1@example.com', u'user2@example.com'], [('', u'user1@example.com'), ('', u'user2@example.com')]), + ] + + for addrs, expected in test_cases: + result = utils.getaddresses(addrs) + self.assertEqual(result, expected) + for realname, email in result: + if realname: + self.assertIsInstance(realname, unicode) + if email: + self.assertIsInstance(email, unicode) + + def test__quote_unquote(self): eq = self.assertEqual msg = Message() msg.add_header('content-disposition', 'attachment', diff --git a/Lib/email/utils.py b/Lib/email/utils.py index 56578ba800abb6..11097ad603f92b 100644 --- a/Lib/email/utils.py +++ b/Lib/email/utils.py @@ -162,8 +162,19 @@ def getaddresses(fieldvalues, strict=True): a = _AddressList(all) return a.addresslist - fieldvalues = [str(v) for v in fieldvalues] - fieldvalues = _pre_parse_validation(fieldvalues) + unicode_flags = [] + converted_values = [] + for v in fieldvalues: + is_unicode = isinstance(v, unicode) + unicode_flags.append(is_unicode) + + if is_unicode: + v = v.encode('utf-8') + elif not isinstance(v, str): + v = str(v) + converted_values.append(v) + + fieldvalues = _pre_parse_validation(converted_values) addr = COMMASPACE.join(fieldvalues) a = _AddressList(addr) result = _post_parse_validation(a.addresslist) @@ -180,7 +191,29 @@ def getaddresses(fieldvalues, strict=True): if len(result) != n: return [('', '')] - return result + final_result = [] + result_idx = 0 + + for i, was_unicode in enumerate(unicode_flags): + if result_idx >= len(result): + break + + realname, email = result[result_idx] + + if was_unicode: + if realname: + realname = realname.decode('utf-8') + if email: + email = email.decode('utf-8') + + final_result.append((realname, email)) + result_idx += 1 + + while result_idx < len(result): + final_result.append(result[result_idx]) + result_idx += 1 + + return final_result def _check_parenthesis(addr): @@ -339,17 +372,31 @@ def parseaddr(addr, strict=True): if isinstance(addr, list): addr = addr[0] - if not isinstance(addr, str): + is_unicode = isinstance(addr, unicode) + + if not isinstance(addr, (str, unicode)): return ('', '') + if is_unicode: + addr = addr.encode('utf-8') + addr = _pre_parse_validation([addr])[0] addrs = _post_parse_validation(_AddressList(addr).addresslist) if not addrs or len(addrs) > 1: return ('', '') - return addrs[0] + result = addrs[0] + + if is_unicode: + realname, email = result + if realname: + realname = realname.decode('utf-8') + if email: + email = email.decode('utf-8') + return (realname, email) + return result # rfc822.unquote() doesn't properly de-backslash-ify in Python pre-2.3. diff --git a/Misc/NEWS.d/2.7.18.13.rst b/Misc/NEWS.d/2.7.18.13.rst new file mode 100644 index 00000000000000..df28689cd5ff1a --- /dev/null +++ b/Misc/NEWS.d/2.7.18.13.rst @@ -0,0 +1,7 @@ +.. bpo: ? +.. date: 2026-03-06 +.. nonce: +.. release date: 2026-03-06 +.. section: Core and Builtins + +Refactor CVE-2023-27043 patch to support Unicode characters