Merge pull request #2107 from vinyldarkscratch/validators/is_file

Add new IS_FILE validator
This commit is contained in:
mdipierro
2019-03-02 23:07:22 -08:00
committed by GitHub
4 changed files with 154 additions and 2 deletions

View File

@@ -10,7 +10,7 @@ Web2Py framework modules
========================
"""
__all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_LIST_OF_EMAILS', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_JSON', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'LOAD', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STRONG', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML', 'redirect', 'current', 'embed64']
__all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_LIST_OF_EMAILS', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FILE', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_JSON', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'LOAD', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STRONG', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML', 'redirect', 'current', 'embed64']
#: add pydal to sys.modules
import os

View File

@@ -166,7 +166,7 @@ class Highlighter(object):
+ r'from|True|False)(?![a-zA-Z0-9_])'),
'color:#185369; font-weight: bold'),
('WEB2PY',
re.compile(r'(request|response|session|cache|redirect|local_import|HTTP|TR|XML|URL|BEAUTIFY|A|BODY|BR|B|CAT|CENTER|CODE|COL|COLGROUP|DIV|EM|EMBED|FIELDSET|LEGEND|FORM|H1|H2|H3|H4|H5|H6|IFRAME|HEAD|HR|HTML|I|IMG|INPUT|LABEL|LI|LINK|MARKMIN|MENU|META|OBJECT|OL|ON|OPTION|P|PRE|SCRIPT|SELECT|SPAN|STYLE|TABLE|THEAD|TBODY|TFOOT|TAG|TD|TEXTAREA|TH|TITLE|TT|T|UL|XHTML|IS_SLUG|IS_STRONG|IS_LOWER|IS_UPPER|IS_ALPHANUMERIC|IS_DATETIME|IS_DATETIME_IN_RANGE|IS_DATE|IS_DATE_IN_RANGE|IS_DECIMAL_IN_RANGE|IS_EMAIL|IS_EXPR|IS_FLOAT_IN_RANGE|IS_IMAGE|IS_INT_IN_RANGE|IS_IN_SET|IS_IPV4|IS_LIST_OF|IS_LENGTH|IS_MATCH|IS_EQUAL_TO|IS_EMPTY_OR|IS_NULL_OR|IS_NOT_EMPTY|IS_TIME|IS_UPLOAD_FILENAME|IS_URL|CLEANUP|CRYPT|IS_IN_DB|IS_NOT_IN_DB|DAL|Field|SQLFORM|SQLTABLE|xmlescape|embed64)(?![a-zA-Z0-9_])'
re.compile(r'(request|response|session|cache|redirect|local_import|HTTP|TR|XML|URL|BEAUTIFY|A|BODY|BR|B|CAT|CENTER|CODE|COL|COLGROUP|DIV|EM|EMBED|FIELDSET|LEGEND|FORM|H1|H2|H3|H4|H5|H6|IFRAME|HEAD|HR|HTML|I|IMG|INPUT|LABEL|LI|LINK|MARKMIN|MENU|META|OBJECT|OL|ON|OPTION|P|PRE|SCRIPT|SELECT|SPAN|STYLE|TABLE|THEAD|TBODY|TFOOT|TAG|TD|TEXTAREA|TH|TITLE|TT|T|UL|XHTML|IS_SLUG|IS_STRONG|IS_LOWER|IS_UPPER|IS_ALPHANUMERIC|IS_DATETIME|IS_DATETIME_IN_RANGE|IS_DATE|IS_DATE_IN_RANGE|IS_DECIMAL_IN_RANGE|IS_EMAIL|IS_EXPR|IS_FILE|IS_FLOAT_IN_RANGE|IS_IMAGE|IS_INT_IN_RANGE|IS_IN_SET|IS_IPV4|IS_LIST_OF|IS_LENGTH|IS_MATCH|IS_EQUAL_TO|IS_EMPTY_OR|IS_NULL_OR|IS_NOT_EMPTY|IS_TIME|IS_UPLOAD_FILENAME|IS_URL|CLEANUP|CRYPT|IS_IN_DB|IS_NOT_IN_DB|DAL|Field|SQLFORM|SQLTABLE|xmlescape|embed64)(?![a-zA-Z0-9_])'
), 'link:%(link)s;text-decoration:None;color:#FF5C1F;'),
('MAGIC', re.compile(r'self|None'),
'color:#185369; font-weight: bold'),

View File

@@ -1021,6 +1021,65 @@ class TestValidators(unittest.TestCase):
rtn = IS_IMAGE(error_message='oops')(img)
self.assertEqual(rtn, (img, 'oops'))
def test_IS_FILE(self):
import cgi
from io import BytesIO
def gen_fake(filename):
formdata_file_data = """
---123
Content-Disposition: form-data; name="key2"
value2y
---123
Content-Disposition: form-data; name="file_attach"; filename="%s"
Content-Type: text/plain
this is the content of the fake file
---123--
""" % filename
formdata_file_environ = {
'CONTENT_LENGTH': str(len(formdata_file_data)),
'CONTENT_TYPE': 'multipart/form-data; boundary=-123',
'QUERY_STRING': 'key1=value1&key2=value2x',
'REQUEST_METHOD': 'POST',
}
return cgi.FieldStorage(fp=BytesIO(to_bytes(formdata_file_data)), environ=formdata_file_environ)['file_attach']
fake = gen_fake('example.pdf')
rtn = IS_FILE(extension='pdf')(fake)
self.assertEqual(rtn, (fake, None))
fake = gen_fake('example.gif')
rtn = IS_FILE(extension='pdf')(fake)
self.assertEqual(rtn, (fake, 'Enter valid filename'))
fake = gen_fake('multiple.pdf')
rtn = IS_FILE(extension=['pdf', 'png'])(fake)
self.assertEqual(rtn, (fake, None))
fake = gen_fake('multiple.png')
rtn = IS_FILE(extension=['pdf', 'png'])(fake)
self.assertEqual(rtn, (fake, None))
fake = gen_fake('multiple.gif')
rtn = IS_FILE(extension=['pdf', 'png'])(fake)
self.assertEqual(rtn, (fake, 'Enter valid filename'))
fake = gen_fake('backup2014.tar.gz')
rtn = IS_FILE(filename=re.compile('backup.*'), extension='tar.gz', lastdot=False)(fake)
self.assertEqual(rtn, (fake, None))
fake = gen_fake('README')
rtn = IS_FILE(filename='README', extension='', case=0)(fake)
self.assertEqual(rtn, (fake, None))
fake = gen_fake('readme')
rtn = IS_FILE(filename='README', extension='', case=0)(fake)
self.assertEqual(rtn, (fake, 'Enter valid filename'))
fake = gen_fake('readme')
rtn = IS_FILE(filename='README', case=2)(fake)
self.assertEqual(rtn, (fake, None))
fake = gen_fake('README')
rtn = IS_FILE(filename='README', case=2)(fake)
self.assertEqual(rtn, (fake, None))
rtn = IS_FILE(extension='pdf')('example.pdf')
self.assertEqual(rtn, ('example.pdf', 'Enter valid filename'))
def test_IS_UPLOAD_FILENAME(self):
import cgi
from io import BytesIO

View File

@@ -48,6 +48,7 @@ __all__ = [
'IS_LIST_OF_EMAILS',
'IS_EMPTY_OR',
'IS_EXPR',
'IS_FILE',
'IS_FLOAT_IN_RANGE',
'IS_IMAGE',
'IS_IN_DB',
@@ -3336,8 +3337,100 @@ class IS_IMAGE(Validator):
return (-1, -1)
class IS_FILE(Validator):
"""
Checks if name and extension of file uploaded through file input matches
given criteria.
Does *not* ensure the file type in any way. Returns validation failure
if no data was uploaded.
Args:
filename: string/compiled regex or a list of strings/regex of valid filenames
extension: string/compiled regex or a list of strings/regex of valid extensions
lastdot: which dot should be used as a filename / extension separator:
True means last dot, eg. file.jpg.png -> file.jpg / png
False means first dot, eg. file.tar.gz -> file / tar.gz
case: 0 - keep the case, 1 - transform the string into lowercase (default),
2 - transform the string into uppercase
If there is no dot present, extension checks will be done against empty
string and filename checks against whole value.
Examples:
Check if file has a pdf extension (case insensitive):
INPUT(_type='file', _name='name',
requires=IS_FILE(extension='pdf'))
Check if file is called 'thumbnail' and has a jpg or png extension
(case insensitive):
INPUT(_type='file', _name='name',
requires=IS_FILE(filename='thumbnail',
extension=['jpg', 'png']))
Check if file has a tar.gz extension and name starting with backup:
INPUT(_type='file', _name='name',
requires=IS_FILE(filename=re.compile('backup.*'),
extension='tar.gz', lastdot=False))
Check if file has no extension and name matching README
(case sensitive):
INPUT(_type='file', _name='name',
requires=IS_FILE(filename='README',
extension='', case=0)
"""
def __init__(self, filename=None, extension=None, lastdot=True, case=1,
error_message='Enter valid filename'):
self.filename = filename
self.extension = extension
self.lastdot = lastdot
self.case = case
self.error_message = error_message
def match(self, value1, value2):
if isinstance(value1, (list, tuple)):
for v in value1:
if self.match(v, value2):
return True
return False
elif isinstance(value1, type(regex_isint)):
return value1.match(value2)
elif isinstance(value1, str):
return value1 == value2
def __call__(self, value):
try:
string = value.filename
except:
return (value, translator(self.error_message))
if self.case == 1:
string = string.lower()
elif self.case == 2:
string = string.upper()
if self.lastdot:
dot = string.rfind('.')
else:
dot = string.find('.')
if dot == -1:
dot = len(string)
if self.filename and not self.match(self.filename, string[:dot]):
return (value, translator(self.error_message))
elif self.extension and not self.match(self.extension, string[dot + 1:]):
return (value, translator(self.error_message))
else:
return (value, None)
class IS_UPLOAD_FILENAME(Validator):
"""
For new applications, use IS_FILE().
Checks if name and extension of file uploaded through file input matches
given criteria.