summaryrefslogtreecommitdiff
path: root/django/utils
diff options
context:
space:
mode:
authorNick Pope <nick.pope@flightdataservices.com>2019-11-04 12:06:49 +0000
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2020-09-03 14:24:42 +0200
commitfd209f62f1d83233cc634443cfac5ee4328d98b8 (patch)
tree476f3b0c54339831288823b75afa74822198188a /django/utils
parent8d59075184f4cd2852b374359850ff7aeb412ec1 (diff)
Refs #21231 -- Backport urllib.parse.parse_qsl() from Python 3.8.
Diffstat (limited to 'django/utils')
-rw-r--r--django/utils/http.py58
1 files changed, 37 insertions, 21 deletions
diff --git a/django/utils/http.py b/django/utils/http.py
index c1005458e7..810d7970ba 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -12,7 +12,6 @@ from urllib.parse import (
urlencode as original_urlencode, uses_params,
)
-from django.core.exceptions import TooManyFieldsSent
from django.utils.datastructures import MultiValueDict
from django.utils.deprecation import RemovedInDjango40Warning
from django.utils.functional import keep_lazy_text
@@ -42,8 +41,6 @@ ASCTIME_DATE = _lazy_re_compile(r'^\w{3} %s %s %s %s$' % (__M, __D2, __T, __Y))
RFC3986_GENDELIMS = ":/?#[]@"
RFC3986_SUBDELIMS = "!$&'()*+,;="
-FIELDS_MATCH = _lazy_re_compile('[&;]')
-
@keep_lazy_text
def urlquote(url, safe='/'):
@@ -415,13 +412,20 @@ def _url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
(not scheme or scheme in valid_schemes))
-def limited_parse_qsl(qs, keep_blank_values=False, encoding='utf-8',
- errors='replace', fields_limit=None):
+# TODO: Remove when dropping support for PY37.
+def parse_qsl(
+ qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8',
+ errors='replace', max_num_fields=None,
+):
"""
Return a list of key/value tuples parsed from query string.
- Copied from urlparse with an additional "fields_limit" argument.
- Copyright (C) 2013 Python Software Foundation (see LICENSE.python).
+ Backport of urllib.parse.parse_qsl() from Python 3.8.
+ Copyright (C) 2020 Python Software Foundation (see LICENSE.python).
+
+ ----
+
+ Parse a query given as a string argument.
Arguments:
@@ -433,37 +437,49 @@ def limited_parse_qsl(qs, keep_blank_values=False, encoding='utf-8',
strings. The default false value indicates that blank values
are to be ignored and treated as if they were not included.
+ strict_parsing: flag indicating what to do with parsing errors. If false
+ (the default), errors are silently ignored. If true, errors raise a
+ ValueError exception.
+
encoding and errors: specify how to decode percent-encoded sequences
into Unicode characters, as accepted by the bytes.decode() method.
- fields_limit: maximum number of fields parsed or an exception
- is raised. None means no limit and is the default.
+ max_num_fields: int. If set, then throws a ValueError if there are more
+ than n fields read by parse_qsl().
+
+ Returns a list, as G-d intended.
"""
- if fields_limit:
- pairs = FIELDS_MATCH.split(qs, fields_limit)
- if len(pairs) > fields_limit:
- raise TooManyFieldsSent(
- 'The number of GET/POST parameters exceeded '
- 'settings.DATA_UPLOAD_MAX_NUMBER_FIELDS.'
- )
- else:
- pairs = FIELDS_MATCH.split(qs)
+ qs, _coerce_result = _coerce_args(qs)
+
+ # If max_num_fields is defined then check that the number of fields is less
+ # than max_num_fields. This prevents a memory exhaustion DOS attack via
+ # post bodies with many fields.
+ if max_num_fields is not None:
+ num_fields = 1 + qs.count('&') + qs.count(';')
+ if max_num_fields < num_fields:
+ raise ValueError('Max number of fields exceeded')
+
+ pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
r = []
for name_value in pairs:
- if not name_value:
+ if not name_value and not strict_parsing:
continue
nv = name_value.split('=', 1)
if len(nv) != 2:
- # Handle case of a control-name with no equal sign
+ if strict_parsing:
+ raise ValueError("bad query field: %r" % (name_value,))
+ # Handle case of a control-name with no equal sign.
if keep_blank_values:
nv.append('')
else:
continue
- if nv[1] or keep_blank_values:
+ if len(nv[1]) or keep_blank_values:
name = nv[0].replace('+', ' ')
name = unquote(name, encoding=encoding, errors=errors)
+ name = _coerce_result(name)
value = nv[1].replace('+', ' ')
value = unquote(value, encoding=encoding, errors=errors)
+ value = _coerce_result(value)
r.append((name, value))
return r