Skip to content

Commit 3fa13e8

Browse files
committed
gh-39128: Fix email.utils.unquote() parameter parsing
1 parent 630cd37 commit 3fa13e8

3 files changed

Lines changed: 43 additions & 2 deletions

File tree

Lib/email/utils.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,8 +357,16 @@ def parseaddr(addr, *, strict=True):
357357
def unquote(str):
358358
"""Remove quotes from a string."""
359359
if len(str) > 1:
360-
if str.startswith('"') and str.endswith('"'):
361-
return str[1:-1].replace('\\\\', '\\').replace('\\"', '"')
360+
if str.startswith('"'):
361+
pos = 1
362+
while pos < len(str):
363+
if str[pos] == '\\' and pos + 1 < len(str):
364+
pos += 2
365+
elif str[pos] == '"':
366+
content = str[1:pos]
367+
return re.sub(r'\\(.)', r'\1', content)
368+
else:
369+
pos += 1
362370
if str.startswith('<') and str.endswith('>'):
363371
return str[1:-1]
364372
return str

Lib/test/test_email/test_utils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,5 +186,35 @@ def test_formatdate_with_localtime(self):
186186
string = utils.formatdate(timeval, localtime=True)
187187
self.assertEqual(string, 'Thu, 01 Dec 2011 18:00:00 +0300')
188188

189+
class UnquoteTests(unittest.TestCase):
190+
191+
def test_unquote_basic(self):
192+
self.assertEqual(utils.unquote('"value"'), 'value')
193+
194+
def test_unquote_with_trailing_garbage(self):
195+
self.assertEqual(utils.unquote('"bound"\n\tX-Priority: 3'), 'bound')
196+
197+
def test_unquote_with_escaped_quote(self):
198+
self.assertEqual(utils.unquote(r'"val\"ue"'), 'val"ue')
199+
200+
def test_unquote_with_escaped_backslash(self):
201+
self.assertEqual(utils.unquote(r'"val\\ue"'), r'val\ue')
202+
203+
def test_unquote_angle_brackets(self):
204+
self.assertEqual(utils.unquote('<value>'), 'value')
205+
206+
def test_unquote_no_quotes(self):
207+
self.assertEqual(utils.unquote('value'), 'value')
208+
209+
def test_unquote_single_char(self):
210+
self.assertEqual(utils.unquote('v'), 'v')
211+
212+
def test_unquote_empty_quoted(self):
213+
self.assertEqual(utils.unquote('""'), '')
214+
215+
def test_unquote_mixed_escapes(self):
216+
self.assertEqual(utils.unquote(r'"a\\b\"c"'), r'a\b"c')
217+
218+
189219
if __name__ == '__main__':
190220
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`email.utils.unquote` to properly handle quoted strings with
2+
trailing garbage by extracting only content between quotes and using
3+
single-pass unescaping for RFC 2822 compliance. Patched by Shamil Abdulaev.

0 commit comments

Comments
 (0)