Skip to content

Commit be74d11

Browse files
authored
Fallback behavior for request parsing when request.POST already accessed. (#4500)
1 parent e82ee91 commit be74d11

File tree

2 files changed

+66
-4
lines changed

2 files changed

+66
-4
lines changed

rest_framework/request.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from django.conf import settings
1616
from django.http import QueryDict
1717
from django.http.multipartparser import parse_header
18+
from django.http.request import RawPostDataException
1819
from django.utils import six
1920
from django.utils.datastructures import MultiValueDict
2021

@@ -263,19 +264,39 @@ def _load_stream(self):
263264

264265
if content_length == 0:
265266
self._stream = None
266-
elif hasattr(self._request, 'read'):
267+
elif not self._request._read_started:
267268
self._stream = self._request
268269
else:
269-
self._stream = six.BytesIO(self.raw_post_data)
270+
self._stream = six.BytesIO(self.body)
271+
272+
def _supports_form_parsing(self):
273+
"""
274+
Return True if this requests supports parsing form data.
275+
"""
276+
form_media = (
277+
'application/x-www-form-urlencoded',
278+
'multipart/form-data'
279+
)
280+
return any([parser.media_type in form_media for parser in self.parsers])
270281

271282
def _parse(self):
272283
"""
273284
Parse the request content, returning a two-tuple of (data, files)
274285
275286
May raise an `UnsupportedMediaType`, or `ParseError` exception.
276287
"""
277-
stream = self.stream
278288
media_type = self.content_type
289+
try:
290+
stream = self.stream
291+
except RawPostDataException:
292+
if not hasattr(self._request, '_post'):
293+
raise
294+
# If request.POST has been accessed in middleware, and a method='POST'
295+
# request was made with 'multipart/form-data', then the request stream
296+
# will already have been exhausted.
297+
if self._supports_form_parsing():
298+
return (self._request.POST, self._request.FILES)
299+
stream = None
279300

280301
if stream is None or media_type is None:
281302
empty_data = QueryDict('', encoding=self._request._encoding)

tests/test_parsers.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@
77
from django.core.files.uploadhandler import (
88
MemoryFileUploadHandler, TemporaryFileUploadHandler
99
)
10+
from django.http.request import RawPostDataException
1011
from django.test import TestCase
1112
from django.utils.six.moves import StringIO
1213

1314
from rest_framework.exceptions import ParseError
14-
from rest_framework.parsers import FileUploadParser, FormParser
15+
from rest_framework.parsers import (
16+
FileUploadParser, FormParser, JSONParser, MultiPartParser
17+
)
18+
from rest_framework.request import Request
19+
from rest_framework.test import APIRequestFactory
1520

1621

1722
class Form(forms.Form):
@@ -122,3 +127,39 @@ def test_get_encoded_filename(self):
122127

123128
def __replace_content_disposition(self, disposition):
124129
self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = disposition
130+
131+
132+
class TestPOSTAccessed(TestCase):
133+
def setUp(self):
134+
self.factory = APIRequestFactory()
135+
136+
def test_post_accessed_in_post_method(self):
137+
django_request = self.factory.post('/', {'foo': 'bar'})
138+
request = Request(django_request, parsers=[FormParser(), MultiPartParser()])
139+
django_request.POST
140+
assert request.POST == {'foo': ['bar']}
141+
assert request.data == {'foo': ['bar']}
142+
143+
def test_post_accessed_in_post_method_with_json_parser(self):
144+
django_request = self.factory.post('/', {'foo': 'bar'})
145+
request = Request(django_request, parsers=[JSONParser()])
146+
django_request.POST
147+
assert request.POST == {}
148+
assert request.data == {}
149+
150+
def test_post_accessed_in_put_method(self):
151+
django_request = self.factory.put('/', {'foo': 'bar'})
152+
request = Request(django_request, parsers=[FormParser(), MultiPartParser()])
153+
django_request.POST
154+
assert request.POST == {'foo': ['bar']}
155+
assert request.data == {'foo': ['bar']}
156+
157+
def test_request_read_before_parsing(self):
158+
django_request = self.factory.put('/', {'foo': 'bar'})
159+
request = Request(django_request, parsers=[FormParser(), MultiPartParser()])
160+
django_request.read()
161+
with pytest.raises(RawPostDataException):
162+
request.POST
163+
with pytest.raises(RawPostDataException):
164+
request.POST
165+
request.data

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy