diff --git a/Lib/email/utils.py b/Lib/email/utils.py index d4824dc3601b2d..4aff03d186643e 100644 --- a/Lib/email/utils.py +++ b/Lib/email/utils.py @@ -357,8 +357,16 @@ def parseaddr(addr, *, strict=True): def unquote(str): """Remove quotes from a string.""" if len(str) > 1: - if str.startswith('"') and str.endswith('"'): - return str[1:-1].replace('\\\\', '\\').replace('\\"', '"') + if str.startswith('"'): + pos = 1 + while pos < len(str): + if str[pos] == '\\' and pos + 1 < len(str): + pos += 2 + elif str[pos] == '"': + content = str[1:pos] + return re.sub(r'\\(.)', r'\1', content) + else: + pos += 1 if str.startswith('<') and str.endswith('>'): return str[1:-1] return str diff --git a/Lib/test/test_email/test_utils.py b/Lib/test/test_email/test_utils.py index c9d09098b502f9..f1982722d5ebaf 100644 --- a/Lib/test/test_email/test_utils.py +++ b/Lib/test/test_email/test_utils.py @@ -186,5 +186,35 @@ def test_formatdate_with_localtime(self): string = utils.formatdate(timeval, localtime=True) self.assertEqual(string, 'Thu, 01 Dec 2011 18:00:00 +0300') +class UnquoteTests(unittest.TestCase): + + def test_unquote_basic(self): + self.assertEqual(utils.unquote('"value"'), 'value') + + def test_unquote_with_trailing_garbage(self): + self.assertEqual(utils.unquote('"bound"\n\tX-Priority: 3'), 'bound') + + def test_unquote_with_escaped_quote(self): + self.assertEqual(utils.unquote(r'"val\"ue"'), 'val"ue') + + def test_unquote_with_escaped_backslash(self): + self.assertEqual(utils.unquote(r'"val\\ue"'), r'val\ue') + + def test_unquote_angle_brackets(self): + self.assertEqual(utils.unquote(''), 'value') + + def test_unquote_no_quotes(self): + self.assertEqual(utils.unquote('value'), 'value') + + def test_unquote_single_char(self): + self.assertEqual(utils.unquote('v'), 'v') + + def test_unquote_empty_quoted(self): + self.assertEqual(utils.unquote('""'), '') + + def test_unquote_mixed_escapes(self): + self.assertEqual(utils.unquote(r'"a\\b\"c"'), r'a\b"c') + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2025-11-18-09-59-41.gh-issue-39128.xK7mPq.rst b/Misc/NEWS.d/next/Library/2025-11-18-09-59-41.gh-issue-39128.xK7mPq.rst new file mode 100644 index 00000000000000..78bba451b613fa --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-11-18-09-59-41.gh-issue-39128.xK7mPq.rst @@ -0,0 +1,3 @@ +Fix :func:`email.utils.unquote` to properly handle quoted strings with +trailing garbage by extracting only content between quotes and using +single-pass unescaping for RFC 2822 compliance. Patched by Shamil Abdulaev.