Skip to content

Commit

Permalink
Implement allowedContentTypes
Browse files Browse the repository at this point in the history
Support to constrain files to specific content types with a
"allowedContentTypes" attribute on file and image fields.

Fixes: #157
  • Loading branch information
thet committed Mar 13, 2024
1 parent e8a44b8 commit bb9581c
Show file tree
Hide file tree
Showing 7 changed files with 355 additions and 86 deletions.
3 changes: 3 additions & 0 deletions news/157.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Support to constrain files to specific content types with a "allowedContentTypes" attribute on file and image fields.
Fixes: #157
[thet]
106 changes: 61 additions & 45 deletions plone/namedfile/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,55 @@
_ = MessageFactory("plone")


@implementer(IPluggableImageFieldValidation)
@adapter(INamedImageField, Interface)
class ImageContenttypeValidator:
class InvalidFile(ValidationError):
"""Exception for a invalid file."""

__doc__ = _("Invalid file")


class InvalidImageFile(ValidationError):
"""Exception for a invalid image file."""

__doc__ = _("Invalid image file")


class BinaryContenttypeValidator:
def __init__(self, field, value):
self.field = field
self.value = value

def __call__(self):
if self.value is None:
return
if not self.value.data:
# An empty file is invalid
raise self.exception(None, self.field.__name__)
mimetype = get_contenttype(self.value)
if mimetype.split("/")[0] != "image":
raise InvalidImageFile(mimetype, self.field.__name__)
if self.field.allowedContentTypes:
is_allowed = False
for allowed in self.field.allowedContentTypes:
allowed_group, allowed_type = allowed.split("/")
content_group, content_type = mimetype.split("/")
if allowed_group == content_group and (
allowed_type == content_type or allowed_type == "*"
):
is_allowed = True
break

if not is_allowed:
raise self.exception(mimetype, self.field.__name__)

class InvalidImageFile(ValidationError):
"""Exception for invalid image file"""

__doc__ = _("Invalid image file")
@implementer(IPluggableFileFieldValidation)
@adapter(INamedFileField, Interface)
class FileContenttypeValidator(BinaryContenttypeValidator):
exception = InvalidFile


@implementer(IPluggableImageFieldValidation)
@adapter(INamedImageField, Interface)
class ImageContenttypeValidator(BinaryContenttypeValidator):
exception = InvalidImageFile


def validate_binary_field(interface, field, value):
Expand All @@ -59,69 +89,55 @@ def validate_file_field(field, value):
validate_binary_field(IPluggableFileFieldValidation, field, value)


@implementer(INamedFileField)
class NamedFile(Object):
"""A NamedFile field"""

_type = FileValueType
schema = INamedFile
class NamedField(Object):

def __init__(self, **kw):
if "allowedContentTypes" in kw:
self.allowedContentTypes = kw.pop("allowedContentTypes")
if "schema" in kw:
self.schema = kw.pop("schema")
super().__init__(schema=self.schema, **kw)

def _validate(self, value):
super()._validate(value)
validate_file_field(self, value)
self.validator(value)


@implementer(INamedFileField)
class NamedFile(NamedField):
"""A NamedFile field"""

_type = FileValueType
schema = INamedFile
allowedContentTypes = ()
validator = validate_file_field


@implementer(INamedImageField)
class NamedImage(Object):
class NamedImage(NamedField):
"""A NamedImage field"""

_type = ImageValueType
schema = INamedImage

def __init__(self, **kw):
if "schema" in kw:
self.schema = kw.pop("schema")
super().__init__(schema=self.schema, **kw)

def _validate(self, value):
super()._validate(value)
validate_image_field(self, value)
allowedContentTypes = ("image/*",)
validator = validate_image_field


@implementer(INamedBlobFileField)
class NamedBlobFile(Object):
class NamedBlobFile(NamedField):
"""A NamedBlobFile field"""

_type = BlobFileValueType
schema = INamedBlobFile

def __init__(self, **kw):
if "schema" in kw:
self.schema = kw.pop("schema")
super().__init__(schema=self.schema, **kw)

def _validate(self, value):
super()._validate(value)
validate_file_field(self, value)
allowedContentTypes = ()
validator = validate_file_field


@implementer(INamedBlobImageField)
class NamedBlobImage(Object):
class NamedBlobImage(NamedField):
"""A NamedBlobImage field"""

_type = BlobImageValueType
schema = INamedBlobImage

def __init__(self, **kw):
if "schema" in kw:
self.schema = kw.pop("schema")
super().__init__(schema=self.schema, **kw)

def _validate(self, value):
super()._validate(value)
validate_image_field(self, value)
allowedContentTypes = ("image/*",)
validator = validate_image_field
7 changes: 6 additions & 1 deletion plone/namedfile/field.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
xmlns:zcml="http://namespaces.zope.org/zcml"
xmlns:browser="http://namespaces.zope.org/browser">

<adapter
factory=".field.FileContenttypeValidator"
name="file_contenttype"
/>

<adapter
factory=".field.ImageContenttypeValidator"
name="image_contenttype"
/>

</configure>
</configure>
Loading

0 comments on commit bb9581c

Please sign in to comment.