Fixed #5611 -- Restricted accepted content types in parsing POST data

Thanks paulegan for the report and Preston Holmes for the review.
This commit is contained in:
Claude Paroz 2012-10-20 14:33:57 +02:00
parent eed4faf16f
commit dfd4a71751
4 changed files with 50 additions and 5 deletions

View File

@ -315,7 +315,7 @@ class HttpRequest(object):
self._post_parse_error = True self._post_parse_error = True
def _load_post_and_files(self): def _load_post_and_files(self):
# Populates self._post and self._files """Populate self._post and self._files if the content-type is a form type"""
if self.method != 'POST': if self.method != 'POST':
self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict() self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()
return return
@ -323,7 +323,7 @@ class HttpRequest(object):
self._mark_post_parse_error() self._mark_post_parse_error()
return return
if self.META.get('CONTENT_TYPE', '').startswith('multipart'): if self.META.get('CONTENT_TYPE', '').startswith('multipart/form-data'):
if hasattr(self, '_body'): if hasattr(self, '_body'):
# Use already read data # Use already read data
data = BytesIO(self._body) data = BytesIO(self._body)
@ -341,8 +341,10 @@ class HttpRequest(object):
# empty POST # empty POST
self._mark_post_parse_error() self._mark_post_parse_error()
raise raise
else: elif self.META.get('CONTENT_TYPE', '').startswith('application/x-www-form-urlencoded'):
self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict() self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()
else:
self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()
## File-like and iterator interface. ## File-like and iterator interface.
## ##

View File

@ -92,8 +92,14 @@ All attributes should be considered read-only, unless stated otherwise below.
.. attribute:: HttpRequest.POST .. attribute:: HttpRequest.POST
A dictionary-like object containing all given HTTP POST parameters. See the A dictionary-like object containing all given HTTP POST parameters,
:class:`QueryDict` documentation below. providing that the request contains form data. See the
:class:`QueryDict` documentation below. If you need to access raw or
non-form data posted in the request, access this through the
:attr:`HttpRequest.body` attribute instead.
.. versionchanged:: 1.5
Before Django 1.5, HttpRequest.POST contained non-form data.
It's possible that a request can come in via POST with an empty ``POST`` It's possible that a request can come in via POST with an empty ``POST``
dictionary -- if, say, a form is requested via the POST HTTP method but dictionary -- if, say, a form is requested via the POST HTTP method but

View File

@ -245,6 +245,18 @@ For consistency with the design of the other generic views,
dictionary into the context, instead passing the variables from the URLconf dictionary into the context, instead passing the variables from the URLconf
directly into the context. directly into the context.
Non-form data in HTTP requests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:attr:`request.POST <django.http.HttpRequest.POST>` will no longer include data
posted via HTTP requests with non form-specific content-types in the header.
In prior versions, data posted with content-types other than
``multipart/form-data`` or ``application/x-www-form-urlencoded`` would still
end up represented in the :attr:`request.POST <django.http.HttpRequest.POST>`
attribute. Developers wishing to access the raw POST data for these cases,
should use the :attr:`request.body <django.http.HttpRequest.body>` attribute
instead.
OPTIONS, PUT and DELETE requests in the test client OPTIONS, PUT and DELETE requests in the test client
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -330,6 +330,7 @@ class RequestsTests(unittest.TestCase):
def test_stream(self): def test_stream(self):
payload = b'name=value' payload = b'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST', request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
'CONTENT_LENGTH': len(payload), 'CONTENT_LENGTH': len(payload),
'wsgi.input': BytesIO(payload)}) 'wsgi.input': BytesIO(payload)})
self.assertEqual(request.read(), b'name=value') self.assertEqual(request.read(), b'name=value')
@ -341,6 +342,7 @@ class RequestsTests(unittest.TestCase):
""" """
payload = b'name=value' payload = b'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST', request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
'CONTENT_LENGTH': len(payload), 'CONTENT_LENGTH': len(payload),
'wsgi.input': BytesIO(payload)}) 'wsgi.input': BytesIO(payload)})
self.assertEqual(request.POST, {'name': ['value']}) self.assertEqual(request.POST, {'name': ['value']})
@ -354,6 +356,7 @@ class RequestsTests(unittest.TestCase):
""" """
payload = b'name=value' payload = b'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST', request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
'CONTENT_LENGTH': len(payload), 'CONTENT_LENGTH': len(payload),
'wsgi.input': BytesIO(payload)}) 'wsgi.input': BytesIO(payload)})
self.assertEqual(request.read(2), b'na') self.assertEqual(request.read(2), b'na')
@ -402,9 +405,28 @@ class RequestsTests(unittest.TestCase):
'wsgi.input': BytesIO(payload)}) 'wsgi.input': BytesIO(payload)})
self.assertEqual(request.POST, {}) self.assertEqual(request.POST, {})
def test_POST_binary_only(self):
payload = b'\r\n\x01\x00\x00\x00ab\x00\x00\xcd\xcc,@'
environ = {'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': 'application/octet-stream',
'CONTENT_LENGTH': len(payload),
'wsgi.input': BytesIO(payload)}
request = WSGIRequest(environ)
self.assertEqual(request.POST, {})
self.assertEqual(request.FILES, {})
self.assertEqual(request.body, payload)
# Same test without specifying content-type
environ.update({'CONTENT_TYPE': '', 'wsgi.input': BytesIO(payload)})
request = WSGIRequest(environ)
self.assertEqual(request.POST, {})
self.assertEqual(request.FILES, {})
self.assertEqual(request.body, payload)
def test_read_by_lines(self): def test_read_by_lines(self):
payload = b'name=value' payload = b'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST', request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
'CONTENT_LENGTH': len(payload), 'CONTENT_LENGTH': len(payload),
'wsgi.input': BytesIO(payload)}) 'wsgi.input': BytesIO(payload)})
self.assertEqual(list(request), [b'name=value']) self.assertEqual(list(request), [b'name=value'])
@ -415,6 +437,7 @@ class RequestsTests(unittest.TestCase):
""" """
payload = b'name=value' payload = b'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST', request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
'CONTENT_LENGTH': len(payload), 'CONTENT_LENGTH': len(payload),
'wsgi.input': BytesIO(payload)}) 'wsgi.input': BytesIO(payload)})
raw_data = request.body raw_data = request.body
@ -427,6 +450,7 @@ class RequestsTests(unittest.TestCase):
""" """
payload = b'name=value' payload = b'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST', request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
'CONTENT_LENGTH': len(payload), 'CONTENT_LENGTH': len(payload),
'wsgi.input': BytesIO(payload)}) 'wsgi.input': BytesIO(payload)})
raw_data = request.body raw_data = request.body
@ -479,6 +503,7 @@ class RequestsTests(unittest.TestCase):
payload = b'name=value' payload = b'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST', request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
'CONTENT_LENGTH': len(payload), 'CONTENT_LENGTH': len(payload),
'wsgi.input': ExplodingBytesIO(payload)}) 'wsgi.input': ExplodingBytesIO(payload)})