Initial commit
This commit is contained in:
3
.venv/Lib/site-packages/django/core/files/__init__.py
Normal file
3
.venv/Lib/site-packages/django/core/files/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.core.files.base import File
|
||||
|
||||
__all__ = ["File"]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
161
.venv/Lib/site-packages/django/core/files/base.py
Normal file
161
.venv/Lib/site-packages/django/core/files/base.py
Normal file
@@ -0,0 +1,161 @@
|
||||
import os
|
||||
from io import BytesIO, StringIO, UnsupportedOperation
|
||||
|
||||
from django.core.files.utils import FileProxyMixin
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
class File(FileProxyMixin):
|
||||
DEFAULT_CHUNK_SIZE = 64 * 2**10
|
||||
|
||||
def __init__(self, file, name=None):
|
||||
self.file = file
|
||||
if name is None:
|
||||
name = getattr(file, "name", None)
|
||||
self.name = name
|
||||
if hasattr(file, "mode"):
|
||||
self.mode = file.mode
|
||||
|
||||
def __str__(self):
|
||||
return self.name or ""
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s>" % (self.__class__.__name__, self or "None")
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.name)
|
||||
|
||||
def __len__(self):
|
||||
return self.size
|
||||
|
||||
@cached_property
|
||||
def size(self):
|
||||
if hasattr(self.file, "size"):
|
||||
return self.file.size
|
||||
if hasattr(self.file, "name"):
|
||||
try:
|
||||
return os.path.getsize(self.file.name)
|
||||
except (OSError, TypeError):
|
||||
pass
|
||||
if hasattr(self.file, "tell") and hasattr(self.file, "seek"):
|
||||
pos = self.file.tell()
|
||||
self.file.seek(0, os.SEEK_END)
|
||||
size = self.file.tell()
|
||||
self.file.seek(pos)
|
||||
return size
|
||||
raise AttributeError("Unable to determine the file's size.")
|
||||
|
||||
def chunks(self, chunk_size=None):
|
||||
"""
|
||||
Read the file and yield chunks of ``chunk_size`` bytes (defaults to
|
||||
``File.DEFAULT_CHUNK_SIZE``).
|
||||
"""
|
||||
chunk_size = chunk_size or self.DEFAULT_CHUNK_SIZE
|
||||
try:
|
||||
self.seek(0)
|
||||
except (AttributeError, UnsupportedOperation):
|
||||
pass
|
||||
|
||||
while True:
|
||||
data = self.read(chunk_size)
|
||||
if not data:
|
||||
break
|
||||
yield data
|
||||
|
||||
def multiple_chunks(self, chunk_size=None):
|
||||
"""
|
||||
Return ``True`` if you can expect multiple chunks.
|
||||
|
||||
NB: If a particular file representation is in memory, subclasses should
|
||||
always return ``False`` -- there's no good reason to read from memory in
|
||||
chunks.
|
||||
"""
|
||||
return self.size > (chunk_size or self.DEFAULT_CHUNK_SIZE)
|
||||
|
||||
def __iter__(self):
|
||||
# Iterate over this file-like object by newlines
|
||||
buffer_ = None
|
||||
for chunk in self.chunks():
|
||||
for line in chunk.splitlines(True):
|
||||
if buffer_:
|
||||
if endswith_cr(buffer_) and not equals_lf(line):
|
||||
# Line split after a \r newline; yield buffer_.
|
||||
yield buffer_
|
||||
# Continue with line.
|
||||
else:
|
||||
# Line either split without a newline (line
|
||||
# continues after buffer_) or with \r\n
|
||||
# newline (line == b'\n').
|
||||
line = buffer_ + line
|
||||
# buffer_ handled, clear it.
|
||||
buffer_ = None
|
||||
|
||||
# If this is the end of a \n or \r\n line, yield.
|
||||
if endswith_lf(line):
|
||||
yield line
|
||||
else:
|
||||
buffer_ = line
|
||||
|
||||
if buffer_ is not None:
|
||||
yield buffer_
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.close()
|
||||
|
||||
def open(self, mode=None, *args, **kwargs):
|
||||
if not self.closed:
|
||||
self.seek(0)
|
||||
elif self.name and os.path.exists(self.name):
|
||||
self.file = open(self.name, mode or self.mode, *args, **kwargs)
|
||||
else:
|
||||
raise ValueError("The file cannot be reopened.")
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
self.file.close()
|
||||
|
||||
|
||||
class ContentFile(File):
|
||||
"""
|
||||
A File-like object that takes just raw content, rather than an actual file.
|
||||
"""
|
||||
|
||||
def __init__(self, content, name=None):
|
||||
stream_class = StringIO if isinstance(content, str) else BytesIO
|
||||
super().__init__(stream_class(content), name=name)
|
||||
self.size = len(content)
|
||||
|
||||
def __str__(self):
|
||||
return "Raw content"
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
def open(self, mode=None):
|
||||
self.seek(0)
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def write(self, data):
|
||||
self.__dict__.pop("size", None) # Clear the computed size.
|
||||
return self.file.write(data)
|
||||
|
||||
|
||||
def endswith_cr(line):
|
||||
"""Return True if line (a text or bytestring) ends with '\r'."""
|
||||
return line.endswith("\r" if isinstance(line, str) else b"\r")
|
||||
|
||||
|
||||
def endswith_lf(line):
|
||||
"""Return True if line (a text or bytestring) ends with '\n'."""
|
||||
return line.endswith("\n" if isinstance(line, str) else b"\n")
|
||||
|
||||
|
||||
def equals_lf(line):
|
||||
"""Return True if line (a text or bytestring) equals '\n'."""
|
||||
return line == ("\n" if isinstance(line, str) else b"\n")
|
||||
89
.venv/Lib/site-packages/django/core/files/images.py
Normal file
89
.venv/Lib/site-packages/django/core/files/images.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
Utility functions for handling images.
|
||||
|
||||
Requires Pillow as you might imagine.
|
||||
"""
|
||||
|
||||
import struct
|
||||
import zlib
|
||||
|
||||
from django.core.files import File
|
||||
|
||||
|
||||
class ImageFile(File):
|
||||
"""
|
||||
A mixin for use alongside django.core.files.base.File, which provides
|
||||
additional features for dealing with images.
|
||||
"""
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._get_image_dimensions()[0]
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self._get_image_dimensions()[1]
|
||||
|
||||
def _get_image_dimensions(self):
|
||||
if not hasattr(self, "_dimensions_cache"):
|
||||
close = self.closed
|
||||
self.open()
|
||||
self._dimensions_cache = get_image_dimensions(self, close=close)
|
||||
return self._dimensions_cache
|
||||
|
||||
|
||||
def get_image_dimensions(file_or_path, close=False):
|
||||
"""
|
||||
Return the (width, height) of an image, given an open file or a path. Set
|
||||
'close' to True to close the file at the end if it is initially in an open
|
||||
state.
|
||||
"""
|
||||
from PIL import ImageFile as PillowImageFile
|
||||
|
||||
p = PillowImageFile.Parser()
|
||||
if hasattr(file_or_path, "read"):
|
||||
file = file_or_path
|
||||
file_pos = file.tell()
|
||||
file.seek(0)
|
||||
else:
|
||||
try:
|
||||
file = open(file_or_path, "rb")
|
||||
except OSError:
|
||||
return (None, None)
|
||||
close = True
|
||||
try:
|
||||
# Most of the time Pillow only needs a small chunk to parse the image
|
||||
# and get the dimensions, but with some TIFF files Pillow needs to
|
||||
# parse the whole file.
|
||||
chunk_size = 1024
|
||||
while 1:
|
||||
data = file.read(chunk_size)
|
||||
if not data:
|
||||
break
|
||||
try:
|
||||
p.feed(data)
|
||||
except zlib.error as e:
|
||||
# ignore zlib complaining on truncated stream, just feed more
|
||||
# data to parser (ticket #19457).
|
||||
if e.args[0].startswith("Error -5"):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
except struct.error:
|
||||
# Ignore PIL failing on a too short buffer when reads return
|
||||
# less bytes than expected. Skip and feed more data to the
|
||||
# parser (ticket #24544).
|
||||
pass
|
||||
except RuntimeError:
|
||||
# e.g. "RuntimeError: could not create decoder object" for
|
||||
# WebP files. A different chunk_size may work.
|
||||
pass
|
||||
if p.image:
|
||||
return p.image.size
|
||||
chunk_size *= 2
|
||||
return (None, None)
|
||||
finally:
|
||||
if close:
|
||||
file.close()
|
||||
else:
|
||||
file.seek(file_pos)
|
||||
128
.venv/Lib/site-packages/django/core/files/locks.py
Normal file
128
.venv/Lib/site-packages/django/core/files/locks.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""
|
||||
Portable file locking utilities.
|
||||
|
||||
Based partially on an example by Jonathan Feignberg in the Python
|
||||
Cookbook [1] (licensed under the Python Software License) and a ctypes port by
|
||||
Anatoly Techtonik for Roundup [2] (license [3]).
|
||||
|
||||
[1] https://code.activestate.com/recipes/65203/
|
||||
[2] https://sourceforge.net/p/roundup/code/ci/default/tree/roundup/backends/portalocker.py # NOQA
|
||||
[3] https://sourceforge.net/p/roundup/code/ci/default/tree/COPYING.txt
|
||||
|
||||
Example Usage::
|
||||
|
||||
>>> from django.core.files import locks
|
||||
>>> with open('./file', 'wb') as f:
|
||||
... locks.lock(f, locks.LOCK_EX)
|
||||
... f.write('Django')
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
__all__ = ("LOCK_EX", "LOCK_SH", "LOCK_NB", "lock", "unlock")
|
||||
|
||||
|
||||
def _fd(f):
|
||||
"""Get a filedescriptor from something which could be a file or an fd."""
|
||||
return f.fileno() if hasattr(f, "fileno") else f
|
||||
|
||||
|
||||
if os.name == "nt":
|
||||
import msvcrt
|
||||
from ctypes import (
|
||||
POINTER,
|
||||
Structure,
|
||||
Union,
|
||||
WinDLL,
|
||||
byref,
|
||||
c_int64,
|
||||
c_ulong,
|
||||
c_void_p,
|
||||
sizeof,
|
||||
)
|
||||
from ctypes.wintypes import BOOL, DWORD, HANDLE
|
||||
|
||||
LOCK_SH = 0 # the default
|
||||
LOCK_NB = 0x1 # LOCKFILE_FAIL_IMMEDIATELY
|
||||
LOCK_EX = 0x2 # LOCKFILE_EXCLUSIVE_LOCK
|
||||
|
||||
# --- Adapted from the pyserial project ---
|
||||
# detect size of ULONG_PTR
|
||||
if sizeof(c_ulong) != sizeof(c_void_p):
|
||||
ULONG_PTR = c_int64
|
||||
else:
|
||||
ULONG_PTR = c_ulong
|
||||
PVOID = c_void_p
|
||||
|
||||
# --- Union inside Structure by stackoverflow:3480240 ---
|
||||
class _OFFSET(Structure):
|
||||
_fields_ = [("Offset", DWORD), ("OffsetHigh", DWORD)]
|
||||
|
||||
class _OFFSET_UNION(Union):
|
||||
_anonymous_ = ["_offset"]
|
||||
_fields_ = [("_offset", _OFFSET), ("Pointer", PVOID)]
|
||||
|
||||
class OVERLAPPED(Structure):
|
||||
_anonymous_ = ["_offset_union"]
|
||||
_fields_ = [
|
||||
("Internal", ULONG_PTR),
|
||||
("InternalHigh", ULONG_PTR),
|
||||
("_offset_union", _OFFSET_UNION),
|
||||
("hEvent", HANDLE),
|
||||
]
|
||||
|
||||
LPOVERLAPPED = POINTER(OVERLAPPED)
|
||||
|
||||
# --- Define function prototypes for extra safety ---
|
||||
kernel32 = WinDLL("kernel32")
|
||||
LockFileEx = kernel32.LockFileEx
|
||||
LockFileEx.restype = BOOL
|
||||
LockFileEx.argtypes = [HANDLE, DWORD, DWORD, DWORD, DWORD, LPOVERLAPPED]
|
||||
UnlockFileEx = kernel32.UnlockFileEx
|
||||
UnlockFileEx.restype = BOOL
|
||||
UnlockFileEx.argtypes = [HANDLE, DWORD, DWORD, DWORD, LPOVERLAPPED]
|
||||
|
||||
def lock(f, flags):
|
||||
hfile = msvcrt.get_osfhandle(_fd(f))
|
||||
overlapped = OVERLAPPED()
|
||||
ret = LockFileEx(hfile, flags, 0, 0, 0xFFFF0000, byref(overlapped))
|
||||
return bool(ret)
|
||||
|
||||
def unlock(f):
|
||||
hfile = msvcrt.get_osfhandle(_fd(f))
|
||||
overlapped = OVERLAPPED()
|
||||
ret = UnlockFileEx(hfile, 0, 0, 0xFFFF0000, byref(overlapped))
|
||||
return bool(ret)
|
||||
|
||||
else:
|
||||
try:
|
||||
import fcntl
|
||||
|
||||
LOCK_SH = fcntl.LOCK_SH # shared lock
|
||||
LOCK_NB = fcntl.LOCK_NB # non-blocking
|
||||
LOCK_EX = fcntl.LOCK_EX
|
||||
except (ImportError, AttributeError):
|
||||
# File locking is not supported.
|
||||
LOCK_EX = LOCK_SH = LOCK_NB = 0
|
||||
|
||||
# Dummy functions that don't do anything.
|
||||
def lock(f, flags):
|
||||
# File is not locked
|
||||
return False
|
||||
|
||||
def unlock(f):
|
||||
# File is unlocked
|
||||
return True
|
||||
|
||||
else:
|
||||
|
||||
def lock(f, flags):
|
||||
try:
|
||||
fcntl.flock(_fd(f), flags)
|
||||
return True
|
||||
except BlockingIOError:
|
||||
return False
|
||||
|
||||
def unlock(f):
|
||||
fcntl.flock(_fd(f), fcntl.LOCK_UN)
|
||||
return True
|
||||
91
.venv/Lib/site-packages/django/core/files/move.py
Normal file
91
.venv/Lib/site-packages/django/core/files/move.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
Move a file in the safest way possible::
|
||||
|
||||
>>> from django.core.files.move import file_move_safe
|
||||
>>> file_move_safe("/tmp/old_file", "/tmp/new_file")
|
||||
"""
|
||||
|
||||
import os
|
||||
from shutil import copymode, copystat
|
||||
|
||||
from django.core.files import locks
|
||||
|
||||
__all__ = ["file_move_safe"]
|
||||
|
||||
|
||||
def file_move_safe(
|
||||
old_file_name, new_file_name, chunk_size=1024 * 64, allow_overwrite=False
|
||||
):
|
||||
"""
|
||||
Move a file from one location to another in the safest way possible.
|
||||
|
||||
First, try ``os.rename``, which is simple but will break across filesystems.
|
||||
If that fails, stream manually from one file to another in pure Python.
|
||||
|
||||
If the destination file exists and ``allow_overwrite`` is ``False``, raise
|
||||
``FileExistsError``.
|
||||
"""
|
||||
# There's no reason to move if we don't have to.
|
||||
try:
|
||||
if os.path.samefile(old_file_name, new_file_name):
|
||||
return
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if not allow_overwrite and os.access(new_file_name, os.F_OK):
|
||||
raise FileExistsError(
|
||||
f"Destination file {new_file_name} exists and allow_overwrite is False."
|
||||
)
|
||||
|
||||
try:
|
||||
os.rename(old_file_name, new_file_name)
|
||||
return
|
||||
except OSError:
|
||||
# OSError happens with os.rename() if moving to another filesystem or
|
||||
# when moving opened files on certain operating systems.
|
||||
pass
|
||||
|
||||
# first open the old file, so that it won't go away
|
||||
with open(old_file_name, "rb") as old_file:
|
||||
# now open the new file, not forgetting allow_overwrite
|
||||
fd = os.open(
|
||||
new_file_name,
|
||||
(
|
||||
os.O_WRONLY
|
||||
| os.O_CREAT
|
||||
| getattr(os, "O_BINARY", 0)
|
||||
| (os.O_EXCL if not allow_overwrite else 0)
|
||||
| os.O_TRUNC
|
||||
),
|
||||
)
|
||||
try:
|
||||
locks.lock(fd, locks.LOCK_EX)
|
||||
current_chunk = None
|
||||
while current_chunk != b"":
|
||||
current_chunk = old_file.read(chunk_size)
|
||||
os.write(fd, current_chunk)
|
||||
finally:
|
||||
locks.unlock(fd)
|
||||
os.close(fd)
|
||||
|
||||
try:
|
||||
copystat(old_file_name, new_file_name)
|
||||
except PermissionError:
|
||||
# Certain filesystems (e.g. CIFS) fail to copy the file's metadata if
|
||||
# the type of the destination filesystem isn't the same as the source
|
||||
# filesystem. This also happens with some SELinux-enabled systems.
|
||||
# Ignore that, but try to set basic permissions.
|
||||
try:
|
||||
copymode(old_file_name, new_file_name)
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
try:
|
||||
os.remove(old_file_name)
|
||||
except PermissionError as e:
|
||||
# Certain operating systems (Cygwin and Windows)
|
||||
# fail when deleting opened files, ignore it. (For the
|
||||
# systems where this happens, temporary files will be auto-deleted
|
||||
# on close anyway.)
|
||||
if getattr(e, "winerror", 0) != 32:
|
||||
raise
|
||||
@@ -0,0 +1,27 @@
|
||||
from django.conf import DEFAULT_STORAGE_ALIAS
|
||||
from django.utils.functional import LazyObject
|
||||
|
||||
from .base import Storage
|
||||
from .filesystem import FileSystemStorage
|
||||
from .handler import InvalidStorageError, StorageHandler
|
||||
from .memory import InMemoryStorage
|
||||
|
||||
__all__ = (
|
||||
"FileSystemStorage",
|
||||
"InMemoryStorage",
|
||||
"Storage",
|
||||
"DefaultStorage",
|
||||
"default_storage",
|
||||
"InvalidStorageError",
|
||||
"StorageHandler",
|
||||
"storages",
|
||||
)
|
||||
|
||||
|
||||
class DefaultStorage(LazyObject):
|
||||
def _setup(self):
|
||||
self._wrapped = storages[DEFAULT_STORAGE_ALIAS]
|
||||
|
||||
|
||||
storages = StorageHandler()
|
||||
default_storage = DefaultStorage()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
206
.venv/Lib/site-packages/django/core/files/storage/base.py
Normal file
206
.venv/Lib/site-packages/django/core/files/storage/base.py
Normal file
@@ -0,0 +1,206 @@
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
from django.core.exceptions import SuspiciousFileOperation
|
||||
from django.core.files import File
|
||||
from django.core.files.utils import validate_file_name
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.text import get_valid_filename
|
||||
|
||||
|
||||
class Storage:
|
||||
"""
|
||||
A base storage class, providing some default behaviors that all other
|
||||
storage systems can inherit or override, as necessary.
|
||||
"""
|
||||
|
||||
# The following methods represent a public interface to private methods.
|
||||
# These shouldn't be overridden by subclasses unless absolutely necessary.
|
||||
|
||||
def open(self, name, mode="rb"):
|
||||
"""Retrieve the specified file from storage."""
|
||||
return self._open(name, mode)
|
||||
|
||||
def save(self, name, content, max_length=None):
|
||||
"""
|
||||
Save new content to the file specified by name. The content should be
|
||||
a proper File object or any Python file-like object, ready to be read
|
||||
from the beginning.
|
||||
"""
|
||||
# Get the proper name for the file, as it will actually be saved.
|
||||
if name is None:
|
||||
name = content.name
|
||||
|
||||
if not hasattr(content, "chunks"):
|
||||
content = File(content, name)
|
||||
|
||||
# Ensure that the name is valid, before and after having the storage
|
||||
# system potentially modifying the name. This duplicates the check made
|
||||
# inside `get_available_name` but it's necessary for those cases where
|
||||
# `get_available_name` is overriden and validation is lost.
|
||||
validate_file_name(name, allow_relative_path=True)
|
||||
|
||||
# Potentially find a different name depending on storage constraints.
|
||||
name = self.get_available_name(name, max_length=max_length)
|
||||
# Validate the (potentially) new name.
|
||||
validate_file_name(name, allow_relative_path=True)
|
||||
|
||||
# The save operation should return the actual name of the file saved.
|
||||
name = self._save(name, content)
|
||||
# Ensure that the name returned from the storage system is still valid.
|
||||
validate_file_name(name, allow_relative_path=True)
|
||||
return name
|
||||
|
||||
def is_name_available(self, name, max_length=None):
|
||||
exceeds_max_length = max_length and len(name) > max_length
|
||||
return not self.exists(name) and not exceeds_max_length
|
||||
|
||||
# These methods are part of the public API, with default implementations.
|
||||
|
||||
def get_valid_name(self, name):
|
||||
"""
|
||||
Return a filename, based on the provided filename, that's suitable for
|
||||
use in the target storage system.
|
||||
"""
|
||||
return get_valid_filename(name)
|
||||
|
||||
def get_alternative_name(self, file_root, file_ext):
|
||||
"""
|
||||
Return an alternative filename, by adding an underscore and a random 7
|
||||
character alphanumeric string (before the file extension, if one
|
||||
exists) to the filename.
|
||||
"""
|
||||
return "%s_%s%s" % (file_root, get_random_string(7), file_ext)
|
||||
|
||||
def get_available_name(self, name, max_length=None):
|
||||
"""
|
||||
Return a filename that's free on the target storage system and
|
||||
available for new content to be written to.
|
||||
"""
|
||||
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)
|
||||
file_ext = "".join(pathlib.PurePath(file_name).suffixes)
|
||||
file_root = file_name.removesuffix(file_ext)
|
||||
# If the filename is not available, generate an alternative
|
||||
# filename until one is available.
|
||||
# Truncate original name if required, so the new filename does not
|
||||
# exceed the max_length.
|
||||
while not self.is_name_available(name, max_length=max_length):
|
||||
# file_ext includes the dot.
|
||||
name = os.path.join(
|
||||
dir_name, self.get_alternative_name(file_root, file_ext)
|
||||
)
|
||||
if max_length is None:
|
||||
continue
|
||||
# Truncate file_root if max_length exceeded.
|
||||
truncation = len(name) - max_length
|
||||
if truncation > 0:
|
||||
file_root = file_root[:-truncation]
|
||||
# Entire file_root was truncated in attempt to find an
|
||||
# available filename.
|
||||
if not file_root:
|
||||
raise SuspiciousFileOperation(
|
||||
'Storage can not find an available filename for "%s". '
|
||||
"Please make sure that the corresponding file field "
|
||||
'allows sufficient "max_length".' % name
|
||||
)
|
||||
name = os.path.join(
|
||||
dir_name, self.get_alternative_name(file_root, file_ext)
|
||||
)
|
||||
return name
|
||||
|
||||
def generate_filename(self, filename):
|
||||
"""
|
||||
Validate the filename by calling get_valid_name() and return a filename
|
||||
to be passed to the save() method.
|
||||
"""
|
||||
filename = str(filename).replace("\\", "/")
|
||||
# `filename` may include a path as returned by FileField.upload_to.
|
||||
dirname, filename = os.path.split(filename)
|
||||
if ".." in pathlib.PurePath(dirname).parts:
|
||||
raise SuspiciousFileOperation(
|
||||
"Detected path traversal attempt in '%s'" % dirname
|
||||
)
|
||||
return os.path.normpath(os.path.join(dirname, self.get_valid_name(filename)))
|
||||
|
||||
def path(self, name):
|
||||
"""
|
||||
Return a local filesystem path where the file can be retrieved using
|
||||
Python's built-in open() function. Storage systems that can't be
|
||||
accessed using open() should *not* implement this method.
|
||||
"""
|
||||
raise NotImplementedError("This backend doesn't support absolute paths.")
|
||||
|
||||
# The following methods form the public API for storage systems, but with
|
||||
# no default implementations. Subclasses must implement *all* of these.
|
||||
|
||||
def delete(self, name):
|
||||
"""
|
||||
Delete the specified file from the storage system.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of Storage must provide a delete() method"
|
||||
)
|
||||
|
||||
def exists(self, name):
|
||||
"""
|
||||
Return True if a file referenced by the given name already exists in the
|
||||
storage system, or False if the name is available for a new file.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of Storage must provide an exists() method"
|
||||
)
|
||||
|
||||
def listdir(self, path):
|
||||
"""
|
||||
List the contents of the specified path. Return a 2-tuple of lists:
|
||||
the first item being directories, the second item being files.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of Storage must provide a listdir() method"
|
||||
)
|
||||
|
||||
def size(self, name):
|
||||
"""
|
||||
Return the total size, in bytes, of the file specified by name.
|
||||
"""
|
||||
raise NotImplementedError("subclasses of Storage must provide a size() method")
|
||||
|
||||
def url(self, name):
|
||||
"""
|
||||
Return an absolute URL where the file's contents can be accessed
|
||||
directly by a web browser.
|
||||
"""
|
||||
raise NotImplementedError("subclasses of Storage must provide a url() method")
|
||||
|
||||
def get_accessed_time(self, name):
|
||||
"""
|
||||
Return the last accessed time (as a datetime) of the file specified by
|
||||
name. The datetime will be timezone-aware if USE_TZ=True.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of Storage must provide a get_accessed_time() method"
|
||||
)
|
||||
|
||||
def get_created_time(self, name):
|
||||
"""
|
||||
Return the creation time (as a datetime) of the file specified by name.
|
||||
The datetime will be timezone-aware if USE_TZ=True.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of Storage must provide a get_created_time() method"
|
||||
)
|
||||
|
||||
def get_modified_time(self, name):
|
||||
"""
|
||||
Return the last modified time (as a datetime) of the file specified by
|
||||
name. The datetime will be timezone-aware if USE_TZ=True.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of Storage must provide a get_modified_time() method"
|
||||
)
|
||||
248
.venv/Lib/site-packages/django/core/files/storage/filesystem.py
Normal file
248
.venv/Lib/site-packages/django/core/files/storage/filesystem.py
Normal file
@@ -0,0 +1,248 @@
|
||||
import os
|
||||
import warnings
|
||||
from datetime import datetime, timezone
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files import File, locks
|
||||
from django.core.files.move import file_move_safe
|
||||
from django.core.signals import setting_changed
|
||||
from django.utils._os import safe_join
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.deprecation import RemovedInDjango60Warning
|
||||
from django.utils.encoding import filepath_to_uri
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from .base import Storage
|
||||
from .mixins import StorageSettingsMixin
|
||||
|
||||
|
||||
@deconstructible(path="django.core.files.storage.FileSystemStorage")
|
||||
class FileSystemStorage(Storage, StorageSettingsMixin):
|
||||
"""
|
||||
Standard filesystem storage
|
||||
"""
|
||||
|
||||
# RemovedInDjango60Warning: remove OS_OPEN_FLAGS.
|
||||
OS_OPEN_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0)
|
||||
|
||||
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)
|
||||
# RemovedInDjango60Warning: remove this warning.
|
||||
if self.OS_OPEN_FLAGS != os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(
|
||||
os, "O_BINARY", 0
|
||||
):
|
||||
warnings.warn(
|
||||
"Overriding OS_OPEN_FLAGS is deprecated. Use "
|
||||
"the allow_overwrite parameter instead.",
|
||||
RemovedInDjango60Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def base_location(self):
|
||||
return self._value_or_setting(self._location, settings.MEDIA_ROOT)
|
||||
|
||||
@cached_property
|
||||
def location(self):
|
||||
return os.path.abspath(self.base_location)
|
||||
|
||||
@cached_property
|
||||
def base_url(self):
|
||||
if self._base_url is not None and not self._base_url.endswith("/"):
|
||||
self._base_url += "/"
|
||||
return self._value_or_setting(self._base_url, settings.MEDIA_URL)
|
||||
|
||||
@cached_property
|
||||
def file_permissions_mode(self):
|
||||
return self._value_or_setting(
|
||||
self._file_permissions_mode, settings.FILE_UPLOAD_PERMISSIONS
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def directory_permissions_mode(self):
|
||||
return self._value_or_setting(
|
||||
self._directory_permissions_mode, settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS
|
||||
)
|
||||
|
||||
def _open(self, name, mode="rb"):
|
||||
return File(open(self.path(name), mode))
|
||||
|
||||
def _save(self, name, content):
|
||||
full_path = self.path(name)
|
||||
|
||||
# Create any intermediate directories that do not exist.
|
||||
directory = os.path.dirname(full_path)
|
||||
try:
|
||||
if self.directory_permissions_mode is not None:
|
||||
# Set the umask because os.makedirs() doesn't apply the "mode"
|
||||
# argument to intermediate-level directories.
|
||||
old_umask = os.umask(0o777 & ~self.directory_permissions_mode)
|
||||
try:
|
||||
os.makedirs(
|
||||
directory, self.directory_permissions_mode, exist_ok=True
|
||||
)
|
||||
finally:
|
||||
os.umask(old_umask)
|
||||
else:
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
except FileExistsError:
|
||||
raise FileExistsError("%s exists and is not a directory." % directory)
|
||||
|
||||
# There's a potential race condition between get_available_name and
|
||||
# saving the file; it's possible that two threads might return the
|
||||
# same name, at which point all sorts of fun happens. So we need to
|
||||
# try to create the file, but if it already exists we have to go back
|
||||
# to get_available_name() and try again.
|
||||
|
||||
while True:
|
||||
try:
|
||||
# This file has a file path that we can move.
|
||||
if hasattr(content, "temporary_file_path"):
|
||||
file_move_safe(
|
||||
content.temporary_file_path(),
|
||||
full_path,
|
||||
allow_overwrite=self._allow_overwrite,
|
||||
)
|
||||
|
||||
# This is a normal uploadedfile that we can stream.
|
||||
else:
|
||||
# The combination of O_CREAT and O_EXCL makes os.open() raises an
|
||||
# OSError if the file already exists before it's opened.
|
||||
open_flags = (
|
||||
os.O_WRONLY
|
||||
| os.O_CREAT
|
||||
| os.O_EXCL
|
||||
| getattr(os, "O_BINARY", 0)
|
||||
)
|
||||
# RemovedInDjango60Warning: when the deprecation ends, replace with:
|
||||
# if self._allow_overwrite:
|
||||
# open_flags = open_flags & ~os.O_EXCL | os.O_TRUNC
|
||||
if self.OS_OPEN_FLAGS != open_flags:
|
||||
open_flags = self.OS_OPEN_FLAGS
|
||||
elif self._allow_overwrite:
|
||||
open_flags = open_flags & ~os.O_EXCL | os.O_TRUNC
|
||||
fd = os.open(full_path, open_flags, 0o666)
|
||||
_file = None
|
||||
try:
|
||||
locks.lock(fd, locks.LOCK_EX)
|
||||
for chunk in content.chunks():
|
||||
if _file is None:
|
||||
mode = "wb" if isinstance(chunk, bytes) else "wt"
|
||||
_file = os.fdopen(fd, mode)
|
||||
_file.write(chunk)
|
||||
finally:
|
||||
locks.unlock(fd)
|
||||
if _file is not None:
|
||||
_file.close()
|
||||
else:
|
||||
os.close(fd)
|
||||
except FileExistsError:
|
||||
# A new name is needed if the file exists.
|
||||
name = self.get_available_name(name)
|
||||
full_path = self.path(name)
|
||||
else:
|
||||
# OK, the file save worked. Break out of the loop.
|
||||
break
|
||||
|
||||
if self.file_permissions_mode is not None:
|
||||
os.chmod(full_path, self.file_permissions_mode)
|
||||
|
||||
# Ensure the saved path is always relative to the storage root.
|
||||
name = os.path.relpath(full_path, self.location)
|
||||
# Ensure the moved file has the same gid as the storage root.
|
||||
self._ensure_location_group_id(full_path)
|
||||
# Store filenames with forward slashes, even on Windows.
|
||||
return str(name).replace("\\", "/")
|
||||
|
||||
def _ensure_location_group_id(self, full_path):
|
||||
if os.name == "posix":
|
||||
file_gid = os.stat(full_path).st_gid
|
||||
location_gid = os.stat(self.location).st_gid
|
||||
if file_gid != location_gid:
|
||||
try:
|
||||
os.chown(full_path, uid=-1, gid=location_gid)
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
def delete(self, name):
|
||||
if not name:
|
||||
raise ValueError("The name must be given to delete().")
|
||||
name = self.path(name)
|
||||
# If the file or directory exists, delete it from the filesystem.
|
||||
try:
|
||||
if os.path.isdir(name):
|
||||
os.rmdir(name)
|
||||
else:
|
||||
os.remove(name)
|
||||
except FileNotFoundError:
|
||||
# FileNotFoundError is raised if the file or directory was removed
|
||||
# concurrently.
|
||||
pass
|
||||
|
||||
def is_name_available(self, name, max_length=None):
|
||||
if self._allow_overwrite:
|
||||
return not (max_length and len(name) > max_length)
|
||||
return super().is_name_available(name, max_length=max_length)
|
||||
|
||||
def get_alternative_name(self, file_root, file_ext):
|
||||
if self._allow_overwrite:
|
||||
return f"{file_root}{file_ext}"
|
||||
return super().get_alternative_name(file_root, file_ext)
|
||||
|
||||
def exists(self, name):
|
||||
return os.path.lexists(self.path(name))
|
||||
|
||||
def listdir(self, path):
|
||||
path = self.path(path)
|
||||
directories, files = [], []
|
||||
with os.scandir(path) as entries:
|
||||
for entry in entries:
|
||||
if entry.is_dir():
|
||||
directories.append(entry.name)
|
||||
else:
|
||||
files.append(entry.name)
|
||||
return directories, files
|
||||
|
||||
def path(self, name):
|
||||
return safe_join(self.location, name)
|
||||
|
||||
def size(self, name):
|
||||
return os.path.getsize(self.path(name))
|
||||
|
||||
def url(self, name):
|
||||
if self.base_url is None:
|
||||
raise ValueError("This file is not accessible via a URL.")
|
||||
url = filepath_to_uri(name)
|
||||
if url is not None:
|
||||
url = url.lstrip("/")
|
||||
return urljoin(self.base_url, url)
|
||||
|
||||
def _datetime_from_timestamp(self, ts):
|
||||
"""
|
||||
If timezone support is enabled, make an aware datetime object in UTC;
|
||||
otherwise make a naive one in the local timezone.
|
||||
"""
|
||||
tz = timezone.utc if settings.USE_TZ else None
|
||||
return datetime.fromtimestamp(ts, tz=tz)
|
||||
|
||||
def get_accessed_time(self, name):
|
||||
return self._datetime_from_timestamp(os.path.getatime(self.path(name)))
|
||||
|
||||
def get_created_time(self, name):
|
||||
return self._datetime_from_timestamp(os.path.getctime(self.path(name)))
|
||||
|
||||
def get_modified_time(self, name):
|
||||
return self._datetime_from_timestamp(os.path.getmtime(self.path(name)))
|
||||
46
.venv/Lib/site-packages/django/core/files/storage/handler.py
Normal file
46
.venv/Lib/site-packages/django/core/files/storage/handler.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
|
||||
class InvalidStorageError(ImproperlyConfigured):
|
||||
pass
|
||||
|
||||
|
||||
class StorageHandler:
|
||||
def __init__(self, backends=None):
|
||||
# backends is an optional dict of storage backend definitions
|
||||
# (structured like settings.STORAGES).
|
||||
self._backends = backends
|
||||
self._storages = {}
|
||||
|
||||
@cached_property
|
||||
def backends(self):
|
||||
if self._backends is None:
|
||||
self._backends = settings.STORAGES.copy()
|
||||
return self._backends
|
||||
|
||||
def __getitem__(self, alias):
|
||||
try:
|
||||
return self._storages[alias]
|
||||
except KeyError:
|
||||
try:
|
||||
params = self.backends[alias]
|
||||
except KeyError:
|
||||
raise InvalidStorageError(
|
||||
f"Could not find config for '{alias}' in settings.STORAGES."
|
||||
)
|
||||
storage = self.create_storage(params)
|
||||
self._storages[alias] = storage
|
||||
return storage
|
||||
|
||||
def create_storage(self, params):
|
||||
params = params.copy()
|
||||
backend = params.pop("BACKEND")
|
||||
options = params.pop("OPTIONS", {})
|
||||
try:
|
||||
storage_cls = import_string(backend)
|
||||
except ImportError as e:
|
||||
raise InvalidStorageError(f"Could not find backend {backend!r}: {e}") from e
|
||||
return storage_cls(**options)
|
||||
293
.venv/Lib/site-packages/django/core/files/storage/memory.py
Normal file
293
.venv/Lib/site-packages/django/core/files/storage/memory.py
Normal file
@@ -0,0 +1,293 @@
|
||||
"""
|
||||
Based on dj-inmemorystorage (BSD) by Cody Soyland, Seán Hayes, Tore Birkeland,
|
||||
and Nick Presta.
|
||||
"""
|
||||
|
||||
import errno
|
||||
import io
|
||||
import os
|
||||
import pathlib
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.signals import setting_changed
|
||||
from django.utils._os import safe_join
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.encoding import filepath_to_uri
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
|
||||
from .base import Storage
|
||||
from .mixins import StorageSettingsMixin
|
||||
|
||||
__all__ = ("InMemoryStorage",)
|
||||
|
||||
|
||||
class TimingMixin:
|
||||
def _initialize_times(self):
|
||||
self.created_time = now()
|
||||
self.accessed_time = self.created_time
|
||||
self.modified_time = self.created_time
|
||||
|
||||
def _update_accessed_time(self):
|
||||
self.accessed_time = now()
|
||||
|
||||
def _update_modified_time(self):
|
||||
self.modified_time = now()
|
||||
|
||||
|
||||
class InMemoryFileNode(ContentFile, TimingMixin):
|
||||
"""
|
||||
Helper class representing an in-memory file node.
|
||||
|
||||
Handle unicode/bytes conversion during I/O operations and record creation,
|
||||
modification, and access times.
|
||||
"""
|
||||
|
||||
def __init__(self, content="", name=None):
|
||||
super().__init__(content, name)
|
||||
self._content_type = type(content)
|
||||
self._initialize_times()
|
||||
|
||||
def open(self, mode):
|
||||
self._convert_stream_content(mode)
|
||||
self._update_accessed_time()
|
||||
return super().open(mode)
|
||||
|
||||
def write(self, data):
|
||||
super().write(data)
|
||||
self._update_modified_time()
|
||||
|
||||
def _initialize_stream(self):
|
||||
"""Initialize underlying stream according to the content type."""
|
||||
self.file = io.BytesIO() if self._content_type == bytes else io.StringIO()
|
||||
|
||||
def _convert_stream_content(self, mode):
|
||||
"""Convert actual file content according to the opening mode."""
|
||||
new_content_type = bytes if "b" in mode else str
|
||||
# No conversion needed.
|
||||
if self._content_type == new_content_type:
|
||||
return
|
||||
|
||||
content = self.file.getvalue()
|
||||
content = content.encode() if isinstance(content, str) else content.decode()
|
||||
self._content_type = new_content_type
|
||||
self._initialize_stream()
|
||||
|
||||
self.file.write(content)
|
||||
|
||||
|
||||
class InMemoryDirNode(TimingMixin):
|
||||
"""
|
||||
Helper class representing an in-memory directory node.
|
||||
|
||||
Handle path navigation of directory trees, creating missing nodes if
|
||||
needed.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._children = {}
|
||||
self._initialize_times()
|
||||
|
||||
def resolve(self, path, create_if_missing=False, leaf_cls=None, check_exists=True):
|
||||
"""
|
||||
Navigate current directory tree, returning node matching path or
|
||||
creating a new one, if missing.
|
||||
- path: path of the node to search
|
||||
- create_if_missing: create nodes if not exist. Defaults to False.
|
||||
- leaf_cls: expected type of leaf node. Defaults to None.
|
||||
- check_exists: if True and the leaf node does not exist, raise a
|
||||
FileNotFoundError. Defaults to True.
|
||||
"""
|
||||
path_segments = list(pathlib.Path(path).parts)
|
||||
current_node = self
|
||||
|
||||
while path_segments:
|
||||
path_segment = path_segments.pop(0)
|
||||
# If current node is a file node and there are unprocessed
|
||||
# segments, raise an error.
|
||||
if isinstance(current_node, InMemoryFileNode):
|
||||
path_segments = os.path.split(path)
|
||||
current_path = "/".join(
|
||||
path_segments[: path_segments.index(path_segment)]
|
||||
)
|
||||
raise NotADirectoryError(
|
||||
errno.ENOTDIR, os.strerror(errno.ENOTDIR), current_path
|
||||
)
|
||||
current_node = current_node._resolve_child(
|
||||
path_segment,
|
||||
create_if_missing,
|
||||
leaf_cls if len(path_segments) == 0 else InMemoryDirNode,
|
||||
)
|
||||
if current_node is None:
|
||||
break
|
||||
|
||||
if current_node is None and check_exists:
|
||||
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), path)
|
||||
|
||||
# If a leaf_cls is not None, check if leaf node is of right type.
|
||||
if leaf_cls and not isinstance(current_node, leaf_cls):
|
||||
error_cls, error_code = (
|
||||
(NotADirectoryError, errno.ENOTDIR)
|
||||
if leaf_cls is InMemoryDirNode
|
||||
else (IsADirectoryError, errno.EISDIR)
|
||||
)
|
||||
raise error_cls(error_code, os.strerror(error_code), path)
|
||||
|
||||
return current_node
|
||||
|
||||
def _resolve_child(self, path_segment, create_if_missing, child_cls):
|
||||
if create_if_missing:
|
||||
self._update_accessed_time()
|
||||
self._update_modified_time()
|
||||
if child_cls is InMemoryFileNode:
|
||||
child = child_cls(name=path_segment)
|
||||
else:
|
||||
child = child_cls()
|
||||
return self._children.setdefault(path_segment, child)
|
||||
return self._children.get(path_segment)
|
||||
|
||||
def listdir(self):
|
||||
directories, files = [], []
|
||||
for name, entry in self._children.items():
|
||||
if isinstance(entry, InMemoryDirNode):
|
||||
directories.append(name)
|
||||
else:
|
||||
files.append(name)
|
||||
return directories, files
|
||||
|
||||
def remove_child(self, name):
|
||||
if name in self._children:
|
||||
self._update_accessed_time()
|
||||
self._update_modified_time()
|
||||
del self._children[name]
|
||||
|
||||
|
||||
@deconstructible(path="django.core.files.storage.InMemoryStorage")
|
||||
class InMemoryStorage(Storage, StorageSettingsMixin):
|
||||
"""A storage saving files in memory."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
location=None,
|
||||
base_url=None,
|
||||
file_permissions_mode=None,
|
||||
directory_permissions_mode=None,
|
||||
):
|
||||
self._location = location
|
||||
self._base_url = base_url
|
||||
self._file_permissions_mode = file_permissions_mode
|
||||
self._directory_permissions_mode = directory_permissions_mode
|
||||
self._root = InMemoryDirNode()
|
||||
self._resolve(
|
||||
self.base_location, create_if_missing=True, leaf_cls=InMemoryDirNode
|
||||
)
|
||||
setting_changed.connect(self._clear_cached_properties)
|
||||
|
||||
@cached_property
|
||||
def base_location(self):
|
||||
return self._value_or_setting(self._location, settings.MEDIA_ROOT)
|
||||
|
||||
@cached_property
|
||||
def location(self):
|
||||
return os.path.abspath(self.base_location)
|
||||
|
||||
@cached_property
|
||||
def base_url(self):
|
||||
if self._base_url is not None and not self._base_url.endswith("/"):
|
||||
self._base_url += "/"
|
||||
return self._value_or_setting(self._base_url, settings.MEDIA_URL)
|
||||
|
||||
@cached_property
|
||||
def file_permissions_mode(self):
|
||||
return self._value_or_setting(
|
||||
self._file_permissions_mode, settings.FILE_UPLOAD_PERMISSIONS
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def directory_permissions_mode(self):
|
||||
return self._value_or_setting(
|
||||
self._directory_permissions_mode, settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS
|
||||
)
|
||||
|
||||
def _relative_path(self, name):
|
||||
full_path = self.path(name)
|
||||
return os.path.relpath(full_path, self.location)
|
||||
|
||||
def _resolve(self, name, create_if_missing=False, leaf_cls=None, check_exists=True):
|
||||
try:
|
||||
relative_path = self._relative_path(name)
|
||||
return self._root.resolve(
|
||||
relative_path,
|
||||
create_if_missing=create_if_missing,
|
||||
leaf_cls=leaf_cls,
|
||||
check_exists=check_exists,
|
||||
)
|
||||
except NotADirectoryError as exc:
|
||||
absolute_path = self.path(exc.filename)
|
||||
raise FileExistsError(f"{absolute_path} exists and is not a directory.")
|
||||
|
||||
def _open(self, name, mode="rb"):
|
||||
create_if_missing = "w" in mode
|
||||
file_node = self._resolve(
|
||||
name, create_if_missing=create_if_missing, leaf_cls=InMemoryFileNode
|
||||
)
|
||||
return file_node.open(mode)
|
||||
|
||||
def _save(self, name, content):
|
||||
file_node = self._resolve(
|
||||
name, create_if_missing=True, leaf_cls=InMemoryFileNode
|
||||
)
|
||||
fd = None
|
||||
for chunk in content.chunks():
|
||||
if fd is None:
|
||||
mode = "wb" if isinstance(chunk, bytes) else "wt"
|
||||
fd = file_node.open(mode)
|
||||
fd.write(chunk)
|
||||
|
||||
if hasattr(content, "temporary_file_path"):
|
||||
os.remove(content.temporary_file_path())
|
||||
|
||||
file_node.modified_time = now()
|
||||
return self._relative_path(name).replace("\\", "/")
|
||||
|
||||
def path(self, name):
|
||||
return safe_join(self.location, name)
|
||||
|
||||
def delete(self, name):
|
||||
path, filename = os.path.split(name)
|
||||
dir_node = self._resolve(path, check_exists=False)
|
||||
if dir_node is None:
|
||||
return None
|
||||
dir_node.remove_child(filename)
|
||||
|
||||
def exists(self, name):
|
||||
return self._resolve(name, check_exists=False) is not None
|
||||
|
||||
def listdir(self, path):
|
||||
node = self._resolve(path, leaf_cls=InMemoryDirNode)
|
||||
return node.listdir()
|
||||
|
||||
def size(self, name):
|
||||
return len(self._open(name, "rb").file.getvalue())
|
||||
|
||||
def url(self, name):
|
||||
if self.base_url is None:
|
||||
raise ValueError("This file is not accessible via a URL.")
|
||||
url = filepath_to_uri(name)
|
||||
if url is not None:
|
||||
url = url.lstrip("/")
|
||||
return urljoin(self.base_url, url)
|
||||
|
||||
def get_accessed_time(self, name):
|
||||
file_node = self._resolve(name)
|
||||
return file_node.accessed_time
|
||||
|
||||
def get_created_time(self, name):
|
||||
file_node = self._resolve(name)
|
||||
return file_node.created_time
|
||||
|
||||
def get_modified_time(self, name):
|
||||
file_node = self._resolve(name)
|
||||
return file_node.modified_time
|
||||
15
.venv/Lib/site-packages/django/core/files/storage/mixins.py
Normal file
15
.venv/Lib/site-packages/django/core/files/storage/mixins.py
Normal file
@@ -0,0 +1,15 @@
|
||||
class StorageSettingsMixin:
|
||||
def _clear_cached_properties(self, setting, **kwargs):
|
||||
"""Reset setting based property values."""
|
||||
if setting == "MEDIA_ROOT":
|
||||
self.__dict__.pop("base_location", None)
|
||||
self.__dict__.pop("location", None)
|
||||
elif setting == "MEDIA_URL":
|
||||
self.__dict__.pop("base_url", None)
|
||||
elif setting == "FILE_UPLOAD_PERMISSIONS":
|
||||
self.__dict__.pop("file_permissions_mode", None)
|
||||
elif setting == "FILE_UPLOAD_DIRECTORY_PERMISSIONS":
|
||||
self.__dict__.pop("directory_permissions_mode", None)
|
||||
|
||||
def _value_or_setting(self, value, setting):
|
||||
return setting if value is None else value
|
||||
79
.venv/Lib/site-packages/django/core/files/temp.py
Normal file
79
.venv/Lib/site-packages/django/core/files/temp.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
The temp module provides a NamedTemporaryFile that can be reopened in the same
|
||||
process on any platform. Most platforms use the standard Python
|
||||
tempfile.NamedTemporaryFile class, but Windows users are given a custom class.
|
||||
|
||||
This is needed because the Python implementation of NamedTemporaryFile uses the
|
||||
O_TEMPORARY flag under Windows, which prevents the file from being reopened
|
||||
if the same flag is not provided [1][2]. Note that this does not address the
|
||||
more general issue of opening a file for writing and reading in multiple
|
||||
processes in a manner that works across platforms.
|
||||
|
||||
The custom version of NamedTemporaryFile doesn't support the same keyword
|
||||
arguments available in tempfile.NamedTemporaryFile.
|
||||
|
||||
1: https://mail.python.org/pipermail/python-list/2005-December/336955.html
|
||||
2: https://bugs.python.org/issue14243
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from django.core.files.utils import FileProxyMixin
|
||||
|
||||
__all__ = (
|
||||
"NamedTemporaryFile",
|
||||
"gettempdir",
|
||||
)
|
||||
|
||||
|
||||
if os.name == "nt":
|
||||
|
||||
class TemporaryFile(FileProxyMixin):
|
||||
"""
|
||||
Temporary file object constructor that supports reopening of the
|
||||
temporary file in Windows.
|
||||
|
||||
Unlike tempfile.NamedTemporaryFile from the standard library,
|
||||
__init__() doesn't support the 'delete', 'buffering', 'encoding', or
|
||||
'newline' keyword arguments.
|
||||
"""
|
||||
|
||||
def __init__(self, mode="w+b", bufsize=-1, suffix="", prefix="", dir=None):
|
||||
fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir)
|
||||
self.name = name
|
||||
self.file = os.fdopen(fd, mode, bufsize)
|
||||
self.close_called = False
|
||||
|
||||
# Because close can be called during shutdown
|
||||
# we need to cache os.unlink and access it
|
||||
# as self.unlink only
|
||||
unlink = os.unlink
|
||||
|
||||
def close(self):
|
||||
if not self.close_called:
|
||||
self.close_called = True
|
||||
try:
|
||||
self.file.close()
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
self.unlink(self.name)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def __enter__(self):
|
||||
self.file.__enter__()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc, value, tb):
|
||||
self.file.__exit__(exc, value, tb)
|
||||
|
||||
NamedTemporaryFile = TemporaryFile
|
||||
else:
|
||||
NamedTemporaryFile = tempfile.NamedTemporaryFile
|
||||
|
||||
gettempdir = tempfile.gettempdir
|
||||
150
.venv/Lib/site-packages/django/core/files/uploadedfile.py
Normal file
150
.venv/Lib/site-packages/django/core/files/uploadedfile.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
Classes representing uploaded files.
|
||||
"""
|
||||
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files import temp as tempfile
|
||||
from django.core.files.base import File
|
||||
from django.core.files.utils import validate_file_name
|
||||
|
||||
__all__ = (
|
||||
"UploadedFile",
|
||||
"TemporaryUploadedFile",
|
||||
"InMemoryUploadedFile",
|
||||
"SimpleUploadedFile",
|
||||
)
|
||||
|
||||
|
||||
class UploadedFile(File):
|
||||
"""
|
||||
An abstract uploaded file (``TemporaryUploadedFile`` and
|
||||
``InMemoryUploadedFile`` are the built-in concrete subclasses).
|
||||
|
||||
An ``UploadedFile`` object behaves somewhat like a file object and
|
||||
represents some file data that the user submitted with a form.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
file=None,
|
||||
name=None,
|
||||
content_type=None,
|
||||
size=None,
|
||||
charset=None,
|
||||
content_type_extra=None,
|
||||
):
|
||||
super().__init__(file, name)
|
||||
self.size = size
|
||||
self.content_type = content_type
|
||||
self.charset = charset
|
||||
self.content_type_extra = content_type_extra
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s (%s)>" % (self.__class__.__name__, self.name, self.content_type)
|
||||
|
||||
def _get_name(self):
|
||||
return self._name
|
||||
|
||||
def _set_name(self, name):
|
||||
# Sanitize the file name so that it can't be dangerous.
|
||||
if name is not None:
|
||||
# Just use the basename of the file -- anything else is dangerous.
|
||||
name = os.path.basename(name)
|
||||
|
||||
# File names longer than 255 characters can cause problems on older OSes.
|
||||
if len(name) > 255:
|
||||
name, ext = os.path.splitext(name)
|
||||
ext = ext[:255]
|
||||
name = name[: 255 - len(ext)] + ext
|
||||
|
||||
name = validate_file_name(name)
|
||||
|
||||
self._name = name
|
||||
|
||||
name = property(_get_name, _set_name)
|
||||
|
||||
|
||||
class TemporaryUploadedFile(UploadedFile):
|
||||
"""
|
||||
A file uploaded to a temporary location (i.e. stream-to-disk).
|
||||
"""
|
||||
|
||||
def __init__(self, name, content_type, size, charset, content_type_extra=None):
|
||||
_, ext = os.path.splitext(name)
|
||||
file = tempfile.NamedTemporaryFile(
|
||||
suffix=".upload" + ext, dir=settings.FILE_UPLOAD_TEMP_DIR
|
||||
)
|
||||
super().__init__(file, name, content_type, size, charset, content_type_extra)
|
||||
|
||||
def temporary_file_path(self):
|
||||
"""Return the full path of this file."""
|
||||
return self.file.name
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
return self.file.close()
|
||||
except FileNotFoundError:
|
||||
# The file was moved or deleted before the tempfile could unlink
|
||||
# it. Still sets self.file.close_called and calls
|
||||
# self.file.file.close() before the exception.
|
||||
pass
|
||||
|
||||
|
||||
class InMemoryUploadedFile(UploadedFile):
|
||||
"""
|
||||
A file uploaded into memory (i.e. stream-to-memory).
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
file,
|
||||
field_name,
|
||||
name,
|
||||
content_type,
|
||||
size,
|
||||
charset,
|
||||
content_type_extra=None,
|
||||
):
|
||||
super().__init__(file, name, content_type, size, charset, content_type_extra)
|
||||
self.field_name = field_name
|
||||
|
||||
def open(self, mode=None):
|
||||
self.file.seek(0)
|
||||
return self
|
||||
|
||||
def chunks(self, chunk_size=None):
|
||||
self.file.seek(0)
|
||||
yield self.read()
|
||||
|
||||
def multiple_chunks(self, chunk_size=None):
|
||||
# Since it's in memory, we'll never have multiple chunks.
|
||||
return False
|
||||
|
||||
|
||||
class SimpleUploadedFile(InMemoryUploadedFile):
|
||||
"""
|
||||
A simple representation of a file, which just has content, size, and a name.
|
||||
"""
|
||||
|
||||
def __init__(self, name, content, content_type="text/plain"):
|
||||
content = content or b""
|
||||
super().__init__(
|
||||
BytesIO(content), None, name, content_type, len(content), None, None
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, file_dict):
|
||||
"""
|
||||
Create a SimpleUploadedFile object from a dictionary with keys:
|
||||
- filename
|
||||
- content-type
|
||||
- content
|
||||
"""
|
||||
return cls(
|
||||
file_dict["filename"],
|
||||
file_dict["content"],
|
||||
file_dict.get("content-type", "text/plain"),
|
||||
)
|
||||
252
.venv/Lib/site-packages/django/core/files/uploadhandler.py
Normal file
252
.venv/Lib/site-packages/django/core/files/uploadhandler.py
Normal file
@@ -0,0 +1,252 @@
|
||||
"""
|
||||
Base file upload handler classes, and the built-in concrete subclasses
|
||||
"""
|
||||
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
__all__ = [
|
||||
"UploadFileException",
|
||||
"StopUpload",
|
||||
"SkipFile",
|
||||
"FileUploadHandler",
|
||||
"TemporaryFileUploadHandler",
|
||||
"MemoryFileUploadHandler",
|
||||
"load_handler",
|
||||
"StopFutureHandlers",
|
||||
]
|
||||
|
||||
|
||||
class UploadFileException(Exception):
|
||||
"""
|
||||
Any error having to do with uploading files.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class StopUpload(UploadFileException):
|
||||
"""
|
||||
This exception is raised when an upload must abort.
|
||||
"""
|
||||
|
||||
def __init__(self, connection_reset=False):
|
||||
"""
|
||||
If ``connection_reset`` is ``True``, Django knows will halt the upload
|
||||
without consuming the rest of the upload. This will cause the browser to
|
||||
show a "connection reset" error.
|
||||
"""
|
||||
self.connection_reset = connection_reset
|
||||
|
||||
def __str__(self):
|
||||
if self.connection_reset:
|
||||
return "StopUpload: Halt current upload."
|
||||
else:
|
||||
return "StopUpload: Consume request data, then halt."
|
||||
|
||||
|
||||
class SkipFile(UploadFileException):
|
||||
"""
|
||||
This exception is raised by an upload handler that wants to skip a given file.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class StopFutureHandlers(UploadFileException):
|
||||
"""
|
||||
Upload handlers that have handled a file and do not want future handlers to
|
||||
run should raise this exception instead of returning None.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class FileUploadHandler:
|
||||
"""
|
||||
Base class for streaming upload handlers.
|
||||
"""
|
||||
|
||||
chunk_size = 64 * 2**10 # : The default chunk size is 64 KB.
|
||||
|
||||
def __init__(self, request=None):
|
||||
self.file_name = None
|
||||
self.content_type = None
|
||||
self.content_length = None
|
||||
self.charset = None
|
||||
self.content_type_extra = None
|
||||
self.request = request
|
||||
|
||||
def handle_raw_input(
|
||||
self, input_data, META, content_length, boundary, encoding=None
|
||||
):
|
||||
"""
|
||||
Handle the raw input from the client.
|
||||
|
||||
Parameters:
|
||||
|
||||
:input_data:
|
||||
An object that supports reading via .read().
|
||||
:META:
|
||||
``request.META``.
|
||||
:content_length:
|
||||
The (integer) value of the Content-Length header from the
|
||||
client.
|
||||
:boundary: The boundary from the Content-Type header. Be sure to
|
||||
prepend two '--'.
|
||||
"""
|
||||
pass
|
||||
|
||||
def new_file(
|
||||
self,
|
||||
field_name,
|
||||
file_name,
|
||||
content_type,
|
||||
content_length,
|
||||
charset=None,
|
||||
content_type_extra=None,
|
||||
):
|
||||
"""
|
||||
Signal that a new file has been started.
|
||||
|
||||
Warning: As with any data from the client, you should not trust
|
||||
content_length (and sometimes won't even get it).
|
||||
"""
|
||||
self.field_name = field_name
|
||||
self.file_name = file_name
|
||||
self.content_type = content_type
|
||||
self.content_length = content_length
|
||||
self.charset = charset
|
||||
self.content_type_extra = content_type_extra
|
||||
|
||||
def receive_data_chunk(self, raw_data, start):
|
||||
"""
|
||||
Receive data from the streamed upload parser. ``start`` is the position
|
||||
in the file of the chunk.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of FileUploadHandler must provide a receive_data_chunk() method"
|
||||
)
|
||||
|
||||
def file_complete(self, file_size):
|
||||
"""
|
||||
Signal that a file has completed. File size corresponds to the actual
|
||||
size accumulated by all the chunks.
|
||||
|
||||
Subclasses should return a valid ``UploadedFile`` object.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of FileUploadHandler must provide a file_complete() method"
|
||||
)
|
||||
|
||||
def upload_complete(self):
|
||||
"""
|
||||
Signal that the upload is complete. Subclasses should perform cleanup
|
||||
that is necessary for this handler.
|
||||
"""
|
||||
pass
|
||||
|
||||
def upload_interrupted(self):
|
||||
"""
|
||||
Signal that the upload was interrupted. Subclasses should perform
|
||||
cleanup that is necessary for this handler.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class TemporaryFileUploadHandler(FileUploadHandler):
|
||||
"""
|
||||
Upload handler that streams data into a temporary file.
|
||||
"""
|
||||
|
||||
def new_file(self, *args, **kwargs):
|
||||
"""
|
||||
Create the file object to append to as data is coming in.
|
||||
"""
|
||||
super().new_file(*args, **kwargs)
|
||||
self.file = TemporaryUploadedFile(
|
||||
self.file_name, self.content_type, 0, self.charset, self.content_type_extra
|
||||
)
|
||||
|
||||
def receive_data_chunk(self, raw_data, start):
|
||||
self.file.write(raw_data)
|
||||
|
||||
def file_complete(self, file_size):
|
||||
self.file.seek(0)
|
||||
self.file.size = file_size
|
||||
return self.file
|
||||
|
||||
def upload_interrupted(self):
|
||||
if hasattr(self, "file"):
|
||||
temp_location = self.file.temporary_file_path()
|
||||
try:
|
||||
self.file.close()
|
||||
os.remove(temp_location)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
class MemoryFileUploadHandler(FileUploadHandler):
|
||||
"""
|
||||
File upload handler to stream uploads into memory (used for small files).
|
||||
"""
|
||||
|
||||
def handle_raw_input(
|
||||
self, input_data, META, content_length, boundary, encoding=None
|
||||
):
|
||||
"""
|
||||
Use the content_length to signal whether or not this handler should be
|
||||
used.
|
||||
"""
|
||||
# Check the content-length header to see if we should
|
||||
# If the post is too large, we cannot use the Memory handler.
|
||||
self.activated = content_length <= settings.FILE_UPLOAD_MAX_MEMORY_SIZE
|
||||
|
||||
def new_file(self, *args, **kwargs):
|
||||
super().new_file(*args, **kwargs)
|
||||
if self.activated:
|
||||
self.file = BytesIO()
|
||||
raise StopFutureHandlers()
|
||||
|
||||
def receive_data_chunk(self, raw_data, start):
|
||||
"""Add the data to the BytesIO file."""
|
||||
if self.activated:
|
||||
self.file.write(raw_data)
|
||||
else:
|
||||
return raw_data
|
||||
|
||||
def file_complete(self, file_size):
|
||||
"""Return a file object if this handler is activated."""
|
||||
if not self.activated:
|
||||
return
|
||||
|
||||
self.file.seek(0)
|
||||
return InMemoryUploadedFile(
|
||||
file=self.file,
|
||||
field_name=self.field_name,
|
||||
name=self.file_name,
|
||||
content_type=self.content_type,
|
||||
size=file_size,
|
||||
charset=self.charset,
|
||||
content_type_extra=self.content_type_extra,
|
||||
)
|
||||
|
||||
|
||||
def load_handler(path, *args, **kwargs):
|
||||
"""
|
||||
Given a path to a handler, return an instance of that handler.
|
||||
|
||||
E.g.::
|
||||
>>> from django.http import HttpRequest
|
||||
>>> request = HttpRequest()
|
||||
>>> load_handler(
|
||||
... 'django.core.files.uploadhandler.TemporaryFileUploadHandler',
|
||||
... request,
|
||||
... )
|
||||
<TemporaryFileUploadHandler object at 0x...>
|
||||
"""
|
||||
return import_string(path)(*args, **kwargs)
|
||||
77
.venv/Lib/site-packages/django/core/files/utils.py
Normal file
77
.venv/Lib/site-packages/django/core/files/utils.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
from django.core.exceptions import SuspiciousFileOperation
|
||||
|
||||
|
||||
def validate_file_name(name, allow_relative_path=False):
|
||||
# Remove potentially dangerous names
|
||||
if os.path.basename(name) in {"", ".", ".."}:
|
||||
raise SuspiciousFileOperation("Could not derive file name from '%s'" % name)
|
||||
|
||||
if allow_relative_path:
|
||||
# Ensure that name can be treated as a pure posix path, i.e. Unix
|
||||
# style (with forward slashes).
|
||||
path = pathlib.PurePosixPath(str(name).replace("\\", "/"))
|
||||
if path.is_absolute() or ".." in path.parts:
|
||||
raise SuspiciousFileOperation(
|
||||
"Detected path traversal attempt in '%s'" % name
|
||||
)
|
||||
elif name != os.path.basename(name):
|
||||
raise SuspiciousFileOperation("File name '%s' includes path elements" % name)
|
||||
|
||||
return name
|
||||
|
||||
|
||||
class FileProxyMixin:
|
||||
"""
|
||||
A mixin class used to forward file methods to an underlying file
|
||||
object. The internal file object has to be called "file"::
|
||||
|
||||
class FileProxy(FileProxyMixin):
|
||||
def __init__(self, file):
|
||||
self.file = file
|
||||
"""
|
||||
|
||||
encoding = property(lambda self: self.file.encoding)
|
||||
fileno = property(lambda self: self.file.fileno)
|
||||
flush = property(lambda self: self.file.flush)
|
||||
isatty = property(lambda self: self.file.isatty)
|
||||
newlines = property(lambda self: self.file.newlines)
|
||||
read = property(lambda self: self.file.read)
|
||||
readinto = property(lambda self: self.file.readinto)
|
||||
readline = property(lambda self: self.file.readline)
|
||||
readlines = property(lambda self: self.file.readlines)
|
||||
seek = property(lambda self: self.file.seek)
|
||||
tell = property(lambda self: self.file.tell)
|
||||
truncate = property(lambda self: self.file.truncate)
|
||||
write = property(lambda self: self.file.write)
|
||||
writelines = property(lambda self: self.file.writelines)
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return not self.file or self.file.closed
|
||||
|
||||
def readable(self):
|
||||
if self.closed:
|
||||
return False
|
||||
if hasattr(self.file, "readable"):
|
||||
return self.file.readable()
|
||||
return True
|
||||
|
||||
def writable(self):
|
||||
if self.closed:
|
||||
return False
|
||||
if hasattr(self.file, "writable"):
|
||||
return self.file.writable()
|
||||
return "w" in getattr(self.file, "mode", "")
|
||||
|
||||
def seekable(self):
|
||||
if self.closed:
|
||||
return False
|
||||
if hasattr(self.file, "seekable"):
|
||||
return self.file.seekable()
|
||||
return True
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.file)
|
||||
Reference in New Issue
Block a user