def parse_qs_bytes(
- qs: str | bytes, keep_blank_values: bool = False, strict_parsing: bool = False
+ qs: str | bytes,
+ keep_blank_values: bool = False,
+ strict_parsing: bool = False,
+ *,
+ max_num_fields: int | None = None,
) -> dict[str, list[bytes]]:
"""Parses a query string like urlparse.parse_qs,
but takes bytes and returns the values as byte strings.
Keys still become type str (interpreted as latin1 in python3!)
because it's too painful to keep them as byte strings in
python3 and in practice they're nearly always ascii anyway.
+
+ .. versionadded:: 6.5.8
+ The ``max_num_fields`` argument. ValueError is raised if this limit is exceeded.
"""
# This is gross, but python3 doesn't give us another way.
# Latin1 is the universal donor of character encodings.
if isinstance(qs, bytes):
qs = qs.decode("latin1")
result = urllib.parse.parse_qs(
- qs, keep_blank_values, strict_parsing, encoding="latin1", errors="strict"
+ qs,
+ keep_blank_values,
+ strict_parsing,
+ encoding="latin1",
+ errors="strict",
+ max_num_fields=max_num_fields,
)
encoded = {}
for k, v in result.items():
"""
+@dataclasses.dataclass
+class ParseUrlEncodedConfig:
+ """This class configures the parsing of ``application/x-www-form-urlencoded`` request bodies.
+
+ Its primary purpose is to place limits on the size and complexity of request messages
+ to avoid potential denial-of-service attacks.
+
+ .. versionadded:: 6.5.8
+ """
+
+ max_arguments: int = 1000
+ """The maximum number of arguments accepted in a urlencoded request.
+
+ Each ``<input>`` element in an HTML form corresponds to at least one argument.
+ """
+
+
@dataclasses.dataclass
class ParseBodyConfig:
"""This class configures the parsing of request bodies.
multipart: ParseMultipartConfig = dataclasses.field(
default_factory=ParseMultipartConfig
)
+ urlencoded: ParseUrlEncodedConfig = dataclasses.field(
+ default_factory=ParseUrlEncodedConfig
+ )
"""Configuration for ``multipart/form-data`` request bodies."""
)
try:
# real charset decoding will happen in RequestHandler.decode_argument()
- uri_arguments = parse_qs_bytes(body, keep_blank_values=True)
+ uri_arguments = parse_qs_bytes(
+ body,
+ keep_blank_values=True,
+ max_num_fields=config.urlencoded.max_arguments,
+ )
except Exception as e:
raise HTTPInputError("Invalid x-www-form-urlencoded body: %s" % e) from e
for name, values in uri_arguments.items():
ParseMultipartConfig,
RequestStartLine,
format_timestamp,
+ parse_body_arguments,
parse_cookie,
parse_multipart_form_data,
parse_request_start_line,
self.assertIn(("b", "2"), qsl)
+class UrlEncodedDataTest(unittest.TestCase):
+ def test_urlencoded_data(self):
+ data = b"a=1&b=2&a=3"
+ args, files = form_data_args()
+ parse_body_arguments("application/x-www-form-urlencoded", data, args, files)
+ self.assertEqual(args["a"], [b"1", b"3"])
+ self.assertEqual(args["b"], [b"2"])
+ self.assertEqual(files, {})
+
+ def test_max_arguments(self):
+ data = b"".join(b"a=1&" for _ in range(1001))
+ args, files = form_data_args()
+ with self.assertRaises(HTTPInputError) as cm:
+ parse_body_arguments("application/x-www-form-urlencoded", data, args, files)
+ self.assertIn("Max number of fields exceeded", str(cm.exception))
+
+
class MultipartFormDataTest(unittest.TestCase):
def test_file_upload(self):
data = b"""\