From 9433f4bbc9652bdde82bbe380984e32f8cfc89c4 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sun, 25 Jan 2026 10:37:09 +0100 Subject: [PATCH] Merge commit from fork --- python_multipart/multipart.py | 4 +++- tests/test_file.py | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/test_file.py diff --git a/python_multipart/multipart.py b/python_multipart/multipart.py index 0cc4c82..1489b7a 100644 --- a/python_multipart/multipart.py +++ b/python_multipart/multipart.py @@ -375,7 +375,9 @@ def __init__(self, file_name: bytes | None, field_name: bytes | None = None, con # Split the extension from the filename. if file_name is not None: - base, ext = os.path.splitext(file_name) + # Extract just the basename to avoid directory traversal + basename = os.path.basename(file_name) + base, ext = os.path.splitext(basename) self._file_base = base self._ext = ext diff --git a/tests/test_file.py b/tests/test_file.py new file mode 100644 index 0000000..4d65232 --- /dev/null +++ b/tests/test_file.py @@ -0,0 +1,26 @@ +from pathlib import Path + +from python_multipart.multipart import File + + +def test_upload_dir_with_leading_slash_in_filename(tmp_path: Path): + upload_dir = tmp_path / "upload" + upload_dir.mkdir() + + # When the file_name provided has a leading slash, we should only use the basename. + # This is to avoid directory traversal. + to_upload = tmp_path / "foo.txt" + + file = File( + bytes(to_upload), + config={ + "UPLOAD_DIR": bytes(upload_dir), + "UPLOAD_KEEP_FILENAME": True, + "UPLOAD_KEEP_EXTENSIONS": True, + "MAX_MEMORY_FILE_SIZE": 10, + }, + ) + file.write(b"123456789012") + assert not file.in_memory + assert Path(upload_dir / "foo.txt").exists() + assert Path(upload_dir / "foo.txt").read_bytes() == b"123456789012"