10000 only use '&' as a query string separator · raspbian-packages/python3.5@b1f98fd · GitHub
[go: up one dir, main page]

Skip to content

Commit b1f98fd

Browse files
committed
only use '&' as a query string separator
Origin: python/cpython#24297 Last-Update: 2021-04-03 CVE-2021-23336 Gbp-Pq: Name CVE-2021-23336.patch
1 parent f131219 commit b1f98fd

File tree

6 files changed

+111
-42
lines changed

6 files changed

+111
-42
lines changed

Doc/library/cgi.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ A form submitted via POST that also has a query string will contain both
188188
Added support for the context management protocol to the
189189
:class:`FieldStorage` class.
190190

191+
.. versionchanged:: 3.5.3-1+deb9u4
192+
Added the *separator* parameter.
193+
191194

192195
Higher Level Interface
193196
----------------------
@@ -277,10 +280,10 @@ These are useful if you want more control, or if you want to employ some of the
277280
algorithms implemented in this module in other circumstances.
278281

279282

280-
.. function:: parse(fp=None, environ=os.environ, keep_blank_values=False, strict_parsing=False)
283+
.. function:: parse(fp=None, environ=os.environ, keep_blank_values=False, strict_parsing=False, separator="&")
281284

282285
Parse a query in the environment or from a file (the file defaults to
283-
``sys.stdin``). The *keep_blank_values* and *strict_parsing* parameters are
286+
``sys.stdin``). The *keep_blank_values*, *strict_parsing* parameters and *separator* parameters are
284287
passed to :func:`urllib.parse.parse_qs` unchanged.
285288

286289

Doc/library/urllib.parse.rst

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ or on combining URL components into a URL string.
135135
now raise :exc:`ValueError`.
136136

137137

138-
.. function:: parse_qs(qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8', errors='replace')
138+
.. function:: parse_qs(qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8', errors='replace', separator='&')
139139

140140
Parse a query string given as a string argument (data of type
141141
:mimetype:`application/x-www-form-urlencoded`). Data are returned as a
@@ -156,6 +156,9 @@ or on combining URL components into a URL string.
156156
percent-encoded sequences into Unicode characters, as accepted by the
157157
:meth:`bytes.decode` method.
158158

159+
The optional argument *separator* is the symbol to use for separating the
160+
query arguments. It defaults to ``&``.
161+
159162
Use the :func:`urllib.parse.urlencode` function (with the ``doseq``
160163
parameter set to ``True``) to convert such dictionaries into query
161164
strings.
@@ -164,8 +167,13 @@ or on combining URL components into a URL string.
164167
.. versionchanged:: 3.2
165168
Add *encoding* and *errors* parameters.
166169

170+
.. versionchanged:: 3.5.3-1+deb9u4
171+
Added *separator* parameter with the default value of ``&``. Python
172+
versions earlier than Python 3.7.10 allowed using both ``;`` and ``&`` as
173+
query parameter separator. This has been changed to allow only a single
174+
separator key, with ``&`` as the default separator.
167175

168-
.. function:: parse_qsl(qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8', errors='replace')
176+
.. function:: parse_qsl(qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8', errors='replace', separator='&')
169177

170178
Parse a query string given as a string argument (data of type
171179
:mimetype:`application/x-www-form-urlencoded`). Data are returned as a list of
@@ -185,6 +193,9 @@ or on combining URL components into a URL string.
185193
percent-encoded sequences into Unicode characters, as accepted by the
186194
:meth:`bytes.decode` method.
187195

196+
The optional argument *separator* is the symbol to use for separating the
197+
query arguments. It defaults to ``&``.
198+
188199
Use the :func:`urllib.parse.urlencode` function to convert such lists of pairs into
189200
query strings.
190201

Lib/cgi.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def closelog():
117117
# 0 ==> unlimited input
118118
maxlen = 0
119119

120-
def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
120+
def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0, separator='&'):
121121
"""Parse a query in the environment or from a file (default stdin)
122122
123123
Arguments, all optional:
@@ -136,6 +136,9 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
136136
strict_parsing: flag indicating what to do with parsing errors.
137137
If false (the default), errors are silently ignored.
138138
If true, errors raise a ValueError exception.
139+
140+
separator: str. The symbol to use for separating the query arguments.
141+
Defaults to &.
139142
"""
140143
if fp is None:
141144
fp = sys.stdin
@@ -156,7 +159,7 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
156159
if environ['REQUEST_METHOD'] == 'POST':
157160
ctype, pdict = parse_header(environ['CONTENT_TYPE'])
158161
if ctype == 'multipart/form-data':
159-
return parse_multipart(fp, pdict)
162+
return parse_multipart(fp, pdict, separator=separator)
160163
elif ctype == 'application/x-www-form-urlencoded':
161164
clength = int(environ['CONTENT_LENGTH'])
162165
if maxlen and clength > maxlen:
@@ -180,7 +183,7 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
180183
qs = ""
181184
environ['QUERY_STRING'] = qs # XXX Shouldn't, really
182185
return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing,
183-
encoding=encoding)
186+
encoding=encoding, separator=separator)
184187

185188

186189
# parse query string function called from urlparse,
@@ -198,7 +201,7 @@ def parse_qsl(qs, keep_blank_values=0, strict_parsing=0):
198201
DeprecationWarning, 2)
199202
return urllib.parse.parse_qsl(qs, keep_blank_values, strict_parsing)
200203

201-
def parse_multipart(fp, pdict):
204+
def parse_multipart(fp, pdict, separator='&'):
202205
"""Parse multipart input.
203206
204207
Arguments:
@@ -404,7 +407,7 @@ class FieldStorage:
404407
"""
405408
def __init__(self, fp=None, headers=None, outerboundary=b'',
406409
environ=os.environ, keep_blank_values=0, strict_parsing=0,
407-
limit=None, encoding='utf-8', errors='replace'):
410+
limit=None, encoding='utf-8', errors='replace', separator='&'):
408411
"""Constructor. Read multipart/* until last part.
409412
410413
Arguments, all optional:
@@ -448,6 +451,7 @@ def __init__(self, fp=None, headers=None, outerboundary=b'',
448451
method = 'GET'
449452
self.keep_blank_values = keep_blank_values
450453
self.strict_parsing = strict_parsing
454+
self.separator = separator
451455
if 'REQUEST_METHOD' in environ:
452456
method = environ['REQUEST_METHOD'].upper()
453457
self.qs_on_post = None
@@ -673,7 +677,7 @@ def read_urlencoded(self):
673677
self.list = []
674678
query = urllib.parse.parse_qsl(
675679
qs, self.keep_blank_values, self.strict_parsing,
676-
encoding=self.encoding, errors=self.errors)
680+
encoding=self.encoding, errors=self.errors, separator=self.separator)
677681
for key, value in query:
678682
self.list.append(MiniFieldStorage(key, value))
679683
self.skip_lines()
@@ -689,7 +693,7 @@ def read_multi(self, environ, keep_blank_values, strict_parsing):
689693
if self.qs_on_post:
690694
query = urllib.parse.parse_qsl(
691695
self.qs_on_post, self.keep_blank_values, self.strict_parsing,
692-
encoding=self.encoding, errors=self.errors)
696+
encoding=self.encoding, errors=self.errors, separator=self.separator)
693697
for key, value in query:
694698
self.list.append(MiniFieldStorage(key, value))
695699

@@ -727,7 +731,7 @@ def read_multi(self, environ, keep_blank_values, strict_parsing):
727731

728732
part = klass(self.fp, headers, ib, environ, keep_blank_values,
729733
strict_parsing,self.limit-self.bytes_read,
730-
self.encoding, self.errors)
734+
self.encoding, self.errors, self.separator)
731735
self.bytes_read += part.bytes_read
732736
self.list.append(part)
733737
if part.done or self.bytes_read >= self.length > 0:

Lib/test/test_cgi.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,9 @@ def do_test(buf, method):
5454
("", ValueError("bad query field: ''")),
5555
("&", ValueError("bad query field: ''")),
5656
("&&", ValueError("bad query field: ''")),
57-
(";", ValueError("bad query field: ''")),
58-
(";&;", ValueError("bad query field: ''")),
5957
# Should the next few really be valid?
6058
("=", {}),
6159
("=&=", {}),
62-
("=;=", {}),
6360
# This rest seem to make sense
6461
("=a", {'': ['a']}),
6562
("&=a", ValueError("bad query field: ''")),
@@ -74,8 +71,6 @@ def do_test(buf, method):
7471
("a=a+b&b=b+c", {'a': ['a b'], 'b': ['b c']}),
7572
("a=a+b&a=b+a", {'a': ['a b', 'b a']}),
7673
("x=1&y=2.0&z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
77-
("x=1;y=2.0&z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
78-
("x=1;y=2.0;z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
7974
("Hbc5161168c542333633315dee1182227:key_store_seqid=400006&cuyer=r&view=bustomer&order_id=0bb2e248638833d48cb7fed300000f1b&expire=964546263&lobale=en-US&kid=130003.300038&ss=env",
8075
{'Hbc5161168c542333633315dee1182227:key_store_seqid': ['400006'],
8176
'cuyer': ['r'],
@@ -178,7 +173,29 @@ def test_strict(self):
178173
self.assertEqual(fs.getvalue(key), expect_val)
179174
else:
180175
self.assertEqual(fs.getvalue(key), expect_val[0])
181-
176+
def test_separator(self):
177+
parse_semicolon = [
178+
("x=1;y=2.0", {'x': ['1'], 'y': ['2.0']}),
179+
("x=1;y=2.0;z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
180+
(";", ValueError("bad query field: ''")),
181+
(";;", ValueError("bad query field: ''")),
182+
("=;a", ValueError("bad query field: 'a'")),
183+
(";b=a", ValueError("bad query field: ''")),
184+
("b;=a", ValueError("bad query field: 'b'")),
185+
("a=a+b;b=b+c", {'a': ['a b'], 'b': ['b c']}),
186+
("a=a+b;a=b+a", {'a': ['a b', 'b a']}),
187+
]
188+
for orig, expect in parse_semicolon:
189+
env = {'QUERY_STRING': orig}
190+
fs = cgi.FieldStorage(separator=';', environ=env)
191+
if isinstance(expect, dict):
192+
for key in expect.keys():
193+
expect_val = expect[key]
194+
self.assertIn(key, fs)
195+
if len(expect_val) > 1:
196+
self.assertEqual(fs.getvalue(key), expect_val)
197+
else:
198+
self.assertEqual(fs.getvalue(key), expect_val[0])
182199
def test_log(self):
183200
cgi.log("Testing")
184201

Lib/test/test_urlparse.py

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,10 @@
3232
(b"&a=b", [(b'a', b'b')]),
3333
(b"a=a+b&b=b+c", [(b'a', b'a b'), (b'b', b'b c')]),
3434
(b"a=1&a=2", [(b'a', b'1'), (b'a', b'2')]),
35-
(";", []),
36-
(";;", []),
37-
(";a=b", [('a', 'b')]),
38-
("a=a+b;b=b+c", [('a', 'a b'), ('b', 'b c')]),
39-
("a=1;a=2", [('a', '1'), ('a', '2')]),
40-
(b";", []),
41-
(b";;", []),
42-
(b";a=b", [(b'a', b'b')]),
43-
(b"a=a+b;b=b+c", [(b'a', b'a b'), (b'b', b'b c')]),
44-
(b"a=1;a=2", [(b'a', b'1'), (b'a', b'2')]),
35+
(";a=b", [(';a', 'b')]),
36+
("a=a+b;b=b+c", [('a', 'a b;b=b c')]),
37+
(b";a=b", [(b';a', b'b')]),
38+
(b"a=a+b;b=b+c", [(b'a', b'a b;b=b c')]),
4539
]
4640

4741
parse_qs_test_cases = [
@@ -65,16 +59,10 @@
6559
(b"&a=b", {b'a': [b'b']}),
6660
(b"a=a+b&b=b+c", {b'a': [b'a b'], b'b': [b'b c']}),
6761
(b"a=1&a=2", {b'a': [b'1', b'2']}),
68-
(";", {}),
69-
(";;", {}),
70-
(";a=b", {'a': ['b']}),
71-
("a=a+b;b=b+c", {'a': ['a b'], 'b': ['b c']}),
72-
("a=1;a=2", {'a': ['1', '2']}),
73-
(b";", {}),
74-
(b";;", {}),
75-
(b";a=b", {b'a': [b'b']}),
76-
(b"a=a+b;b=b+c", {b'a': [b'a b'], b'b': [b'b c']}),
77-
(b"a=1;a=2", {b'a': [b'1', b'2']}),
62+
(";a=b", {';a': ['b']}),
63+
("a=a+b;b=b+c", {'a': ['a b;b=b c']}),
64+
(b";a=b", {b';a': [b'b']}),
65+
(b"a=a+b;b=b+c", {b'a':[ b'a b;b=b c']}),
7866
]
7967

8068
class UrlParseTestCase(unittest.TestCase):
@@ -867,6 +855,42 @@ def test_parse_qsl_encoding(self):
867855
errors="ignore")
868856
self.assertEqual(result, [('key', '\u0141-')])
869857

858+
def test_parse_qs_separator(self):
859+
parse_qs_semicolon_cases = [
860+
(";", {}),
861+
(";;", {}),
862+
(";a=b", {'a': ['b']}),
863+
("a=a+b;b=b+c", {'a': ['a b'], 'b': ['b c']}),
864+
("a=1;a=2", {'a': ['1', '2']}),
865+
(b";", {}),
866+
(b";;", {}),
867+
(b";a=b", {b'a': [b'b']}),
868+
(b"a=a+b;b=b+c", {b'a': [b'a b'], b'b': [b'b c']}),
869+
(b"a=1;a=2", {b'a': [b'1', b'2']}),
870+
]
871+
for orig, expect in parse_qs_semicolon_cases:
872+
with self.subTest("Original: %s, Expected: %s"%(orig, expect)):
873+
result = urllib.parse.parse_qs(orig, separator=';')
874+
self.assertEqual(result, expect, "Error parsing %r" % orig)
875+
876+
def test_parse_qsl_separator(self):
877+
parse_qsl_semicolon_cases = [
878+
(";", []),
879+
(";;", []),
880+
(";a=b", [('a', 'b')]),
881+
("a=a+b;b=b+c", [('a', 'a b'), ('b', 'b c')]),
882+
("a=1;a=2", [('a', '1'), ('a', '2')]),
883+
(b";", []),
884+
(b";;", []),
885+
(b";a=b", [(b'a', b'b')]),
886+
(b"a=a+b;b=b+c", [(b'a', b'a b'), (b'b', b'b c')]),
887+
(b"a=1;a=2", [(b'a', b'1'), (b'a', b'2')]),
888+
]
889+
for orig, expect in parse_qsl_semicolon_cases:
890+
with self.subTest("Original: %s, Expected: %s"%(orig, expect)):
891+
result = urllib.parse.parse_qsl(orig, separator=';')
892+
self.assertEqual(result, expect, "Error parsing %r" % orig)
893+
870894
def test_urlencode_sequences(self):
871895
# Other tests incidentally urlencode things; test non-covered cases:
872896
# Sequence and object values.

Lib/urllib/parse.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ def unquote(string, encoding='utf-8', errors='replace'):
571571
return ''.join(res)
572572

573573
def parse_qs(qs, keep_blank_values=False, strict_parsing=False,
574-
encoding='utf-8', errors='replace'):
574+
encoding='utf-8', errors='replace', separator='&'):
575575
"""Parse a query given as a string argument.
576576
577577
Arguments:
@@ -591,10 +591,13 @@ def parse_qs(qs, keep_blank_values=False, strict_parsing=False,
591591
592592
encoding and errors: specify how to decode percent-encoded sequences
593593
into Unicode characters, as accepted by the bytes.decode() method.
594+
595+
separator: str. The symbol to use for separating the query arguments.
596+
Defaults to &.
594597
"""
595598
parsed_result = {}
596599
pairs = parse_qsl(qs, keep_blank_values, strict_parsing,
597-
encoding=encoding, errors=errors)
600+
encoding=encoding, errors=errors, separator=separator)
598601
for name, value in pairs:
599602
if name in parsed_result:
600603
parsed_result[name].append(value)
@@ -603,7 +606,7 @@ def parse_qs(qs, keep_blank_values=False, strict_parsing=False,
603606
return parsed_result
604607

605608
def parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
606-
encoding='utf-8', errors='replace'):
609+
encoding='utf-8', errors='replace', separator='&'):
607610
"""Parse a query given as a string argument.
608611
609612
Arguments:
@@ -623,10 +626,17 @@ def parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
623626
encoding and errors: specify how to decode percent-encoded sequences
624627
into Unicode characters, as accepted by the bytes.decode() method.
625628
629+
separator: str. The symbol to use for separating the query arguments.
630+
Defaults to &.
631+
626632
Returns a list, as G-d intended.
627633
"""
628634
qs, _coerce_result = _coerce_args(qs)
629-
pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
635+
636+
if not separator or (not isinstance(separator, (str, bytes))):
637+
raise ValueError("Separator must be of type string or bytes.")
638+
639+
pairs = [s1 for s1 in qs.split(separator)]
630640
r = []
631641
for name_value in pairs:
632642
if not name_value and not strict_parsing:

0 commit comments

Comments
 (0)
0