Skip to content

Commit

Permalink
fix: don't implicitly parse URL encoded form data as JSON (#2394)
Browse files Browse the repository at this point in the history
fix: don't implicitly parse form data as JSON

Signed-off-by: Janek Nouvertné <25355197+provinzkraut@users.noreply.github.com>
  • Loading branch information
provinzkraut committed Oct 2, 2023
1 parent 61b71d4 commit 3d950e9
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 18 deletions.
24 changes: 17 additions & 7 deletions litestar/_parsers.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
from __future__ import annotations

from collections import defaultdict
from functools import lru_cache
from http.cookies import _unquote as unquote_cookie
from typing import Any, Iterable
from typing import Iterable
from urllib.parse import unquote

from fast_query_parsers import parse_query_string as fast_parse_query_string
from fast_query_parsers import parse_url_encoded_dict
try:
from fast_query_parsers import parse_query_string as parse_qsl
except ImportError:
from urllib.parse import parse_qsl as _parse_qsl

def parse_qsl(qs: bytes, separator: str) -> list[tuple[str, str]]:
return _parse_qsl(qs.decode("latin-1"), keep_blank_values=True, separator=separator)


__all__ = ("parse_cookie_string", "parse_headers", "parse_query_string", "parse_url_encoded_form_data")


@lru_cache(1024)
def parse_url_encoded_form_data(encoded_data: bytes) -> dict[str, Any]:
def parse_url_encoded_form_data(encoded_data: bytes) -> dict[str, str | list[str]]:
"""Parse an url encoded form data dict.
Args:
Expand All @@ -21,11 +28,14 @@ def parse_url_encoded_form_data(encoded_data: bytes) -> dict[str, Any]:
Returns:
A parsed dict.
"""
return parse_url_encoded_dict(qs=encoded_data, parse_numbers=False)
decoded_dict: defaultdict[str, list[str]] = defaultdict(list)
for k, v in parse_qsl(encoded_data, separator="&"):
decoded_dict[k].append(v)
return {k: v if len(v) > 1 else v[0] for k, v in decoded_dict.items()}


@lru_cache(1024)
def parse_query_string(query_string: bytes) -> tuple[tuple[str, Any], ...]:
def parse_query_string(query_string: bytes) -> tuple[tuple[str, str], ...]:
"""Parse a query string into a tuple of key value pairs.
Args:
Expand All @@ -34,7 +44,7 @@ def parse_query_string(query_string: bytes) -> tuple[tuple[str, Any], ...]:
Returns:
A tuple of key value pairs.
"""
return tuple(fast_parse_query_string(query_string, "&"))
return tuple(parse_qsl(query_string, separator="&"))


@lru_cache(1024)
Expand Down
8 changes: 4 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ attrs = { version = "*", optional = true }
brotli = { version = "*", optional = true }
click = "*"
cryptography = { version = "*", optional = true }
fast-query-parsers = ">=1.0.2"
fast-query-parsers = {version = ">=1.0.2", optional = true }
httpx = ">=0.22"
importlib-metadata = { version = "*", python = "<3.10" }
importlib-resources = { version = ">=5.12.0", python = "<3.9" }
Expand Down Expand Up @@ -180,7 +180,7 @@ prometheus = ["prometheus-client"]
pydantic = ["pydantic", "pydantic-extra-types"]
redis = ["redis"]
sqlalchemy = ["advanced-alchemy"]
standard = ["jinja2", "jsbeautifier", "uvicorn"]
standard = ["jinja2", "jsbeautifier", "uvicorn", "fast-query-parsers"]
structlog = ["structlog"]

full = [
Expand All @@ -202,7 +202,8 @@ full = [
"redis",
"structlog",
"uvicorn",
"advanced-alchemy"
"advanced-alchemy",
"fast-query-parsers",
]

[tool.poetry.scripts]
Expand Down
8 changes: 4 additions & 4 deletions tests/unit/test_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ def test_parse_form_data() -> None:
)
assert result == {
"value": ["10", "12"],
"veggies": ["tomato", "potato", "aubergine"],
"nested": {"some_key": "some_value"},
"veggies": '["tomato", "potato", "aubergine"]',
"nested": '{"some_key": "some_value"}',
"calories": "122.53",
"healthy": True,
"polluting": False,
"healthy": "true",
"polluting": "false",
}


Expand Down

0 comments on commit 3d950e9

Please sign in to comment.