Skip to content

Commit

Permalink
add allow_overwrite parameter in file system storage
Browse files Browse the repository at this point in the history
  • Loading branch information
bcail committed Apr 23, 2024
1 parent 19b090c commit 59124ab
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 18 deletions.
24 changes: 20 additions & 4 deletions django/core/files/storage/filesystem.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import os
import pathlib
from datetime import datetime, timezone
from urllib.parse import urljoin

from django.conf import settings
from django.core.exceptions import SuspiciousFileOperation
from django.core.files import File, locks
from django.core.files.move import file_move_safe
from django.core.files.utils import validate_file_name
from django.core.signals import setting_changed
from django.utils._os import safe_join
from django.utils.deconstruct import deconstructible
Expand All @@ -21,19 +24,19 @@ class FileSystemStorage(Storage, StorageSettingsMixin):
Standard filesystem storage
"""

ALLOW_OVERWRITE = False

def __init__(
self,
location=None,
base_url=None,
file_permissions_mode=None,
directory_permissions_mode=None,
allow_overwrite=False,
):
self._location = location
self._base_url = base_url
self._file_permissions_mode = file_permissions_mode
self._directory_permissions_mode = directory_permissions_mode
self._allow_overwrite = allow_overwrite
setting_changed.connect(self._clear_cached_properties)

@cached_property
Expand Down Expand Up @@ -62,6 +65,19 @@ def directory_permissions_mode(self):
self._directory_permissions_mode, settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS
)

def get_available_name(self, name, max_length=None):
if self._allow_overwrite:
name = str(name).replace("\\", "/")
dir_name, file_name = os.path.split(name)
if ".." in pathlib.PurePath(dir_name).parts:
raise SuspiciousFileOperation(
"Detected path traversal attempt in '%s'" % dir_name
)
validate_file_name(file_name)
return name
else:
return super().get_available_name(name, max_length)

def _open(self, name, mode="rb"):
return File(open(self.path(name), mode))

Expand Down Expand Up @@ -99,13 +115,13 @@ def _save(self, name, content):
file_move_safe(
content.temporary_file_path(),
full_path,
allow_overwrite=self.ALLOW_OVERWRITE,
allow_overwrite=self._allow_overwrite,
)

# This is a normal uploadedfile that we can stream.
else:
open_flags = os.O_WRONLY | os.O_CREAT | getattr(os, "O_BINARY", 0)
if not self.ALLOW_OVERWRITE:
if not self._allow_overwrite:
open_flags |= os.O_EXCL
fd = os.open(full_path, open_flags, 0o666)
_file = None
Expand Down
22 changes: 8 additions & 14 deletions tests/file_storage/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,21 +607,15 @@ def test_custom_get_available_name(self):
self.storage.delete(second)


class OverwritingStorage(FileSystemStorage):
"""
Overwrite existing files instead of appending a suffix to generate an
unused name.
"""

ALLOW_OVERWRITE = True

def get_available_name(self, name, max_length=None):
"""Override the effort to find an used name."""
return name


class OverwritingStorageTests(FileStorageTests):
storage_class = OverwritingStorage
storage_class = FileSystemStorage

def setUp(self):
self.temp_dir = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, self.temp_dir)
self.storage = self.storage_class(
location=self.temp_dir, base_url="/test_media_url/", allow_overwrite=True
)

def test_save_overwrite_behavior(self):
"""Saving to same file name twice overwrites the first file."""
Expand Down

0 comments on commit 59124ab

Please sign in to comment.