Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/onegov/agency/forms/agency.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from onegov.form.fields import ChosenSelectField, HtmlField
from onegov.form.fields import MultiCheckboxField
from onegov.form.fields import UploadField
from onegov.form.validators import FileSizeLimit
from onegov.form.validators import FileSizeLimit, MIME_TYPES_IMAGE
from onegov.form.validators import WhitelistedMimeType
from onegov.gis import CoordinatesField
from sqlalchemy import func
Expand Down Expand Up @@ -73,10 +73,7 @@ class ExtendedAgencyForm(Form):
organigram = UploadField(
label=_('Organigram'),
validators=[
WhitelistedMimeType({
'image/jpeg',
'image/png',
}),
WhitelistedMimeType(MIME_TYPES_IMAGE),
FileSizeLimit(1 * 1024 * 1024)
]
)
Expand Down
4 changes: 2 additions & 2 deletions src/onegov/election_day/forms/election.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from onegov.form.fields import ChosenSelectMultipleField
from onegov.form.fields import PanelField
from onegov.form.fields import UploadField
from onegov.form.validators import FileSizeLimit
from onegov.form.validators import FileSizeLimit, MIME_TYPES_PDF
from onegov.form.validators import WhitelistedMimeType
from re import findall
from sqlalchemy import or_
Expand Down Expand Up @@ -323,7 +323,7 @@ class ElectionForm(Form):
explanations_pdf = UploadField(
label=_('Explanations (PDF)'),
validators=[
WhitelistedMimeType({'application/pdf'}),
WhitelistedMimeType(MIME_TYPES_PDF),
FileSizeLimit(100 * 1024 * 1024)
],
fieldset=_('Related link')
Expand Down
8 changes: 4 additions & 4 deletions src/onegov/election_day/forms/election_compound.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from onegov.form.fields import ChosenSelectMultipleField
from onegov.form.fields import PanelField
from onegov.form.fields import UploadField
from onegov.form.validators import FileSizeLimit
from onegov.form.validators import FileSizeLimit, MIME_TYPES_PDF
from onegov.form.validators import WhitelistedMimeType
from re import findall
from sqlalchemy import or_
Expand Down Expand Up @@ -228,7 +228,7 @@ class ElectionCompoundForm(Form):
explanations_pdf = UploadField(
label=_('Explanations (PDF)'),
validators=[
WhitelistedMimeType({'application/pdf'}),
WhitelistedMimeType(MIME_TYPES_PDF),
FileSizeLimit(100 * 1024 * 1024)
],
fieldset=_('Related link')
Expand All @@ -237,7 +237,7 @@ class ElectionCompoundForm(Form):
upper_apportionment_pdf = UploadField(
label=_('Upper apportionment (PDF)'),
validators=[
WhitelistedMimeType({'application/pdf'}),
WhitelistedMimeType(MIME_TYPES_PDF),
FileSizeLimit(100 * 1024 * 1024)
],
fieldset=_('Related link'),
Expand All @@ -247,7 +247,7 @@ class ElectionCompoundForm(Form):
lower_apportionment_pdf = UploadField(
label=_('Lower apportionment (PDF)'),
validators=[
WhitelistedMimeType({'application/pdf'}),
WhitelistedMimeType(MIME_TYPES_PDF),
FileSizeLimit(100 * 1024 * 1024)
],
fieldset=_('Related link'),
Expand Down
4 changes: 2 additions & 2 deletions src/onegov/election_day/forms/upload/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
'text/csv'
}
ALLOWED_MIME_TYPES_XML = {
'application/xml',
'text/xml',
'application/xml', # official, standard
'text/xml', # deprecated MIME type for XML content
'text/plain'
}

Expand Down
4 changes: 2 additions & 2 deletions src/onegov/election_day/forms/vote.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from onegov.form.fields import ChosenSelectField
from onegov.form.fields import PanelField
from onegov.form.fields import UploadField
from onegov.form.validators import FileSizeLimit
from onegov.form.validators import FileSizeLimit, MIME_TYPES_PDF
from onegov.form.validators import WhitelistedMimeType
from wtforms.fields import BooleanField
from wtforms.fields import DateField
Expand Down Expand Up @@ -280,7 +280,7 @@ class VoteForm(Form):
explanations_pdf = UploadField(
label=_('Explanations (PDF)'),
validators=[
WhitelistedMimeType({'application/pdf'}),
WhitelistedMimeType(MIME_TYPES_PDF),
FileSizeLimit(100 * 1024 * 1024)
],
fieldset=_('Related link')
Expand Down
9 changes: 1 addition & 8 deletions src/onegov/file/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,8 @@ def get_supported_image_mime_types() -> set[str]:

# Not all PIL formats register a mime type, fill in the blanks ourselves.
supported_types = {
'image/bmp',
'image/x-bmp',
'image/x-MS-bmp',
'image/x-icon',
'image/x-ico',
'image/x-win-bitmap',
'image/x-pcx',
'image/x-portable-pixmap',
'image/x-tga'
'image/x-xcf',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the resulting list for get_supported_image_mime_types is:

 - image/tiff
 - image/palm
 - image/x-MS-bmp
 - image/avif
 - image/x-portable-anymap
 - image/x-icon
 - image/xbm
 - image/x-tga
 - image/xpm
 - image/icns
 - image/mpo
 - image/bmp
 - image/webp
 - image/jp2
 - image/sgi
 - image/vnd.adobe.photoshop
 - video/mpeg
 - image/jpeg
 - image/png
 - image/gif
 - image/svg+xml
 - image/x-pcx

}

for mime in Image.MIME.values():
Expand Down
6 changes: 4 additions & 2 deletions src/onegov/form/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from onegov.file.utils import IMAGE_MIME_TYPES_AND_SVG
from onegov.form import log, _
from onegov.form.utils import path_to_filename
from onegov.form.validators import ValidPhoneNumber
from onegov.form.validators import ValidPhoneNumber, WhitelistedMimeType
from onegov.form.widgets import ChosenSelectWidget
from onegov.form.widgets import LinkPanelWidget
from onegov.form.widgets import DurationInput
Expand Down Expand Up @@ -260,6 +260,7 @@ class UploadField(FileField):
action: Literal['keep', 'replace', 'delete']
file: IO[bytes] | None
filename: str | None
validators = [WhitelistedMimeType()]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not very robust, we definitely should overwrite __init__ instead, the only remaining question is, whether or not we want to add an extra parameter allowed_mimetypes or if we want to change the default of the validators argument to (WhitelistedMimeType(),).

I kind of like the extra parameter better, since it means we don't need to import WhitelistedMimeType everywhere.

You can then pass it on to super().__init__ as validators=[*(validators or ()), WhitelistedMimeType(allowed_mimetypes)].


if TYPE_CHECKING:
def __init__(
Expand Down Expand Up @@ -448,6 +449,7 @@ def _add_entry(self, d: _MultiDictLikeWithGetlist, /) -> UploadField:

upload_field_class: type[UploadField] = UploadField
upload_widget: Widget[UploadField] = UploadWidget()
validators = [WhitelistedMimeType()]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here


def __init__(
self,
Expand Down Expand Up @@ -479,11 +481,11 @@ def __init__(

# a lot of the arguments we just pass through to the subfield
unbound_field = self.upload_field_class(
validators=validators, # type:ignore[arg-type]
filters=filters,
description=description,
widget=upload_widget,
render_kw=render_kw,
validators=validators, # type:ignore[arg-type]
**extra_arguments
)
super().__init__(
Expand Down
113 changes: 98 additions & 15 deletions src/onegov/form/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from decimal import Decimal
from dateutil.relativedelta import relativedelta
from mimetypes import types_map

from onegov.file.utils import get_supported_image_mime_types
from onegov.form import _
from onegov.form.errors import (DuplicateLabelError, InvalidIndentSyntax,
EmptyFieldsetError)
Expand All @@ -31,7 +33,8 @@
from wtforms.validators import ValidationError


from typing import Generic, TYPE_CHECKING
from typing import Generic, TYPE_CHECKING, Any

if TYPE_CHECKING:
from collections.abc import Collection, Sequence
from onegov.core.orm import Base
Expand Down Expand Up @@ -114,13 +117,85 @@ def __call__(self, form: Form, field: Field) -> None:
if not field.data:
return

if field.data.get('size', 0) > self.max_bytes:
if isinstance(field.data, list): # UploadMultipleField
for data in field.data:
if not data:
continue # in case of file deletion

self.validate_filesize(field, data)

else:
self.validate_filesize(field, field.data)

def validate_filesize(self, field: Field, data: dict[Any, Any]) -> None:
if data.get('size', 0) > self.max_bytes:
message = field.gettext(self.message).format(
humanize.naturalsize(self.max_bytes)
)
raise ValidationError(message)


MIME_TYPES_PDF = {
'application/pdf',
}

# for now not allowed by default
MIME_TYPES_JSON = {
'application/json',
}

MIME_TYPES_DOCUMENT = {
'application/msword', # doc
'application/rtf',
*MIME_TYPES_PDF,
'application/vnd.ms-excel', # xls
('application/vnd.openxmlformats-officedocument.'
'presentationml.presentation'), # pptx
('application/vnd.openxmlformats-officedocument.'
'spreadsheetml.sheet'), # xlsx
('application/vnd.openxmlformats-officedocument.'
'wordprocessingml.document'), # docx
'application/CDFV2', # old ms office docs
'application/x-ole-storage', # old ms office docs
'application/CDFV2-unknown' # old ms office docs
}

MIME_TYPES_XML = {
'application/xml',
}

MIME_TYPES_ARCHIVE = {
'application/zip',
}

MIME_TYPES_TEXT_DATA = {
'text/csv',
'text/plain',
}

MIME_TYPES_IMAGE = {
# allowed types based on PIL
*get_supported_image_mime_types(),
'image/svg+xml',
}

MIME_TYPES_AUDIO = {
'audio/mp4',
'audio/mpeg',
'audio/wav',
'audio/webm', # weba
}

MIME_TYPES_VIDEO = {
'video/mp4',
'video/mpeg', # mpg, mpeg
'video/ogg',
'video/quicktime', # mov
'video/webm', # webm
'video/x-msvideo', # avi
}


class WhitelistedMimeType:
""" Makes sure an uploaded file is in a whitelist of allowed mimetypes.

Expand All @@ -129,17 +204,13 @@ class WhitelistedMimeType:
"""

whitelist: Collection[str] = {
'application/excel',
'application/vnd.ms-excel',
'application/msword',
'application/pdf',
'application/zip',
'image/gif',
'image/jpeg',
'image/png',
'image/x-ms-bmp',
'text/plain',
'text/csv'
*MIME_TYPES_DOCUMENT,
*MIME_TYPES_XML,
*MIME_TYPES_ARCHIVE,
*MIME_TYPES_TEXT_DATA,
*MIME_TYPES_IMAGE,
*MIME_TYPES_AUDIO,
*MIME_TYPES_VIDEO,
}

message = _('Files of this type are not supported.')
Expand All @@ -152,8 +223,20 @@ def __call__(self, form: Form, field: Field) -> None:
if not field.data:
return

if field.data['mimetype'] not in self.whitelist:
raise ValidationError(field.gettext(self.message))
if isinstance(field.data, list): # UploadMultipleField
for data in field.data:
if not data:
continue # in case of file deletion

self.validate_mimetype(field, data)

else:
self.validate_mimetype(field, field.data)

def validate_mimetype(self, field: Field, data: dict[Any, Any]) -> None:
if data['mimetype'] not in self.whitelist:
message = field.gettext(self.message)
raise ValidationError(field.gettext(message))


class ExpectedExtensions(WhitelistedMimeType):
Expand Down
12 changes: 8 additions & 4 deletions src/onegov/landsgemeinde/forms/agenda.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@
from onegov.form.fields import TimeField
from onegov.form.fields import UploadField
from onegov.form.forms import NamedFileForm
from onegov.form.validators import FileSizeLimit
from onegov.form.validators import WhitelistedMimeType
from onegov.form.validators import (
FileSizeLimit,
MIME_TYPES_PDF,
MIME_TYPES_ARCHIVE,
WhitelistedMimeType
)
from onegov.landsgemeinde import _
from onegov.landsgemeinde.layouts import DefaultLayout
from onegov.landsgemeinde.models import AgendaItem, LandsgemeindeFile
Expand Down Expand Up @@ -80,7 +84,7 @@ class AgendaItemForm(NamedFileForm):
label=_('Excerpt from the Memorial (PDF)'),
fieldset=_('Memorial'),
validators=[
WhitelistedMimeType({'application/pdf'}),
WhitelistedMimeType(MIME_TYPES_PDF),
FileSizeLimit(100 * 1024 * 1024)
]
)
Expand Down Expand Up @@ -226,7 +230,7 @@ class AgendaItemUploadForm(Form):
label=_('Agenda Item ZIP'),
fieldset=_('Import'),
validators=[
WhitelistedMimeType({'application/zip'}),
WhitelistedMimeType(MIME_TYPES_ARCHIVE),
FileSizeLimit(100 * 1024 * 1024)
]
)
Expand Down
Loading
Loading