diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 66e4521538..e0cc80f1b0 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,6 +4,7 @@ Changelog 5.0 (xx.xx.xxxx) - IN DEVELOPMENT ~~~~~~~~~~~~~~~~ + * Add `WAGTAILIMAGES_EXTENSIONS` setting to restrict image uploads to specific file types (Aman Pandey, Ananjan-R) * Docs: Add code block to make it easier to understand contribution docs (Suyash Singh) * Maintenance: Update djhtml (html formatting) library to v 1.5.2 (Loveth Omokaro) * Maintenance: Re-enable `strictPropertyInitialization` in tsconfig (Thibaud Colas) diff --git a/docs/reference/settings.md b/docs/reference/settings.md index 06c1d8050e..0956341581 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -360,6 +360,17 @@ This setting allows image renditions to be stored using an alternative storage b Custom storage classes should subclass `django.core.files.storage.Storage`. See the {doc}`Django file storage API `. +### `WAGTAILIMAGES_EXTENSIONS` + +```python +WAGTAILIMAGES_EXTENSIONS = ['png', 'jpg'] +``` + +A list of allowed image extensions that will be validated during image uploading. +If this isn't supplied, all of GIF, JPG, JPEG, PNG, WEBP are allowed. +Warning: this doesn't always ensure that the uploaded file is valid as files can +be renamed to have an extension no matter what data they contain. + ## Documents ### `WAGTAILDOCS_DOCUMENT_MODEL` diff --git a/docs/releases/5.0.md b/docs/releases/5.0.md index e02cb01d83..8896197a44 100644 --- a/docs/releases/5.0.md +++ b/docs/releases/5.0.md @@ -15,7 +15,7 @@ depth: 1 ### Other features - * ... + * Add `WAGTAILIMAGES_EXTENSIONS` setting to restrict image uploads to specific file types (Aman Pandey, Ananjan-R) ### Bug fixes diff --git a/wagtail/images/fields.py b/wagtail/images/fields.py index 2147d38a4c..3f0097b4f2 100644 --- a/wagtail/images/fields.py +++ b/wagtail/images/fields.py @@ -10,7 +10,6 @@ from django.template.defaultfilters import filesizeformat from django.utils.translation import gettext_lazy as _ ALLOWED_EXTENSIONS = ["gif", "jpg", "jpeg", "png", "webp"] -SUPPORTED_FORMATS_TEXT = _("GIF, JPEG, PNG, WEBP") class WagtailImageField(ImageField): @@ -28,17 +27,23 @@ class WagtailImageField(ImageField): ) self.max_upload_size_text = filesizeformat(self.max_upload_size) + self.allowed_image_extensions = getattr( + settings, "WAGTAILIMAGES_EXTENSIONS", ALLOWED_EXTENSIONS + ) + + self.supported_formats_text = ", ".join(self.allowed_image_extensions).upper() + # Help text if self.max_upload_size is not None: self.help_text = _( "Supported formats: %(supported_formats)s. Maximum filesize: %(max_upload_size)s." ) % { - "supported_formats": SUPPORTED_FORMATS_TEXT, + "supported_formats": self.supported_formats_text, "max_upload_size": self.max_upload_size_text, } else: self.help_text = _("Supported formats: %(supported_formats)s.") % { - "supported_formats": SUPPORTED_FORMATS_TEXT, + "supported_formats": self.supported_formats_text, } # Error messages @@ -46,7 +51,7 @@ class WagtailImageField(ImageField): # either right now if all values are known, otherwise when used. self.error_messages["invalid_image_extension"] = _( "Not a supported image format. Supported formats: %(supported_formats)s." - ) % {"supported_formats": SUPPORTED_FORMATS_TEXT} + ) % {"supported_formats": self.supported_formats_text} self.error_messages["invalid_image_known_format"] = _( "Not a valid .%(extension)s image. The extension does not match the file format (%(image_format)s)" @@ -68,7 +73,7 @@ class WagtailImageField(ImageField): # Check file extension extension = os.path.splitext(f.name)[1].lower()[1:] - if extension not in ALLOWED_EXTENSIONS: + if extension not in self.allowed_image_extensions: raise ValidationError( self.error_messages["invalid_image_extension"], code="invalid_image_extension", diff --git a/wagtail/images/tests/test_admin_views.py b/wagtail/images/tests/test_admin_views.py index 2c0c0fcaec..6824439739 100644 --- a/wagtail/images/tests/test_admin_views.py +++ b/wagtail/images/tests/test_admin_views.py @@ -1882,7 +1882,7 @@ class TestImageChooserUploadView(TestCase, WagtailTestUtils): response, "form", "file", - "Not a supported image format. Supported formats: GIF, JPEG, PNG, WEBP.", + "Not a supported image format. Supported formats: GIF, JPG, JPEG, PNG, WEBP.", ) # the action URL of the re-rendered form should include the select_format=true parameter @@ -2238,6 +2238,7 @@ class TestMultipleImageUploader(TestCase, WagtailTestUtils): "Upload a valid image. The file you uploaded was either not an image or a corrupted image.", ) + @override_settings(WAGTAILIMAGES_EXTENSIONS=["jpg", "gif"]) def test_add_post_bad_extension(self): """ The add view must check that the uploaded file extension is a valid @@ -2251,6 +2252,14 @@ class TestMultipleImageUploader(TestCase, WagtailTestUtils): }, ) + post_with_invalid_extension = self.client.post( + reverse("wagtailimages:add_multiple"), + { + "files[]": SimpleUploadedFile( + "test.png", get_test_image_file().file.getvalue() + ), + }, + ) # Check response self.assertEqual(response.status_code, 200) self.assertEqual(response["Content-Type"], "application/json") @@ -2264,7 +2273,25 @@ class TestMultipleImageUploader(TestCase, WagtailTestUtils): self.assertFalse(response_json["success"]) self.assertEqual( response_json["error_message"], - "Not a supported image format. Supported formats: GIF, JPEG, PNG, WEBP.", + "Not a supported image format. Supported formats: JPG, GIF.", + ) + + # Check post_with_invalid_extension + self.assertEqual(post_with_invalid_extension.status_code, 200) + self.assertEqual( + post_with_invalid_extension["Content-Type"], "application/json" + ) + + # Check JSON + response_json = json.loads(post_with_invalid_extension.content.decode()) + self.assertNotIn("image_id", response_json) + self.assertNotIn("form", response_json) + self.assertIn("success", response_json) + self.assertIn("error_message", response_json) + self.assertFalse(response_json["success"]) + self.assertEqual( + response_json["error_message"], + "Not a supported image format. Supported formats: JPG, GIF.", ) def test_add_post_duplicate(self): diff --git a/wagtail/images/tests/test_models.py b/wagtail/images/tests/test_models.py index 65a387b11b..effd6f8d79 100644 --- a/wagtail/images/tests/test_models.py +++ b/wagtail/images/tests/test_models.py @@ -48,7 +48,7 @@ class TestImage(TestCase): self.assertTrue(self.image.is_landscape()) def test_get_rect(self): - self.assertTrue(self.image.get_rect(), Rect(0, 0, 640, 480)) + self.assertEqual(self.image.get_rect(), Rect(0, 0, 640, 480)) def test_get_focal_point(self): self.assertIsNone(self.image.get_focal_point())