summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/utils/http.py11
-rw-r--r--tests/utils_tests/test_http.py1
2 files changed, 11 insertions, 1 deletions
diff --git a/django/utils/http.py b/django/utils/http.py
index bf783562dd..2098cbc36d 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -362,10 +362,19 @@ def content_disposition_header(as_attachment, filename):
disposition = "attachment" if as_attachment else "inline"
try:
filename.encode("ascii")
+ is_ascii = True
+ except UnicodeEncodeError:
+ is_ascii = False
+ # Quoted strings can contain horizontal tabs, space characters, and
+ # characters from 0x21 to 0x7e, except 0x22 (`"`) and 0x5C (`\`) which
+ # can still be expressed but must be escaped with their own `\`.
+ # https://datatracker.ietf.org/doc/html/rfc9110#name-quoted-strings
+ quotable_characters = r"^[\t \x21-\x7e]*$"
+ if is_ascii and re.match(quotable_characters, filename):
file_expr = 'filename="{}"'.format(
filename.replace("\\", "\\\\").replace('"', r"\"")
)
- except UnicodeEncodeError:
+ else:
file_expr = "filename*=utf-8''{}".format(quote(filename))
return f"{disposition}; {file_expr}"
elif as_attachment:
diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py
index 68df04696a..c04f0a03d7 100644
--- a/tests/utils_tests/test_http.py
+++ b/tests/utils_tests/test_http.py
@@ -511,6 +511,7 @@ class ContentDispositionHeaderTests(unittest.TestCase):
(True, '"espécimen" filename'),
"attachment; filename*=utf-8''%22esp%C3%A9cimen%22%20filename",
),
+ ((True, "some\nfile"), "attachment; filename*=utf-8''some%0Afile"),
)
for (is_attachment, filename), expected in tests: