|
4 | 4 | from urllib.parse import urlencode |
5 | 5 |
|
6 | 6 | from django.core.exceptions import BadRequest, DisallowedHost |
| 7 | +from django.core.files.uploadedfile import InMemoryUploadedFile |
| 8 | +from django.core.files.uploadhandler import MemoryFileUploadHandler |
7 | 9 | from django.core.handlers.wsgi import LimitedStream, WSGIRequest |
8 | 10 | from django.http import ( |
9 | 11 | HttpHeaders, |
|
17 | 19 | from django.test.client import BOUNDARY, MULTIPART_CONTENT, FakePayload |
18 | 20 |
|
19 | 21 |
|
| 22 | +class ErrorFileUploadHandler(MemoryFileUploadHandler): |
| 23 | + def handle_raw_input( |
| 24 | + self, input_data, META, content_length, boundary, encoding=None |
| 25 | + ): |
| 26 | + raise ValueError |
| 27 | + |
| 28 | + |
| 29 | +class CustomFileUploadHandler(MemoryFileUploadHandler): |
| 30 | + def handle_raw_input( |
| 31 | + self, input_data, META, content_length, boundary, encoding=None |
| 32 | + ): |
| 33 | + return ("_POST", "_FILES") |
| 34 | + |
| 35 | + |
20 | 36 | class RequestsTests(SimpleTestCase): |
21 | 37 | def test_httprequest(self): |
22 | 38 | request = HttpRequest() |
@@ -491,6 +507,261 @@ def test_POST_multipart_with_content_length_zero(self): |
491 | 507 | ) |
492 | 508 | self.assertEqual(request.POST, {}) |
493 | 509 |
|
| 510 | + @override_settings( |
| 511 | + FILE_UPLOAD_HANDLERS=["requests_tests.tests.ErrorFileUploadHandler"] |
| 512 | + ) |
| 513 | + def test_POST_multipart_handler_error(self): |
| 514 | + payload = FakePayload( |
| 515 | + "\r\n".join( |
| 516 | + [ |
| 517 | + f"--{BOUNDARY}", |
| 518 | + 'Content-Disposition: form-data; name="name"', |
| 519 | + "", |
| 520 | + "value", |
| 521 | + f"--{BOUNDARY}--", |
| 522 | + ] |
| 523 | + ) |
| 524 | + ) |
| 525 | + request = WSGIRequest( |
| 526 | + { |
| 527 | + "REQUEST_METHOD": "POST", |
| 528 | + "CONTENT_TYPE": MULTIPART_CONTENT, |
| 529 | + "CONTENT_LENGTH": len(payload), |
| 530 | + "wsgi.input": payload, |
| 531 | + } |
| 532 | + ) |
| 533 | + with self.assertRaises(ValueError): |
| 534 | + request.POST |
| 535 | + |
| 536 | + @override_settings( |
| 537 | + FILE_UPLOAD_HANDLERS=["requests_tests.tests.CustomFileUploadHandler"] |
| 538 | + ) |
| 539 | + def test_POST_multipart_handler_parses_input(self): |
| 540 | + payload = FakePayload( |
| 541 | + "\r\n".join( |
| 542 | + [ |
| 543 | + f"--{BOUNDARY}", |
| 544 | + 'Content-Disposition: form-data; name="name"', |
| 545 | + "", |
| 546 | + "value", |
| 547 | + f"--{BOUNDARY}--", |
| 548 | + ] |
| 549 | + ) |
| 550 | + ) |
| 551 | + request = WSGIRequest( |
| 552 | + { |
| 553 | + "REQUEST_METHOD": "POST", |
| 554 | + "CONTENT_TYPE": MULTIPART_CONTENT, |
| 555 | + "CONTENT_LENGTH": len(payload), |
| 556 | + "wsgi.input": payload, |
| 557 | + } |
| 558 | + ) |
| 559 | + self.assertEqual(request.POST, "_POST") |
| 560 | + self.assertEqual(request.FILES, "_FILES") |
| 561 | + |
| 562 | + def test_request_methods_with_content(self): |
| 563 | + for method in ["GET", "PUT", "DELETE"]: |
| 564 | + with self.subTest(method=method): |
| 565 | + payload = FakePayload(urlencode({"key": "value"})) |
| 566 | + request = WSGIRequest( |
| 567 | + { |
| 568 | + "REQUEST_METHOD": method, |
| 569 | + "CONTENT_LENGTH": len(payload), |
| 570 | + "CONTENT_TYPE": "application/x-www-form-urlencoded", |
| 571 | + "wsgi.input": payload, |
| 572 | + } |
| 573 | + ) |
| 574 | + self.assertEqual(request.POST, {}) |
| 575 | + |
| 576 | + def test_POST_content_type_json(self): |
| 577 | + payload = FakePayload( |
| 578 | + "\r\n".join( |
| 579 | + [ |
| 580 | + '{"pk": 1, "model": "store.book", "fields": {"name": "Mostly Ha', |
| 581 | + 'rmless", "author": ["Douglas", Adams"]}}', |
| 582 | + ] |
| 583 | + ) |
| 584 | + ) |
| 585 | + request = WSGIRequest( |
| 586 | + { |
| 587 | + "REQUEST_METHOD": "POST", |
| 588 | + "CONTENT_TYPE": "application/json", |
| 589 | + "CONTENT_LENGTH": len(payload), |
| 590 | + "wsgi.input": payload, |
| 591 | + } |
| 592 | + ) |
| 593 | + self.assertEqual(request.POST, {}) |
| 594 | + self.assertEqual(request.FILES, {}) |
| 595 | + |
| 596 | + _json_payload = [ |
| 597 | + 'Content-Disposition: form-data; name="JSON"', |
| 598 | + "Content-Type: application/json", |
| 599 | + "", |
| 600 | + '{"pk": 1, "model": "store.book", "fields": {"name": "Mostly Harmless", ' |
| 601 | + '"author": ["Douglas", Adams"]}}', |
| 602 | + ] |
| 603 | + |
| 604 | + def test_POST_form_data_json(self): |
| 605 | + payload = FakePayload( |
| 606 | + "\r\n".join([f"--{BOUNDARY}", *self._json_payload, f"--{BOUNDARY}--"]) |
| 607 | + ) |
| 608 | + request = WSGIRequest( |
| 609 | + { |
| 610 | + "REQUEST_METHOD": "POST", |
| 611 | + "CONTENT_TYPE": MULTIPART_CONTENT, |
| 612 | + "CONTENT_LENGTH": len(payload), |
| 613 | + "wsgi.input": payload, |
| 614 | + } |
| 615 | + ) |
| 616 | + self.assertEqual( |
| 617 | + request.POST, |
| 618 | + { |
| 619 | + "JSON": [ |
| 620 | + '{"pk": 1, "model": "store.book", "fields": {"name": "Mostly ' |
| 621 | + 'Harmless", "author": ["Douglas", Adams"]}}' |
| 622 | + ], |
| 623 | + }, |
| 624 | + ) |
| 625 | + |
| 626 | + def test_POST_multipart_json(self): |
| 627 | + payload = FakePayload( |
| 628 | + "\r\n".join( |
| 629 | + [ |
| 630 | + f"--{BOUNDARY}", |
| 631 | + 'Content-Disposition: form-data; name="name"', |
| 632 | + "", |
| 633 | + "value", |
| 634 | + f"--{BOUNDARY}", |
| 635 | + *self._json_payload, |
| 636 | + f"--{BOUNDARY}--", |
| 637 | + ] |
| 638 | + ) |
| 639 | + ) |
| 640 | + request = WSGIRequest( |
| 641 | + { |
| 642 | + "REQUEST_METHOD": "POST", |
| 643 | + "CONTENT_TYPE": MULTIPART_CONTENT, |
| 644 | + "CONTENT_LENGTH": len(payload), |
| 645 | + "wsgi.input": payload, |
| 646 | + } |
| 647 | + ) |
| 648 | + self.assertEqual( |
| 649 | + request.POST, |
| 650 | + { |
| 651 | + "name": ["value"], |
| 652 | + "JSON": [ |
| 653 | + '{"pk": 1, "model": "store.book", "fields": {"name": "Mostly ' |
| 654 | + 'Harmless", "author": ["Douglas", Adams"]}}' |
| 655 | + ], |
| 656 | + }, |
| 657 | + ) |
| 658 | + |
| 659 | + def test_POST_multipart_json_csv(self): |
| 660 | + payload = FakePayload( |
| 661 | + "\r\n".join( |
| 662 | + [ |
| 663 | + f"--{BOUNDARY}", |
| 664 | + 'Content-Disposition: form-data; name="name"', |
| 665 | + "", |
| 666 | + "value", |
| 667 | + f"--{BOUNDARY}", |
| 668 | + *self._json_payload, |
| 669 | + f"--{BOUNDARY}", |
| 670 | + 'Content-Disposition: form-data; name="CSV"', |
| 671 | + "Content-Type: text/csv", |
| 672 | + "", |
| 673 | + "Framework,ID.Django,1.Flask,2.", |
| 674 | + f"--{BOUNDARY}--", |
| 675 | + ] |
| 676 | + ) |
| 677 | + ) |
| 678 | + request = WSGIRequest( |
| 679 | + { |
| 680 | + "REQUEST_METHOD": "POST", |
| 681 | + "CONTENT_TYPE": MULTIPART_CONTENT, |
| 682 | + "CONTENT_LENGTH": len(payload), |
| 683 | + "wsgi.input": payload, |
| 684 | + } |
| 685 | + ) |
| 686 | + self.assertEqual( |
| 687 | + request.POST, |
| 688 | + { |
| 689 | + "name": ["value"], |
| 690 | + "JSON": [ |
| 691 | + '{"pk": 1, "model": "store.book", "fields": {"name": "Mostly ' |
| 692 | + 'Harmless", "author": ["Douglas", Adams"]}}' |
| 693 | + ], |
| 694 | + "CSV": ["Framework,ID.Django,1.Flask,2."], |
| 695 | + }, |
| 696 | + ) |
| 697 | + |
| 698 | + def test_POST_multipart_with_file(self): |
| 699 | + payload = FakePayload( |
| 700 | + "\r\n".join( |
| 701 | + [ |
| 702 | + f"--{BOUNDARY}", |
| 703 | + 'Content-Disposition: form-data; name="name"', |
| 704 | + "", |
| 705 | + "value", |
| 706 | + f"--{BOUNDARY}", |
| 707 | + *self._json_payload, |
| 708 | + f"--{BOUNDARY}", |
| 709 | + 'Content-Disposition: form-data; name="File"; filename="test.csv"', |
| 710 | + "Content-Type: application/octet-stream", |
| 711 | + "", |
| 712 | + "Framework,ID", |
| 713 | + "Django,1", |
| 714 | + "Flask,2", |
| 715 | + f"--{BOUNDARY}--", |
| 716 | + ] |
| 717 | + ) |
| 718 | + ) |
| 719 | + request = WSGIRequest( |
| 720 | + { |
| 721 | + "REQUEST_METHOD": "POST", |
| 722 | + "CONTENT_TYPE": MULTIPART_CONTENT, |
| 723 | + "CONTENT_LENGTH": len(payload), |
| 724 | + "wsgi.input": payload, |
| 725 | + } |
| 726 | + ) |
| 727 | + self.assertEqual( |
| 728 | + request.POST, |
| 729 | + { |
| 730 | + "name": ["value"], |
| 731 | + "JSON": [ |
| 732 | + '{"pk": 1, "model": "store.book", "fields": {"name": "Mostly ' |
| 733 | + 'Harmless", "author": ["Douglas", Adams"]}}' |
| 734 | + ], |
| 735 | + }, |
| 736 | + ) |
| 737 | + self.assertEqual(len(request.FILES), 1) |
| 738 | + self.assertIsInstance((request.FILES["File"]), InMemoryUploadedFile) |
| 739 | + |
| 740 | + def test_base64_invalid_encoding(self): |
| 741 | + payload = FakePayload( |
| 742 | + "\r\n".join( |
| 743 | + [ |
| 744 | + f"--{BOUNDARY}", |
| 745 | + 'Content-Disposition: form-data; name="file"; filename="test.txt"', |
| 746 | + "Content-Type: application/octet-stream", |
| 747 | + "Content-Transfer-Encoding: base64", |
| 748 | + "", |
| 749 | + f"\r\nZsg£\r\n--{BOUNDARY}--", |
| 750 | + ] |
| 751 | + ) |
| 752 | + ) |
| 753 | + request = WSGIRequest( |
| 754 | + { |
| 755 | + "REQUEST_METHOD": "POST", |
| 756 | + "CONTENT_TYPE": MULTIPART_CONTENT, |
| 757 | + "CONTENT_LENGTH": len(payload), |
| 758 | + "wsgi.input": payload, |
| 759 | + } |
| 760 | + ) |
| 761 | + msg = "Could not decode base64 data." |
| 762 | + with self.assertRaisesMessage(MultiPartParserError, msg): |
| 763 | + request.POST |
| 764 | + |
494 | 765 | def test_POST_binary_only(self): |
495 | 766 | payload = b"\r\n\x01\x00\x00\x00ab\x00\x00\xcd\xcc,@" |
496 | 767 | environ = { |
|
0 commit comments