From 396fb21651de862f266b93d91b21ab2f72fef3bf Mon Sep 17 00:00:00 2001 From: Legolas Bloom Date: Tue, 9 Oct 2018 20:25:17 +0800 Subject: [PATCH 1/3] secure_filename support py3 --- hobbit_core/flask_hobbit/utils.py | 47 +++++++++++++++++++++++++++++++ tests/test_utils.py | 22 +++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 tests/test_utils.py diff --git a/hobbit_core/flask_hobbit/utils.py b/hobbit_core/flask_hobbit/utils.py index e92c977..978a587 100644 --- a/hobbit_core/flask_hobbit/utils.py +++ b/hobbit_core/flask_hobbit/utils.py @@ -1,5 +1,8 @@ # -*- encoding: utf-8 -*- +import os +import re import six +from unicodedata import normalize class dict2object(dict): @@ -26,3 +29,47 @@ def __setattr__(self, name, value): if not isinstance(name, six.string_types): raise TypeError('key must be string type.') self[name] = value + + +def secure_filename(filename): + """Borrowed from werkzeug.utils.secure_filename. + + Pass it a filename and it will return a secure version of it. This + filename can then safely be stored on a regular file system and passed + to :func:`os.path.join`. + + On windows systems the function also makes sure that the file is not + named after one of the special device files. + + >>> secure_filename("My cool movie.mov") + 'My_cool_movie.mov' + >>> secure_filename("../../../etc/passwd") + 'etc_passwd' + >>> secure_filename(u'i contain cool \xfcml\xe4uts.txt') + 'i_contain_cool_umlauts.txt' + """ + if isinstance(filename, six.text_type): + filename = normalize('NFKD', filename).encode('utf-8') + if not six.PY2: + filename = filename.decode('utf-8') + + for sep in os.path.sep, os.path.altsep: + if sep: + filename = filename.replace(sep, ' ') + + filename = '_'.join(filename.split()) + filename_strip_re = re.compile(r'[^A-Za-z0-9\u4e00-\u9fa5_.-]') + filename = filename_strip_re.sub('', filename).strip('._') + + # on nt a couple of special files are present in each folder. We + # have to ensure that the target file is not such a filename. In + # this case we prepend an underline + windows_device_files = ( + 'CON', 'AUX', 'COM1', 'COM2', 'COM3', 'COM4', 'LPT1', + 'LPT2', 'LPT3', 'PRN', 'NUL', + ) + if os.name == 'nt' and filename and \ + filename.split('.')[0].upper() in windows_device_files: + filename = '_' + filename + + return filename diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..b45818b --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,22 @@ +# -*- encoding: utf-8 -*- + +from hobbit_core.flask_hobbit.utils import secure_filename + +from . import BaseTest + + +class TestUtils(BaseTest): + + def test_secure_filename(self): + filenames = ( + u'哈哈.zip', '../../../etc/passwd', 'My cool movie.mov', + '__filename__', 'foo$&^*)bar', + 'i contain cool \xfcml\xe4uts.txt', + ) + excepted = ( + u'哈哈.zip', 'etc_passwd', 'My_cool_movie.mov', + 'filename', 'foobar', + 'i_contain_cool_umlauts.txt', + ) + for i, filename in enumerate(filenames): + assert secure_filename(filename) == excepted[i] From 07628ef172a92287bd330c8e00c5ca2ea471592f Mon Sep 17 00:00:00 2001 From: Legolas Bloom Date: Wed, 10 Oct 2018 11:49:55 +0800 Subject: [PATCH 2/3] skip python2 when test_secure_filename --- hobbit_core/flask_hobbit/utils.py | 13 +++++++------ tests/__init__.py | 6 ++++++ tests/test_utils.py | 8 ++++---- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/hobbit_core/flask_hobbit/utils.py b/hobbit_core/flask_hobbit/utils.py index 978a587..a9c8852 100644 --- a/hobbit_core/flask_hobbit/utils.py +++ b/hobbit_core/flask_hobbit/utils.py @@ -32,7 +32,7 @@ def __setattr__(self, name, value): def secure_filename(filename): - """Borrowed from werkzeug.utils.secure_filename. + """Borrowed from werkzeug.utils.secure_filename. Python3 only. Pass it a filename and it will return a secure version of it. This filename can then safely be stored on a regular file system and passed @@ -48,16 +48,17 @@ def secure_filename(filename): >>> secure_filename(u'i contain cool \xfcml\xe4uts.txt') 'i_contain_cool_umlauts.txt' """ - if isinstance(filename, six.text_type): - filename = normalize('NFKD', filename).encode('utf-8') - if not six.PY2: - filename = filename.decode('utf-8') - for sep in os.path.sep, os.path.altsep: if sep: filename = filename.replace(sep, ' ') filename = '_'.join(filename.split()) + + if isinstance(filename, six.text_type): + filename = normalize('NFKD', filename).encode('utf-8') + if not six.PY2: + filename = filename.decode('utf-8') + filename_strip_re = re.compile(r'[^A-Za-z0-9\u4e00-\u9fa5_.-]') filename = filename_strip_re.sub('', filename).strip('._') diff --git a/tests/__init__.py b/tests/__init__.py index c77e923..f81a274 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,7 +1,10 @@ import os import shutil +import six import functools +import pytest + class BaseTest(object): root_path = os.path.split(os.path.abspath(__name__))[0] @@ -30,3 +33,6 @@ def inner(*args, **kwargs): os.chdir(cwd) return inner return wrapper + + +python3_only = pytest.mark.skipif(six.PY2, reason='only support Python3') diff --git a/tests/test_utils.py b/tests/test_utils.py index b45818b..9ccd6b6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,20 +1,20 @@ # -*- encoding: utf-8 -*- - from hobbit_core.flask_hobbit.utils import secure_filename -from . import BaseTest +from . import BaseTest, python3_only class TestUtils(BaseTest): + @python3_only def test_secure_filename(self): filenames = ( - u'哈哈.zip', '../../../etc/passwd', 'My cool movie.mov', + '哈哈.zip', '../../../etc/passwd', 'My cool movie.mov', '__filename__', 'foo$&^*)bar', 'i contain cool \xfcml\xe4uts.txt', ) excepted = ( - u'哈哈.zip', 'etc_passwd', 'My_cool_movie.mov', + '哈哈.zip', 'etc_passwd', 'My_cool_movie.mov', 'filename', 'foobar', 'i_contain_cool_umlauts.txt', ) From ab83580ec2a0ad3ed078dc62ad3f1f571d6ce0b4 Mon Sep 17 00:00:00 2001 From: Legolas Bloom Date: Wed, 10 Oct 2018 11:53:28 +0800 Subject: [PATCH 3/3] add test for utils.dict2object --- hobbit_core/flask_hobbit/utils.py | 6 +++--- tests/test_utils.py | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/hobbit_core/flask_hobbit/utils.py b/hobbit_core/flask_hobbit/utils.py index a9c8852..de4c352 100644 --- a/hobbit_core/flask_hobbit/utils.py +++ b/hobbit_core/flask_hobbit/utils.py @@ -10,7 +10,7 @@ class dict2object(dict): Examples:: - In [2]: obj = dict2object({'a':2, 'c':3}) + In [2]: obj = dict2object({'a': 2, 'c': 3}) In [3]: obj.a Out[3]: 2 @@ -32,7 +32,7 @@ def __setattr__(self, name, value): def secure_filename(filename): - """Borrowed from werkzeug.utils.secure_filename. Python3 only. + """Borrowed from werkzeug.utils.secure_filename. **Python3 only**. Pass it a filename and it will return a secure version of it. This filename can then safely be stored on a regular file system and passed @@ -70,7 +70,7 @@ def secure_filename(filename): 'LPT2', 'LPT3', 'PRN', 'NUL', ) if os.name == 'nt' and filename and \ - filename.split('.')[0].upper() in windows_device_files: + filename.split('.')[0].upper() in windows_device_files: filename = '_' + filename return filename diff --git a/tests/test_utils.py b/tests/test_utils.py index 9ccd6b6..7048cac 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,11 +1,26 @@ # -*- encoding: utf-8 -*- -from hobbit_core.flask_hobbit.utils import secure_filename +import pytest + +from hobbit_core.flask_hobbit import utils from . import BaseTest, python3_only class TestUtils(BaseTest): + def test_dict2object(self): + obj = utils.dict2object({'a': 2, 'c': 3}) + assert obj.a == 2 + assert obj.c == 3 + + # test setattr + obj.a = 4 + assert obj.a == 4 + + # test getattr + with pytest.raises(AttributeError): + print(obj.b) + @python3_only def test_secure_filename(self): filenames = ( @@ -19,4 +34,4 @@ def test_secure_filename(self): 'i_contain_cool_umlauts.txt', ) for i, filename in enumerate(filenames): - assert secure_filename(filename) == excepted[i] + assert utils.secure_filename(filename) == excepted[i]