+229
-10
lines changedFilter options
+229
-10
lines changed Original file line number Diff line number Diff line change
@@ -413,17 +413,41 @@ def format_value(self, value):
413
413
414
414
415
415
class FileInput(Input):
416
+
allow_multiple_selected = False
416
417
input_type = "file"
417
418
needs_multipart_form = True
418
419
template_name = "django/forms/widgets/file.html"
419
420
421
+
def __init__(self, attrs=None):
422
+
if (
423
+
attrs is not None
424
+
and not self.allow_multiple_selected
425
+
and attrs.get("multiple", False)
426
+
):
427
+
raise ValueError(
428
+
"%s doesn't support uploading multiple files."
429
+
% self.__class__.__qualname__
430
+
)
431
+
if self.allow_multiple_selected:
432
+
if attrs is None:
433
+
attrs = {"multiple": True}
434
+
else:
435
+
attrs.setdefault("multiple", True)
436
+
super().__init__(attrs)
437
+
420
438
def format_value(self, value):
421
439
"""File input never renders a value."""
422
440
return
423
441
424
442
def value_from_datadict(self, data, files, name):
425
443
"File widgets take data from FILES, not POST"
426
-
return files.get(name)
444
+
getter = files.get
445
+
if self.allow_multiple_selected:
446
+
try:
447
+
getter = files.getlist
448
+
except AttributeError:
449
+
pass
450
+
return getter(name)
427
451
428
452
def value_omitted_from_data(self, data, files, name):
429
453
return name not in files
Original file line number Diff line number Diff line change
@@ -6,4 +6,18 @@ Django 3.2.19 release notes
6
6
7
7
Django 3.2.19 fixes a security issue with severity "low" in 3.2.18.
8
8
9
-
...
9
+
CVE-2023-31047: Potential bypass of validation when uploading multiple files using one form field
10
+
=================================================================================================
11
+
12
+
Uploading multiple files using one form field has never been supported by
13
+
:class:`.forms.FileField` or :class:`.forms.ImageField` as only the last
14
+
uploaded file was validated. Unfortunately, :ref:`uploading_multiple_files`
15
+
topic suggested otherwise.
16
+
17
+
In order to avoid the vulnerability, :class:`~django.forms.ClearableFileInput`
18
+
and :class:`~django.forms.FileInput` form widgets now raise ``ValueError`` when
19
+
the ``multiple`` HTML attribute is set on them. To prevent the exception and
20
+
keep the old behavior, set ``allow_multiple_selected`` to ``True``.
21
+
22
+
For more details on using the new attribute and handling of multiple files
23
+
through a single field, see :ref:`uploading_multiple_files`.
Original file line number Diff line number Diff line change
@@ -6,4 +6,18 @@ Django 4.1.9 release notes
6
6
7
7
Django 4.1.9 fixes a security issue with severity "low" in 4.1.8.
8
8
9
-
...
9
+
CVE-2023-31047: Potential bypass of validation when uploading multiple files using one form field
10
+
=================================================================================================
11
+
12
+
Uploading multiple files using one form field has never been supported by
13
+
:class:`.forms.FileField` or :class:`.forms.ImageField` as only the last
14
+
uploaded file was validated. Unfortunately, :ref:`uploading_multiple_files`
15
+
topic suggested otherwise.
16
+
17
+
In order to avoid the vulnerability, :class:`~django.forms.ClearableFileInput`
18
+
and :class:`~django.forms.FileInput` form widgets now raise ``ValueError`` when
19
+
the ``multiple`` HTML attribute is set on them. To prevent the exception and
20
+
keep the old behavior, set ``allow_multiple_selected`` to ``True``.
21
+
22
+
For more details on using the new attribute and handling of multiple files
23
+
through a single field, see :ref:`uploading_multiple_files`.
Original file line number Diff line number Diff line change
@@ -139,19 +139,53 @@ a :class:`~django.core.files.File` like object to the
139
139
instance = ModelWithFileField(file_field=content_file)
140
140
instance.save()
141
141
142
+
.. _uploading_multiple_files:
143
+
142
144
Uploading multiple files
143
145
------------------------
144
146
145
-
If you want to upload multiple files using one form field, set the ``multiple``
146
-
HTML attribute of field's widget:
147
+
..
148
+
Tests in tests.forms_tests.field_tests.test_filefield.MultipleFileFieldTest
149
+
should be updated after any changes in the following snippets.
150
+
151
+
If you want to upload multiple files using one form field, create a subclass
152
+
of the field's widget and set the ``allow_multiple_selected`` attribute on it
153
+
to ``True``.
154
+
155
+
In order for such files to be all validated by your form (and have the value of
156
+
the field include them all), you will also have to subclass ``FileField``. See
157
+
below for an example.
158
+
159
+
.. admonition:: Multiple file field
160
+
161
+
Django is likely to have a proper multiple file field support at some point
162
+
in the future.
147
163
148
164
.. code-block:: python
149
165
:caption: ``forms.py``
150
166
151
167
from django import forms
152
168
169
+
class MultipleFileInput(forms.ClearableFileInput):
170
+
allow_multiple_selected = True
171
+
172
+
173
+
class MultipleFileField(forms.FileField):
174
+
def __init__(self, *args, **kwargs):
175
+
kwargs.setdefault("widget", MultipleFileInput())
176
+
super().__init__(*args, **kwargs)
177
+
178
+
def clean(self, data, initial=None):
179
+
single_file_clean = super().clean
180
+
if isinstance(data, (list, tuple)):
181
+
result = [single_file_clean(d, initial) for d in data]
182
+
else:
183
+
result = single_file_clean(data, initial)
184
+
return result
185
+
186
+
153
187
class FileFieldForm(forms.Form):
154
-
file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
188
+
file_field = MultipleFileField()
155
189
156
190
Then override the ``post`` method of your
157
191
:class:`~django.views.generic.edit.FormView` subclass to handle multiple file
@@ -171,14 +205,32 @@ uploads:
171
205
def post(self, request, *args, **kwargs):
172
206
form_class = self.get_form_class()
173
207
form = self.get_form(form_class)
174
-
files = request.FILES.getlist('file_field')
175
208
if form.is_valid():
176
-
for f in files:
177
-
... # Do something with each file.
178
209
return self.form_valid(form)
179
210
else:
180
211
return self.form_invalid(form)
181
212
213
+
def form_valid(self, form):
214
+
files = form.cleaned_data["file_field"]
215
+
for f in files:
216
+
... # Do something with each file.
217
+
return super().form_valid()
218
+
219
+
.. warning::
220
+
221
+
This will allow you to handle multiple files at the form level only. Be
222
+
aware that you cannot use it to put multiple files on a single model
223
+
instance (in a single field), for example, even if the custom widget is used
224
+
with a form field related to a model ``FileField``.
225
+
226
+
.. versionchanged:: 3.2.19
227
+
228
+
In previous versions, there was no support for the ``allow_multiple_selected``
229
+
class attribute, and users were advised to create the widget with the HTML
230
+
attribute ``multiple`` set through the ``attrs`` argument. However, this
231
+
caused validation of the form field to be applied only to the last file
232
+
submitted, which could have adverse security implications.
233
+
182
234
Upload Handlers
183
235
===============
184
236
Original file line number Diff line number Diff line change
@@ -2,7 +2,8 @@
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)
Original file line number Diff line number Diff line change
@@ -232,3 +232,8 @@ class TestForm(Form):
232
232
'<input type="file" name="clearable_file" id="id_clearable_file"></div>',
233
233
form.render(),
234
234
)
235
+
236
+
def test_multiple_error(self):
237
+
msg = "ClearableFileInput doesn't support uploading multiple files."
238
+
with self.assertRaisesMessage(ValueError, msg):
239
+
ClearableFileInput(attrs={"multiple": True})
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
1
+
from django.core.files.uploadedfile import SimpleUploadedFile
1
2
from django.forms import FileField, FileInput, Form
3
+
from django.utils.datastructures import MultiValueDict
2
4
3
5
from .base import WidgetTest
4
6
@@ -48,3 +50,45 @@ class TestForm(Form):
48
50
'name="field" required type="file"></div>',
49
51
form.render(),
50
52
)
53
+
54
+
def test_multiple_error(self):
55
+
msg = "FileInput doesn't support uploading multiple files."
56
+
with self.assertRaisesMessage(ValueError, msg):
57
+
FileInput(attrs={"multiple": True})
58
+
59
+
def test_value_from_datadict_multiple(self):
60
+
class MultipleFileInput(FileInput):
61
+
allow_multiple_selected = True
62
+
63
+
file_1 = SimpleUploadedFile("something1.txt", b"content 1")
64
+
file_2 = SimpleUploadedFile("something2.txt", b"content 2")
65
+
# Uploading multiple files is allowed.
66
+
widget = MultipleFileInput(attrs={"multiple": True})
67
+
value = widget.value_from_datadict(
68
+
data={"name": "Test name"},
69
+
files=MultiValueDict({"myfile": [file_1, file_2]}),
70
+
name="myfile",
71
+
)
72
+
self.assertEqual(value, [file_1, file_2])
73
+
# Uploading multiple files is not allowed.
74
+
widget = FileInput()
75
+
value = widget.value_from_datadict(
76
+
data={"name": "Test name"},
77
+
files=MultiValueDict({"myfile": [file_1, file_2]}),
78
+
name="myfile",
79
+
)
80
+
self.assertEqual(value, file_2)
81
+
82
+
def test_multiple_default(self):
83
+
class MultipleFileInput(FileInput):
84
+
allow_multiple_selected = True
85
+
86
+
tests = [
87
+
(None, True),
88
+
({"class": "myclass"}, True),
89
+
({"multiple": False}, False),
90
+
]
91
+
for attrs, expected in tests:
92
+
with self.subTest(attrs=attrs):
93
+
widget = MultipleFileInput(attrs=attrs)
94
+
self.assertIs(widget.attrs["multiple"], expected)
You can’t perform that action at this time.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4