mirror of
https://github.com/jazzband/django-downloadview.git
synced 2026-03-16 22:40:25 +00:00
Merge pull request #211 from tari/196-rfd
Guard against reflected file download
This commit is contained in:
commit
18cb41f760
4 changed files with 26 additions and 4 deletions
|
|
@ -72,9 +72,16 @@ def content_disposition(filename):
|
||||||
"""
|
"""
|
||||||
if not filename:
|
if not filename:
|
||||||
return "attachment"
|
return "attachment"
|
||||||
ascii_filename = encode_basename_ascii(filename)
|
# ASCII filenames are quoted and must ensure escape sequences
|
||||||
|
# in the filename won't break out of the quoted header value
|
||||||
|
# which can permit a reflected file download attack. The UTF-8
|
||||||
|
# version is immune because it's not quoted.
|
||||||
|
ascii_filename = (
|
||||||
|
encode_basename_ascii(filename).replace("\\", "\\\\").replace('"', r'\"')
|
||||||
|
)
|
||||||
utf8_filename = encode_basename_utf8(filename)
|
utf8_filename = encode_basename_utf8(filename)
|
||||||
if ascii_filename == utf8_filename: # ASCII only.
|
if ascii_filename == utf8_filename: # ASCII only.
|
||||||
|
|
||||||
return f'attachment; filename="{ascii_filename}"'
|
return f'attachment; filename="{ascii_filename}"'
|
||||||
else:
|
else:
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
"""Tests around project's distribution and packaging."""
|
"""Tests around project's distribution and packaging."""
|
||||||
|
import importlib.metadata
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,16 @@ class DownloadResponseTestCase(unittest.TestCase):
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
"filename*=UTF-8''espac%C3%A9%20.txt", headers["Content-Disposition"]
|
"filename*=UTF-8''espac%C3%A9%20.txt", headers["Content-Disposition"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_content_disposition_escaping(self):
|
||||||
|
"""Content-Disposition headers escape special characters."""
|
||||||
|
response = DownloadResponse(
|
||||||
|
"fake file",
|
||||||
|
attachment=True,
|
||||||
|
basename=r'"malicious\file.exe'
|
||||||
|
)
|
||||||
|
headers = response.default_headers
|
||||||
|
self.assertIn(
|
||||||
|
r'filename="\"malicious\\file.exe"',
|
||||||
|
headers["Content-Disposition"]
|
||||||
|
)
|
||||||
7
tox.ini
7
tox.ini
|
|
@ -31,10 +31,10 @@ deps =
|
||||||
commands =
|
commands =
|
||||||
pip install -e .
|
pip install -e .
|
||||||
pip install -e demo
|
pip install -e demo
|
||||||
# doctests
|
# doctests and unit tests
|
||||||
pytest --cov=django_downloadview --cov=demoproject {posargs}
|
pytest --cov=django_downloadview --cov=demoproject {posargs}
|
||||||
# all other test cases
|
# demo project integration tests
|
||||||
coverage run --append {envbindir}/demo test {posargs: tests demoproject}
|
coverage run --append {envbindir}/demo test {posargs: demoproject}
|
||||||
coverage xml
|
coverage xml
|
||||||
pip freeze
|
pip freeze
|
||||||
ignore_outcome =
|
ignore_outcome =
|
||||||
|
|
@ -76,3 +76,4 @@ source = django_downloadview,demo
|
||||||
[pytest]
|
[pytest]
|
||||||
DJANGO_SETTINGS_MODULE = demoproject.settings
|
DJANGO_SETTINGS_MODULE = demoproject.settings
|
||||||
addopts = --doctest-modules --ignore=docs/
|
addopts = --doctest-modules --ignore=docs/
|
||||||
|
python_files = tests/*.py
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue