From 64537398b29653cc8fe948a4a4df16d5b61eca80 Mon Sep 17 00:00:00 2001 From: Edward Njoroge Date: Mon, 14 Sep 2020 01:16:02 +0300 Subject: [PATCH] Use bytes to determine file types as a default (#14) Fixe issue #13 --- Pipfile | 1 + Pipfile.lock | 12 ++++++++++-- flask_sieve/rules_processor.py | 13 +++++++++++-- requirements.txt | 1 + setup.py | 3 ++- tests/test_rules_processor.py | 24 +++++++++++++++++++++++- 6 files changed, 48 insertions(+), 6 deletions(-) diff --git a/Pipfile b/Pipfile index b8f13bf..ca900cf 100644 --- a/Pipfile +++ b/Pipfile @@ -14,6 +14,7 @@ pytz = "*" flask = "*" pillow = ">=7.1.0" werkzeug = "*" +filetype = "*" [requires] python_version = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 1121544..94f3bd1 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "210f0d903f858818aee67eb13cc59d8c6c591ce0d0137516371737716d3b2a98" + "sha256": "381d3542ec2a397d3000182bfbd713b6b998869256ed1aa9cd56a0ee04002117" }, "pipfile-spec": 6, "requires": { @@ -38,6 +38,14 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==7.1.2" }, + "filetype": { + "hashes": [ + "sha256:353369948bb1c09b8b3ea3d78390b5586e9399bff9aab894a1dff954e31a66f6", + "sha256:da393ece8d98b47edf2dd5a85a2c8733e44b769e32c71af4cd96ed8d38d96aa7" + ], + "index": "pypi", + "version": "==1.0.7" + }, "flask": { "hashes": [ "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", @@ -172,7 +180,7 @@ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, "urllib3": { diff --git a/flask_sieve/rules_processor.py b/flask_sieve/rules_processor.py index 841b227..c6e4f7e 100644 --- a/flask_sieve/rules_processor.py +++ b/flask_sieve/rules_processor.py @@ -7,6 +7,7 @@ import pytz import operator import requests +import filetype from PIL import Image from dateutil.parser import parse as dateparse @@ -231,7 +232,11 @@ def validate_extension(self, value, params, **kwargs): if not self.validate_file(value): return False self._assert_params_size(size=1, params=params, rule='extension') - return value.filename.split('.')[-1].lower() == params[0] + kind = filetype.guess(value.stream.read(512)) + value.seek(0) + if kind is None: + return value.filename.split('.')[-1].lower() == params[0] + return kind.extension in params @staticmethod def validate_file(value, **kwargs): @@ -375,7 +380,11 @@ def validate_mime_types(self, value, params, **kwargs): if not self.validate_file(value): return False self._assert_params_size(size=1, params=params, rule='mime_types') - return value.mimetype in params + kind = filetype.guess(value.stream.read(512)) + value.seek(0) + if kind is None: + return value.mimetype in params + return kind.mime in params def validate_min(self, value, params, **kwargs): self._assert_params_size(size=1, params=params, rule='min') diff --git a/requirements.txt b/requirements.txt index b048e46..344b81a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ click==7.1.2 coverage==5.2.1 coveralls==2.1.2 docopt==0.6.2 +filetype==1.0.7 Flask==1.1.2 idna==2.10 itsdangerous==1.1.0 diff --git a/setup.py b/setup.py index ce40b45..72036ad 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ name='flask-sieve', description='A Laravel inspired requests validator for Flask', long_description='Find the documentation at https://flask-sieve.readthedocs.io/en/latest/', - version='1.1.2', + version='1.1.3', url='https://github.com/codingedward/flask-sieve', license='BSD-2', author='Edward Njoroge', @@ -18,6 +18,7 @@ 'python-dateutil', 'pytz', 'requests', + 'filetype', ], classifiers=[ 'Development Status :: 5 - Production/Stable', diff --git a/tests/test_rules_processor.py b/tests/test_rules_processor.py index 4b968a2..abbf64b 100644 --- a/tests/test_rules_processor.py +++ b/tests/test_rules_processor.py @@ -6,6 +6,16 @@ from flask_sieve.parser import Parser from flask_sieve.rules_processor import RulesProcessor +class FakeFileStream: + def __init__(self, buf): + self.buf = buf + + def read(self, buf_size): + return self.buf[:buf_size] + + def seek(self, value): + pass + class TestRulesProcessor(unittest.TestCase): def setUp(self): @@ -19,7 +29,7 @@ def setUp(self): name='image' ) self.invalid_file = FileStorage( - stream=self.stream, + stream=FakeFileStream(bytearray([0xFE, 0x58, 0x8F, 0x00, 0x08])), filename='invalid.png' ) @@ -285,6 +295,10 @@ def test_validates_dimensions(self): rules={'field': ['dimensions:2x1']}, request={'field': 'not a file'} ) + self.assert_fails( + rules={'field': ['dimensions:1x1']}, + request={'field': self.invalid_file} + ) def test_validates_distinct(self): self.assert_passes( @@ -327,6 +341,10 @@ def test_validates_extension(self): rules={'field': ['extension:png']}, request={'field': 1} ) + self.assert_fails( + rules={'field': ['extension:tnp']}, + request={'field': self.invalid_file} + ) def test_validates_email(self): self.assert_passes( @@ -565,6 +583,10 @@ def test_validates_mime_types(self): rules={'field': ['mime_types:image/png']}, request={'field': 1} ) + self.assert_fails( + rules={'field': ['mime_types:image/xpng']}, + request={'field': self.invalid_file} + ) def test_validates_min(self): self.assert_passes(