@@ -51,7 +51,10 @@ def save(self, name, content, max_length=None):
51
51
content = File (content , name )
52
52
53
53
name = self .get_available_name (name , max_length = max_length )
54
- return self ._save (name , content )
54
+ name = self ._save (name , content )
55
+ # Ensure that the name returned from the storage system is still valid.
56
+ validate_file_name (name , allow_relative_path = True )
57
+ return name
55
58
56
59
# These methods are part of the public API, with default implementations.
57
60
@@ -75,6 +78,7 @@ def get_available_name(self, name, max_length=None):
75
78
Return a filename that's free on the target storage system and
76
79
available for new content to be written to.
77
80
"""
81
+ name = str (name ).replace ('\\ ' , '/' )
78
82
dir_name , file_name = os .path .split (name )
79
83
if '..' in pathlib .PurePath (dir_name ).parts :
80
84
raise SuspiciousFileOperation ("Detected path traversal attempt in '%s'" % dir_name )
@@ -108,6 +112,7 @@ def generate_filename(self, filename):
108
112
Validate the filename by calling get_valid_name() and return a filename
109
113
to be passed to the save() method.
110
114
"""
115
+ filename = str (filename ).replace ('\\ ' , '/' )
111
116
# `filename` may include a path as returned by FileField.upload_to.
112
117
dirname , filename = os .path .split (filename )
113
118
if '..' in pathlib .PurePath (dirname ).parts :
@@ -297,6 +302,8 @@ def _save(self, name, content):
297
302
if self .file_permissions_mode is not None :
298
303
os .chmod (full_path , self .file_permissions_mode )
299
304
305
+ # Ensure the saved path is always relative to the storage root.
306
+ name = os .path .relpath (full_path , self .location )
300
307
# Store filenames with forward slashes, even on Windows.
301
308
return str (name ).replace ('\\ ' , '/' )
302
309
0 commit comments