diff --git a/AUTHORS.rst b/AUTHORS.rst index dc645c3c6..a43a00f02 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -176,3 +176,4 @@ Contributors (chronological) - Peter C `@somethingnew2-0 `_ - Marcel Jackwerth `@mrcljx` `_ - Fares Abubaker `@Fares-Abubaker `_ +- Nicolas Simonds `@0xDEC0DE `_ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 73ef21dd6..e0e92e489 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,10 @@ Features: accept their internal value types as valid input (:issue:`1415`). Thanks :user:`bitdancer` for the suggestion. +Bug Fixes: + +- ``URL`` fields now properly validate ``file`` paths. + Other changes: - Typing: `Field ` is now a generic type with a type argument for the internal value type. diff --git a/src/marshmallow/validate.py b/src/marshmallow/validate.py index 23e14019e..8a880aaf0 100644 --- a/src/marshmallow/validate.py +++ b/src/marshmallow/validate.py @@ -203,6 +203,7 @@ def __call__(self, value: str) -> str: raise ValidationError(message) # Check first if the scheme is valid + scheme = None if "://" in value: scheme = value.split("://")[0].lower() if scheme not in self.schemes: @@ -212,7 +213,15 @@ def __call__(self, value: str) -> str: relative=self.relative, absolute=self.absolute, require_tld=self.require_tld ) - if not regex.search(value): + # The `file` scheme is a slightly-special case: hostnames are optional, + # and if absent it means `localhost`; fill it in for the validation if + # needed + if scheme == "file" and value.startswith("file:///"): + matched = regex.search(value.replace("file:///", "file://localhost/", 1)) + else: + matched = regex.search(value) + + if not matched: raise ValidationError(message) return value diff --git a/tests/test_validate.py b/tests/test_validate.py index 866276de6..d6d8ce919 100644 --- a/tests/test_validate.py +++ b/tests/test_validate.py @@ -205,6 +205,13 @@ def test_url_custom_scheme(): assert validator(url) == url +def test_url_file_scheme(): + # Ensure that file URLs don't get confused + url = "file:///tmp/tmp1234" + validator = validate.URL(schemes={"file"}) + assert validator(url) == url + + def test_url_relative_and_custom_schemes(): validator = validate.URL(relative=True) # By default, ws not allowed