14
14
from django .conf import settings
15
15
from django .core .exceptions import (
16
16
RequestDataTooBig , SuspiciousMultipartForm , TooManyFieldsSent ,
17
+ TooManyFilesSent ,
17
18
)
18
19
from django .core .files .uploadhandler import (
19
20
SkipFile , StopFutureHandlers , StopUpload ,
@@ -38,6 +39,7 @@ class InputStreamExhausted(Exception):
38
39
RAW = "raw"
39
40
FILE = "file"
40
41
FIELD = "field"
42
+ FIELD_TYPES = frozenset ([FIELD , RAW ])
41
43
42
44
43
45
class MultiPartParser :
@@ -102,6 +104,22 @@ def __init__(self, META, input_data, upload_handlers, encoding=None):
102
104
self ._upload_handlers = upload_handlers
103
105
104
106
def parse (self ):
107
+ # Call the actual parse routine and close all open files in case of
108
+ # errors. This is needed because if exceptions are thrown the
109
+ # MultiPartParser will not be garbage collected immediately and
110
+ # resources would be kept alive. This is only needed for errors because
111
+ # the Request object closes all uploaded files at the end of the
112
+ # request.
113
+ try :
114
+ return self ._parse ()
115
+ except Exception :
116
+ if hasattr (self , "_files" ):
117
+ for _ , files in self ._files .lists ():
118
+ for fileobj in files :
119
+ fileobj .close ()
120
+ raise
121
+
122
+ def _parse (self ):
105
123
"""
106
124
Parse the POST data and break it into a FILES MultiValueDict and a POST
107
125
MultiValueDict.
@@ -147,6 +165,8 @@ def parse(self):
147
165
num_bytes_read = 0
148
166
# To count the number of keys in the request.
149
167
num_post_keys = 0
168
+ # To count the number of files in the request.
169
+ num_files = 0
150
170
# To limit the amount of data read from the request.
151
171
read_size = None
152
172
# Whether a file upload is finished.
@@ -162,6 +182,20 @@ def parse(self):
162
182
old_field_name = None
163
183
uploaded_file = True
164
184
185
+ if (
186
+ item_type in FIELD_TYPES and
187
+ settings .DATA_UPLOAD_MAX_NUMBER_FIELDS is not None
188
+ ):
189
+ # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FIELDS.
190
+ num_post_keys += 1
191
+ # 2 accounts for empty raw fields before and after the
192
+ # last boundary.
193
+ if settings .DATA_UPLOAD_MAX_NUMBER_FIELDS + 2 < num_post_keys :
194
+ raise TooManyFieldsSent (
195
+ "The number of GET/POST parameters exceeded "
196
+ "settings.DATA_UPLOAD_MAX_NUMBER_FIELDS."
197
+ )
198
+
165
199
try :
166
200
disposition = meta_data ['content-disposition' ][1 ]
167
201
field_name = disposition ['name' ].strip ()
@@ -174,15 +208,6 @@ def parse(self):
174
208
field_name = force_str (field_name , encoding , errors = 'replace' )
175
209
176
210
if item_type == FIELD :
177
- # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FIELDS.
178
- num_post_keys += 1
179
- if (settings .DATA_UPLOAD_MAX_NUMBER_FIELDS is not None and
180
- settings .DATA_UPLOAD_MAX_NUMBER_FIELDS < num_post_keys ):
181
- raise TooManyFieldsSent (
182
- 'The number of GET/POST parameters exceeded '
183
- 'settings.DATA_UPLOAD_MAX_NUMBER_FIELDS.'
184
- )
185
-
186
211
# Avoid reading more than DATA_UPLOAD_MAX_MEMORY_SIZE.
187
212
if settings .DATA_UPLOAD_MAX_MEMORY_SIZE is not None :
188
213
read_size = settings .DATA_UPLOAD_MAX_MEMORY_SIZE - num_bytes_read
@@ -208,6 +233,16 @@ def parse(self):
208
233
209
234
self ._post .appendlist (field_name , force_str (data , encoding , errors = 'replace' ))
210
235
elif item_type == FILE :
236
+ # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FILES.
237
+ num_files += 1
238
+ if (
239
+ settings .DATA_UPLOAD_MAX_NUMBER_FILES is not None and
240
+ num_files > settings .DATA_UPLOAD_MAX_NUMBER_FILES
241
+ ):
242
+ raise TooManyFilesSent (
243
+ "The number of files exceeded "
244
+ "settings.DATA_UPLOAD_MAX_NUMBER_FILES."
245
+ )
211
246
# This is a file, use the handler...
212
247
file_name = disposition .get ('filename' )
213
248
if file_name :
@@ -276,8 +311,13 @@ def parse(self):
276
311
# Handle file upload completions on next iteration.
277
312
old_field_name = field_name
278
313
else :
279
- # If this is neither a FIELD or a FILE, just exhaust the stream.
280
- exhaust (stream )
314
+ # If this is neither a FIELD nor a FILE, exhaust the field
315
+ # stream. Note: There could be an error here at some point,
316
+ # but there will be at least two RAW types (before and
317
+ # after the other boundaries). This branch is usually not
318
+ # reached at all, because a missing content-disposition
319
+ # header will skip the whole boundary.
320
+ exhaust (field_stream )
281
321
except StopUpload as e :
282
322
self ._close_files ()
283
323
if not e .connection_reset :
0 commit comments