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()." |