diff --git a/rest_framework/request.py b/rest_framework/request.py index 355cccad77..0a827728a6 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -15,6 +15,7 @@ from django.conf import settings from django.http import QueryDict from django.http.multipartparser import parse_header +from django.http.request import RawPostDataException from django.utils import six from django.utils.datastructures import MultiValueDict @@ -263,10 +264,20 @@ def _load_stream(self): if content_length == 0: self._stream = None - elif hasattr(self._request, 'read'): + elif not self._request._read_started: self._stream = self._request else: - self._stream = six.BytesIO(self.raw_post_data) + self._stream = six.BytesIO(self.body) + + def _supports_form_parsing(self): + """ + Return True if this requests supports parsing form data. + """ + form_media = ( + 'application/x-www-form-urlencoded', + 'multipart/form-data' + ) + return any([parser.media_type in form_media for parser in self.parsers]) def _parse(self): """ @@ -274,8 +285,18 @@ def _parse(self): May raise an `UnsupportedMediaType`, or `ParseError` exception. """ - stream = self.stream media_type = self.content_type + try: + stream = self.stream + except RawPostDataException: + if not hasattr(self._request, '_post'): + raise + # If request.POST has been accessed in middleware, and a method='POST' + # request was made with 'multipart/form-data', then the request stream + # will already have been exhausted. + if self._supports_form_parsing(): + return (self._request.POST, self._request.FILES) + stream = None if stream is None or media_type is None: empty_data = QueryDict('', encoding=self._request._encoding) diff --git a/tests/test_parsers.py b/tests/test_parsers.py index f3af6817f4..5052e2e538 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -7,11 +7,16 @@ from django.core.files.uploadhandler import ( MemoryFileUploadHandler, TemporaryFileUploadHandler ) +from django.http.request import RawPostDataException from django.test import TestCase from django.utils.six.moves import StringIO from rest_framework.exceptions import ParseError -from rest_framework.parsers import FileUploadParser, FormParser +from rest_framework.parsers import ( + FileUploadParser, FormParser, JSONParser, MultiPartParser +) +from rest_framework.request import Request +from rest_framework.test import APIRequestFactory class Form(forms.Form): @@ -122,3 +127,39 @@ def test_get_encoded_filename(self): def __replace_content_disposition(self, disposition): self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = disposition + + +class TestPOSTAccessed(TestCase): + def setUp(self): + self.factory = APIRequestFactory() + + def test_post_accessed_in_post_method(self): + django_request = self.factory.post('/', {'foo': 'bar'}) + request = Request(django_request, parsers=[FormParser(), MultiPartParser()]) + django_request.POST + assert request.POST == {'foo': ['bar']} + assert request.data == {'foo': ['bar']} + + def test_post_accessed_in_post_method_with_json_parser(self): + django_request = self.factory.post('/', {'foo': 'bar'}) + request = Request(django_request, parsers=[JSONParser()]) + django_request.POST + assert request.POST == {} + assert request.data == {} + + def test_post_accessed_in_put_method(self): + django_request = self.factory.put('/', {'foo': 'bar'}) + request = Request(django_request, parsers=[FormParser(), MultiPartParser()]) + django_request.POST + assert request.POST == {'foo': ['bar']} + assert request.data == {'foo': ['bar']} + + def test_request_read_before_parsing(self): + django_request = self.factory.put('/', {'foo': 'bar'}) + request = Request(django_request, parsers=[FormParser(), MultiPartParser()]) + django_request.read() + with pytest.raises(RawPostDataException): + request.POST + with pytest.raises(RawPostDataException): + request.POST + request.data
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: