Commit dfd4a717 authored by Claude Paroz's avatar Claude Paroz
Browse files

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

Thanks paulegan for the report and Preston Holmes for the review.
parent eed4faf1
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -315,7 +315,7 @@ class HttpRequest(object):
        self._post_parse_error = True

    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':
            self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()
            return
@@ -323,7 +323,7 @@ class HttpRequest(object):
            self._mark_post_parse_error()
            return

        if self.META.get('CONTENT_TYPE', '').startswith('multipart'):
        if self.META.get('CONTENT_TYPE', '').startswith('multipart/form-data'):
            if hasattr(self, '_body'):
                # Use already read data
                data = BytesIO(self._body)
@@ -341,8 +341,10 @@ class HttpRequest(object):
                # empty POST
                self._mark_post_parse_error()
                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()
        else:
            self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()

    ## File-like and iterator interface.
    ##
+8 −2
Original line number Diff line number Diff line
@@ -92,8 +92,14 @@ All attributes should be considered read-only, unless stated otherwise below.

.. attribute:: HttpRequest.POST

    A dictionary-like object containing all given HTTP POST parameters. See the
    :class:`QueryDict` documentation below.
    A dictionary-like object containing all given HTTP POST parameters,
    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``
    dictionary -- if, say, a form is requested via the POST HTTP method but
+12 −0
Original line number Diff line number Diff line
@@ -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
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+25 −0
Original line number Diff line number Diff line
@@ -330,6 +330,7 @@ class RequestsTests(unittest.TestCase):
    def test_stream(self):
        payload = b'name=value'
        request = WSGIRequest({'REQUEST_METHOD': 'POST',
                               'CONTENT_TYPE': 'application/x-www-form-urlencoded',
                               'CONTENT_LENGTH': len(payload),
                               'wsgi.input': BytesIO(payload)})
        self.assertEqual(request.read(), b'name=value')
@@ -341,6 +342,7 @@ class RequestsTests(unittest.TestCase):
        """
        payload = b'name=value'
        request = WSGIRequest({'REQUEST_METHOD': 'POST',
                               'CONTENT_TYPE': 'application/x-www-form-urlencoded',
                               'CONTENT_LENGTH': len(payload),
                               'wsgi.input': BytesIO(payload)})
        self.assertEqual(request.POST, {'name': ['value']})
@@ -354,6 +356,7 @@ class RequestsTests(unittest.TestCase):
        """
        payload = b'name=value'
        request = WSGIRequest({'REQUEST_METHOD': 'POST',
                               'CONTENT_TYPE': 'application/x-www-form-urlencoded',
                               'CONTENT_LENGTH': len(payload),
                               'wsgi.input': BytesIO(payload)})
        self.assertEqual(request.read(2), b'na')
@@ -402,9 +405,28 @@ class RequestsTests(unittest.TestCase):
                               'wsgi.input': BytesIO(payload)})
        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):
        payload = b'name=value'
        request = WSGIRequest({'REQUEST_METHOD': 'POST',
                               'CONTENT_TYPE': 'application/x-www-form-urlencoded',
                               'CONTENT_LENGTH': len(payload),
                               'wsgi.input': BytesIO(payload)})
        self.assertEqual(list(request), [b'name=value'])
@@ -415,6 +437,7 @@ class RequestsTests(unittest.TestCase):
        """
        payload = b'name=value'
        request = WSGIRequest({'REQUEST_METHOD': 'POST',
                               'CONTENT_TYPE': 'application/x-www-form-urlencoded',
                               'CONTENT_LENGTH': len(payload),
                               'wsgi.input': BytesIO(payload)})
        raw_data = request.body
@@ -427,6 +450,7 @@ class RequestsTests(unittest.TestCase):
        """
        payload = b'name=value'
        request = WSGIRequest({'REQUEST_METHOD': 'POST',
                               'CONTENT_TYPE': 'application/x-www-form-urlencoded',
                               'CONTENT_LENGTH': len(payload),
                               'wsgi.input': BytesIO(payload)})
        raw_data = request.body
@@ -479,6 +503,7 @@ class RequestsTests(unittest.TestCase):

        payload = b'name=value'
        request = WSGIRequest({'REQUEST_METHOD': 'POST',
                               'CONTENT_TYPE': 'application/x-www-form-urlencoded',
                               'CONTENT_LENGTH': len(payload),
                               'wsgi.input': ExplodingBytesIO(payload)})