mirror of
https://github.com/python/cpython.git
synced 2026-01-26 21:03:34 +00:00
[3.14] gh-143919: Reject control characters in http cookies (#144089)
Co-authored-by: Seth Michael Larson <seth@python.org> Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com> Co-authored-by: sobolevn <mail@sobolevn.me>
This commit is contained in:
parent
80a0ce40c3
commit
712452e6f1
@ -292,9 +292,9 @@ The following example demonstrates how to use the :mod:`http.cookies` module.
|
||||
Set-Cookie: chips=ahoy
|
||||
Set-Cookie: vienna=finger
|
||||
>>> C = cookies.SimpleCookie()
|
||||
>>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";')
|
||||
>>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=;";')
|
||||
>>> print(C)
|
||||
Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;"
|
||||
Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=;"
|
||||
>>> C = cookies.SimpleCookie()
|
||||
>>> C["oreo"] = "doublestuff"
|
||||
>>> C["oreo"]["path"] = "/"
|
||||
|
||||
@ -87,9 +87,9 @@ within a string. Escaped quotation marks, nested semicolons, and other
|
||||
such trickeries do not confuse it.
|
||||
|
||||
>>> C = cookies.SimpleCookie()
|
||||
>>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";')
|
||||
>>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=;";')
|
||||
>>> print(C)
|
||||
Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;"
|
||||
Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=;"
|
||||
|
||||
Each element of the Cookie also supports all of the RFC 2109
|
||||
Cookie attributes. Here's an example which sets the Path
|
||||
@ -170,6 +170,15 @@ _Translator.update({
|
||||
})
|
||||
|
||||
_is_legal_key = re.compile('[%s]+' % re.escape(_LegalChars)).fullmatch
|
||||
_control_character_re = re.compile(r'[\x00-\x1F\x7F]')
|
||||
|
||||
|
||||
def _has_control_character(*val):
|
||||
"""Detects control characters within a value.
|
||||
Supports any type, as header values can be any type.
|
||||
"""
|
||||
return any(_control_character_re.search(str(v)) for v in val)
|
||||
|
||||
|
||||
def _quote(str):
|
||||
r"""Quote a string for use in a cookie header.
|
||||
@ -294,12 +303,16 @@ class Morsel(dict):
|
||||
K = K.lower()
|
||||
if not K in self._reserved:
|
||||
raise CookieError("Invalid attribute %r" % (K,))
|
||||
if _has_control_character(K, V):
|
||||
raise CookieError(f"Control characters are not allowed in cookies {K!r} {V!r}")
|
||||
dict.__setitem__(self, K, V)
|
||||
|
||||
def setdefault(self, key, val=None):
|
||||
key = key.lower()
|
||||
if key not in self._reserved:
|
||||
raise CookieError("Invalid attribute %r" % (key,))
|
||||
if _has_control_character(key, val):
|
||||
raise CookieError("Control characters are not allowed in cookies %r %r" % (key, val,))
|
||||
return dict.setdefault(self, key, val)
|
||||
|
||||
def __eq__(self, morsel):
|
||||
@ -335,6 +348,9 @@ class Morsel(dict):
|
||||
raise CookieError('Attempt to set a reserved key %r' % (key,))
|
||||
if not _is_legal_key(key):
|
||||
raise CookieError('Illegal key %r' % (key,))
|
||||
if _has_control_character(key, val, coded_val):
|
||||
raise CookieError(
|
||||
"Control characters are not allowed in cookies %r %r %r" % (key, val, coded_val,))
|
||||
|
||||
# It's a good key, so save it.
|
||||
self._key = key
|
||||
@ -488,7 +504,10 @@ class BaseCookie(dict):
|
||||
result = []
|
||||
items = sorted(self.items())
|
||||
for key, value in items:
|
||||
result.append(value.output(attrs, header))
|
||||
value_output = value.output(attrs, header)
|
||||
if _has_control_character(value_output):
|
||||
raise CookieError("Control characters are not allowed in cookies")
|
||||
result.append(value_output)
|
||||
return sep.join(result)
|
||||
|
||||
__str__ = output
|
||||
|
||||
@ -17,10 +17,10 @@ class CookieTests(unittest.TestCase):
|
||||
'repr': "<SimpleCookie: chips='ahoy' vienna='finger'>",
|
||||
'output': 'Set-Cookie: chips=ahoy\nSet-Cookie: vienna=finger'},
|
||||
|
||||
{'data': 'keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"',
|
||||
'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=\012;'},
|
||||
'repr': '''<SimpleCookie: keebler='E=mc2; L="Loves"; fudge=\\n;'>''',
|
||||
'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'},
|
||||
{'data': 'keebler="E=mc2; L=\\"Loves\\"; fudge=;"',
|
||||
'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=;'},
|
||||
'repr': '''<SimpleCookie: keebler='E=mc2; L="Loves"; fudge=;'>''',
|
||||
'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=;"'},
|
||||
|
||||
# Check illegal cookies that have an '=' char in an unquoted value
|
||||
{'data': 'keebler=E=mc2',
|
||||
@ -571,6 +571,50 @@ class MorselTests(unittest.TestCase):
|
||||
r'Set-Cookie: key=coded_val; '
|
||||
r'expires=\w+, \d+ \w+ \d+ \d+:\d+:\d+ \w+')
|
||||
|
||||
def test_control_characters(self):
|
||||
for c0 in support.control_characters_c0():
|
||||
morsel = cookies.Morsel()
|
||||
|
||||
# .__setitem__()
|
||||
with self.assertRaises(cookies.CookieError):
|
||||
morsel[c0] = "val"
|
||||
with self.assertRaises(cookies.CookieError):
|
||||
morsel["path"] = c0
|
||||
|
||||
# .setdefault()
|
||||
with self.assertRaises(cookies.CookieError):
|
||||
morsel.setdefault("path", c0)
|
||||
with self.assertRaises(cookies.CookieError):
|
||||
morsel.setdefault(c0, "val")
|
||||
|
||||
# .set()
|
||||
with self.assertRaises(cookies.CookieError):
|
||||
morsel.set(c0, "val", "coded-value")
|
||||
with self.assertRaises(cookies.CookieError):
|
||||
morsel.set("path", c0, "coded-value")
|
||||
with self.assertRaises(cookies.CookieError):
|
||||
morsel.set("path", "val", c0)
|
||||
|
||||
def test_control_characters_output(self):
|
||||
# Tests that even if the internals of Morsel are modified
|
||||
# that a call to .output() has control character safeguards.
|
||||
for c0 in support.control_characters_c0():
|
||||
morsel = cookies.Morsel()
|
||||
morsel.set("key", "value", "coded-value")
|
||||
morsel._key = c0 # Override private variable.
|
||||
cookie = cookies.SimpleCookie()
|
||||
cookie["cookie"] = morsel
|
||||
with self.assertRaises(cookies.CookieError):
|
||||
cookie.output()
|
||||
|
||||
morsel = cookies.Morsel()
|
||||
morsel.set("key", "value", "coded-value")
|
||||
morsel._coded_value = c0 # Override private variable.
|
||||
cookie = cookies.SimpleCookie()
|
||||
cookie["cookie"] = morsel
|
||||
with self.assertRaises(cookies.CookieError):
|
||||
cookie.output()
|
||||
|
||||
|
||||
def load_tests(loader, tests, pattern):
|
||||
tests.addTest(doctest.DocTestSuite(cookies))
|
||||
|
||||
@ -0,0 +1 @@
|
||||
Reject control characters in :class:`http.cookies.Morsel` fields and values.
|
||||
Loading…
x
Reference in New Issue
Block a user