File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -72,9 +72,16 @@ def content_disposition(filename):
7272 """
7373 if not filename :
7474 return "attachment"
75- ascii_filename = encode_basename_ascii (filename )
75+ # ASCII filenames are quoted and must ensure escape sequences
76+ # in the filename won't break out of the quoted header value
77+ # which can permit a reflected file download attack. The UTF-8
78+ # version is immune because it's not quoted.
79+ ascii_filename = (
80+ encode_basename_ascii (filename ).replace ("\\ " , "\\ \\ " ).replace ('"' , r'\"' )
81+ )
7682 utf8_filename = encode_basename_utf8 (filename )
7783 if ascii_filename == utf8_filename : # ASCII only.
84+
7885 return f'attachment; filename="{ ascii_filename } "'
7986 else :
8087 return (
Original file line number Diff line number Diff line change 11"""Tests around project's distribution and packaging."""
2+ import importlib .metadata
23import os
34import unittest
45
Original file line number Diff line number Diff line change @@ -19,3 +19,16 @@ def test_content_disposition_encoding(self):
1919 self .assertIn (
2020 "filename*=UTF-8''espac%C3%A9%20.txt" , headers ["Content-Disposition" ]
2121 )
22+
23+ def test_content_disposition_escaping (self ):
24+ """Content-Disposition headers escape special characters."""
25+ response = DownloadResponse (
26+ "fake file" ,
27+ attachment = True ,
28+ basename = r'"malicious\file.exe'
29+ )
30+ headers = response .default_headers
31+ self .assertIn (
32+ r'filename="\"malicious\\file.exe"' ,
33+ headers ["Content-Disposition" ]
34+ )
Original file line number Diff line number Diff line change @@ -31,10 +31,10 @@ deps =
3131commands =
3232 pip install -e .
3333 pip install -e demo
34- # doctests
34+ # doctests and unit tests
3535 pytest --cov =django_downloadview --cov =demoproject {posargs}
36- # all other test cases
37- coverage run --append {envbindir}/demo test {posargs: tests demoproject}
36+ # demo project integration tests
37+ coverage run --append {envbindir}/demo test {posargs: demoproject}
3838 coverage xml
3939 pip freeze
4040ignore_outcome =
@@ -76,3 +76,4 @@ source = django_downloadview,demo
7676[pytest]
7777DJANGO_SETTINGS_MODULE = demoproject.settings
7878addopts = --doctest-modules --ignore =docs/
79+ python_files = tests/*.py
You can’t perform that action at this time.
0 commit comments