Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 fix incorrect parsing of nested params with closing square bracket ] in the property name #1

Merged
merged 1 commit into from
Apr 27, 2024
Merged
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
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP",
"Topic :: Software Development :: Libraries",
]
dependencies = [
"regex>=2024.4.16",
"types-regex>=2024.4.16.20240423",
]
dynamic = ["version"]

[project.urls]
Expand Down
4 changes: 3 additions & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pytest>=8.1.1
regex>=2024.4.16
types-regex>=2024.4.16.20240423
pytest>=8.1.2
pytest-cov>=5.0.0
mypy>=1.10.0
toml>=0.10.2
Expand Down
8 changes: 5 additions & 3 deletions src/qs_codec/decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import typing as t
from math import isinf

from regex import regex

from .enums.charset import Charset
from .enums.duplicates import Duplicates
from .enums.sentinel import Sentinel
Expand Down Expand Up @@ -159,10 +161,10 @@ def _parse_keys(given_key: t.Optional[str], val: t.Any, options: DecodeOptions,
key: str = re.sub(r"\.([^.[]+)", r"[\1]", given_key) if options.allow_dots else given_key

# The regex chunks
brackets: re.Pattern[str] = re.compile(r"(\[[^[\]]*])")
brackets: regex.Pattern[str] = regex.compile(r"\[(?:[^\[\]]|(?R))*\]")

# Get the parent
segment: t.Optional[t.Match] = brackets.search(key) if options.depth > 0 else None
segment: t.Optional[regex.Match] = brackets.search(key) if options.depth > 0 else None
parent: str = key[0 : segment.start()] if segment is not None else key

# Stash the parent if it exists
Expand All @@ -173,7 +175,7 @@ def _parse_keys(given_key: t.Optional[str], val: t.Any, options: DecodeOptions,
while options.depth > 0 and (segment := brackets.search(key)) is not None and i < options.depth:
i += 1
if segment is not None:
keys.append(segment.group(1))
keys.append(segment.group())
# Update the key to start searching from the next position
key = key[segment.end() :]

Expand Down
14 changes: 14 additions & 0 deletions tests/unit/fixed_qs_issues_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import typing as t

from qs_codec import EncodeOptions, decode, encode


class TestFixedQsIssues:
"""Test cases for fixed issues."""

def test_qs_issue_493(self) -> None:
"""Test case for https://github.com/ljharb/qs/issues/493"""
original: t.Dict[str, t.Any] = {"search": {"withbracket[]": "foobar"}}
encoded: str = "search[withbracket[]]=foobar"
assert encode(original, options=EncodeOptions(encode=False)) == encoded
assert decode(encoded) == original
Loading