diff options
| author | Nick Pope <nick.pope@flightdataservices.com> | 2019-11-04 12:06:49 +0000 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2020-09-03 14:24:42 +0200 |
| commit | fd209f62f1d83233cc634443cfac5ee4328d98b8 (patch) | |
| tree | 476f3b0c54339831288823b75afa74822198188a /django/utils | |
| parent | 8d59075184f4cd2852b374359850ff7aeb412ec1 (diff) | |
Refs #21231 -- Backport urllib.parse.parse_qsl() from Python 3.8.
Diffstat (limited to 'django/utils')
| -rw-r--r-- | django/utils/http.py | 58 |
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 |
