8
8
from io import BytesIO , StringIO
9
9
from urllib .parse import quote
10
10
11
+ from django .core .exceptions import SuspiciousFileOperation
11
12
from django .core .files import temp as tempfile
12
- from django .core .files .uploadedfile import SimpleUploadedFile
13
+ from django .core .files .uploadedfile import SimpleUploadedFile , UploadedFile
13
14
from django .http .multipartparser import (
14
15
MultiPartParser , MultiPartParserError , parse_header ,
15
16
)
37
38
'../hax0rd.txt' , # HTML entities.
38
39
]
39
40
41
+ CANDIDATE_INVALID_FILE_NAMES = [
42
+ '/tmp/' , # Directory, *nix-style.
43
+ 'c:\\ tmp\\ ' , # Directory, win-style.
44
+ '/tmp/.' , # Directory dot, *nix-style.
45
+ 'c:\\ tmp\\ .' , # Directory dot, *nix-style.
46
+ '/tmp/..' , # Parent directory, *nix-style.
47
+ 'c:\\ tmp\\ ..' , # Parent directory, win-style.
48
+ '' , # Empty filename.
49
+ ]
50
+
40
51
41
52
@override_settings (MEDIA_ROOT = MEDIA_ROOT , ROOT_URLCONF = 'file_uploads.urls' , MIDDLEWARE = [])
42
53
class FileUploadTests (TestCase ):
@@ -52,6 +63,22 @@ def tearDownClass(cls):
52
63
shutil .rmtree (MEDIA_ROOT )
53
64
super ().tearDownClass ()
54
65
66
+ def test_upload_name_is_validated (self ):
67
+ candidates = [
68
+ '/tmp/' ,
69
+ '/tmp/..' ,
70
+ '/tmp/.' ,
71
+ ]
72
+ if sys .platform == 'win32' :
73
+ candidates .extend ([
74
+ 'c:\\ tmp\\ ' ,
75
+ 'c:\\ tmp\\ ..' ,
76
+ 'c:\\ tmp\\ .' ,
77
+ ])
78
+ for file_name in candidates :
79
+ with self .subTest (file_name = file_name ):
80
+ self .assertRaises (SuspiciousFileOperation , UploadedFile , name = file_name )
81
+
55
82
def test_simple_upload (self ):
56
83
with open (__file__ , 'rb' ) as fp :
57
84
post_data = {
@@ -631,6 +658,15 @@ def test_sanitize_file_name(self):
631
658
with self .subTest (file_name = file_name ):
632
659
self .assertEqual (parser .sanitize_file_name (file_name ), 'hax0rd.txt' )
633
660
661
+ def test_sanitize_invalid_file_name (self ):
662
+ parser = MultiPartParser ({
663
+ 'CONTENT_TYPE' : 'multipart/form-data; boundary=_foo' ,
664
+ 'CONTENT_LENGTH' : '1' ,
665
+ }, StringIO ('x' ), [], 'utf-8' )
666
+ for file_name in CANDIDATE_INVALID_FILE_NAMES :
667
+ with self .subTest (file_name = file_name ):
668
+ self .assertIsNone (parser .sanitize_file_name (file_name ))
669
+
634
670
def test_rfc2231_parsing (self ):
635
671
test_data = (
636
672
(b"Content-Type: application/x-stuff; title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A" ,
0 commit comments