From 066cc22a4ef7164008f039bd8c9e85a1ca0df8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Krienb=C3=BChl?= Date: Fri, 29 May 2015 16:57:33 +0200 Subject: [PATCH] Adds a file upload syntax --- HISTORY.rst | 3 +++ onegov/form/core.py | 35 +++++++++++++++++++++++++++-------- onegov/form/parser/core.py | 18 ++++++++++++++++++ onegov/form/validators.py | 7 ++----- 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 229a130..15aed95 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,9 @@ Changelog Unreleased ~~~~~~~~~~ +- Adds a file upload syntax. + [href] + - Show the 'required' flag, even if the requirement is conditional. [href] diff --git a/onegov/form/core.py b/onegov/form/core.py index 65aed8f..a77b9e6 100644 --- a/onegov/form/core.py +++ b/onegov/form/core.py @@ -1,3 +1,4 @@ +import base64 import inspect import magic import weakref @@ -7,6 +8,7 @@ from onegov.form.errors import InvalidMimeType from operator import itemgetter from mimetypes import types_map +from wtforms import FileField from wtforms import Form as BaseForm @@ -90,22 +92,35 @@ def __init__(self, *args, **kwargs): fields=(self._fields[f[1]] for f in fields) )) - def submitted(self, request): + def submitted(self, request, whitelist=default_whitelist): """ Returns true if the given request is a successful post request. """ - return request.POST and self.validate() + valid = request.POST and self.validate() - def load_file(self, request, field_id, whitelist=default_whitelist): + if not valid: + return False + + # load the files, making sure their mime type is on a whitelist and + # that their mimetype matches the extension they have + # + # access the files through self._files after that + for field in self._fields.values(): + if isinstance(field, FileField): + field.data = self.load_file(request, field) + + return True + + def load_file(self, request, field, whitelist=default_whitelist): """ Loads the given input field from the request, making sure it's mimetype matches the extension and is found in the mimetype whitelist. """ - field = getattr(self, field_id) - file_ext = '.' + field.data.split('.')[-1] - file_data = request.FILES[field.name].read() + file_ext = '.' + field.data.filename.split('.')[-1] + file_data = request.POST[field.id].file.read() mimetype_by_extension = types_map.get(file_ext, '0xdeadbeef') - mimetype_by_introspection = magic.from_buffer(file_data) + mimetype_by_introspection = magic.from_buffer(file_data, mime=True) + mimetype_by_introspection = mimetype_by_introspection.decode('utf-8') if mimetype_by_extension != mimetype_by_introspection: raise InvalidMimeType() @@ -113,7 +128,11 @@ def load_file(self, request, field_id, whitelist=default_whitelist): if mimetype_by_introspection not in whitelist: raise InvalidMimeType() - return file_data + return { + 'filename': field.data.filename, + 'base64': base64.b64encode(file_data).decode('ascii'), + 'mimetype': mimetype_by_introspection + } class Fieldset(object): diff --git a/onegov/form/parser/core.py b/onegov/form/parser/core.py index d6f9722..e5777b9 100644 --- a/onegov/form/parser/core.py +++ b/onegov/form/parser/core.py @@ -138,6 +138,24 @@ One more time, this doesn't mean that the datetime format can be influenced. +Files +~~~~~ + +A file upload is defined like this:: + + I'm a file upload field = *.* + +This particular example would allow any file. To allow only certain files +do something like this:: + + I'm a image filed = *.png|*.jpg|*.gif + I'm a document = *.doc + I'm any document = *.doc|*.pdf + +The files are checked against their file extension. Onegov.form also checks +that uploaded files have the mimetype they claim to have and it won't accept +obviously dangerous uploads like binaries (unless you really want to). + Standard Numbers ~~~~~~~~~~~~~~~~ diff --git a/onegov/form/validators.py b/onegov/form/validators.py index 97efb52..8828d36 100644 --- a/onegov/form/validators.py +++ b/onegov/form/validators.py @@ -40,11 +40,8 @@ class ExpectedExtensions(object): """ def __init__(self, extensions): - self.extensions = ['.' + ext.lstrip('.') for ext in extensions] + self.extensions = tuple('.' + ext.lstrip('.') for ext in extensions) def __call__(self, form, field): - if not field.data: - return - - if not field.data.endswith(self.extension): + if not field.data.filename.endswith(self.extensions): raise ValidationError(field.gettext(u'Invalid input.'))