Allow validation of uploaded file extensions.

pull/7/head
Ferry Boender 10 years ago
parent 5cbf942f99
commit 742b4ec0e3
  1. 6
      doc/MANUAL.md
  2. 7
      examples/validate/validate.json
  3. 81
      src/scriptform.py

@ -212,8 +212,12 @@ This allows users to upload large files.
The original file name of the uploaded file is stored in a new variable The original file name of the uploaded file is stored in a new variable
'<field_name>__name'. '<field_name>__name'.
No additional validation is done on the file contents, or the file name. The `file` field type supports the following additional options:
- **`extensions`**: A list of extensions (minus leading dot) that are accepted
for file uploads. For example: `"extensions": ["csv", "tsv"]`
No additional validatikon is done on the file contents.
## <a name="output">Output</a> ## <a name="output">Output</a>

@ -66,6 +66,13 @@
"type": "password", "type": "password",
"required": true, "required": true,
"minlen": 5 "minlen": 5
},
{
"name": "file",
"title": "A file upload field",
"type": "file",
"required": true,
"extensions": ["csv"]
} }
] ]
} }

@ -8,6 +8,7 @@
# * Uploaded files mime-types/extensions # * Uploaded files mime-types/extensions
# - Radio field type has no correct default value. # - Radio field type has no correct default value.
# - Default values for input fields. # - Default values for input fields.
# - If there are errors in the form, its values are empties.
import sys import sys
import optparse import optparse
@ -163,7 +164,7 @@ class FormDefinition:
Validate all relevant fields for this form against form_values. Validate all relevant fields for this form against form_values.
""" """
errors = {} errors = {}
values = {} values = form_values.copy()
# First make sure all required fields are there # First make sure all required fields are there
for field in self.fields: for field in self.fields:
@ -174,14 +175,13 @@ class FormDefinition:
"This field is required" "This field is required"
) )
# Now validate their actual values. # Validate the field values, possible casting them to the correct type.
for field_name in form_values: for field in self.fields:
if field_name == 'form_name' or \ field_name = field['name']
form_values[field_name].filename: if field_name == 'form_name':
continue continue
try: try:
v = self.validate_field(field_name, v = self.validate_field(field_name, form_values)
form_values.getfirst(field_name))
if v is not None: if v is not None:
values[field_name] = v values[field_name] = v
except Exception, e: except Exception, e:
@ -189,7 +189,7 @@ class FormDefinition:
return (errors, values) return (errors, values)
def validate_field(self, field_name, value): def validate_field(self, field_name, form_values):
""" """
Validate a field in this form. Validate a field in this form.
""" """
@ -200,9 +200,10 @@ class FormDefinition:
field_type = field_def['type'] field_type = field_def['type']
validate_cb = getattr(self, 'validate_{0}'.format(field_type), None) validate_cb = getattr(self, 'validate_{0}'.format(field_type), None)
return validate_cb(field_def, value) return validate_cb(field_def, form_values)
def validate_string(self, field_def, value): def validate_string(self, field_def, form_values):
value = form_values[field_def['name']]
maxlen = field_def.get('maxlen', None) maxlen = field_def.get('maxlen', None)
minlen = field_def.get('minlen', None) minlen = field_def.get('minlen', None)
@ -213,7 +214,8 @@ class FormDefinition:
return value return value
def validate_integer(self, field_def, value): def validate_integer(self, field_def, form_values):
value = form_values[field_def['name']]
max = field_def.get('max', None) max = field_def.get('max', None)
min = field_def.get('min', None) min = field_def.get('min', None)
@ -229,7 +231,8 @@ class FormDefinition:
return int(value) return int(value)
def validate_float(self, field_def, value): def validate_float(self, field_def, form_values):
value = form_values[field_def['name']]
max = field_def.get('max', None) max = field_def.get('max', None)
min = field_def.get('min', None) min = field_def.get('min', None)
@ -245,7 +248,8 @@ class FormDefinition:
return float(value) return float(value)
def validate_date(self, field_def, value): def validate_date(self, field_def, form_values):
value = form_values[field_def['name']]
max = field_def.get('max', None) max = field_def.get('max', None)
min = field_def.get('min', None) min = field_def.get('min', None)
@ -263,19 +267,22 @@ class FormDefinition:
return value return value
def validate_radio(self, field_def, value): def validate_radio(self, field_def, form_values):
value = form_values[field_def['name']]
if not value in [o[0] for o in field_def['options']]: if not value in [o[0] for o in field_def['options']]:
raise ValueError( raise ValueError(
"Invalid value for radio button: {0}".format(value)) "Invalid value for radio button: {0}".format(value))
return value return value
def validate_select(self, field_def, value): def validate_select(self, field_def, form_values):
value = form_values[field_def['name']]
if not value in [o[0] for o in field_def['options']]: if not value in [o[0] for o in field_def['options']]:
raise ValueError( raise ValueError(
"Invalid value for dropdown: {0}".format(value)) "Invalid value for dropdown: {0}".format(value))
return value return value
def validate_text(self, field_def, value): def validate_text(self, field_def, form_values):
value = form_values[field_def['name']]
minlen = field_def.get('minlen', None) minlen = field_def.get('minlen', None)
maxlen = field_def.get('maxlen', None) maxlen = field_def.get('maxlen', None)
@ -289,7 +296,8 @@ class FormDefinition:
return value return value
def validate_password(self, field_def, value): def validate_password(self, field_def, form_values):
value = form_values[field_def['name']]
minlen = field_def.get('minlen', None) minlen = field_def.get('minlen', None)
if minlen is not None: if minlen is not None:
@ -298,6 +306,18 @@ class FormDefinition:
return value return value
def validate_file(self, field_def, form_values):
value = form_values[field_def['name']]
field_name = field_def['name']
upload_fname = form_values['{0}__name'.format(field_name)]
upload_fname_ext = os.path.splitext(upload_fname)[-1].lstrip('.')
extensions = field_def.get('extensions', None)
if extensions is not None and upload_fname_ext not in extensions:
raise Exception("Only file types allowed: {0}".format(','.join(extensions)))
return value
class ThreadedHTTPServer(ThreadingMixIn, BaseHTTPServer.HTTPServer): class ThreadedHTTPServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
pass pass
@ -555,12 +575,18 @@ class ScriptFormWebApp(WebAppHandler):
self.username not in form_def.allowed_users: self.username not in form_def.allowed_users:
raise Exception("Not authorized") raise Exception("Not authorized")
# Write contents of all uploaded files to temp files. These temp # Convert FieldStorage to a simple dict, because we're not allowd to
# filenames are passed to the callbacks instead of the actual contents. # add items to it. For normal fields, the form field name becomes the
file_fields = {} # key and the value becomes the field value. For file upload fields, we
# stream the uploaded file to a temp file and then put the temp file in
# the destination dict. We also add an extra field with the originally
# uploaded file's name.
values = {}
tmp_files = []
for field_name in form_values: for field_name in form_values:
field = form_values[field_name] field = form_values[field_name]
if field.filename: if field.filename:
# Field is an uploaded file. Stream it to a temp file
tmpfile = tempfile.mktemp(prefix="scriptform_") tmpfile = tempfile.mktemp(prefix="scriptform_")
f = file(tmpfile, 'w') f = file(tmpfile, 'w')
while True: while True:
@ -571,16 +597,17 @@ class ScriptFormWebApp(WebAppHandler):
f.close() f.close()
field.file.close() field.file.close()
file_fields[field_name] = tmpfile tmp_files.append(tmpfile) # For later cleanup
file_fields['{0}__name'.format(field_name)] = field.filename values[field_name] = tmpfile
values['{0}__name'.format(field_name)] = field.filename
else:
# Field is a normal form field. Store its value.
values[field_name] = form_values.getfirst(field_name, None)
# Validate the form values # Validate the form values
form_errors, form_values = form_def.validate(form_values) form_errors, form_values = form_def.validate(values)
if not form_errors: if not form_errors:
# Repopulate form values with uploaded tmp filenames
form_values.update(file_fields)
# Call user's callback. If a result is returned, we wrap its output # Call user's callback. If a result is returned, we wrap its output
# in some nice HTML. If no result is returned, the output was raw # in some nice HTML. If no result is returned, the output was raw
# and the callback should have written its own response to the # and the callback should have written its own response to the
@ -611,7 +638,7 @@ class ScriptFormWebApp(WebAppHandler):
self.h_form(form_name, form_errors) self.h_form(form_name, form_errors)
# Clean up uploaded files # Clean up uploaded files
for file_name in file_fields.values(): for file_name in tmp_files:
if os.path.exists(file_name): if os.path.exists(file_name):
os.unlink(file_name) os.unlink(file_name)

Loading…
Cancel
Save