Skip to content

Commit 85ad45c

Browse files
authored
Merge pull request #16 from TTWShell/feature/secure_filename
Feature/secure filename
2 parents 7f2d936 + ab83580 commit 85ad45c

File tree

3 files changed

+92
-1
lines changed

3 files changed

+92
-1
lines changed

hobbit_core/flask_hobbit/utils.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
# -*- encoding: utf-8 -*-
2+
import os
3+
import re
24
import six
5+
from unicodedata import normalize
36

47

58
class dict2object(dict):
69
"""Dict to fake object that can use getattr.
710
811
Examples::
912
10-
In [2]: obj = dict2object({'a':2, 'c':3})
13+
In [2]: obj = dict2object({'a': 2, 'c': 3})
1114
1215
In [3]: obj.a
1316
Out[3]: 2
@@ -26,3 +29,48 @@ def __setattr__(self, name, value):
2629
if not isinstance(name, six.string_types):
2730
raise TypeError('key must be string type.')
2831
self[name] = value
32+
33+
34+
def secure_filename(filename):
35+
"""Borrowed from werkzeug.utils.secure_filename. **Python3 only**.
36+
37+
Pass it a filename and it will return a secure version of it. This
38+
filename can then safely be stored on a regular file system and passed
39+
to :func:`os.path.join`.
40+
41+
On windows systems the function also makes sure that the file is not
42+
named after one of the special device files.
43+
44+
>>> secure_filename("My cool movie.mov")
45+
'My_cool_movie.mov'
46+
>>> secure_filename("../../../etc/passwd")
47+
'etc_passwd'
48+
>>> secure_filename(u'i contain cool \xfcml\xe4uts.txt')
49+
'i_contain_cool_umlauts.txt'
50+
"""
51+
for sep in os.path.sep, os.path.altsep:
52+
if sep:
53+
filename = filename.replace(sep, ' ')
54+
55+
filename = '_'.join(filename.split())
56+
57+
if isinstance(filename, six.text_type):
58+
filename = normalize('NFKD', filename).encode('utf-8')
59+
if not six.PY2:
60+
filename = filename.decode('utf-8')
61+
62+
filename_strip_re = re.compile(r'[^A-Za-z0-9\u4e00-\u9fa5_.-]')
63+
filename = filename_strip_re.sub('', filename).strip('._')
64+
65+
# on nt a couple of special files are present in each folder. We
66+
# have to ensure that the target file is not such a filename. In
67+
# this case we prepend an underline
68+
windows_device_files = (
69+
'CON', 'AUX', 'COM1', 'COM2', 'COM3', 'COM4', 'LPT1',
70+
'LPT2', 'LPT3', 'PRN', 'NUL',
71+
)
72+
if os.name == 'nt' and filename and \
73+
filename.split('.')[0].upper() in windows_device_files:
74+
filename = '_' + filename
75+
76+
return filename

tests/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import os
22
import shutil
3+
import six
34
import functools
45

6+
import pytest
7+
58

69
class BaseTest(object):
710
root_path = os.path.split(os.path.abspath(__name__))[0]
@@ -30,3 +33,6 @@ def inner(*args, **kwargs):
3033
os.chdir(cwd)
3134
return inner
3235
return wrapper
36+
37+
38+
python3_only = pytest.mark.skipif(six.PY2, reason='only support Python3')

tests/test_utils.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# -*- encoding: utf-8 -*-
2+
import pytest
3+
4+
from hobbit_core.flask_hobbit import utils
5+
6+
from . import BaseTest, python3_only
7+
8+
9+
class TestUtils(BaseTest):
10+
11+
def test_dict2object(self):
12+
obj = utils.dict2object({'a': 2, 'c': 3})
13+
assert obj.a == 2
14+
assert obj.c == 3
15+
16+
# test setattr
17+
obj.a = 4
18+
assert obj.a == 4
19+
20+
# test getattr
21+
with pytest.raises(AttributeError):
22+
print(obj.b)
23+
24+
@python3_only
25+
def test_secure_filename(self):
26+
filenames = (
27+
'哈哈.zip', '../../../etc/passwd', 'My cool movie.mov',
28+
'__filename__', 'foo$&^*)bar',
29+
'i contain cool \xfcml\xe4uts.txt',
30+
)
31+
excepted = (
32+
'哈哈.zip', 'etc_passwd', 'My_cool_movie.mov',
33+
'filename', 'foobar',
34+
'i_contain_cool_umlauts.txt',
35+
)
36+
for i, filename in enumerate(filenames):
37+
assert utils.secure_filename(filename) == excepted[i]

0 commit comments

Comments
 (0)