Loading django/http/__init__.py +5 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading @@ -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. ## Loading docs/ref/request-response.txt +8 −2 Original line number Diff line number Diff line Loading @@ -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 Loading docs/releases/1.5.txt +12 −0 Original line number Diff line number Diff line Loading @@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Loading tests/regressiontests/requests/tests.py +25 −0 Original line number Diff line number Diff line Loading @@ -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') Loading @@ -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']}) Loading @@ -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') Loading Loading @@ -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']) Loading @@ -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 Loading @@ -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 Loading Loading @@ -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)}) Loading Loading
django/http/__init__.py +5 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading @@ -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. ## Loading
docs/ref/request-response.txt +8 −2 Original line number Diff line number Diff line Loading @@ -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 Loading
docs/releases/1.5.txt +12 −0 Original line number Diff line number Diff line Loading @@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Loading
tests/regressiontests/requests/tests.py +25 −0 Original line number Diff line number Diff line Loading @@ -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') Loading @@ -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']}) Loading @@ -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') Loading Loading @@ -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']) Loading @@ -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 Loading @@ -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 Loading Loading @@ -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)}) Loading