diff --git a/Lib/Cookie.py b/Lib/Cookie.py index 108236371ee333b..a6ba4a92ed7227c 100644 --- a/Lib/Cookie.py +++ b/Lib/Cookie.py @@ -323,9 +323,13 @@ def _quote(str, LegalChars=_LegalChars, return '"' + _nulljoin( map(_Translator.get, str, str) ) + '"' # end _quote +_unquote_sub = re.compile(r'\\(?:([0-3][0-7][0-7])|(.))').sub -_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]") -_QuotePatt = re.compile(r"[\\].") +def _unquote_replace(m): + if m.group(1): + return chr(int(m.group(1), 8)) + else: + return m.group(2) def _unquote(str): # If there aren't any doublequotes, @@ -345,28 +349,7 @@ def _unquote(str): # \012 --> \n # \" --> " # - i = 0 - n = len(str) - res = [] - while 0 <= i < n: - Omatch = _OctalPatt.search(str, i) - Qmatch = _QuotePatt.search(str, i) - if not Omatch and not Qmatch: # Neither matched - res.append(str[i:]) - break - # else: - j = k = -1 - if Omatch: j = Omatch.start(0) - if Qmatch: k = Qmatch.start(0) - if Qmatch and ( not Omatch or k < j ): # QuotePatt matched - res.append(str[i:k]) - res.append(str[k+1]) - i = k+2 - else: # OctalPatt matched - res.append(str[i:j]) - res.append( chr( int(str[j+1:j+4], 8) ) ) - i = j+4 - return _nulljoin(res) + return _unquote_sub(_unquote_replace, str) # end _unquote # The _getdate() routine is used to set the expiration time in diff --git a/Lib/test/test_cookie.py b/Lib/test/test_cookie.py index 404190123fae998..efd2ed3cea49970 100644 --- a/Lib/test/test_cookie.py +++ b/Lib/test/test_cookie.py @@ -1,6 +1,7 @@ # Simple test suite for Cookie.py from test.test_support import run_unittest, run_doctest, check_warnings +from test import support import unittest import Cookie import pickle @@ -156,6 +157,44 @@ def test_invalid_cookies(self): self.assertEqual(dict(C), {}) self.assertEqual(C.output(), '') + def test_unquote(self): + cases = [ + (r'a="b=\""', 'b="'), + (r'a="b=\\"', 'b=\\'), + (r'a="b=\="', 'b=='), + (r'a="b=\n"', 'b=n'), + (r'a="b=\042"', 'b="'), + (r'a="b=\134"', 'b=\\'), + (r'a="b=\377"', 'b=\xff'), + (r'a="b=\400"', 'b=400'), + (r'a="b=\42"', 'b=42'), + (r'a="b=\\042"', 'b=\\042'), + (r'a="b=\\134"', 'b=\\134'), + (r'a="b=\\\""', 'b=\\"'), + (r'a="b=\\\042"', 'b=\\"'), + (r'a="b=\134\""', 'b=\\"'), + (r'a="b=\134\042"', 'b=\\"'), + ] + for encoded, decoded in cases: + # with self.subTest(encoded): + C = Cookie.SimpleCookie() + C.load(encoded) + self.assertEqual(C['a'].value, decoded) + + @support.requires_resource('cpu') + def test_unquote_large(self): + n = 10**6 + for encoded in r'\\', r'\134': + # with self.subTest(encoded): + data = 'a="b=' + encoded*n + ';"' + C = Cookie.SimpleCookie() + C.load(data) + value = C['a'].value + self.assertEqual(value[:3], 'b=\\') + self.assertEqual(value[-2:], '\\;') + self.assertEqual(len(value), n + 3) + + def test_pickle(self): rawdata = 'Customer="WILE_E_COYOTE"; Path=/acme; Version=1' expected_output = 'Set-Cookie: %s' % rawdata diff --git a/Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst b/Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst new file mode 100644 index 000000000000000..6a234561fe31a3c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst @@ -0,0 +1 @@ +Fix quadratic complexity in parsing ``"``-quoted cookie values with backslashes by :mod:`http.cookies`.