Loading django/core/files/storage.py +37 −7 Original line number Diff line number Diff line import os import errno from datetime import datetime import errno from inspect import getargspec import os import warnings from django.conf import settings from django.core.exceptions import SuspiciousFileOperation from django.core.files import locks, File from django.core.files.move import file_move_safe from django.utils.crypto import get_random_string Loading @@ -13,6 +16,7 @@ from django.utils.six.moves.urllib.parse import urljoin from django.utils.text import get_valid_filename from django.utils._os import safe_join, abspathu from django.utils.deconstruct import deconstructible from django.utils.deprecation import RemovedInDjango20Warning __all__ = ('Storage', 'FileSystemStorage', 'DefaultStorage', 'default_storage') Loading @@ -33,7 +37,7 @@ class Storage(object): """ return self._open(name, mode) def save(self, name, content): def save(self, name, content, max_length=None): """ Saves 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 Loading @@ -46,7 +50,18 @@ class Storage(object): if not hasattr(content, 'chunks'): content = File(content) args, varargs, varkw, defaults = getargspec(self.get_available_name) if 'max_length' in args: name = self.get_available_name(name, max_length=max_length) else: warnings.warn( 'Backwards compatibility for storage backends without ' 'support for the `max_length` argument in ' 'Storage.get_available_name() will be removed in Django 2.0.', RemovedInDjango20Warning, stacklevel=2 ) name = self.get_available_name(name) name = self._save(name, content) # Store filenames with forward slashes, even on Windows Loading @@ -61,7 +76,7 @@ class Storage(object): """ return get_valid_filename(name) def get_available_name(self, name): def get_available_name(self, name, max_length=None): """ Returns a filename that's free on the target storage system, and available for new content to be written to. Loading @@ -71,10 +86,25 @@ class Storage(object): # If the filename already exists, add an underscore and a random 7 # character alphanumeric string (before the file extension, if one # exists) to the filename until the generated filename doesn't exist. while self.exists(name): # Truncate original name if required, so the new filename does not # exceed the max_length. while self.exists(name) or (max_length and len(name) > max_length): # file_ext includes the dot. name = os.path.join(dir_name, "%s_%s%s" % (file_root, get_random_string(7), 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, "%s_%s%s" % (file_root, get_random_string(7), file_ext)) return name def path(self, name): Loading django/db/models/fields/files.py +16 −1 Original line number Diff line number Diff line import datetime from inspect import getargspec import os import warnings from django import forms from django.db.models.fields import Field Loading @@ -11,6 +13,7 @@ from django.db.models import signals from django.utils.encoding import force_str, force_text from django.utils import six from django.utils.translation import ugettext_lazy as _ from django.utils.deprecation import RemovedInDjango20Warning class FieldFile(File): Loading Loading @@ -85,7 +88,19 @@ class FieldFile(File): def save(self, name, content, save=True): name = self.field.generate_filename(self.instance, name) args, varargs, varkw, defaults = getargspec(self.storage.save) if 'max_length' in args: self.name = self.storage.save(name, content, max_length=self.field.max_length) else: warnings.warn( 'Backwards compatibility for storage backends without ' 'support for the `max_length` argument in ' 'Storage.save() will be removed in Django 2.0.', RemovedInDjango20Warning, stacklevel=2 ) self.name = self.storage.save(name, content) setattr(self.instance, self.field.name, self.name) # Update the filesize cache Loading docs/howto/custom-file-storage.txt +13 −7 Original line number Diff line number Diff line Loading @@ -87,7 +87,6 @@ instead). .. method:: get_valid_name(name) Returns a filename suitable for use with the underlying storage system. The ``name`` argument passed to this method is the original filename sent to the server, after having any path information removed. Override this to customize Loading @@ -96,21 +95,28 @@ how non-standard characters are converted to safe filenames. The code provided on ``Storage`` retains only alpha-numeric characters, periods and underscores from the original filename, removing everything else. .. method:: get_available_name(name) .. method:: get_available_name(name, max_length=None) Returns a filename that is available in the storage mechanism, possibly taking the provided filename into account. The ``name`` argument passed to this method will have already cleaned to a filename valid for the storage system, according to the ``get_valid_name()`` method described above. .. versionchanged:: 1.7 The length of the filename will not exceed ``max_length``, if provided. If a free unique filename cannot be found, a :exc:`SuspiciousFileOperation <django.core.exceptions.SuspiciousOperation>` exception is raised. If a file with ``name`` already exists, an underscore plus a random 7 character alphanumeric string is appended to the filename before the extension. If a file with ``name`` already exists, an underscore plus a random 7 character alphanumeric string is appended to the filename before the extension. .. versionchanged:: 1.7 Previously, an underscore followed by a number (e.g. ``"_1"``, ``"_2"``, etc.) was appended to the filename until an available name in the destination directory was found. A malicious user could exploit this deterministic algorithm to create a denial-of-service attack. This change was also made in Django 1.6.6, 1.5.9, and 1.4.14. .. versionchanged:: 1.8 The ``max_length`` argument was added. docs/internals/deprecation.txt +4 −0 Original line number Diff line number Diff line Loading @@ -143,6 +143,10 @@ details on these changes. * Support for the ``=`` comparison operator in the ``if`` template tag will be removed. * The backwards compatibility shims to allow ``Storage.get_available_name()`` and ``Storage.save()`` to be defined without a ``max_length`` argument will be removed. .. _deprecation-removed-in-1.9: 1.9 Loading docs/ref/files/storage.txt +18 −2 Original line number Diff line number Diff line Loading @@ -114,12 +114,17 @@ The Storage Class in the storage system, or ``False`` if the name is available for a new file. .. method:: get_available_name(name) .. method:: get_available_name(name, max_length=None) Returns a filename based on the ``name`` parameter that's free and available for new content to be written to on the target storage system. The length of the filename will not exceed ``max_length``, if provided. If a free unique filename cannot be found, a :exc:`SuspiciousFileOperation <django.core.exceptions.SuspiciousOperation>` exception will be raised. If a file with ``name`` already exists, an underscore plus a random 7 character alphanumeric string is appended to the filename before the extension. Loading @@ -133,6 +138,10 @@ The Storage Class attack. This change was also made in Django 1.6.6, 1.5.9, and 1.4.14. .. versionchanged:: 1.8 The ``max_length`` argument was added. .. method:: get_valid_name(name) Returns a filename based on the ``name`` parameter that's suitable Loading Loading @@ -165,17 +174,24 @@ The Storage Class standard ``open()``. For storage systems that aren't accessible from the local filesystem, this will raise ``NotImplementedError`` instead. .. method:: save(name, content) .. method:: save(name, content, max_length=None) Saves a new file using the storage system, preferably with the name specified. If there already exists a file with this name ``name``, the storage system may modify the filename as necessary to get a unique name. The actual name of the stored file will be returned. The ``max_length`` argument is passed along to :meth:`get_available_name`. The ``content`` argument must be an instance of :class:`django.core.files.File` or of a subclass of :class:`~django.core.files.File`. .. versionchanged:: 1.8 The ``max_length`` argument was added. .. method:: size(name) Returns the total size, in bytes, of the file referenced by ``name``. Loading Loading
django/core/files/storage.py +37 −7 Original line number Diff line number Diff line import os import errno from datetime import datetime import errno from inspect import getargspec import os import warnings from django.conf import settings from django.core.exceptions import SuspiciousFileOperation from django.core.files import locks, File from django.core.files.move import file_move_safe from django.utils.crypto import get_random_string Loading @@ -13,6 +16,7 @@ from django.utils.six.moves.urllib.parse import urljoin from django.utils.text import get_valid_filename from django.utils._os import safe_join, abspathu from django.utils.deconstruct import deconstructible from django.utils.deprecation import RemovedInDjango20Warning __all__ = ('Storage', 'FileSystemStorage', 'DefaultStorage', 'default_storage') Loading @@ -33,7 +37,7 @@ class Storage(object): """ return self._open(name, mode) def save(self, name, content): def save(self, name, content, max_length=None): """ Saves 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 Loading @@ -46,7 +50,18 @@ class Storage(object): if not hasattr(content, 'chunks'): content = File(content) args, varargs, varkw, defaults = getargspec(self.get_available_name) if 'max_length' in args: name = self.get_available_name(name, max_length=max_length) else: warnings.warn( 'Backwards compatibility for storage backends without ' 'support for the `max_length` argument in ' 'Storage.get_available_name() will be removed in Django 2.0.', RemovedInDjango20Warning, stacklevel=2 ) name = self.get_available_name(name) name = self._save(name, content) # Store filenames with forward slashes, even on Windows Loading @@ -61,7 +76,7 @@ class Storage(object): """ return get_valid_filename(name) def get_available_name(self, name): def get_available_name(self, name, max_length=None): """ Returns a filename that's free on the target storage system, and available for new content to be written to. Loading @@ -71,10 +86,25 @@ class Storage(object): # If the filename already exists, add an underscore and a random 7 # character alphanumeric string (before the file extension, if one # exists) to the filename until the generated filename doesn't exist. while self.exists(name): # Truncate original name if required, so the new filename does not # exceed the max_length. while self.exists(name) or (max_length and len(name) > max_length): # file_ext includes the dot. name = os.path.join(dir_name, "%s_%s%s" % (file_root, get_random_string(7), 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, "%s_%s%s" % (file_root, get_random_string(7), file_ext)) return name def path(self, name): Loading
django/db/models/fields/files.py +16 −1 Original line number Diff line number Diff line import datetime from inspect import getargspec import os import warnings from django import forms from django.db.models.fields import Field Loading @@ -11,6 +13,7 @@ from django.db.models import signals from django.utils.encoding import force_str, force_text from django.utils import six from django.utils.translation import ugettext_lazy as _ from django.utils.deprecation import RemovedInDjango20Warning class FieldFile(File): Loading Loading @@ -85,7 +88,19 @@ class FieldFile(File): def save(self, name, content, save=True): name = self.field.generate_filename(self.instance, name) args, varargs, varkw, defaults = getargspec(self.storage.save) if 'max_length' in args: self.name = self.storage.save(name, content, max_length=self.field.max_length) else: warnings.warn( 'Backwards compatibility for storage backends without ' 'support for the `max_length` argument in ' 'Storage.save() will be removed in Django 2.0.', RemovedInDjango20Warning, stacklevel=2 ) self.name = self.storage.save(name, content) setattr(self.instance, self.field.name, self.name) # Update the filesize cache Loading
docs/howto/custom-file-storage.txt +13 −7 Original line number Diff line number Diff line Loading @@ -87,7 +87,6 @@ instead). .. method:: get_valid_name(name) Returns a filename suitable for use with the underlying storage system. The ``name`` argument passed to this method is the original filename sent to the server, after having any path information removed. Override this to customize Loading @@ -96,21 +95,28 @@ how non-standard characters are converted to safe filenames. The code provided on ``Storage`` retains only alpha-numeric characters, periods and underscores from the original filename, removing everything else. .. method:: get_available_name(name) .. method:: get_available_name(name, max_length=None) Returns a filename that is available in the storage mechanism, possibly taking the provided filename into account. The ``name`` argument passed to this method will have already cleaned to a filename valid for the storage system, according to the ``get_valid_name()`` method described above. .. versionchanged:: 1.7 The length of the filename will not exceed ``max_length``, if provided. If a free unique filename cannot be found, a :exc:`SuspiciousFileOperation <django.core.exceptions.SuspiciousOperation>` exception is raised. If a file with ``name`` already exists, an underscore plus a random 7 character alphanumeric string is appended to the filename before the extension. If a file with ``name`` already exists, an underscore plus a random 7 character alphanumeric string is appended to the filename before the extension. .. versionchanged:: 1.7 Previously, an underscore followed by a number (e.g. ``"_1"``, ``"_2"``, etc.) was appended to the filename until an available name in the destination directory was found. A malicious user could exploit this deterministic algorithm to create a denial-of-service attack. This change was also made in Django 1.6.6, 1.5.9, and 1.4.14. .. versionchanged:: 1.8 The ``max_length`` argument was added.
docs/internals/deprecation.txt +4 −0 Original line number Diff line number Diff line Loading @@ -143,6 +143,10 @@ details on these changes. * Support for the ``=`` comparison operator in the ``if`` template tag will be removed. * The backwards compatibility shims to allow ``Storage.get_available_name()`` and ``Storage.save()`` to be defined without a ``max_length`` argument will be removed. .. _deprecation-removed-in-1.9: 1.9 Loading
docs/ref/files/storage.txt +18 −2 Original line number Diff line number Diff line Loading @@ -114,12 +114,17 @@ The Storage Class in the storage system, or ``False`` if the name is available for a new file. .. method:: get_available_name(name) .. method:: get_available_name(name, max_length=None) Returns a filename based on the ``name`` parameter that's free and available for new content to be written to on the target storage system. The length of the filename will not exceed ``max_length``, if provided. If a free unique filename cannot be found, a :exc:`SuspiciousFileOperation <django.core.exceptions.SuspiciousOperation>` exception will be raised. If a file with ``name`` already exists, an underscore plus a random 7 character alphanumeric string is appended to the filename before the extension. Loading @@ -133,6 +138,10 @@ The Storage Class attack. This change was also made in Django 1.6.6, 1.5.9, and 1.4.14. .. versionchanged:: 1.8 The ``max_length`` argument was added. .. method:: get_valid_name(name) Returns a filename based on the ``name`` parameter that's suitable Loading Loading @@ -165,17 +174,24 @@ The Storage Class standard ``open()``. For storage systems that aren't accessible from the local filesystem, this will raise ``NotImplementedError`` instead. .. method:: save(name, content) .. method:: save(name, content, max_length=None) Saves a new file using the storage system, preferably with the name specified. If there already exists a file with this name ``name``, the storage system may modify the filename as necessary to get a unique name. The actual name of the stored file will be returned. The ``max_length`` argument is passed along to :meth:`get_available_name`. The ``content`` argument must be an instance of :class:`django.core.files.File` or of a subclass of :class:`~django.core.files.File`. .. versionchanged:: 1.8 The ``max_length`` argument was added. .. method:: size(name) Returns the total size, in bytes, of the file referenced by ``name``. Loading