Skip to content

Commit 76bc28a

Browse files
0xDEC0DEsloria
andauthored
fix: add file handling to URL fields (#2800)
* fix: add `file` handling to URL fields Requires a modicum of special handling due to hostnames being optional. Fixes: Issue #2249 * Add more tests cases; update changelog --------- Co-authored-by: Steven Loria <[email protected]>
1 parent ea26aeb commit 76bc28a

File tree

4 files changed

+48
-3
lines changed

4 files changed

+48
-3
lines changed

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,4 @@ Contributors (chronological)
176176
- Peter C `@somethingnew2-0 <https://github.com/somethingnew2-0>`_
177177
- Marcel Jackwerth `@mrcljx` <https://github.com/mrcljx>`_
178178
- Fares Abubaker `@Fares-Abubaker <https://github.com/Fares-Abubaker>`_
179+
- Nicolas Simonds `@0xDEC0DE <https://github.com/0xDEC0DE>`_

CHANGELOG.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Bug fixes:
1717
- Correctly handle multiple `@post_load <marshmallow.post_load>` methods where one method appends to
1818
the data and another passes ``pass_original=True`` (:issue:`1755`).
1919
Thanks :user:`ghostwheel42` for reporting.
20+
- ``URL`` fields now properly validate ``file`` paths (:issue:`2249`).
21+
Thanks :user:`0xDEC0DE` for reporting and fixing.
2022

2123
Documentation:
2224

@@ -25,8 +27,8 @@ Documentation:
2527

2628
Deprecations:
2729

28-
- The ``ordered`` `class Meta <marshmallow.Schema.Meta>` option is deprecated (:issue:`2146`, :pr:`2762`).
29-
Field order is already preserved by default. Set `marshmallow.Schema.dict_class` to `collections.OrderedDict`
30+
- The ``ordered`` `class Meta <marshmallow.Schema.Meta>` option is deprecated (:issue:`2146`, :pr:`2762`).
31+
Field order is already preserved by default. Set `marshmallow.Schema.dict_class` to `collections.OrderedDict`
3032
to maintain the previous behavior.
3133

3234
3.25.1 (2025-01-11)

src/marshmallow/validate.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ def __call__(self, value: str) -> str:
216216
raise ValidationError(message)
217217

218218
# Check first if the scheme is valid
219+
scheme = None
219220
if "://" in value:
220221
scheme = value.split("://")[0].lower()
221222
if scheme not in self.schemes:
@@ -225,7 +226,14 @@ def __call__(self, value: str) -> str:
225226
relative=self.relative, absolute=self.absolute, require_tld=self.require_tld
226227
)
227228

228-
if not regex.search(value):
229+
# Hostname is optional for file URLS. If absent it means `localhost`.
230+
# Fill it in for the validation if needed
231+
if scheme == "file" and value.startswith("file:///"):
232+
matched = regex.search(value.replace("file:///", "file://localhost/", 1))
233+
else:
234+
matched = regex.search(value)
235+
236+
if not matched:
229237
raise ValidationError(message)
230238

231239
return value

tests/test_validate.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,40 @@ def test_url_custom_scheme():
205205
assert validator(url) == url
206206

207207

208+
@pytest.mark.parametrize(
209+
"valid_url",
210+
(
211+
"file:///tmp/tmp1234",
212+
"file://localhost/tmp/tmp1234",
213+
"file:///C:/Users/test/file.txt",
214+
"file://localhost/C:/Program%20Files/file.exe",
215+
"file:///home/user/documents/test.pdf",
216+
"file:///tmp/test%20file.txt",
217+
"file:///",
218+
"file://localhost/",
219+
),
220+
)
221+
def test_url_accepts_valid_file_urls(valid_url):
222+
validator = validate.URL(schemes={"file"})
223+
assert validator(valid_url) == valid_url
224+
225+
226+
@pytest.mark.parametrize(
227+
"invalid_url",
228+
(
229+
"file://",
230+
"file:/tmp/file.txt",
231+
"file:tmp/file.txt",
232+
"file://hostname/path",
233+
"file:///tmp/test file.txt",
234+
),
235+
)
236+
def test_url_rejects_invalid_file_urls(invalid_url):
237+
validator = validate.URL(schemes={"file"})
238+
with pytest.raises(ValidationError, match="Not a valid URL."):
239+
assert validator(invalid_url)
240+
241+
208242
def test_url_relative_and_custom_schemes():
209243
validator = validate.URL(relative=True)
210244
# By default, ws not allowed

0 commit comments

Comments
 (0)