1414from django .conf import settings
1515from django .core .exceptions import (
1616 RequestDataTooBig , SuspiciousMultipartForm , TooManyFieldsSent ,
17+ TooManyFilesSent ,
1718)
1819from django .core .files .uploadhandler import (
1920 SkipFile , StopFutureHandlers , StopUpload ,
@@ -38,6 +39,7 @@ class InputStreamExhausted(Exception):
3839RAW = "raw"
3940FILE = "file"
4041FIELD = "field"
42+ FIELD_TYPES = frozenset ([FIELD , RAW ])
4143
4244
4345class MultiPartParser :
@@ -102,6 +104,22 @@ def __init__(self, META, input_data, upload_handlers, encoding=None):
102104 self ._upload_handlers = upload_handlers
103105
104106 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 ):
105123 """
106124 Parse the POST data and break it into a FILES MultiValueDict and a POST
107125 MultiValueDict.
@@ -147,6 +165,8 @@ def parse(self):
147165 num_bytes_read = 0
148166 # To count the number of keys in the request.
149167 num_post_keys = 0
168+ # To count the number of files in the request.
169+ num_files = 0
150170 # To limit the amount of data read from the request.
151171 read_size = None
152172 # Whether a file upload is finished.
@@ -162,6 +182,20 @@ def parse(self):
162182 old_field_name = None
163183 uploaded_file = True
164184
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+
165199 try :
166200 disposition = meta_data ['content-disposition' ][1 ]
167201 field_name = disposition ['name' ].strip ()
@@ -174,15 +208,6 @@ def parse(self):
174208 field_name = force_str (field_name , encoding , errors = 'replace' )
175209
176210 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-
186211 # Avoid reading more than DATA_UPLOAD_MAX_MEMORY_SIZE.
187212 if settings .DATA_UPLOAD_MAX_MEMORY_SIZE is not None :
188213 read_size = settings .DATA_UPLOAD_MAX_MEMORY_SIZE - num_bytes_read
@@ -208,6 +233,16 @@ def parse(self):
208233
209234 self ._post .appendlist (field_name , force_str (data , encoding , errors = 'replace' ))
210235 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+ )
211246 # This is a file, use the handler...
212247 file_name = disposition .get ('filename' )
213248 if file_name :
@@ -276,8 +311,13 @@ def parse(self):
276311 # Handle file upload completions on next iteration.
277312 old_field_name = field_name
278313 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 )
281321 except StopUpload as e :
282322 self ._close_files ()
283323 if not e .connection_reset :
0 commit comments