Skip to content

Commit c456b3c

Browse files
Ryan P Kilbycarltongibson
authored andcommitted
Fix request formdata handling (encode#5800)
* Rename 'wsgi' request test to more accurate 'http' * Test duplicate request stream parsing * Fix setting post/files on the underlying request
1 parent 0d5a3a0 commit c456b3c

File tree

2 files changed

+60
-12
lines changed

2 files changed

+60
-12
lines changed

rest_framework/request.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -278,10 +278,11 @@ def _load_data_and_files(self):
278278
else:
279279
self._full_data = self._data
280280

281-
# copy data & files refs to the underlying request so that closable
282-
# objects are handled appropriately.
283-
self._request._post = self.POST
284-
self._request._files = self.FILES
281+
# if a form media type, copy data & files refs to the underlying
282+
# http request so that closable objects are handled appropriately.
283+
if is_form_media_type(self.content_type):
284+
self._request._post = self.POST
285+
self._request._files = self.FILES
285286

286287
def _load_stream(self):
287288
"""

tests/test_request.py

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from django.contrib.auth.models import User
1414
from django.contrib.sessions.middleware import SessionMiddleware
1515
from django.core.files.uploadedfile import SimpleUploadedFile
16+
from django.http.request import RawPostDataException
1617
from django.test import TestCase, override_settings
1718
from django.utils import six
1819

@@ -137,6 +138,11 @@ def post(self, request):
137138
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
138139

139140

141+
class EchoView(APIView):
142+
def post(self, request):
143+
return Response(status=status.HTTP_200_OK, data=request.data)
144+
145+
140146
class FileUploadView(APIView):
141147
def post(self, request):
142148
filenames = [file.temporary_file_path() for file in request.FILES.values()]
@@ -149,6 +155,7 @@ def post(self, request):
149155

150156
urlpatterns = [
151157
url(r'^$', MockView.as_view()),
158+
url(r'^echo/$', EchoView.as_view()),
152159
url(r'^upload/$', FileUploadView.as_view())
153160
]
154161

@@ -271,24 +278,64 @@ def test_default_secure_true(self):
271278
assert request.scheme == 'https'
272279

273280

274-
class TestWSGIRequestProxy(TestCase):
275-
def test_attribute_access(self):
276-
wsgi_request = factory.get('/')
277-
request = Request(wsgi_request)
281+
class TestHttpRequest(TestCase):
282+
def test_attribute_access_proxy(self):
283+
http_request = factory.get('/')
284+
request = Request(http_request)
278285

279286
inner_sentinel = object()
280-
wsgi_request.inner_property = inner_sentinel
287+
http_request.inner_property = inner_sentinel
281288
assert request.inner_property is inner_sentinel
282289

283290
outer_sentinel = object()
284291
request.inner_property = outer_sentinel
285292
assert request.inner_property is outer_sentinel
286293

287-
def test_exception(self):
294+
def test_exception_proxy(self):
288295
# ensure the exception message is not for the underlying WSGIRequest
289-
wsgi_request = factory.get('/')
290-
request = Request(wsgi_request)
296+
http_request = factory.get('/')
297+
request = Request(http_request)
291298

292299
message = "'Request' object has no attribute 'inner_property'"
293300
with self.assertRaisesMessage(AttributeError, message):
294301
request.inner_property
302+
303+
@override_settings(ROOT_URLCONF='tests.test_request')
304+
def test_duplicate_request_stream_parsing_exception(self):
305+
"""
306+
Check assumption that duplicate stream parsing will result in a
307+
`RawPostDataException` being raised.
308+
"""
309+
response = APIClient().post('/echo/', data={'a': 'b'}, format='json')
310+
request = response.renderer_context['request']
311+
312+
# ensure that request stream was consumed by json parser
313+
assert request.content_type.startswith('application/json')
314+
assert response.data == {'a': 'b'}
315+
316+
# pass same HttpRequest to view, stream already consumed
317+
with pytest.raises(RawPostDataException):
318+
EchoView.as_view()(request._request)
319+
320+
@override_settings(ROOT_URLCONF='tests.test_request')
321+
def test_duplicate_request_form_data_access(self):
322+
"""
323+
Form data is copied to the underlying django request for middleware
324+
and file closing reasons. Duplicate processing of a request with form
325+
data is 'safe' in so far as accessing `request.POST` does not trigger
326+
the duplicate stream parse exception.
327+
"""
328+
response = APIClient().post('/echo/', data={'a': 'b'})
329+
request = response.renderer_context['request']
330+
331+
# ensure that request stream was consumed by form parser
332+
assert request.content_type.startswith('multipart/form-data')
333+
assert response.data == {'a': ['b']}
334+
335+
# pass same HttpRequest to view, form data set on underlying request
336+
response = EchoView.as_view()(request._request)
337+
request = response.renderer_context['request']
338+
339+
# ensure that request stream was consumed by form parser
340+
assert request.content_type.startswith('multipart/form-data')
341+
assert response.data == {'a': ['b']}

0 commit comments

Comments
 (0)