Skip to content

Commit 3dfb616

Browse files
authored
Merge branch '3.12' into backport-052e55e-3.12
2 parents 8df6632 + 6270010 commit 3dfb616

File tree

7 files changed

+87
-9
lines changed

7 files changed

+87
-9
lines changed

Doc/library/http.cookies.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,9 @@ The following example demonstrates how to use the :mod:`http.cookies` module.
272272
Set-Cookie: chips=ahoy
273273
Set-Cookie: vienna=finger
274274
>>> C = cookies.SimpleCookie()
275-
>>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";')
275+
>>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=;";')
276276
>>> print(C)
277-
Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;"
277+
Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=;"
278278
>>> C = cookies.SimpleCookie()
279279
>>> C["oreo"] = "doublestuff"
280280
>>> C["oreo"]["path"] = "/"

Lib/http/cookies.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@
8787
such trickeries do not confuse it.
8888
8989
>>> C = cookies.SimpleCookie()
90-
>>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";')
90+
>>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=;";')
9191
>>> print(C)
92-
Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;"
92+
Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=;"
9393
9494
Each element of the Cookie also supports all of the RFC 2109
9595
Cookie attributes. Here's an example which sets the Path
@@ -170,6 +170,15 @@ class CookieError(Exception):
170170
})
171171

172172
_is_legal_key = re.compile('[%s]+' % re.escape(_LegalChars)).fullmatch
173+
_control_character_re = re.compile(r'[\x00-\x1F\x7F]')
174+
175+
176+
def _has_control_character(*val):
177+
"""Detects control characters within a value.
178+
Supports any type, as header values can be any type.
179+
"""
180+
return any(_control_character_re.search(str(v)) for v in val)
181+
173182

174183
def _quote(str):
175184
r"""Quote a string for use in a cookie header.
@@ -292,12 +301,16 @@ def __setitem__(self, K, V):
292301
K = K.lower()
293302
if not K in self._reserved:
294303
raise CookieError("Invalid attribute %r" % (K,))
304+
if _has_control_character(K, V):
305+
raise CookieError(f"Control characters are not allowed in cookies {K!r} {V!r}")
295306
dict.__setitem__(self, K, V)
296307

297308
def setdefault(self, key, val=None):
298309
key = key.lower()
299310
if key not in self._reserved:
300311
raise CookieError("Invalid attribute %r" % (key,))
312+
if _has_control_character(key, val):
313+
raise CookieError("Control characters are not allowed in cookies %r %r" % (key, val,))
301314
return dict.setdefault(self, key, val)
302315

303316
def __eq__(self, morsel):
@@ -333,6 +346,9 @@ def set(self, key, val, coded_val):
333346
raise CookieError('Attempt to set a reserved key %r' % (key,))
334347
if not _is_legal_key(key):
335348
raise CookieError('Illegal key %r' % (key,))
349+
if _has_control_character(key, val, coded_val):
350+
raise CookieError(
351+
"Control characters are not allowed in cookies %r %r %r" % (key, val, coded_val,))
336352

337353
# It's a good key, so save it.
338354
self._key = key
@@ -486,7 +502,10 @@ def output(self, attrs=None, header="Set-Cookie:", sep="\015\012"):
486502
result = []
487503
items = sorted(self.items())
488504
for key, value in items:
489-
result.append(value.output(attrs, header))
505+
value_output = value.output(attrs, header)
506+
if _has_control_character(value_output):
507+
raise CookieError("Control characters are not allowed in cookies")
508+
result.append(value_output)
490509
return sep.join(result)
491510

492511
__str__ = output

Lib/test/test_http_cookies.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ def test_basic(self):
1717
'repr': "<SimpleCookie: chips='ahoy' vienna='finger'>",
1818
'output': 'Set-Cookie: chips=ahoy\nSet-Cookie: vienna=finger'},
1919

20-
{'data': 'keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"',
21-
'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=\012;'},
22-
'repr': '''<SimpleCookie: keebler='E=mc2; L="Loves"; fudge=\\n;'>''',
23-
'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'},
20+
{'data': 'keebler="E=mc2; L=\\"Loves\\"; fudge=;"',
21+
'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=;'},
22+
'repr': '''<SimpleCookie: keebler='E=mc2; L="Loves"; fudge=;'>''',
23+
'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=;"'},
2424

2525
# Check illegal cookies that have an '=' char in an unquoted value
2626
{'data': 'keebler=E=mc2',
@@ -563,6 +563,50 @@ def test_repr(self):
563563
r'Set-Cookie: key=coded_val; '
564564
r'expires=\w+, \d+ \w+ \d+ \d+:\d+:\d+ \w+')
565565

566+
def test_control_characters(self):
567+
for c0 in support.control_characters_c0():
568+
morsel = cookies.Morsel()
569+
570+
# .__setitem__()
571+
with self.assertRaises(cookies.CookieError):
572+
morsel[c0] = "val"
573+
with self.assertRaises(cookies.CookieError):
574+
morsel["path"] = c0
575+
576+
# .setdefault()
577+
with self.assertRaises(cookies.CookieError):
578+
morsel.setdefault("path", c0)
579+
with self.assertRaises(cookies.CookieError):
580+
morsel.setdefault(c0, "val")
581+
582+
# .set()
583+
with self.assertRaises(cookies.CookieError):
584+
morsel.set(c0, "val", "coded-value")
585+
with self.assertRaises(cookies.CookieError):
586+
morsel.set("path", c0, "coded-value")
587+
with self.assertRaises(cookies.CookieError):
588+
morsel.set("path", "val", c0)
589+
590+
def test_control_characters_output(self):
591+
# Tests that even if the internals of Morsel are modified
592+
# that a call to .output() has control character safeguards.
593+
for c0 in support.control_characters_c0():
594+
morsel = cookies.Morsel()
595+
morsel.set("key", "value", "coded-value")
596+
morsel._key = c0 # Override private variable.
597+
cookie = cookies.SimpleCookie()
598+
cookie["cookie"] = morsel
599+
with self.assertRaises(cookies.CookieError):
600+
cookie.output()
601+
602+
morsel = cookies.Morsel()
603+
morsel.set("key", "value", "coded-value")
604+
morsel._coded_value = c0 # Override private variable.
605+
cookie = cookies.SimpleCookie()
606+
cookie["cookie"] = morsel
607+
with self.assertRaises(cookies.CookieError):
608+
cookie.output()
609+
566610

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

Lib/test/test_urllib.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from test.support import os_helper
1313
from test.support import socket_helper
1414
from test.support import warnings_helper
15+
from test.support import control_characters_c0
1516
import os
1617
try:
1718
import ssl
@@ -688,6 +689,13 @@ def test_invalid_base64_data(self):
688689
# missing padding character
689690
self.assertRaises(ValueError,urllib.request.urlopen,'data:;base64,Cg=')
690691

692+
def test_invalid_mediatype(self):
693+
for c0 in control_characters_c0():
694+
self.assertRaises(ValueError,urllib.request.urlopen,
695+
f'data:text/html;{c0},data')
696+
for c0 in control_characters_c0():
697+
self.assertRaises(ValueError,urllib.request.urlopen,
698+
f'data:text/html{c0};base64,ZGF0YQ==')
691699

692700
class urlretrieve_FileTests(unittest.TestCase):
693701
"""Test urllib.urlretrieve() on local files"""

Lib/urllib/request.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1655,6 +1655,11 @@ def data_open(self, req):
16551655
scheme, data = url.split(":",1)
16561656
mediatype, data = data.split(",",1)
16571657

1658+
# Disallow control characters within mediatype.
1659+
if re.search(r"[\x00-\x1F\x7F]", mediatype):
1660+
raise ValueError(
1661+
"Control characters not allowed in data: mediatype")
1662+
16581663
# even base64 encoded data URLs might be quoted so unquote in any case:
16591664
data = unquote_to_bytes(data)
16601665
if mediatype.endswith(";base64"):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Reject control characters in :class:`http.cookies.Morsel` fields and values.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Reject control characters in ``data:`` URL media types.

0 commit comments

Comments
 (0)