1
0
mirror of https://github.com/django/django.git synced 2024-12-23 01:25:58 +00:00

Fixed #34194 -- Added django.utils.http.content_disposition_header().

This commit is contained in:
Alex Vandiver 2022-11-30 15:09:49 -05:00 committed by Mariusz Felisiak
parent 3d3e955efa
commit cbce427c17
5 changed files with 66 additions and 16 deletions

View File

@ -8,7 +8,7 @@ import sys
import time import time
from email.header import Header from email.header import Header
from http.client import responses from http.client import responses
from urllib.parse import quote, urlparse from urllib.parse import urlparse
from django.conf import settings from django.conf import settings
from django.core import signals, signing from django.core import signals, signing
@ -18,7 +18,7 @@ from django.http.cookie import SimpleCookie
from django.utils import timezone from django.utils import timezone
from django.utils.datastructures import CaseInsensitiveMapping from django.utils.datastructures import CaseInsensitiveMapping
from django.utils.encoding import iri_to_uri 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 from django.utils.regex_helper import _lazy_re_compile
_charset_from_content_type_re = _lazy_re_compile( _charset_from_content_type_re = _lazy_re_compile(
@ -569,20 +569,10 @@ class FileResponse(StreamingHttpResponse):
else: else:
self.headers["Content-Type"] = "application/octet-stream" self.headers["Content-Type"] = "application/octet-stream"
if filename: if content_disposition := content_disposition_header(
disposition = "attachment" if self.as_attachment else "inline" self.as_attachment, filename
try: ):
filename.encode("ascii") self.headers["Content-Disposition"] = content_disposition
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"
class HttpResponseRedirectBase(HttpResponse): class HttpResponseRedirectBase(HttpResponse):

View File

@ -10,6 +10,7 @@ from urllib.parse import (
_coerce_args, _coerce_args,
_splitnetloc, _splitnetloc,
_splitparams, _splitparams,
quote,
scheme_chars, scheme_chars,
unquote, unquote,
) )
@ -425,3 +426,24 @@ def parse_header_parameters(line):
value = unquote(value, encoding=encoding) value = unquote(value, encoding=encoding)
pdict[name] = value pdict[name] = value
return key, pdict 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

View File

@ -729,6 +729,15 @@ escaping HTML.
Outputs a string in the format ``Wdy, DD Mon YYYY HH:MM:SS GMT``. 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) .. function:: base36_to_int(s)
Converts a base 36 string to an integer. Converts a base 36 string to an integer.

View File

@ -321,6 +321,9 @@ Utilities
documented functions for handling URL redirects. The Django functions were documented functions for handling URL redirects. The Django functions were
not affected. 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 Validators
~~~~~~~~~~ ~~~~~~~~~~

View File

@ -7,6 +7,7 @@ from django.test import SimpleTestCase
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
from django.utils.http import ( from django.utils.http import (
base36_to_int, base36_to_int,
content_disposition_header,
escape_leading_slashes, escape_leading_slashes,
http_date, http_date,
int_to_base36, int_to_base36,
@ -511,3 +512,28 @@ class ParseHeaderParameterTests(unittest.TestCase):
for raw_line, expected_title in test_data: for raw_line, expected_title in test_data:
parsed = parse_header_parameters(raw_line) parsed = parse_header_parameters(raw_line)
self.assertEqual(parsed[1]["title"], expected_title) 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
)