summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/file_storage/test_generate_filename.py41
-rw-r--r--tests/file_uploads/tests.py38
-rw-r--r--tests/forms_tests/field_tests/test_filefield.py6
-rw-r--r--tests/utils_tests/test_text.py8
4 files changed, 89 insertions, 4 deletions
diff --git a/tests/file_storage/test_generate_filename.py b/tests/file_storage/test_generate_filename.py
index b4222f4121..9f54f6921e 100644
--- a/tests/file_storage/test_generate_filename.py
+++ b/tests/file_storage/test_generate_filename.py
@@ -1,7 +1,8 @@
import os
+from django.core.exceptions import SuspiciousFileOperation
from django.core.files.base import ContentFile
-from django.core.files.storage import Storage
+from django.core.files.storage import FileSystemStorage, Storage
from django.db.models import FileField
from django.test import SimpleTestCase
@@ -36,6 +37,44 @@ class AWSS3Storage(Storage):
class GenerateFilenameStorageTests(SimpleTestCase):
+ def test_storage_dangerous_paths(self):
+ candidates = [
+ ('/tmp/..', '..'),
+ ('/tmp/.', '.'),
+ ('', ''),
+ ]
+ s = FileSystemStorage()
+ msg = "Could not derive file name from '%s'"
+ for file_name, base_name in candidates:
+ with self.subTest(file_name=file_name):
+ with self.assertRaisesMessage(SuspiciousFileOperation, msg % base_name):
+ s.get_available_name(file_name)
+ with self.assertRaisesMessage(SuspiciousFileOperation, msg % base_name):
+ s.generate_filename(file_name)
+
+ def test_storage_dangerous_paths_dir_name(self):
+ file_name = '/tmp/../path'
+ s = FileSystemStorage()
+ msg = "Detected path traversal attempt in '/tmp/..'"
+ with self.assertRaisesMessage(SuspiciousFileOperation, msg):
+ s.get_available_name(file_name)
+ with self.assertRaisesMessage(SuspiciousFileOperation, msg):
+ s.generate_filename(file_name)
+
+ def test_filefield_dangerous_filename(self):
+ candidates = ['..', '.', '', '???', '$.$.$']
+ f = FileField(upload_to='some/folder/')
+ msg = "Could not derive file name from '%s'"
+ for file_name in candidates:
+ with self.subTest(file_name=file_name):
+ with self.assertRaisesMessage(SuspiciousFileOperation, msg % file_name):
+ f.generate_filename(None, file_name)
+
+ def test_filefield_dangerous_filename_dir(self):
+ f = FileField(upload_to='some/folder/')
+ msg = "File name '/tmp/path' includes path elements"
+ with self.assertRaisesMessage(SuspiciousFileOperation, msg):
+ f.generate_filename(None, '/tmp/path')
def test_filefield_generate_filename(self):
f = FileField(upload_to='some/folder/')
diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py
index e8f91e2fa0..7bc8d41dac 100644
--- a/tests/file_uploads/tests.py
+++ b/tests/file_uploads/tests.py
@@ -9,8 +9,9 @@ from io import BytesIO, StringIO
from unittest import mock
from urllib.parse import quote
+from django.core.exceptions import SuspiciousFileOperation
from django.core.files import temp as tempfile
-from django.core.files.uploadedfile import SimpleUploadedFile
+from django.core.files.uploadedfile import SimpleUploadedFile, UploadedFile
from django.http.multipartparser import (
FILE, MultiPartParser, MultiPartParserError, Parser, parse_header,
)
@@ -39,6 +40,16 @@ CANDIDATE_TRAVERSAL_FILE_NAMES = [
'../hax0rd.txt', # HTML entities.
]
+CANDIDATE_INVALID_FILE_NAMES = [
+ '/tmp/', # Directory, *nix-style.
+ 'c:\\tmp\\', # Directory, win-style.
+ '/tmp/.', # Directory dot, *nix-style.
+ 'c:\\tmp\\.', # Directory dot, *nix-style.
+ '/tmp/..', # Parent directory, *nix-style.
+ 'c:\\tmp\\..', # Parent directory, win-style.
+ '', # Empty filename.
+]
+
@override_settings(MEDIA_ROOT=MEDIA_ROOT, ROOT_URLCONF='file_uploads.urls', MIDDLEWARE=[])
class FileUploadTests(TestCase):
@@ -53,6 +64,22 @@ class FileUploadTests(TestCase):
shutil.rmtree(MEDIA_ROOT)
super().tearDownClass()
+ def test_upload_name_is_validated(self):
+ candidates = [
+ '/tmp/',
+ '/tmp/..',
+ '/tmp/.',
+ ]
+ if sys.platform == 'win32':
+ candidates.extend([
+ 'c:\\tmp\\',
+ 'c:\\tmp\\..',
+ 'c:\\tmp\\.',
+ ])
+ for file_name in candidates:
+ with self.subTest(file_name=file_name):
+ self.assertRaises(SuspiciousFileOperation, UploadedFile, name=file_name)
+
def test_simple_upload(self):
with open(__file__, 'rb') as fp:
post_data = {
@@ -718,6 +745,15 @@ class MultiParserTests(SimpleTestCase):
with self.subTest(file_name=file_name):
self.assertEqual(parser.sanitize_file_name(file_name), 'hax0rd.txt')
+ def test_sanitize_invalid_file_name(self):
+ parser = MultiPartParser({
+ 'CONTENT_TYPE': 'multipart/form-data; boundary=_foo',
+ 'CONTENT_LENGTH': '1',
+ }, StringIO('x'), [], 'utf-8')
+ for file_name in CANDIDATE_INVALID_FILE_NAMES:
+ with self.subTest(file_name=file_name):
+ self.assertIsNone(parser.sanitize_file_name(file_name))
+
def test_rfc2231_parsing(self):
test_data = (
(b"Content-Type: application/x-stuff; title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A",
diff --git a/tests/forms_tests/field_tests/test_filefield.py b/tests/forms_tests/field_tests/test_filefield.py
index 261d9f4ca9..2db106e4a0 100644
--- a/tests/forms_tests/field_tests/test_filefield.py
+++ b/tests/forms_tests/field_tests/test_filefield.py
@@ -21,10 +21,12 @@ class FileFieldTest(SimpleTestCase):
f.clean(None, '')
self.assertEqual('files/test2.pdf', f.clean(None, 'files/test2.pdf'))
no_file_msg = "'No file was submitted. Check the encoding type on the form.'"
+ file = SimpleUploadedFile(None, b'')
+ file._name = ''
with self.assertRaisesMessage(ValidationError, no_file_msg):
- f.clean(SimpleUploadedFile('', b''))
+ f.clean(file)
with self.assertRaisesMessage(ValidationError, no_file_msg):
- f.clean(SimpleUploadedFile('', b''), '')
+ f.clean(file, '')
self.assertEqual('files/test3.pdf', f.clean(None, 'files/test3.pdf'))
with self.assertRaisesMessage(ValidationError, no_file_msg):
f.clean('some content that is not a file')
diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py
index c9c74521e3..852a7970ee 100644
--- a/tests/utils_tests/test_text.py
+++ b/tests/utils_tests/test_text.py
@@ -1,6 +1,7 @@
import json
import sys
+from django.core.exceptions import SuspiciousFileOperation
from django.test import SimpleTestCase
from django.utils import text
from django.utils.functional import lazystr
@@ -228,6 +229,13 @@ class TestUtilsText(SimpleTestCase):
filename = "^&'@{}[],$=!-#()%+~_123.txt"
self.assertEqual(text.get_valid_filename(filename), "-_123.txt")
self.assertEqual(text.get_valid_filename(lazystr(filename)), "-_123.txt")
+ msg = "Could not derive file name from '???'"
+ with self.assertRaisesMessage(SuspiciousFileOperation, msg):
+ text.get_valid_filename('???')
+ # After sanitizing this would yield '..'.
+ msg = "Could not derive file name from '$.$.$'"
+ with self.assertRaisesMessage(SuspiciousFileOperation, msg):
+ text.get_valid_filename('$.$.$')
def test_compress_sequence(self):
data = [{'key': i} for i in range(10)]