Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions benedict/serializers/query_string.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@ def __init__(self) -> None:
def decode( # type: ignore[override]
self, s: str, flat: bool = True
) -> dict[str, str] | dict[str, list[str]]:
qs_re = r"(?:([\w\-\%\+\.\|]+\=[\w\-\%\+\.\|]*)+(?:[\&]{1})?)+"
qs_pattern = re.compile(qs_re)
if qs_pattern.match(s):
# A query string is a sequence of "key=value" pairs joined by "&".
# Each key must be non-empty and free of whitespace, "=" and "&";
# each value must be free of whitespace and "&" (spaces are encoded
# as "+" or "%20"). This accepts real-world keys such as array-style
# "a[]" / "user[name]" while still rejecting other formats (TOML, YAML,
# JSON, XML), plain text and URLs.
pair_re = re.compile(r"^[^\s=&]+=[^\s&]*$")
pairs = s.split("&")
if all(pair_re.match(pair) for pair in pairs):
data = parse_qs(s)
if flat:
return {key: value[0] for key, value in data.items()}
Expand Down
18 changes: 18 additions & 0 deletions tests/dicts/io/test_io_dict_query_string.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ def test_from_query_string_with_valid_data(self) -> None:
self.assertTrue(isinstance(d, dict))
self.assertEqual(d, r)

def test_from_query_string_with_array_style_keys(self) -> None:
# array-style keys (PHP / HTML form syntax) are valid query strings
s = "a[]=1&a[]=2"
r = {"a[]": "1"}
d = IODict.from_query_string(s)
self.assertTrue(isinstance(d, dict))
self.assertEqual(d, r)
d = IODict(s, format="query_string")
self.assertTrue(isinstance(d, dict))
self.assertEqual(d, r)

def test_from_query_string_with_bracketed_keys(self) -> None:
s = "user[name]=joe&user[age]=42"
r = {"user[name]": "joe", "user[age]": "42"}
d = IODict.from_query_string(s)
self.assertTrue(isinstance(d, dict))
self.assertEqual(d, r)

def test_from_query_string_with_invalid_data(self) -> None:
s = "Lorem ipsum est in ea occaecat nisi officia."
# static method
Expand Down