Skip to content

Commit

Permalink
Merge pull request #159 from plone/allowed_content_types--5.x
Browse files Browse the repository at this point in the history
Support for allowed content types. (Branch for 5.x)
  • Loading branch information
mauritsvanrees committed Mar 15, 2024
2 parents 4bec011 + 6663658 commit fce90e0
Show file tree
Hide file tree
Showing 14 changed files with 530 additions and 141 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ See the ``usage.rst`` doctest for more details.
Source Code
===========

Note: This packages is licensed under a *BSD license*.
Note: This package is licensed under a *BSD license*.
Please do not add dependencies on GPL code!

Contributors please read the document `Process for Plone core's development <https://docs.plone.org/develop/coredev/docs/index.html>`_
Expand Down
8 changes: 8 additions & 0 deletions news/157-2.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Improve contenttype detection logic for unregistered but common types.

Change get_contenttype to support common types which are or were not registered
with IANA, like image/webp or audio/midi.

Note: image/webp is already a IANA registered type and also added by
Products.MimetypesRegistry.
[thet]
7 changes: 7 additions & 0 deletions news/157.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Support for allowed media types.

Support to constrain files to specific media types with a "accept" attribute on
file and image fields, just like the "accept" attribute of the HTML file input.

Fixes: #157
[thet]
140 changes: 81 additions & 59 deletions plone/namedfile/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,111 +22,133 @@
from zope.schema import Object
from zope.schema import ValidationError

import mimetypes

_ = MessageFactory('plone')


@implementer(IPluggableImageFieldValidation)
@adapter(INamedImageField, Interface)
class ImageContenttypeValidator(object):
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.field.accept:
# No restrictions.
return

mimetype = get_contenttype(self.value)
if mimetype.split('/')[0] != 'image':
raise InvalidImageFile(mimetype, self.field.__name__)

for accept in self.field.accept:
if accept[0] == ".":
# This is a file extension. Get a media type from it.
accept = mimetypes.guess_type("dummy" + accept, strict=False)[0]
if accept is None:
# This extension is unknown. Skip it.
continue

class InvalidImageFile(ValidationError):
"""Exception for invalid image file"""
__doc__ = _(u'Invalid image file')
try:
accept_type, accept_subtype = accept.split("/")
content_type, content_subtype = mimetype.split("/")
except ValueError:
# The accept type is invalid. Skip it.
continue

if accept_type == content_type and (
accept_subtype == content_subtype or accept_subtype == "*"
):
# This file is allowed, just don't raise a ValidationError.
return

def validate_binary_field(interface, field, value):
for name, validator in getAdapters((field, value), interface):
validator()
# The file's content type is not allowed. Raise a ValidationError.
raise self.exception(mimetype, self.field.__name__)


def validate_image_field(field, value):
validate_binary_field(IPluggableImageFieldValidation, field, value)
@implementer(IPluggableFileFieldValidation)
@adapter(INamedFileField, Interface)
class FileContenttypeValidator(BinaryContenttypeValidator):
exception = InvalidFile


def validate_file_field(field, value):
validate_binary_field(IPluggableFileFieldValidation, field, value)
@implementer(IPluggableImageFieldValidation)
@adapter(INamedImageField, Interface)
class ImageContenttypeValidator(BinaryContenttypeValidator):
exception = InvalidImageFile


class NamedField(Object):

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

def validate(self, value, interface):
super(NamedField, self).validate(value)
for name, validator in getAdapters((self, value), interface):
validator()


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

_type = FileValueType
schema = INamedFile
accept = ()

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

def _validate(self, value):
super(NamedFile, self)._validate(value)
validate_file_field(self, value)
def validate(self, value):
super(NamedFile, self).validate(value, IPluggableFileFieldValidation)


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

_type = ImageValueType
schema = INamedImage
accept = ("image/*",)

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

def _validate(self, value):
super(NamedImage, self)._validate(value)
validate_image_field(self, value)
def validate(self, value):
super(NamedImage, self).validate(value, IPluggableImageFieldValidation)


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

_type = BlobFileValueType
schema = INamedBlobFile
accept = ()

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

def _validate(self, value):
super(NamedBlobFile, self)._validate(value)
validate_file_field(self, value)
def validate(self, value):
super(NamedBlobFile, self).validate(value, IPluggableFileFieldValidation)


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

_type = BlobImageValueType
schema = INamedBlobImage
accept = ("image/*",)

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

def _validate(self, value):
super(NamedBlobImage, self)._validate(value)
validate_image_field(self, value)
def validate(self, value):
super(NamedBlobImage, self).validate(value, IPluggableImageFieldValidation)
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>

0 comments on commit fce90e0

Please sign in to comment.