Skip to content

Commit a8d26bf

Browse files
authored
Merge pull request #1612 from pallets/shared-data
SharedDataMiddleware uses safe_join
2 parents 8afe4eb + acc999e commit a8d26bf

File tree

3 files changed

+23
-20
lines changed

3 files changed

+23
-20
lines changed

CHANGES.rst

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ Unreleased
1919
reloader to fail. :issue:`1607`
2020
- Work around an issue where the reloader couldn't introspect a
2121
setuptools script installed as an egg. :issue:`1600`
22+
- ``SharedDataMiddleware`` safely handles paths with Windows drive
23+
names. :issue:`1589`
2224

2325

2426
Version 0.15.4

src/werkzeug/middleware/shared_data.py

+5-12
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from ..filesystem import get_filesystem_encoding
2323
from ..http import http_date
2424
from ..http import is_resource_modified
25+
from ..security import safe_join
2526
from ..wsgi import get_path_info
2627
from ..wsgi import wrap_file
2728

@@ -149,7 +150,7 @@ def loader(path):
149150
if path is None:
150151
return None, None
151152

152-
path = posixpath.join(package_path, path)
153+
path = safe_join(package_path, path)
153154

154155
if not provider.has_resource(path):
155156
return None, None
@@ -170,7 +171,7 @@ def loader(path):
170171
def get_directory_loader(self, directory):
171172
def loader(path):
172173
if path is not None:
173-
path = os.path.join(directory, path)
174+
path = safe_join(directory, path)
174175
else:
175176
path = directory
176177

@@ -192,19 +193,11 @@ def generate_etag(self, mtime, file_size, real_filename):
192193
)
193194

194195
def __call__(self, environ, start_response):
195-
cleaned_path = get_path_info(environ)
196+
path = get_path_info(environ)
196197

197198
if PY2:
198-
cleaned_path = cleaned_path.encode(get_filesystem_encoding())
199+
path = path.encode(get_filesystem_encoding())
199200

200-
# sanitize the path for non unix systems
201-
cleaned_path = cleaned_path.strip("/")
202-
203-
for sep in os.sep, os.altsep:
204-
if sep and sep != "/":
205-
cleaned_path = cleaned_path.replace(sep, "/")
206-
207-
path = "/" + "/".join(x for x in cleaned_path.split("/") if x and x != "..")
208201
file_loader = None
209202

210203
for search_path, loader in self.exports:

src/werkzeug/security.py

+16-8
Original file line numberDiff line numberDiff line change
@@ -222,20 +222,28 @@ def check_password_hash(pwhash, password):
222222

223223

224224
def safe_join(directory, *pathnames):
225-
"""Safely join `directory` and one or more untrusted `pathnames`. If this
226-
cannot be done, this function returns ``None``.
225+
"""Safely join zero or more untrusted path components to a base
226+
directory to avoid escaping the base directory.
227227
228-
:param directory: the base directory.
229-
:param pathnames: the untrusted pathnames relative to that directory.
228+
:param directory: The trusted base directory.
229+
:param pathnames: The untrusted path components relative to the
230+
base directory.
231+
:return: A safe path, otherwise ``None``.
230232
"""
231233
parts = [directory]
234+
232235
for filename in pathnames:
233236
if filename != "":
234237
filename = posixpath.normpath(filename)
235-
for sep in _os_alt_seps:
236-
if sep in filename:
237-
return None
238-
if os.path.isabs(filename) or filename == ".." or filename.startswith("../"):
238+
239+
if (
240+
any(sep in filename for sep in _os_alt_seps)
241+
or os.path.isabs(filename)
242+
or filename == ".."
243+
or filename.startswith("../")
244+
):
239245
return None
246+
240247
parts.append(filename)
248+
241249
return posixpath.join(*parts)

0 commit comments

Comments
 (0)