diff --git a/buildout.cfg b/buildout.cfg index bb635cb..808636e 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -1,5 +1,10 @@ [buildout] + # use this extend one of the buildout configuration: extends = test_plone52.cfg + +[instance] +eggs += + redturtle.volto diff --git a/collective/limitfilesizepanel/browser/configure.zcml b/collective/limitfilesizepanel/browser/configure.zcml index a1b4a73..c098ede 100644 --- a/collective/limitfilesizepanel/browser/configure.zcml +++ b/collective/limitfilesizepanel/browser/configure.zcml @@ -3,37 +3,55 @@ xmlns:browser="http://namespaces.zope.org/browser" xmlns:plone="http://namespaces.plone.org/plone" xmlns:zcml="http://namespaces.zope.org/zcml" - i18n_domain="collective.limitfilesizepanel"> + i18n_domain="collective.limitfilesizepanel" + > - + - + - + + + + + diff --git a/collective/limitfilesizepanel/browser/folder_contents_upload.py b/collective/limitfilesizepanel/browser/folder_contents_upload.py new file mode 100644 index 0000000..8974ad0 --- /dev/null +++ b/collective/limitfilesizepanel/browser/folder_contents_upload.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +from plone import api +from plone.app.content.browser.file import FileUploadView as BaseFileUploadView + +import mimetypes + + +class FileUpload(BaseFileUploadView): + """ + Add filetype validation for folder_contents massive upload view. + This view is a merge from collective.limitfilesizepanel and rer.hardening + customizations. + """ + + def __call__(self): + filedata = self.request.form.get("file", None) + + if not filedata: + return super(FileUpload, self).__call__() + + # limitfilesizepanel check + filename = filedata.filename + content_type = mimetypes.guess_type(filename)[0] or "" + ctr = api.portal.get_tool(name="content_type_registry") + portal_type = ctr.findTypeName(filename.lower(), content_type, "") or "File" + + helper_view = api.content.get_view( + name="lfsp_helpers_view", + context=self.context, + request=self.context.REQUEST, + ) + + if helper_view.newDataOnly() and "/edit" in self.request.get( + "HTTP_REFERER" + ): # noqa + return super(FileUpload, self).__call__() + maxsize = helper_view.get_maxsize_tiny((portal_type,)) + if not maxsize: + return super(FileUpload, self).__call__() + + size_check = helper_view.check_size(maxsize=maxsize, uploadfile=filedata) + if size_check and not size_check.get("valid", False): + response = self.request.RESPONSE + response.setStatus(403) + return size_check.get("error", "") + + return super(FileUpload, self).__call__() diff --git a/collective/limitfilesizepanel/configure.zcml b/collective/limitfilesizepanel/configure.zcml index d24b80b..346f835 100644 --- a/collective/limitfilesizepanel/configure.zcml +++ b/collective/limitfilesizepanel/configure.zcml @@ -1,50 +1,58 @@ + xmlns:i18n="http://namespaces.zope.org/i18n" + xmlns:monkey="http://namespaces.plone.org/monkey" + xmlns:zcml="http://namespaces.zope.org/zcml" + i18n_domain="collective.limitfilesizepanel" + > - + + id="collective.limitfilesizepanel.LimitFileSizePanel" + title="collective.limitfilesizepanel: Manage limit file size settings" + /> + id="collective.limitfilesizepanel.BypassLimitSize" + title="collective.limitfilesizepanel: Bypass limit size" + /> + - - - + + diff --git a/collective/limitfilesizepanel/tests/test_upload.py b/collective/limitfilesizepanel/tests/test_upload.py new file mode 100644 index 0000000..cffca3d --- /dev/null +++ b/collective/limitfilesizepanel/tests/test_upload.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +from collective.limitfilesizepanel.interfaces import ILimitFileSizePanel +from collective.limitfilesizepanel.testing import LIMITFILESIZEPANEL_FUNCTIONAL_TESTING +from plone import api +from plone.app.testing import setRoles +from plone.app.testing import TEST_USER_ID +from plone.registry.interfaces import IRegistry +from zope.component import queryUtility +from plone.namedfile.field import validate_image_field +from plone.namedfile.field import validate_file_field +from plone.namedfile.interfaces import INamedImageField +from plone.namedfile.interfaces import INamedFileField +from zope.interface import implementer +from plone.namedfile.file import NamedImage +from plone.namedfile.file import NamedFile +from io import BytesIO +from PIL import Image +from transaction import commit +from zope.interface.exceptions import Invalid + +import unittest +import json + + +@implementer(INamedImageField) +class FakeImageField(object): + def __init__(self, name=""): + self.name = name + + __name__ = "logo" + + def getName(self): + return self.name + + +@implementer(INamedFileField) +class FakeFileField(object): + def __init__(self, name=""): + self.name = name + + __name__ = "file" + + def getName(self): + return self.name + + +class TestValidation(unittest.TestCase): + layer = LIMITFILESIZEPANEL_FUNCTIONAL_TESTING + + def setUp(self): + self.portal = self.layer["portal"] + self.request = self.layer["request"] + + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + self.registry = queryUtility(IRegistry) + + api.portal.set_registry_record( + "new_data_only", + False, + interface=ILimitFileSizePanel, + ) + + api.portal.set_registry_record( + "file_size", + 1, + interface=ILimitFileSizePanel, + ) + api.portal.set_registry_record( + "image_size", + 1, + interface=ILimitFileSizePanel, + ) + + api.portal.set_registry_record( + "types_settings", + json.dumps( + [{"content_type": "News Item", "field_name": "image", "size": 2}] + ), + interface=ILimitFileSizePanel, + ) + commit() + + def generate_image(self, needed_size=1): + # Set the dimensions of the image + width = 1024 + height = 1024 + img_size = 1024 * 1024 * needed_size + # Create a new image with white background + image = Image.new("RGB", (width, height), (255, 255, 255)) + + # Save the image to an in-memory buffer as a JPEG file with maximum quality + buffer = BytesIO() + image.save(buffer, format="JPEG", quality=100) + + # Get the size of the image buffer in bytes + size = buffer.tell() + + # Ensure that the image buffer is exactly 1 MB + if size < img_size: + padding = b"\0" * (img_size - size) + buffer.write(padding) + + return buffer + + def test_validate_image_field(self): + # field is empty + field = FakeImageField() + field.context = self.portal + + image = NamedImage() + + # 1mb image is ok + image._setData(self.generate_image()) + validate_image_field(field, image) + + image._setData(self.generate_image(2)) + + with self.assertRaises(Invalid) as cm: + validate_image_field(field, image) + + self.assertIn( + "Validation failed. Uploaded data is too large: 2.0MB (max 1.0MB)", + str(cm.exception), + ) + + def test_validate_file_field(self): + # field is empty + field = FakeFileField() + field.context = self.portal + + example_file = NamedFile() + text = " " * (1024 * 1024) + example_file._setData(BytesIO(text.encode())) + # 1mb file is ok + validate_file_field(field, example_file) + + text = " " * (1024 * 1024 * 2) + example_file._setData(BytesIO(text.encode())) + + with self.assertRaises(Invalid) as cm: + validate_file_field(field, example_file) + + self.assertIn( + "Validation failed. Uploaded data is too large: 2.0MB (max 1.0MB)", + str(cm.exception), + ) + + def test_validate_image_field_on_news(self): + news = api.content.create( + type="News Item", + id="news", + container=self.portal, + ) + + field = FakeImageField(name="image") + field.context = news + image = NamedImage() + + # 1mb image is ok + image._setData(self.generate_image()) + validate_image_field(field, image) + + # 2mb image is ok + image._setData(self.generate_image(2)) + validate_image_field(field, image) + + # 3mb image is not ok + image._setData(self.generate_image(3)) + + with self.assertRaises(Invalid) as cm: + validate_image_field(field, image) + + self.assertIn( + "Validation failed. Uploaded data is too large: 3.0MB (max 2.0MB)", + str(cm.exception), + ) diff --git a/collective/limitfilesizepanel/upgrades.zcml b/collective/limitfilesizepanel/upgrades.zcml index f702ce1..0c2cf08 100644 --- a/collective/limitfilesizepanel/upgrades.zcml +++ b/collective/limitfilesizepanel/upgrades.zcml @@ -1,47 +1,48 @@ + xmlns:i18n="http://namespaces.zope.org/i18n" + xmlns:zcml="http://namespaces.zope.org/zcml" + i18n_domain="collective.limitfilesizepanel" + > + title="Upgrade to collective.limitfilesizepanel to version 1.1" + description="Migrates collective.limitfilesizepanel to 1000" + profile="collective.limitfilesizepanel:default" + source="1.0" + destination="1000" + handler=".setuphandlers.migrateTo1000" + sortkey="1" + /> + title="Upgrade to collective.limitfilesizepanel to version 1.3" + description="Migrates collective.limitfilesizepanel to 1100" + profile="collective.limitfilesizepanel:default" + source="11000" + destination="1100" + handler=".setuphandlers.migrateTo1100" + sortkey="2" + /> + title="Upgrade to collective.limitfilesizepanel to version 1200" + description="Migrates collective.limitfilesizepanel to 1200" + profile="collective.limitfilesizepanel:default" + source="1100" + destination="1200" + handler=".setuphandlers.migrateTo1200" + sortkey="3" + /> + title="Upgrade to collective.limitfilesizepanel to version 1300" + description="Migrates collective.limitfilesizepanel to 1300" + profile="collective.limitfilesizepanel:default" + source="1200" + destination="1300" + handler=".setuphandlers.migrateTo1300" + sortkey="4" + />