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
2 changes: 1 addition & 1 deletion src/validators/url.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ def url(
(Literal[True]): If `value` is a valid url.
(ValidationError): If `value` is an invalid url.
"""
if not value or re.search(r"\s", value):
if not value or re.search(r"[\s\x00-\x1f\x7f]", value):
# url must not contain any white
# spaces, they must be encoded
return False
Expand Down
25 changes: 25 additions & 0 deletions tests/test_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,28 @@ def test_returns_failed_validation_on_invalid_private_url(value: str, private: O
def test_returns_string_for_valid_file_url(value: str):
"""Test returns true on valid file url. Ref: RFC 8089."""
assert url(value)


@pytest.mark.parametrize("control", [chr(code) for code in range(32)] + ["\x7f"])
@pytest.mark.parametrize(
"template",
[
"{control}https://example.com/",
"https://user{control}@example.com/",
"https://example.com/a{control}b",
"https://example.com/?q=a{control}b",
"https://example.com/#a{control}b",
],
)
def test_url_rejects_raw_ascii_controls(control: str, template: str):
"""Reject raw control characters before URL parsing can discard them."""
assert isinstance(url(template.format(control=control)), ValidationError)


@pytest.mark.parametrize("encoded", ["%00", "%09", "%0A", "%1F", "%7F"])
@pytest.mark.parametrize(
"template", ["https://example.com/a{encoded}b", "https://example.com/?q=a{encoded}b"]
)
def test_url_accepts_percent_encoded_controls(encoded: str, template: str):
"""Keep escaped data valid while rejecting literal control characters."""
assert url(template.format(encoded=encoded)) is True