From 39f8681e293ef6a72c3054bd6371aff49410f42d Mon Sep 17 00:00:00 2001 From: Johannes Wilm Date: Tue, 16 Aug 2022 13:28:09 +0200 Subject: [PATCH] Use correct extension for thumbnails, fixes #54 --- avatar/models.py | 25 +++++++++++-------------- avatar/templatetags/avatar_tags.py | 1 + docs/index.txt | 9 +++++++++ tests/tests.py | 6 ++++++ 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/avatar/models.py b/avatar/models.py index 1662ab4..0b16878 100644 --- a/avatar/models.py +++ b/avatar/models.py @@ -34,22 +34,21 @@ def avatar_path_handler( if not filename: # Filename already stored in database filename = instance.avatar.name - if ext and settings.AVATAR_HASH_FILENAMES: - # An extension was provided, probably because the thumbnail - # is in a different format than the file. Use it. Because it's - # only enabled if AVATAR_HASH_FILENAMES is true, we can trust - # it won't conflict with another filename + if ext: (root, oldext) = os.path.splitext(filename) filename = root + "." + ext.lower() else: # File doesn't exist yet + (root, oldext) = os.path.splitext(filename) if settings.AVATAR_HASH_FILENAMES: - (root, ext) = os.path.splitext(filename) if settings.AVATAR_RANDOMIZE_HASHES: - filename = binascii.hexlify(os.urandom(16)).decode("ascii") + root = binascii.hexlify(os.urandom(16)).decode("ascii") else: - filename = hashlib.md5(force_bytes(filename)).hexdigest() - filename = filename + ext + root = hashlib.md5(force_bytes(root)).hexdigest() + if ext: + filename = root + "." + ext.lower() + else: + filename = root + oldext.lower() if width or height: tmppath.extend(["resized", str(width), str(height)]) tmppath.append(os.path.basename(filename)) @@ -70,7 +69,7 @@ def find_extension(format): class AvatarField(models.ImageField): def __init__(self, *args, **kwargs): - super(AvatarField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.max_length = 1024 self.upload_to = avatar_file_path @@ -78,7 +77,7 @@ class AvatarField(models.ImageField): self.blank = True def deconstruct(self): - name, path, args, kwargs = super(models.ImageField, self).deconstruct() + name, path, args, kwargs = super().deconstruct() return name, path, (), {} @@ -116,7 +115,7 @@ class Avatar(models.Model): avatars.update(primary=False) else: avatars.delete() - super(Avatar, self).save(*args, **kwargs) + super().save(*args, **kwargs) def thumbnail_exists(self, width, height=None): return self.avatar.storage.exists(self.avatar_name(width, height)) @@ -162,8 +161,6 @@ class Avatar(models.Model): else: thumb_file = File(orig) thumb_name = self.avatar_name(width, height) - if self.avatar.storage.exists(thumb_name): - self.avatar.storage.delete(thumb_name) thumb = self.avatar.storage.save(thumb_name, thumb_file) except IOError: thumb_file = File(orig) diff --git a/avatar/templatetags/avatar_tags.py b/avatar/templatetags/avatar_tags.py index c50556d..59b4b8b 100644 --- a/avatar/templatetags/avatar_tags.py +++ b/avatar/templatetags/avatar_tags.py @@ -21,6 +21,7 @@ def avatar_url(user, width=settings.AVATAR_DEFAULT_SIZE, height=None): avatar_url = provider.get_avatar_url(user, width, height) if avatar_url: return avatar_url + return get_default_avatar_url() @cache_result() diff --git a/docs/index.txt b/docs/index.txt index cf3628b..dfa70af 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -254,6 +254,15 @@ appear on the site. Listed below are those settings: Path to the Django template to use for confirming a delete of a user's avatar. Defaults to ``avatar/avatar/confirm_delete.html``. +.. py:data:: AVATAR_ALLOWED_MIMETYPES + + Limit allowed avatar image uploads by their actual content payload and what image codecs we wish to support. + This limits website user content site attack vectors against image codec buffer overflow and similar bugs. + `You must have python-imaging library installed `_. + Suggested safe setting: ``("image/png", "image/gif", "image/jpeg")``. + When enabled you'll get the following error on the form upload *File content is invalid. Detected: image/tiff Allowed content types are: image/png, image/gif, image/jpg*. + + Management Commands ------------------- diff --git a/tests/tests.py b/tests/tests.py index 5661b32..d32bf79 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -287,6 +287,12 @@ class AvatarTests(TestCase): ) self.assertEqual(image.mode, "RGB") + def test_automatic_thumbnail_creation_image_type_conversion(self): + upload_helper(self, "django_pony_cmyk.jpg") + self.assertMediaFileExists( + f"/avatars/{self.user.id}/resized/80/80/django_pony_cmyk.png" + ) + def test_thumbnail_transpose_based_on_exif(self): upload_helper(self, "image_no_exif.jpg") avatar = get_primary_avatar(self.user)