diff --git a/django/http/__init__.py b/django/http/__init__.py index 2e4f371fb1..6fccd24da0 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -262,14 +262,18 @@ class HttpRequest(object): if self.method != 'POST': self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict() return - if self._read_started: + if self._read_started and not hasattr(self, '_raw_post_data'): self._mark_post_parse_error() return if self.META.get('CONTENT_TYPE', '').startswith('multipart'): - self._raw_post_data = '' + if hasattr(self, '_raw_post_data'): + # Use already read data + data = StringIO(self._raw_post_data) + else: + data = self try: - self._post, self._files = self.parse_file_upload(self.META, self) + self._post, self._files = self.parse_file_upload(self.META, data) except: # An error occured while parsing POST data. Since when # formatting the error the request handler might access diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py index a80ee9d571..b68201494b 100644 --- a/tests/regressiontests/requests/tests.py +++ b/tests/regressiontests/requests/tests.py @@ -179,6 +179,66 @@ class RequestsTests(unittest.TestCase): self.assertRaises(Exception, lambda: request.raw_post_data) self.assertEqual(request.POST, {}) + def test_raw_post_data_after_POST_multipart(self): + """ + Reading raw_post_data after parsing multipart is not allowed + """ + # Because multipart is used for large amounts fo data i.e. file uploads, + # we don't want the data held in memory twice, and we don't want to + # silence the error by setting raw_post_data = '' either. + payload = "\r\n".join([ + '--boundary', + 'Content-Disposition: form-data; name="name"', + '', + 'value', + '--boundary--' + '']) + request = WSGIRequest({'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'multipart/form-data; boundary=boundary', + 'CONTENT_LENGTH': len(payload), + 'wsgi.input': StringIO(payload)}) + self.assertEqual(request.POST, {u'name': [u'value']}) + self.assertRaises(Exception, lambda: request.raw_post_data) + def test_read_by_lines(self): request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')}) self.assertEqual(list(request), ['name=value']) + + def test_POST_after_raw_post_data_read(self): + """ + POST should be populated even if raw_post_data is read first + """ + request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')}) + raw_data = request.raw_post_data + self.assertEqual(request.POST, {u'name': [u'value']}) + + def test_POST_after_raw_post_data_read_and_stream_read(self): + """ + POST should be populated even if raw_post_data is read first, and then + the stream is read second. + """ + request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')}) + raw_data = request.raw_post_data + self.assertEqual(request.read(1), u'n') + self.assertEqual(request.POST, {u'name': [u'value']}) + + def test_POST_after_raw_post_data_read_and_stream_read_multipart(self): + """ + POST should be populated even if raw_post_data is read first, and then + the stream is read second. Using multipart/form-data instead of urlencoded. + """ + payload = "\r\n".join([ + '--boundary', + 'Content-Disposition: form-data; name="name"', + '', + 'value', + '--boundary--' + '']) + request = WSGIRequest({'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'multipart/form-data; boundary=boundary', + 'CONTENT_LENGTH': len(payload), + 'wsgi.input': StringIO(payload)}) + raw_data = request.raw_post_data + # Consume enough data to mess up the parsing: + self.assertEqual(request.read(13), u'--boundary\r\nC') + self.assertEqual(request.POST, {u'name': [u'value']})