diff options
author | David Lord <davidism@gmail.com> | 2020-11-05 07:46:14 -0800 |
---|---|---|
committer | David Lord <davidism@gmail.com> | 2020-11-05 07:46:14 -0800 |
commit | 2ff0dc47d43f34b475ea0ae23ff070865cccc11e (patch) | |
tree | 7d80722ea1daac99d56b661e8af0f3f6e35af6c2 | |
parent | 8fe59c949ad67e12af93cab9c2faec27beefb1e4 (diff) | |
download | werkzeug-2ff0dc47d43f34b475ea0ae23ff070865cccc11e.tar.gz |
add helpers for Flask's send_file wrapper
Without these changes, Flask's wrappers would need to duplicate the
path processing code. These changes are for internal use only.
_root_path private parameter allows paths to be relative to
app.root_path instead of cwd.
max_age can be a callable that takes an optional path, allows
calling app.get_send_file_max_age.
-rw-r--r-- | src/werkzeug/utils.py | 21 | ||||
-rw-r--r-- | tests/test_send_file.py | 22 |
2 files changed, 42 insertions, 1 deletions
diff --git a/src/werkzeug/utils.py b/src/werkzeug/utils.py index 61a2dc95..cbeec31e 100644 --- a/src/werkzeug/utils.py +++ b/src/werkzeug/utils.py @@ -576,6 +576,7 @@ def send_file( max_age=None, use_x_sendfile=False, response_class=None, + _root_path=None, ): """Send the contents of a file to the client. @@ -621,6 +622,8 @@ def send_file( HTTP server. Requires passing a file path. :param response_class: Build the response using this class. Defaults to :class:`~werkzeug.wrappers.Response`. + :param _root_path: Do not use. For internal use only. Use + :func:`send_from_directory` to safely send files under a path. .. versionadded:: 2.0.0 Adapted from Flask's implementation. @@ -641,7 +644,13 @@ def send_file( if isinstance(path_or_file, (str, os.PathLike)) or hasattr( path_or_file, "__fspath__" ): - path = pathlib.Path(path_or_file).absolute() + # Flask will pass app.root_path, allowing its send_file wrapper + # to not have to deal with paths. + if _root_path is not None: + path = pathlib.Path(_root_path, path_or_file) + else: + path = pathlib.Path(path_or_file).absolute() + stat = path.stat() size = stat.st_size mtime = stat.st_mtime @@ -711,6 +720,11 @@ def send_file( rv.cache_control.no_cache = True + # Flask will pass app.get_send_file_max_age, allowing its send_file + # wrapper to not have to deal with paths. + if callable(max_age): + max_age = max_age(path) + if max_age is not None: if max_age > 0: rv.cache_control.no_cache = None @@ -765,6 +779,11 @@ def send_from_directory(directory, path, environ, **kwargs): if path is None: raise NotFound() + # Flask will pass app.root_path, allowing its send_from_directory + # wrapper to not have to deal with paths. + if "_root_path" in kwargs: + path = os.path.join(kwargs["_root_path"], path) + try: if not os.path.isfile(path): raise NotFound() diff --git a/tests/test_send_file.py b/tests/test_send_file.py index 887315aa..54cf1e12 100644 --- a/tests/test_send_file.py +++ b/tests/test_send_file.py @@ -163,3 +163,25 @@ def test_from_directory(directory, path): def test_from_directory_not_found(path): with pytest.raises(NotFound): send_from_directory(res_path, path, environ) + + +def test_root_path(tmp_path): + # This is a private API, it should only be used by Flask. + d = tmp_path / "d" + d.mkdir() + (d / "test.txt").write_bytes(b"test") + rv = send_file("d/test.txt", environ, _root_path=tmp_path) + rv.direct_passthrough = False + assert rv.data == b"test" + rv.close() + rv = send_from_directory("d", "test.txt", environ, _root_path=tmp_path) + rv.direct_passthrough = False + assert rv.data == b"test" + rv.close() + + +def test_max_age_callable(): + # This is a private API, it should only be used by Flask. + rv = send_file(txt_path, environ, max_age=lambda p: 10) + rv.close() + assert rv.cache_control.max_age == 10 |