|
2 | 2 |
|
3 | 3 | from django.core.exceptions import ValidationError |
4 | 4 | from django.core.files.uploadedfile import SimpleUploadedFile |
5 | | -from django.forms import FileField |
| 5 | +from django.core.validators import validate_image_file_extension |
| 6 | +from django.forms import FileField, FileInput |
6 | 7 | from django.test import SimpleTestCase |
7 | 8 |
|
8 | 9 |
|
@@ -109,3 +110,68 @@ def test_disabled_has_changed(self): |
109 | 110 |
|
110 | 111 | def test_file_picklable(self): |
111 | 112 | self.assertIsInstance(pickle.loads(pickle.dumps(FileField())), FileField) |
| 113 | + |
| 114 | + |
| 115 | +class MultipleFileInput(FileInput): |
| 116 | + allow_multiple_selected = True |
| 117 | + |
| 118 | + |
| 119 | +class MultipleFileField(FileField): |
| 120 | + def __init__(self, *args, **kwargs): |
| 121 | + kwargs.setdefault("widget", MultipleFileInput()) |
| 122 | + super().__init__(*args, **kwargs) |
| 123 | + |
| 124 | + def clean(self, data, initial=None): |
| 125 | + single_file_clean = super().clean |
| 126 | + if isinstance(data, (list, tuple)): |
| 127 | + result = [single_file_clean(d, initial) for d in data] |
| 128 | + else: |
| 129 | + result = single_file_clean(data, initial) |
| 130 | + return result |
| 131 | + |
| 132 | + |
| 133 | +class MultipleFileFieldTest(SimpleTestCase): |
| 134 | + def test_file_multiple(self): |
| 135 | + f = MultipleFileField() |
| 136 | + files = [ |
| 137 | + SimpleUploadedFile("name1", b"Content 1"), |
| 138 | + SimpleUploadedFile("name2", b"Content 2"), |
| 139 | + ] |
| 140 | + self.assertEqual(f.clean(files), files) |
| 141 | + |
| 142 | + def test_file_multiple_empty(self): |
| 143 | + f = MultipleFileField() |
| 144 | + files = [ |
| 145 | + SimpleUploadedFile("empty", b""), |
| 146 | + SimpleUploadedFile("nonempty", b"Some Content"), |
| 147 | + ] |
| 148 | + msg = "'The submitted file is empty.'" |
| 149 | + with self.assertRaisesMessage(ValidationError, msg): |
| 150 | + f.clean(files) |
| 151 | + with self.assertRaisesMessage(ValidationError, msg): |
| 152 | + f.clean(files[::-1]) |
| 153 | + |
| 154 | + def test_file_multiple_validation(self): |
| 155 | + f = MultipleFileField(validators=[validate_image_file_extension]) |
| 156 | + |
| 157 | + good_files = [ |
| 158 | + SimpleUploadedFile("image1.jpg", b"fake JPEG"), |
| 159 | + SimpleUploadedFile("image2.png", b"faux image"), |
| 160 | + SimpleUploadedFile("image3.bmp", b"fraudulent bitmap"), |
| 161 | + ] |
| 162 | + self.assertEqual(f.clean(good_files), good_files) |
| 163 | + |
| 164 | + evil_files = [ |
| 165 | + SimpleUploadedFile("image1.sh", b"#!/bin/bash -c 'echo pwned!'\n"), |
| 166 | + SimpleUploadedFile("image2.png", b"faux image"), |
| 167 | + SimpleUploadedFile("image3.jpg", b"fake JPEG"), |
| 168 | + ] |
| 169 | + |
| 170 | + evil_rotations = ( |
| 171 | + evil_files[i:] + evil_files[:i] # Rotate by i. |
| 172 | + for i in range(len(evil_files)) |
| 173 | + ) |
| 174 | + msg = "File extension “sh” is not allowed. Allowed extensions are: " |
| 175 | + for rotated_evil_files in evil_rotations: |
| 176 | + with self.assertRaisesMessage(ValidationError, msg): |
| 177 | + f.clean(rotated_evil_files) |
0 commit comments