diff --git a/django/http/__init__.py b/django/http/__init__.py
index 476a625..bb1c3b6 100644
a
|
b
|
|
4 | 4 | import os |
5 | 5 | import re |
6 | 6 | import time |
| 7 | import warnings |
| 8 | |
7 | 9 | from pprint import pformat |
8 | 10 | from urllib import urlencode, quote |
9 | 11 | from urlparse import urljoin |
… |
… |
def parse_file_upload(self, META, post_data):
|
300 | 302 | parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding) |
301 | 303 | return parser.parse() |
302 | 304 | |
303 | | def _get_raw_post_data(self): |
304 | | if not hasattr(self, '_raw_post_data'): |
| 305 | @property |
| 306 | def body(self): |
| 307 | if not hasattr(self, '_body'): |
305 | 308 | if self._read_started: |
306 | | raise Exception("You cannot access raw_post_data after reading from request's data stream") |
307 | | self._raw_post_data = self.read() |
308 | | self._stream = StringIO(self._raw_post_data) |
309 | | return self._raw_post_data |
310 | | raw_post_data = property(_get_raw_post_data) |
| 309 | raise Exception("You cannot access body after reading from request's data stream") |
| 310 | self._body = self.read() |
| 311 | self._stream = StringIO(self._body) |
| 312 | return self._body |
| 313 | |
| 314 | @property |
| 315 | def raw_post_data(self): |
| 316 | warnings.warn( |
| 317 | 'The raw_post_data attribute has been renamed to body and the original ' |
| 318 | 'name has been deprecated.', PendingDeprecationWarning) |
| 319 | return self.body |
311 | 320 | |
312 | 321 | def _mark_post_parse_error(self): |
313 | 322 | self._post = QueryDict('') |
… |
… |
def _load_post_and_files(self):
|
319 | 328 | if self.method != 'POST': |
320 | 329 | self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict() |
321 | 330 | return |
322 | | if self._read_started and not hasattr(self, '_raw_post_data'): |
| 331 | if self._read_started and not hasattr(self, '_body'): |
323 | 332 | self._mark_post_parse_error() |
324 | 333 | return |
325 | 334 | |
326 | 335 | if self.META.get('CONTENT_TYPE', '').startswith('multipart'): |
327 | | if hasattr(self, '_raw_post_data'): |
| 336 | if hasattr(self, '_body'): |
328 | 337 | # Use already read data |
329 | | data = StringIO(self._raw_post_data) |
| 338 | data = StringIO(self._body) |
330 | 339 | else: |
331 | 340 | data = self |
332 | 341 | try: |
… |
… |
def _load_post_and_files(self):
|
342 | 351 | self._mark_post_parse_error() |
343 | 352 | raise |
344 | 353 | else: |
345 | | self._post, self._files = QueryDict(self.raw_post_data, encoding=self._encoding), MultiValueDict() |
| 354 | self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict() |
346 | 355 | |
347 | 356 | ## File-like and iterator interface. |
348 | 357 | ## |
349 | 358 | ## Expects self._stream to be set to an appropriate source of bytes by |
350 | 359 | ## a corresponding request subclass (WSGIRequest or ModPythonRequest). |
351 | 360 | ## Also when request data has already been read by request.POST or |
352 | | ## request.raw_post_data, self._stream points to a StringIO instance |
| 361 | ## request.body, self._stream points to a StringIO instance |
353 | 362 | ## containing that data. |
354 | 363 | |
355 | 364 | def read(self, *args, **kwargs): |
diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt
index 64d0e10..b6fffb2 100644
a
|
b
|
Attributes
|
30 | 30 | |
31 | 31 | All attributes except ``session`` should be considered read-only. |
32 | 32 | |
| 33 | .. attribute:: HttpRequest.body |
| 34 | |
| 35 | .. versionadded:: 1.4 |
| 36 | |
| 37 | The raw HTTP request body as a byte string. This is useful for processing |
| 38 | data in different formats than of conventional HTML forms: binary images, |
| 39 | XML payload etc. For processing form data use ``HttpRequest.POST``. |
| 40 | |
| 41 | .. versionadded:: 1.3 |
| 42 | |
| 43 | You can also read from an HttpRequest using file-like interface. See |
| 44 | :meth:`HttpRequest.read()`. |
| 45 | |
33 | 46 | .. attribute:: HttpRequest.path |
34 | 47 | |
35 | 48 | A string representing the full path to the requested page, not including |
… |
… |
All attributes except ``session`` should be considered read-only.
|
172 | 185 | |
173 | 186 | .. attribute:: HttpRequest.raw_post_data |
174 | 187 | |
| 188 | .. deprecated:: 1.4 |
| 189 | |
175 | 190 | The raw HTTP POST data as a byte string. This is useful for processing |
176 | 191 | data in different formats than of conventional HTML forms: binary images, |
177 | 192 | XML payload etc. For processing form data use ``HttpRequest.POST``. |
diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py
index a86064e..6ea7213 100644
a
|
b
|
def raw_post_view(request):
|
44 | 44 | """A view which expects raw XML to be posted and returns content extracted |
45 | 45 | from the XML""" |
46 | 46 | if request.method == 'POST': |
47 | | root = parseString(request.raw_post_data) |
| 47 | root = parseString(request.body) |
48 | 48 | first_book = root.firstChild.firstChild |
49 | 49 | title, author = [n.firstChild.nodeValue for n in first_book.childNodes] |
50 | 50 | t = Template("{{ title }} - {{ author }}", name="Book template") |
diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py
index e96f312..a3bfea0 100644
a
|
b
|
|
1 | 1 | import time |
| 2 | import warnings |
2 | 3 | from datetime import datetime, timedelta |
3 | 4 | from StringIO import StringIO |
4 | 5 | |
… |
… |
def test_stream(self):
|
291 | 292 | 'wsgi.input': StringIO(payload)}) |
292 | 293 | self.assertEqual(request.read(), 'name=value') |
293 | 294 | |
294 | | def test_read_after_value(self): |
| 295 | def test_read_after_value_raw_post_data(self): |
295 | 296 | """ |
296 | 297 | Reading from request is allowed after accessing request contents as |
297 | 298 | POST or raw_post_data. |
298 | 299 | """ |
| 300 | with warnings.catch_warnings(): |
| 301 | warnings.filterwarnings("ignore", category=PendingDeprecationWarning) |
| 302 | warnings.filterwarnings("ignore", category=DeprecationWarning) |
| 303 | |
| 304 | payload = 'name=value' |
| 305 | request = WSGIRequest({'REQUEST_METHOD': 'POST', |
| 306 | 'CONTENT_LENGTH': len(payload), |
| 307 | 'wsgi.input': StringIO(payload)}) |
| 308 | self.assertEqual(request.POST, {u'name': [u'value']}) |
| 309 | self.assertEqual(request.raw_post_data, 'name=value') |
| 310 | self.assertEqual(request.read(), 'name=value') |
| 311 | |
| 312 | def test_read_after_value(self): |
| 313 | """ |
| 314 | Reading from request is allowed after accessing request contents as |
| 315 | POST or body. |
| 316 | """ |
299 | 317 | payload = 'name=value' |
300 | 318 | request = WSGIRequest({'REQUEST_METHOD': 'POST', |
301 | 319 | 'CONTENT_LENGTH': len(payload), |
302 | 320 | 'wsgi.input': StringIO(payload)}) |
303 | 321 | self.assertEqual(request.POST, {u'name': [u'value']}) |
304 | | self.assertEqual(request.raw_post_data, 'name=value') |
| 322 | self.assertEqual(request.body, 'name=value') |
305 | 323 | self.assertEqual(request.read(), 'name=value') |
306 | 324 | |
307 | | def test_value_after_read(self): |
| 325 | def test_value_after_read_raw_post_data(self): |
308 | 326 | """ |
309 | 327 | Construction of POST or raw_post_data is not allowed after reading |
310 | 328 | from request. |
311 | 329 | """ |
| 330 | with warnings.catch_warnings(): |
| 331 | warnings.filterwarnings("ignore", category=PendingDeprecationWarning) |
| 332 | warnings.filterwarnings("ignore", category=DeprecationWarning) |
| 333 | |
| 334 | payload = 'name=value' |
| 335 | request = WSGIRequest({'REQUEST_METHOD': 'POST', |
| 336 | 'CONTENT_LENGTH': len(payload), |
| 337 | 'wsgi.input': StringIO(payload)}) |
| 338 | self.assertEqual(request.read(2), 'na') |
| 339 | self.assertRaises(Exception, lambda: request.raw_post_data) |
| 340 | self.assertEqual(request.POST, {}) |
| 341 | |
| 342 | def test_value_after_read(self): |
| 343 | """ |
| 344 | Construction of POST or body is not allowed after reading |
| 345 | from request. |
| 346 | """ |
312 | 347 | payload = 'name=value' |
313 | 348 | request = WSGIRequest({'REQUEST_METHOD': 'POST', |
314 | 349 | 'CONTENT_LENGTH': len(payload), |
315 | 350 | 'wsgi.input': StringIO(payload)}) |
316 | 351 | self.assertEqual(request.read(2), 'na') |
317 | | self.assertRaises(Exception, lambda: request.raw_post_data) |
| 352 | self.assertRaises(Exception, lambda: request.body) |
318 | 353 | self.assertEqual(request.POST, {}) |
319 | 354 | |
320 | 355 | def test_raw_post_data_after_POST_multipart(self): |
321 | 356 | """ |
322 | 357 | Reading raw_post_data after parsing multipart is not allowed |
323 | 358 | """ |
| 359 | with warnings.catch_warnings(): |
| 360 | warnings.filterwarnings("ignore", category=PendingDeprecationWarning) |
| 361 | warnings.filterwarnings("ignore", category=DeprecationWarning) |
| 362 | |
| 363 | # Because multipart is used for large amounts fo data i.e. file uploads, |
| 364 | # we don't want the data held in memory twice, and we don't want to |
| 365 | # silence the error by setting raw_post_data = '' either. |
| 366 | payload = "\r\n".join([ |
| 367 | '--boundary', |
| 368 | 'Content-Disposition: form-data; name="name"', |
| 369 | '', |
| 370 | 'value', |
| 371 | '--boundary--' |
| 372 | '']) |
| 373 | request = WSGIRequest({'REQUEST_METHOD': 'POST', |
| 374 | 'CONTENT_TYPE': 'multipart/form-data; boundary=boundary', |
| 375 | 'CONTENT_LENGTH': len(payload), |
| 376 | 'wsgi.input': StringIO(payload)}) |
| 377 | self.assertEqual(request.POST, {u'name': [u'value']}) |
| 378 | self.assertRaises(Exception, lambda: request.raw_post_data) |
| 379 | |
| 380 | def test_body_after_POST_multipart(self): |
| 381 | """ |
| 382 | Reading body after parsing multipart is not allowed |
| 383 | """ |
324 | 384 | # Because multipart is used for large amounts fo data i.e. file uploads, |
325 | 385 | # we don't want the data held in memory twice, and we don't want to |
326 | | # silence the error by setting raw_post_data = '' either. |
| 386 | # silence the error by setting body = '' either. |
327 | 387 | payload = "\r\n".join([ |
328 | 388 | '--boundary', |
329 | 389 | 'Content-Disposition: form-data; name="name"', |
… |
… |
def test_raw_post_data_after_POST_multipart(self):
|
336 | 396 | 'CONTENT_LENGTH': len(payload), |
337 | 397 | 'wsgi.input': StringIO(payload)}) |
338 | 398 | self.assertEqual(request.POST, {u'name': [u'value']}) |
339 | | self.assertRaises(Exception, lambda: request.raw_post_data) |
| 399 | self.assertRaises(Exception, lambda: request.body) |
340 | 400 | |
341 | 401 | def test_POST_multipart_with_content_length_zero(self): |
342 | 402 | """ |
… |
… |
def test_POST_after_raw_post_data_read(self):
|
370 | 430 | """ |
371 | 431 | POST should be populated even if raw_post_data is read first |
372 | 432 | """ |
| 433 | with warnings.catch_warnings(): |
| 434 | warnings.filterwarnings("ignore", category=PendingDeprecationWarning) |
| 435 | warnings.filterwarnings("ignore", category=DeprecationWarning) |
| 436 | |
| 437 | payload = 'name=value' |
| 438 | request = WSGIRequest({'REQUEST_METHOD': 'POST', |
| 439 | 'CONTENT_LENGTH': len(payload), |
| 440 | 'wsgi.input': StringIO(payload)}) |
| 441 | raw_data = request.raw_post_data |
| 442 | self.assertEqual(request.POST, {u'name': [u'value']}) |
| 443 | |
| 444 | def test_POST_after_body_read(self): |
| 445 | """ |
| 446 | POST should be populated even if body is read first |
| 447 | """ |
373 | 448 | payload = 'name=value' |
374 | 449 | request = WSGIRequest({'REQUEST_METHOD': 'POST', |
375 | 450 | 'CONTENT_LENGTH': len(payload), |
376 | 451 | 'wsgi.input': StringIO(payload)}) |
377 | | raw_data = request.raw_post_data |
| 452 | raw_data = request.body |
378 | 453 | self.assertEqual(request.POST, {u'name': [u'value']}) |
379 | 454 | |
380 | 455 | def test_POST_after_raw_post_data_read_and_stream_read(self): |
… |
… |
def test_POST_after_raw_post_data_read_and_stream_read(self):
|
382 | 457 | POST should be populated even if raw_post_data is read first, and then |
383 | 458 | the stream is read second. |
384 | 459 | """ |
| 460 | with warnings.catch_warnings(): |
| 461 | warnings.filterwarnings("ignore", category=PendingDeprecationWarning) |
| 462 | warnings.filterwarnings("ignore", category=DeprecationWarning) |
| 463 | |
| 464 | payload = 'name=value' |
| 465 | request = WSGIRequest({'REQUEST_METHOD': 'POST', |
| 466 | 'CONTENT_LENGTH': len(payload), |
| 467 | 'wsgi.input': StringIO(payload)}) |
| 468 | raw_data = request.raw_post_data |
| 469 | self.assertEqual(request.read(1), u'n') |
| 470 | self.assertEqual(request.POST, {u'name': [u'value']}) |
| 471 | |
| 472 | def test_POST_after_body_read_and_stream_read(self): |
| 473 | """ |
| 474 | POST should be populated even if body is read first, and then |
| 475 | the stream is read second. |
| 476 | """ |
385 | 477 | payload = 'name=value' |
386 | 478 | request = WSGIRequest({'REQUEST_METHOD': 'POST', |
387 | 479 | 'CONTENT_LENGTH': len(payload), |
388 | 480 | 'wsgi.input': StringIO(payload)}) |
389 | | raw_data = request.raw_post_data |
| 481 | raw_data = request.body |
390 | 482 | self.assertEqual(request.read(1), u'n') |
391 | 483 | self.assertEqual(request.POST, {u'name': [u'value']}) |
392 | 484 | |
… |
… |
def test_POST_after_raw_post_data_read_and_stream_read_multipart(self):
|
395 | 487 | POST should be populated even if raw_post_data is read first, and then |
396 | 488 | the stream is read second. Using multipart/form-data instead of urlencoded. |
397 | 489 | """ |
| 490 | with warnings.catch_warnings(): |
| 491 | warnings.filterwarnings("ignore", category=PendingDeprecationWarning) |
| 492 | warnings.filterwarnings("ignore", category=DeprecationWarning) |
| 493 | |
| 494 | payload = "\r\n".join([ |
| 495 | '--boundary', |
| 496 | 'Content-Disposition: form-data; name="name"', |
| 497 | '', |
| 498 | 'value', |
| 499 | '--boundary--' |
| 500 | '']) |
| 501 | request = WSGIRequest({'REQUEST_METHOD': 'POST', |
| 502 | 'CONTENT_TYPE': 'multipart/form-data; boundary=boundary', |
| 503 | 'CONTENT_LENGTH': len(payload), |
| 504 | 'wsgi.input': StringIO(payload)}) |
| 505 | raw_data = request.raw_post_data |
| 506 | # Consume enough data to mess up the parsing: |
| 507 | self.assertEqual(request.read(13), u'--boundary\r\nC') |
| 508 | self.assertEqual(request.POST, {u'name': [u'value']}) |
| 509 | |
| 510 | def test_POST_after_body_read_and_stream_read_multipart(self): |
| 511 | """ |
| 512 | POST should be populated even if body is read first, and then |
| 513 | the stream is read second. Using multipart/form-data instead of urlencoded. |
| 514 | """ |
398 | 515 | payload = "\r\n".join([ |
399 | 516 | '--boundary', |
400 | 517 | 'Content-Disposition: form-data; name="name"', |
… |
… |
def test_POST_after_raw_post_data_read_and_stream_read_multipart(self):
|
406 | 523 | 'CONTENT_TYPE': 'multipart/form-data; boundary=boundary', |
407 | 524 | 'CONTENT_LENGTH': len(payload), |
408 | 525 | 'wsgi.input': StringIO(payload)}) |
409 | | raw_data = request.raw_post_data |
| 526 | raw_data = request.body |
410 | 527 | # Consume enough data to mess up the parsing: |
411 | 528 | self.assertEqual(request.read(13), u'--boundary\r\nC') |
412 | 529 | self.assertEqual(request.POST, {u'name': [u'value']}) |
diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py
index 7d0b0e4..160cfa6 100644
a
|
b
|
def test_response_no_template(self):
|
975 | 975 | |
976 | 976 | class ReadLimitedStreamTest(TestCase): |
977 | 977 | """ |
978 | | Tests that ensure that HttpRequest.raw_post_data, HttpRequest.read() and |
| 978 | Tests that ensure that HttpRequest.body, HttpRequest.raw_post_data, HttpRequest.read() and |
979 | 979 | HttpRequest.read(BUFFER) have proper LimitedStream behaviour. |
980 | 980 | |
981 | 981 | Refs #14753, #15785 |
982 | 982 | """ |
| 983 | |
983 | 984 | def test_raw_post_data_from_empty_request(self): |
984 | 985 | """HttpRequest.raw_post_data on a test client GET request should return |
985 | 986 | the empty string.""" |
986 | | self.assertEquals(self.client.get("/test_client_regress/raw_post_data/").content, '') |
| 987 | with warnings.catch_warnings(): |
| 988 | warnings.filterwarnings("ignore", category=PendingDeprecationWarning) |
| 989 | warnings.filterwarnings("ignore", category=DeprecationWarning) |
| 990 | |
| 991 | self.assertEquals(self.client.get("/test_client_regress/raw_post_data/").content, '') |
| 992 | |
| 993 | def test_body_from_empty_request(self): |
| 994 | """HttpRequest.body on a test client GET request should return |
| 995 | the empty string.""" |
| 996 | self.assertEquals(self.client.get("/test_client_regress/body/").content, '') |
987 | 997 | |
988 | 998 | def test_read_from_empty_request(self): |
989 | 999 | """HttpRequest.read() on a test client GET request should return the |
diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/regressiontests/test_client_regress/urls.py
index 93f7a2e..db3c9de 100644
a
|
b
|
|
32 | 32 | (r'^check_headers/$', views.check_headers), |
33 | 33 | (r'^check_headers_redirect/$', RedirectView.as_view(url='/test_client_regress/check_headers/')), |
34 | 34 | (r'^raw_post_data/$', views.raw_post_data), |
| 35 | (r'^body/$', views.body), |
35 | 36 | (r'^read_all/$', views.read_all), |
36 | 37 | (r'^read_buffer/$', views.read_buffer), |
37 | 38 | (r'^request_context_view/$', views.request_context_view), |
diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py
index b398293..21c700c 100644
a
|
b
|
|
| 1 | import warnings |
| 2 | |
1 | 3 | from django.conf import settings |
2 | 4 | from django.contrib.auth.decorators import login_required |
3 | 5 | from django.http import HttpResponse, HttpResponseRedirect |
… |
… |
def return_json_file(request):
|
79 | 81 | charset = settings.DEFAULT_CHARSET |
80 | 82 | |
81 | 83 | # This just checks that the uploaded data is JSON |
82 | | obj_dict = simplejson.loads(request.raw_post_data.decode(charset)) |
| 84 | obj_dict = simplejson.loads(request.body.decode(charset)) |
83 | 85 | obj_json = simplejson.dumps(obj_dict, encoding=charset, |
84 | 86 | cls=DjangoJSONEncoder, |
85 | 87 | ensure_ascii=False) |
… |
… |
def check_headers(request):
|
94 | 96 | |
95 | 97 | def raw_post_data(request): |
96 | 98 | "A view that is requested with GET and accesses request.raw_post_data. Refs #14753." |
97 | | return HttpResponse(request.raw_post_data) |
| 99 | with warnings.catch_warnings(): |
| 100 | warnings.filterwarnings("ignore", category=PendingDeprecationWarning) |
| 101 | warnings.filterwarnings("ignore", category=DeprecationWarning) |
| 102 | |
| 103 | return HttpResponse(request.raw_post_data) |
| 104 | |
| 105 | def body(request): |
| 106 | "A view that is requested with GET and accesses request.body. Refs #14753." |
| 107 | return HttpResponse(request.body) |
98 | 108 | |
99 | 109 | def read_all(request): |
100 | 110 | "A view that is requested with accesses request.read()." |