summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2020-07-12 19:39:59 -0700
committerDavid Lord <davidism@gmail.com>2020-07-12 19:39:59 -0700
commitb01ff62f2867957ec0df983aec6609061e0551dd (patch)
tree15b9f9db14453c586314a7d1b2354676188f183c
parent6c11af5ab0b06eb2fa57b37d3d639c6259601c17 (diff)
downloadwerkzeug-send_file-download_name.tar.gz
replace attachment_filename with download_namesend_file-download_name
Always send download_name, using Content-Disposition: inline if as_attachment=False.
-rw-r--r--CHANGES.rst6
-rw-r--r--src/werkzeug/utils.py56
-rw-r--r--tests/test_send_file.py26
3 files changed, 55 insertions, 33 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 82590509..57e384fd 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -41,8 +41,12 @@ Unreleased
``RAW_URI``. :issue:`1781`
- Switch the parameter order of ``default_stream_factory`` to match
the order used when calling it. :pr:`1085`
-- Add ``send_file()`` function to generate a response that serves a
+- Add ``send_file`` function to generate a response that serves a
file, adapted from Flask's implementation. :issue:`265`, :pr:`1850`
+- ``send_file`` takes ``download_name``, which is passed even if
+ ``as_attachment=False`` by using ``Content-Disposition: inline``.
+ ``download_name`` replaces Flask's ``attachment_filename``.
+ :issue:`1869`
Version 1.0.2
diff --git a/src/werkzeug/utils.py b/src/werkzeug/utils.py
index 1cacdc89..5b5721a8 100644
--- a/src/werkzeug/utils.py
+++ b/src/werkzeug/utils.py
@@ -557,7 +557,7 @@ def send_file(
environ=None,
mimetype=None,
as_attachment=False,
- attachment_filename=None,
+ download_name=None,
add_etags=True,
cache_timeout=43200,
conditional=False,
@@ -592,8 +592,8 @@ def send_file(
provided, it will try to detect it from the file name.
:param as_attachment: Indicate to a browser that it should offer to
save the file instead of displaying it.
- :param attachment_filename: The file name browsers will use when
- saving the file. Defaults to the passed file name.
+ :param download_name: The default name browsers will use when saving
+ the file. Defaults to the passed file name.
:param add_etags: Calculate an ETag for the file. Requires passing a
file path.
:param conditional: Enable conditional and range responses based on
@@ -611,6 +611,11 @@ def send_file(
.. versionadded:: 2.0.0
Adapted from Flask's implementation.
+
+ .. versionchanged:: 2.0.0
+ ``download_name`` replaces Flask's ``attachment_filename``
+ parameter. If ``as_attachment=False``, it is passed with
+ ``Content-Disposition: inline`` instead.
"""
if response_class is None:
from .wrappers import Response as response_class
@@ -627,44 +632,39 @@ def send_file(
path = size = mtime = None
file = path_or_file
- if attachment_filename is None and path is not None:
- attachment_filename = path.name
+ if download_name is None and path is not None:
+ download_name = path.name
if mimetype is None:
- if attachment_filename is not None:
- mimetype = (
- mimetypes.guess_type(attachment_filename)[0]
- or "application/octet-stream"
- )
-
- if mimetype is None:
+ if download_name is None:
raise ValueError(
"Unable to detect the MIME type because a file name is"
- " not available. Either set 'attachment_filename', pass"
- " a path instead of a file, or set 'mimetype'."
+ " not available. Either set 'download_name', pass a"
+ " path instead of a file, or set 'mimetype'."
)
- headers = Headers()
+ mimetype = mimetypes.guess_type(download_name)[0] or "application/octet-stream"
- if as_attachment:
- if attachment_filename is None:
- raise TypeError(
- "No name provided for attachment. Either set"
- " 'attachment_filename' or pass a path instead of a"
- " file."
- )
+ headers = Headers()
+ if download_name is not None:
try:
- attachment_filename = attachment_filename.encode("ascii")
+ download_name = download_name.encode("ascii")
except UnicodeEncodeError:
- simple = unicodedata.normalize("NFKD", attachment_filename)
+ simple = unicodedata.normalize("NFKD", download_name)
simple = simple.encode("ascii", "ignore")
- quoted = url_quote(attachment_filename, safe="")
- filenames = {"filename": simple, "filename*": f"UTF-8''{quoted}"}
+ quoted = url_quote(download_name, safe="")
+ names = {"filename": simple, "filename*": f"UTF-8''{quoted}"}
else:
- filenames = {"filename": attachment_filename}
+ names = {"filename": download_name}
- headers.add("Content-Disposition", "attachment", **filenames)
+ value = "attachment" if as_attachment else "inline"
+ headers.set("Content-Disposition", value, **names)
+ elif as_attachment:
+ raise TypeError(
+ "No name provided for attachment. Either set"
+ " 'download_name' or pass a path instead of a file."
+ )
if use_x_sendfile and path:
headers["X-Sendfile"] = str(path)
diff --git a/tests/test_send_file.py b/tests/test_send_file.py
index 8a44f739..d51b034e 100644
--- a/tests/test_send_file.py
+++ b/tests/test_send_file.py
@@ -55,8 +55,8 @@ def test_object_without_mimetype():
send_file(io.BytesIO(b"test"))
-def test_object_mimetype_from_attachment():
- rv = send_file(io.BytesIO(b"test"), attachment_filename="test.txt")
+def test_object_mimetype_from_name():
+ rv = send_file(io.BytesIO(b"test"), download_name="test.txt")
assert rv.mimetype == "text/plain"
rv.close()
@@ -70,6 +70,24 @@ def test_text_mode_fails(file_factory):
@pytest.mark.parametrize(
+ ("as_attachment", "value"), [(False, "inline"), (True, "attachment")]
+)
+def test_disposition_name(as_attachment, value):
+ rv = send_file(txt_path, as_attachment=as_attachment)
+ assert rv.headers["Content-Disposition"] == f"{value}; filename=test.txt"
+ rv.close()
+
+
+def test_object_attachment_requires_name():
+ with pytest.raises(TypeError, match="attachment"):
+ send_file(io.BytesIO(b"test"), mimetype="text/plain", as_attachment=True)
+
+ rv = send_file(io.BytesIO(b"test"), as_attachment=True, download_name="test.txt")
+ assert rv.headers["Content-Disposition"] == f"attachment; filename=test.txt"
+ rv.close()
+
+
+@pytest.mark.parametrize(
("name", "ascii", "utf8"),
(
("index.html", "index.html", None),
@@ -84,8 +102,8 @@ def test_text_mode_fails(file_factory):
("ั‚ะต:/ัั‚", '":/"', "%D1%82%D0%B5%3A%2F%D1%81%D1%82"),
),
)
-def test_non_ascii_filename(name, ascii, utf8):
- rv = send_file(html_path, as_attachment=True, attachment_filename=name)
+def test_non_ascii_name(name, ascii, utf8):
+ rv = send_file(html_path, as_attachment=True, download_name=name)
rv.close()
content_disposition = rv.headers["Content-Disposition"]
assert f"filename={ascii}" in content_disposition