summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2020-11-05 07:46:14 -0800
committerDavid Lord <davidism@gmail.com>2020-11-05 07:46:14 -0800
commit2ff0dc47d43f34b475ea0ae23ff070865cccc11e (patch)
tree7d80722ea1daac99d56b661e8af0f3f6e35af6c2
parent8fe59c949ad67e12af93cab9c2faec27beefb1e4 (diff)
downloadwerkzeug-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.py21
-rw-r--r--tests/test_send_file.py22
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