From 6f7568c4721647aea965b45066027e0f3bd9af57 Mon Sep 17 00:00:00 2001 From: qu1ck Date: Fri, 13 Aug 2021 20:39:36 -0700 Subject: [PATCH] Add content disposition type parameter to FileResponse disposition "attachment" causes browsers to download the file. E.g. "inline" will will be attempted to be displayed directly. --- docs/responses.md | 3 ++- starlette/responses.py | 9 ++++++--- tests/test_responses.py | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/docs/responses.md b/docs/responses.md index c4cd84ed3..4476e9561 100644 --- a/docs/responses.md +++ b/docs/responses.md @@ -169,6 +169,7 @@ Takes a different set of arguments to instantiate than the other response types: * `headers` - Any custom headers to include, as a dictionary. * `media_type` - A string giving the media type. If unset, the filename or path will be used to infer a media type. * `filename` - If set, this will be included in the response `Content-Disposition`. +* `content_disposition_type` - will be included in the response `Content-Disposition`. Can be set to "attachment" (default) or "inline". File responses will include appropriate `Content-Length`, `Last-Modified` and `ETag` headers. @@ -186,5 +187,5 @@ async def app(scope, receive, send): ### [SSEResponse(EventSourceResponse)](https://github.com/sysid/sse-starlette) -Server Sent Response implements the ServerSentEvent Protocol: https://www.w3.org/TR/2009/WD-eventsource-20090421. +Server Sent Response implements the ServerSentEvent Protocol: https://www.w3.org/TR/2009/WD-eventsource-20090421. It enables event streaming from the server to the client without the complexity of websockets. diff --git a/starlette/responses.py b/starlette/responses.py index d03df2329..e6fb95420 100644 --- a/starlette/responses.py +++ b/starlette/responses.py @@ -243,6 +243,7 @@ def __init__( filename: str = None, stat_result: os.stat_result = None, method: str = None, + content_disposition_type: str = "attachment", ) -> None: self.path = path self.status_code = status_code @@ -256,11 +257,13 @@ def __init__( if self.filename is not None: content_disposition_filename = quote(self.filename) if content_disposition_filename != self.filename: - content_disposition = "attachment; filename*=utf-8''{}".format( - content_disposition_filename + content_disposition = "{}; filename*=utf-8''{}".format( + content_disposition_type, content_disposition_filename ) else: - content_disposition = f'attachment; filename="{self.filename}"' + content_disposition = '{}; filename="{}"'.format( + content_disposition_type, self.filename + ) self.headers.setdefault("content-disposition", content_disposition) self.stat_result = stat_result if stat_result is not None: diff --git a/tests/test_responses.py b/tests/test_responses.py index baba549ba..b9cf01ee9 100644 --- a/tests/test_responses.py +++ b/tests/test_responses.py @@ -257,6 +257,21 @@ def test_file_response_with_chinese_filename(tmpdir, test_client_factory): assert response.headers["content-disposition"] == expected_disposition +def test_file_response_with_inline_disposition(tmpdir, test_client_factory): + content = b"file content" + filename = "hello.txt" + path = os.path.join(tmpdir, filename) + with open(path, "wb") as f: + f.write(content) + app = FileResponse(path=path, filename=filename, content_disposition_type="inline") + client = test_client_factory(app) + response = client.get("/") + expected_disposition = 'inline; filename="hello.txt"' + assert response.status_code == status.HTTP_200_OK + assert response.content == content + assert response.headers["content-disposition"] == expected_disposition + + def test_set_cookie(test_client_factory): async def app(scope, receive, send): response = Response("Hello, world!", media_type="text/plain")