summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Vandiver <alex@chmrr.net>2022-11-30 15:09:49 -0500
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2022-12-05 13:08:00 +0100
commitcbce427c17b66faec7ced0639346dc6905a498f9 (patch)
tree894b0990050dcf94c36010838717dd96351fbcc5
parent3d3e955efaaeb4cc968e522592c5c1e47bdc72c4 (diff)
Fixed #34194 -- Added django.utils.http.content_disposition_header().
-rw-r--r--django/http/response.py22
-rw-r--r--django/utils/http.py22
-rw-r--r--docs/ref/utils.txt9
-rw-r--r--docs/releases/4.2.txt3
-rw-r--r--tests/utils_tests/test_http.py26
5 files changed, 66 insertions, 16 deletions
diff --git a/django/http/response.py b/django/http/response.py
index bb94e81263..3c281f3dd0 100644
--- a/django/http/response.py
+++ b/django/http/response.py
@@ -8,7 +8,7 @@ import sys
import time
from email.header import Header
from http.client import responses
-from urllib.parse import quote, urlparse
+from urllib.parse import urlparse
from django.conf import settings
from django.core import signals, signing
@@ -18,7 +18,7 @@ from django.http.cookie import SimpleCookie
from django.utils import timezone
from django.utils.datastructures import CaseInsensitiveMapping
from django.utils.encoding import iri_to_uri
-from django.utils.http import http_date
+from django.utils.http import content_disposition_header, http_date
from django.utils.regex_helper import _lazy_re_compile
_charset_from_content_type_re = _lazy_re_compile(
@@ -569,20 +569,10 @@ class FileResponse(StreamingHttpResponse):
else:
self.headers["Content-Type"] = "application/octet-stream"
- if filename:
- disposition = "attachment" if self.as_attachment else "inline"
- try:
- filename.encode("ascii")
- file_expr = 'filename="{}"'.format(
- filename.replace("\\", "\\\\").replace('"', r"\"")
- )
- except UnicodeEncodeError:
- file_expr = "filename*=utf-8''{}".format(quote(filename))
- self.headers["Content-Disposition"] = "{}; {}".format(
- disposition, file_expr
- )
- elif self.as_attachment:
- self.headers["Content-Disposition"] = "attachment"
+ if content_disposition := content_disposition_header(
+ self.as_attachment, filename
+ ):
+ self.headers["Content-Disposition"] = content_disposition
class HttpResponseRedirectBase(HttpResponse):
diff --git a/django/utils/http.py b/django/utils/http.py
index db4dee2f27..3e7acb5835 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -10,6 +10,7 @@ from urllib.parse import (
_coerce_args,
_splitnetloc,
_splitparams,
+ quote,
scheme_chars,
unquote,
)
@@ -425,3 +426,24 @@ def parse_header_parameters(line):
value = unquote(value, encoding=encoding)
pdict[name] = value
return key, pdict
+
+
+def content_disposition_header(as_attachment, filename):
+ """
+ Construct a Content-Disposition HTTP header value from the given filename
+ as specified by RFC 6266.
+ """
+ if filename:
+ disposition = "attachment" if as_attachment else "inline"
+ try:
+ filename.encode("ascii")
+ file_expr = 'filename="{}"'.format(
+ filename.replace("\\", "\\\\").replace('"', r"\"")
+ )
+ except UnicodeEncodeError:
+ file_expr = "filename*=utf-8''{}".format(quote(filename))
+ return f"{disposition}; {file_expr}"
+ elif as_attachment:
+ return "attachment"
+ else:
+ return None
diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt
index 1c9141a986..b1a08dc0e1 100644
--- a/docs/ref/utils.txt
+++ b/docs/ref/utils.txt
@@ -729,6 +729,15 @@ escaping HTML.
Outputs a string in the format ``Wdy, DD Mon YYYY HH:MM:SS GMT``.
+.. function:: content_disposition_header(as_attachment, filename)
+
+ .. versionadded:: 4.2
+
+ Constructs a ``Content-Disposition`` HTTP header value from the given
+ ``filename`` as specified by :rfc:`6266`. Returns ``None`` if
+ ``as_attachment`` is ``False`` and ``filename`` is ``None``, otherwise
+ returns a string suitable for the ``Content-Disposition`` HTTP header.
+
.. function:: base36_to_int(s)
Converts a base 36 string to an integer.
diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt
index 054f8d6621..7e2aa6e5d6 100644
--- a/docs/releases/4.2.txt
+++ b/docs/releases/4.2.txt
@@ -321,6 +321,9 @@ Utilities
documented functions for handling URL redirects. The Django functions were
not affected.
+* The new :func:`django.utils.http.content_disposition_header` function returns
+ a ``Content-Disposition`` HTTP header value as specified by :rfc:`6266`.
+
Validators
~~~~~~~~~~
diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py
index add9625685..2290fe85fb 100644
--- a/tests/utils_tests/test_http.py
+++ b/tests/utils_tests/test_http.py
@@ -7,6 +7,7 @@ from django.test import SimpleTestCase
from django.utils.datastructures import MultiValueDict
from django.utils.http import (
base36_to_int,
+ content_disposition_header,
escape_leading_slashes,
http_date,
int_to_base36,
@@ -511,3 +512,28 @@ class ParseHeaderParameterTests(unittest.TestCase):
for raw_line, expected_title in test_data:
parsed = parse_header_parameters(raw_line)
self.assertEqual(parsed[1]["title"], expected_title)
+
+
+class ContentDispositionHeaderTests(unittest.TestCase):
+ def test_basic(self):
+ tests = (
+ ((False, None), None),
+ ((False, "example"), 'inline; filename="example"'),
+ ((True, None), "attachment"),
+ ((True, "example"), 'attachment; filename="example"'),
+ (
+ (True, '"example" file\\name'),
+ 'attachment; filename="\\"example\\" file\\\\name"',
+ ),
+ ((True, "espécimen"), "attachment; filename*=utf-8''esp%C3%A9cimen"),
+ (
+ (True, '"espécimen" filename'),
+ "attachment; filename*=utf-8''%22esp%C3%A9cimen%22%20filename",
+ ),
+ )
+
+ for (is_attachment, filename), expected in tests:
+ with self.subTest(is_attachment=is_attachment, filename=filename):
+ self.assertEqual(
+ content_disposition_header(is_attachment, filename), expected
+ )