Skip to content

Commit a47e9c7

Browse files
fix(download_file): sanitize windows separators and add regression test
Agent-Logs-Url: https://github.com/fabiocaccamo/python-fsutil/sessions/6af6fe4f-2ec3-4e2e-91e3-8ec6fa5cbe5f Co-authored-by: fabiocaccamo <1035294+fabiocaccamo@users.noreply.github.com>
1 parent 07741bc commit a47e9c7

2 files changed

Lines changed: 29 additions & 1 deletion

File tree

src/fsutil/operations.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ def download_file(
214214
filename_match = re.search(filename_pattern, content_disposition)
215215
if filename_match:
216216
# sanitize Content-Disposition filename to prevent path traversal
217-
filename = os.path.basename(filename_match.group(1))
217+
filename = filename_match.group(1).replace("\\", "/")
218+
filename = os.path.basename(filename)
218219
# or detect filename from url
219220
if not filename:
220221
filename = get_filename(url)

tests/test_operations.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,33 @@ def test_download_file_without_requests_installed(temp_path):
232232
fsutil.download_file(url, dirpath=temp_path())
233233

234234

235+
def test_download_file_content_disposition_filename_is_sanitized(temp_path):
236+
class MockResponse:
237+
headers = {"content-disposition": 'attachment; filename="..\\..\\evil.txt"'}
238+
239+
def __enter__(self):
240+
return self
241+
242+
def __exit__(self, exc_type, exc_val, exc_tb):
243+
return None
244+
245+
def raise_for_status(self):
246+
return None
247+
248+
def iter_content(self, chunk_size=1):
249+
yield b"hello world"
250+
251+
class MockRequests:
252+
def get(self, *args, **kwargs):
253+
return MockResponse()
254+
255+
with patch("fsutil.operations.require_requests", return_value=MockRequests()):
256+
path = fsutil.download_file("https://example.com/file.txt", dirpath=temp_path())
257+
assert fsutil.exists(path)
258+
assert fsutil.get_filename(path) == "evil.txt"
259+
assert path == temp_path("evil.txt")
260+
261+
235262
def test_list_dirs(temp_path):
236263
for i in range(0, 5):
237264
fsutil.create_dir(temp_path(f"a/b/c/d-{i}"))

0 commit comments

Comments
 (0)