Merge pull request #2107 from vinyldarkscratch/validators/is_file
Add new IS_FILE validator
This commit is contained in:
+1
-1
@@ -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
|
#: add pydal to sys.modules
|
||||||
import os
|
import os
|
||||||
|
|||||||
+1
-1
@@ -166,7 +166,7 @@ class Highlighter(object):
|
|||||||
+ r'from|True|False)(?![a-zA-Z0-9_])'),
|
+ r'from|True|False)(?![a-zA-Z0-9_])'),
|
||||||
'color:#185369; font-weight: bold'),
|
'color:#185369; font-weight: bold'),
|
||||||
('WEB2PY',
|
('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;'),
|
), 'link:%(link)s;text-decoration:None;color:#FF5C1F;'),
|
||||||
('MAGIC', re.compile(r'self|None'),
|
('MAGIC', re.compile(r'self|None'),
|
||||||
'color:#185369; font-weight: bold'),
|
'color:#185369; font-weight: bold'),
|
||||||
|
|||||||
@@ -1021,6 +1021,65 @@ class TestValidators(unittest.TestCase):
|
|||||||
rtn = IS_IMAGE(error_message='oops')(img)
|
rtn = IS_IMAGE(error_message='oops')(img)
|
||||||
self.assertEqual(rtn, (img, 'oops'))
|
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):
|
def test_IS_UPLOAD_FILENAME(self):
|
||||||
import cgi
|
import cgi
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ __all__ = [
|
|||||||
'IS_LIST_OF_EMAILS',
|
'IS_LIST_OF_EMAILS',
|
||||||
'IS_EMPTY_OR',
|
'IS_EMPTY_OR',
|
||||||
'IS_EXPR',
|
'IS_EXPR',
|
||||||
|
'IS_FILE',
|
||||||
'IS_FLOAT_IN_RANGE',
|
'IS_FLOAT_IN_RANGE',
|
||||||
'IS_IMAGE',
|
'IS_IMAGE',
|
||||||
'IS_IN_DB',
|
'IS_IN_DB',
|
||||||
@@ -3336,8 +3337,100 @@ class IS_IMAGE(Validator):
|
|||||||
return (-1, -1)
|
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):
|
class IS_UPLOAD_FILENAME(Validator):
|
||||||
"""
|
"""
|
||||||
|
For new applications, use IS_FILE().
|
||||||
|
|
||||||
Checks if name and extension of file uploaded through file input matches
|
Checks if name and extension of file uploaded through file input matches
|
||||||
given criteria.
|
given criteria.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user