Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/django/core/files/storage/base.py: 35%
65 statements
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
1import os
2import pathlib
4from django.core.exceptions import SuspiciousFileOperation
5from django.core.files import File
6from django.core.files.utils import validate_file_name
7from django.utils.crypto import get_random_string
8from django.utils.text import get_valid_filename
11class Storage:
12 """
13 A base storage class, providing some default behaviors that all other
14 storage systems can inherit or override, as necessary.
15 """
17 # The following methods represent a public interface to private methods.
18 # These shouldn't be overridden by subclasses unless absolutely necessary.
20 def open(self, name, mode="rb"):
21 """Retrieve the specified file from storage."""
22 return self._open(name, mode)
24 def save(self, name, content, max_length=None):
25 """
26 Save new content to the file specified by name. The content should be
27 a proper File object or any Python file-like object, ready to be read
28 from the beginning.
29 """
30 # Get the proper name for the file, as it will actually be saved.
31 if name is None:
32 name = content.name
34 if not hasattr(content, "chunks"):
35 content = File(content, name)
37 name = self.get_available_name(name, max_length=max_length)
38 name = self._save(name, content)
39 # Ensure that the name returned from the storage system is still valid.
40 validate_file_name(name, allow_relative_path=True)
41 return name
43 # These methods are part of the public API, with default implementations.
45 def get_valid_name(self, name):
46 """
47 Return a filename, based on the provided filename, that's suitable for
48 use in the target storage system.
49 """
50 return get_valid_filename(name)
52 def get_alternative_name(self, file_root, file_ext):
53 """
54 Return an alternative filename, by adding an underscore and a random 7
55 character alphanumeric string (before the file extension, if one
56 exists) to the filename.
57 """
58 return "%s_%s%s" % (file_root, get_random_string(7), file_ext)
60 def get_available_name(self, name, max_length=None):
61 """
62 Return a filename that's free on the target storage system and
63 available for new content to be written to.
64 """
65 name = str(name).replace("\\", "/")
66 dir_name, file_name = os.path.split(name)
67 if ".." in pathlib.PurePath(dir_name).parts:
68 raise SuspiciousFileOperation(
69 "Detected path traversal attempt in '%s'" % dir_name
70 )
71 validate_file_name(file_name)
72 file_root, file_ext = os.path.splitext(file_name)
73 # If the filename already exists, generate an alternative filename
74 # until it doesn't exist.
75 # Truncate original name if required, so the new filename does not
76 # exceed the max_length.
77 while self.exists(name) or (max_length and len(name) > max_length):
78 # file_ext includes the dot.
79 name = os.path.join(
80 dir_name, self.get_alternative_name(file_root, file_ext)
81 )
82 if max_length is None:
83 continue
84 # Truncate file_root if max_length exceeded.
85 truncation = len(name) - max_length
86 if truncation > 0:
87 file_root = file_root[:-truncation]
88 # Entire file_root was truncated in attempt to find an
89 # available filename.
90 if not file_root:
91 raise SuspiciousFileOperation(
92 'Storage can not find an available filename for "%s". '
93 "Please make sure that the corresponding file field "
94 'allows sufficient "max_length".' % name
95 )
96 name = os.path.join(
97 dir_name, self.get_alternative_name(file_root, file_ext)
98 )
99 return name
101 def generate_filename(self, filename):
102 """
103 Validate the filename by calling get_valid_name() and return a filename
104 to be passed to the save() method.
105 """
106 filename = str(filename).replace("\\", "/")
107 # `filename` may include a path as returned by FileField.upload_to.
108 dirname, filename = os.path.split(filename)
109 if ".." in pathlib.PurePath(dirname).parts:
110 raise SuspiciousFileOperation(
111 "Detected path traversal attempt in '%s'" % dirname
112 )
113 return os.path.normpath(os.path.join(dirname, self.get_valid_name(filename)))
115 def path(self, name):
116 """
117 Return a local filesystem path where the file can be retrieved using
118 Python's built-in open() function. Storage systems that can't be
119 accessed using open() should *not* implement this method.
120 """
121 raise NotImplementedError("This backend doesn't support absolute paths.")
123 # The following methods form the public API for storage systems, but with
124 # no default implementations. Subclasses must implement *all* of these.
126 def delete(self, name):
127 """
128 Delete the specified file from the storage system.
129 """
130 raise NotImplementedError(
131 "subclasses of Storage must provide a delete() method"
132 )
134 def exists(self, name):
135 """
136 Return True if a file referenced by the given name already exists in the
137 storage system, or False if the name is available for a new file.
138 """
139 raise NotImplementedError(
140 "subclasses of Storage must provide an exists() method"
141 )
143 def listdir(self, path):
144 """
145 List the contents of the specified path. Return a 2-tuple of lists:
146 the first item being directories, the second item being files.
147 """
148 raise NotImplementedError(
149 "subclasses of Storage must provide a listdir() method"
150 )
152 def size(self, name):
153 """
154 Return the total size, in bytes, of the file specified by name.
155 """
156 raise NotImplementedError("subclasses of Storage must provide a size() method")
158 def url(self, name):
159 """
160 Return an absolute URL where the file's contents can be accessed
161 directly by a web browser.
162 """
163 raise NotImplementedError("subclasses of Storage must provide a url() method")
165 def get_accessed_time(self, name):
166 """
167 Return the last accessed time (as a datetime) of the file specified by
168 name. The datetime will be timezone-aware if USE_TZ=True.
169 """
170 raise NotImplementedError(
171 "subclasses of Storage must provide a get_accessed_time() method"
172 )
174 def get_created_time(self, name):
175 """
176 Return the creation time (as a datetime) of the file specified by name.
177 The datetime will be timezone-aware if USE_TZ=True.
178 """
179 raise NotImplementedError(
180 "subclasses of Storage must provide a get_created_time() method"
181 )
183 def get_modified_time(self, name):
184 """
185 Return the last modified time (as a datetime) of the file specified by
186 name. The datetime will be timezone-aware if USE_TZ=True.
187 """
188 raise NotImplementedError(
189 "subclasses of Storage must provide a get_modified_time() method"
190 )