Skip to content
Merged
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
42 changes: 13 additions & 29 deletions Lib/http/cookies.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,13 @@ def _quote(str):
return '"' + str.translate(_Translator) + '"'


_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
_QuotePatt = re.compile(r"[\\].")
_unquote_sub = re.compile(r'\\(?:([0-3][0-7][0-7])|(.))').sub

def _unquote_replace(m):
if m[1]:
return chr(int(m[1], 8))
else:
return m[2]

def _unquote(str):
# If there aren't any doublequotes,
Expand All @@ -205,36 +210,13 @@ def _unquote(str):
# \012 --> \n
# \" --> "
#
i = 0
n = len(str)
res = []
while 0 <= i < n:
o_match = _OctalPatt.search(str, i)
q_match = _QuotePatt.search(str, i)
if not o_match and not q_match: # Neither matched
res.append(str[i:])
break
# else:
j = k = -1
if o_match:
j = o_match.start(0)
if q_match:
k = q_match.start(0)
if q_match and (not o_match 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)

# The _getdate() routine is used to set the expiration time in the cookie's HTTP
# header. By default, _getdate() returns the current time in the appropriate
# "expires" format for a Set-Cookie header. The one optional argument is an
# offset from now, in seconds. For example, an offset of -3600 means "one hour
# ago". The offset may be a floating point number.
# ago". The offset may be a floating-point number.
#

_weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
Expand Down Expand Up @@ -442,9 +424,11 @@ def OutputString(self, attrs=None):
( # Optional group: there may not be a value.
\s*=\s* # Equal Sign
(?P<val> # Start of group 'val'
"(?:[^\\"]|\\.)*" # Any doublequoted string
"(?:[^\\"]|\\.)*" # Any double-quoted string
| # or
\w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT # Special case for "expires" attr
# Special case for "expires" attr
(\w{3,6}day|\w{3}),\s # Day of the week or abbreviated day
[\w\d\s-]{9,11}\s[\d:]{8}\sGMT # Date and time in specific format
| # or
[""" + _LegalValueChars + r"""]* # Any word or empty string
) # End of group 'val'
Expand Down
102 changes: 95 additions & 7 deletions Lib/test/test_http_cookies.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# Simple test suite for http/cookies.py

import copy
from test.support import run_unittest, run_doctest
import unittest
import doctest
from http import cookies
import pickle
from test import support
from test.support.testcase import ExtraAssertions


class CookieTests(unittest.TestCase):
class CookieTests(unittest.TestCase, ExtraAssertions):

def test_basic(self):
cases = [
Expand Down Expand Up @@ -58,6 +60,90 @@ def test_basic(self):
for k, v in sorted(case['dict'].items()):
self.assertEqual(C[k].value, v)

def test_obsolete_rfc850_date_format(self):
# Test cases with different days and dates in obsolete RFC 850 format
test_cases = [
# from RFC 850, change EST to GMT
# https://datatracker.ietf.org/doc/html/rfc850#section-2
{
'data': 'key=value; expires=Saturday, 01-Jan-83 00:00:00 GMT',
'output': 'Saturday, 01-Jan-83 00:00:00 GMT'
},
{
'data': 'key=value; expires=Friday, 19-Nov-82 16:59:30 GMT',
'output': 'Friday, 19-Nov-82 16:59:30 GMT'
},
# from RFC 9110
# https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.7-6
{
'data': 'key=value; expires=Sunday, 06-Nov-94 08:49:37 GMT',
'output': 'Sunday, 06-Nov-94 08:49:37 GMT'
},
# other test cases
{
'data': 'key=value; expires=Wednesday, 09-Nov-94 08:49:37 GMT',
'output': 'Wednesday, 09-Nov-94 08:49:37 GMT'
},
{
'data': 'key=value; expires=Friday, 11-Nov-94 08:49:37 GMT',
'output': 'Friday, 11-Nov-94 08:49:37 GMT'
},
{
'data': 'key=value; expires=Monday, 14-Nov-94 08:49:37 GMT',
'output': 'Monday, 14-Nov-94 08:49:37 GMT'
},
]

for case in test_cases:
with self.subTest(data=case['data']):
C = cookies.SimpleCookie()
C.load(case['data'])

# Extract the cookie name from the data string
cookie_name = case['data'].split('=')[0]

# Check if the cookie is loaded correctly
self.assertIn(cookie_name, C)
self.assertEqual(C[cookie_name].get('expires'), case['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 = cookies.SimpleCookie()
C.load(encoded)
self.assertEqual(C['a'].value, decoded)

@support.requires_resource('cpu')
def test_unquote_large(self):
#n = 10**6
n = 10**4 # XXX: RUSTPYTHON; This takes more than 10 minutes to run. lower to 4
for encoded in r'\\', r'\134':
with self.subTest(encoded):
data = 'a="b=' + encoded*n + ';"'
C = cookies.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_load(self):
C = cookies.SimpleCookie()
C.load('Customer="WILE_E_COYOTE"; Version=1; Path=/acme')
Expand Down Expand Up @@ -96,7 +182,7 @@ def test_special_attrs(self):
C = cookies.SimpleCookie('Customer="WILE_E_COYOTE"')
C['Customer']['expires'] = 0
# can't test exact output, it always depends on current date/time
self.assertTrue(C.output().endswith('GMT'))
self.assertEndsWith(C.output(), 'GMT')

# loading 'expires'
C = cookies.SimpleCookie()
Expand Down Expand Up @@ -479,9 +565,11 @@ def test_repr(self):
r'Set-Cookie: key=coded_val; '
r'expires=\w+, \d+ \w+ \d+ \d+:\d+:\d+ \w+')

def test_main():
run_unittest(CookieTests, MorselTests)
run_doctest(cookies)

def load_tests(loader, tests, pattern):
tests.addTest(doctest.DocTestSuite(cookies))
return tests


if __name__ == '__main__':
test_main()
unittest.main()
Loading