forked from orbit-oss/flask
Merge pull request #1730 from geusebi/master
make safe_join behave like os.path.join with *args
This commit is contained in:
commit
9f9e1fde8f
3 changed files with 63 additions and 15 deletions
2
CHANGES
2
CHANGES
|
|
@ -9,6 +9,8 @@ Version 0.12
|
||||||
- the cli command now responds to `--version`.
|
- the cli command now responds to `--version`.
|
||||||
- Mimetype guessing for ``send_file`` has been removed, as per issue ``#104``.
|
- Mimetype guessing for ``send_file`` has been removed, as per issue ``#104``.
|
||||||
See pull request ``#1849``.
|
See pull request ``#1849``.
|
||||||
|
- Make ``flask.safe_join`` able to join multiple paths like ``os.path.join``
|
||||||
|
(pull request ``#1730``).
|
||||||
|
|
||||||
Version 0.11.1
|
Version 0.11.1
|
||||||
--------------
|
--------------
|
||||||
|
|
|
||||||
|
|
@ -563,8 +563,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
def safe_join(directory, filename):
|
def safe_join(directory, *pathnames):
|
||||||
"""Safely join `directory` and `filename`.
|
"""Safely join `directory` and zero or more untrusted `pathnames`
|
||||||
|
components.
|
||||||
|
|
||||||
Example usage::
|
Example usage::
|
||||||
|
|
||||||
|
|
@ -574,20 +575,23 @@ def safe_join(directory, filename):
|
||||||
with open(filename, 'rb') as fd:
|
with open(filename, 'rb') as fd:
|
||||||
content = fd.read() # Read and process the file content...
|
content = fd.read() # Read and process the file content...
|
||||||
|
|
||||||
:param directory: the base directory.
|
:param directory: the trusted base directory.
|
||||||
:param filename: the untrusted filename relative to that directory.
|
:param pathnames: the untrusted pathnames relative to that directory.
|
||||||
:raises: :class:`~werkzeug.exceptions.NotFound` if the resulting path
|
:raises: :class:`~werkzeug.exceptions.NotFound` if one or more passed
|
||||||
would fall out of `directory`.
|
paths fall out of its boundaries.
|
||||||
"""
|
"""
|
||||||
filename = posixpath.normpath(filename)
|
for filename in pathnames:
|
||||||
for sep in _os_alt_seps:
|
if filename != '':
|
||||||
if sep in filename:
|
filename = posixpath.normpath(filename)
|
||||||
|
for sep in _os_alt_seps:
|
||||||
|
if sep in filename:
|
||||||
|
raise NotFound()
|
||||||
|
if os.path.isabs(filename) or \
|
||||||
|
filename == '..' or \
|
||||||
|
filename.startswith('../'):
|
||||||
raise NotFound()
|
raise NotFound()
|
||||||
if os.path.isabs(filename) or \
|
directory = os.path.join(directory, filename)
|
||||||
filename == '..' or \
|
return directory
|
||||||
filename.startswith('../'):
|
|
||||||
raise NotFound()
|
|
||||||
return os.path.join(directory, filename)
|
|
||||||
|
|
||||||
|
|
||||||
def send_from_directory(directory, filename, **options):
|
def send_from_directory(directory, filename, **options):
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import os
|
||||||
import datetime
|
import datetime
|
||||||
import flask
|
import flask
|
||||||
from logging import StreamHandler
|
from logging import StreamHandler
|
||||||
from werkzeug.exceptions import BadRequest
|
from werkzeug.exceptions import BadRequest, NotFound
|
||||||
from werkzeug.http import parse_cache_control_header, parse_options_header
|
from werkzeug.http import parse_cache_control_header, parse_options_header
|
||||||
from werkzeug.http import http_date
|
from werkzeug.http import http_date
|
||||||
from flask._compat import StringIO, text_type
|
from flask._compat import StringIO, text_type
|
||||||
|
|
@ -722,3 +722,45 @@ class TestStreaming(object):
|
||||||
rv = c.get('/?name=World')
|
rv = c.get('/?name=World')
|
||||||
assert rv.data == b'Hello World!'
|
assert rv.data == b'Hello World!'
|
||||||
assert called == [42]
|
assert called == [42]
|
||||||
|
|
||||||
|
|
||||||
|
class TestSafeJoin(object):
|
||||||
|
|
||||||
|
def test_safe_join(self):
|
||||||
|
# Valid combinations of *args and expected joined paths.
|
||||||
|
passing = (
|
||||||
|
(('a/b/c', ), 'a/b/c'),
|
||||||
|
(('/', 'a/', 'b/', 'c/', ), '/a/b/c'),
|
||||||
|
(('a', 'b', 'c', ), 'a/b/c'),
|
||||||
|
(('/a', 'b/c', ), '/a/b/c'),
|
||||||
|
(('a/b', 'X/../c'), 'a/b/c', ),
|
||||||
|
(('/a/b', 'c/X/..'), '/a/b/c', ),
|
||||||
|
# If last path is '' add a slash
|
||||||
|
(('/a/b/c', '', ), '/a/b/c/', ),
|
||||||
|
# Preserve dot slash
|
||||||
|
(('/a/b/c', './', ), '/a/b/c/.', ),
|
||||||
|
(('a/b/c', 'X/..'), 'a/b/c/.', ),
|
||||||
|
# Base directory is always considered safe
|
||||||
|
(('../', 'a/b/c'), '../a/b/c'),
|
||||||
|
(('/..', ), '/..'),
|
||||||
|
)
|
||||||
|
|
||||||
|
for args, expected in passing:
|
||||||
|
assert flask.safe_join(*args) == expected
|
||||||
|
|
||||||
|
def test_safe_join_exceptions(self):
|
||||||
|
# Should raise werkzeug.exceptions.NotFound on unsafe joins.
|
||||||
|
failing = (
|
||||||
|
# path.isabs and ``..'' checks
|
||||||
|
('/a', 'b', '/c'),
|
||||||
|
('/a', '../b/c', ),
|
||||||
|
('/a', '..', 'b/c'),
|
||||||
|
# Boundaries violations after path normalization
|
||||||
|
('/a', 'b/../b/../../c', ),
|
||||||
|
('/a', 'b', 'c/../..'),
|
||||||
|
('/a', 'b/../../c', ),
|
||||||
|
)
|
||||||
|
|
||||||
|
for args in failing:
|
||||||
|
with pytest.raises(NotFound):
|
||||||
|
print(flask.safe_join(*args))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue