From caab50bb85cbc951eb4fa253abf60d7e9f72df4f Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 28 Nov 2023 21:04:04 +0000 Subject: [PATCH] Added FormParser and MultiPartParser --- django/http/parsers.py | 53 +++++++++++++++++++++++++++++++++++ django/http/request.py | 42 +++++++++++---------------- tests/requests_tests/tests.py | 16 +++++------ 3 files changed, 77 insertions(+), 34 deletions(-) create mode 100644 django/http/parsers.py diff --git a/django/http/parsers.py b/django/http/parsers.py new file mode 100644 index 0000000000..68f5b7129b --- /dev/null +++ b/django/http/parsers.py @@ -0,0 +1,53 @@ +from io import BytesIO + +from django.core.exceptions import BadRequest +from django.http.multipartparser import MultiPartParser as _MultiPartParser +from django.utils.datastructures import ImmutableList, MultiValueDict + + +class BaseParser: + media_type = None + + def parse(self, request): + pass + + +class FormParser(BaseParser): + media_type = "application/x-www-form-urlencoded" + + def parse(self, request): + from django.http import QueryDict + + # According to RFC 1866, the "application/x-www-form-urlencoded" + # content type does not have a charset and should be always treated + # as UTF-8. + if request._encoding is not None and request._encoding.lower() != "utf-8": + raise BadRequest( + "HTTP requests with the 'application/x-www-form-urlencoded' " + "content type must be UTF-8 encoded." + ) + return QueryDict(request.body, encoding="utf-8"), MultiValueDict() + + +class MultiPartParser(BaseParser): + media_type = "multipart/form-data" + + def parse(self, request): + if hasattr(request, "_body"): + # Use already read data + data = BytesIO(request._body) + else: + data = request + + request.upload_handlers = ImmutableList( + request.upload_handlers, + warning=( + "You cannot alter upload handlers after the upload has been " + "processed." + ), + ) + parser = _MultiPartParser( + request.META, data, request.upload_handlers, request.encoding + ) + _post, _files = parser.parse() + return _post, _files diff --git a/django/http/request.py b/django/http/request.py index fe15a173be..6966edafb6 100644 --- a/django/http/request.py +++ b/django/http/request.py @@ -7,18 +7,14 @@ from urllib.parse import parse_qsl, quote, urlencode, urljoin, urlsplit from django.conf import settings from django.core import signing from django.core.exceptions import ( - BadRequest, DisallowedHost, ImproperlyConfigured, RequestDataTooBig, TooManyFieldsSent, ) from django.core.files import uploadhandler -from django.http.multipartparser import ( - MultiPartParser, - MultiPartParserError, - TooManyFilesSent, -) +from django.http import parsers +from django.http.multipartparser import MultiPartParser from django.utils.datastructures import ( CaseInsensitiveMapping, ImmutableList, @@ -362,32 +358,26 @@ class HttpRequest: self._mark_post_parse_error() return - if self.content_type == "multipart/form-data": - if hasattr(self, "_body"): - # Use already read data - data = BytesIO(self._body) - else: - data = self + # TODO create a parsers setter/getter/initializer like upload_handlers + parser_list = (parsers.FormParser(), parsers.MultiPartParser()) + + selected_parser = None + for parser in parser_list: + if self.content_type == parser.media_type: + selected_parser = parser + break + + if selected_parser: try: - self._post, self._files = self.parse_file_upload(self.META, data) - except (MultiPartParserError, TooManyFilesSent): + self._post, self._files = parser.parse(self) + except Exception as e: + # TODO 'application/x-www-form-urlencoded' didn't do this. # An error occurred while parsing POST data. Since when # formatting the error the request handler might access # self.POST, set self._post and self._file to prevent # attempts to parse POST data again. self._mark_post_parse_error() - raise - elif self.content_type == "application/x-www-form-urlencoded": - # According to RFC 1866, the "application/x-www-form-urlencoded" - # content type does not have a charset and should be always treated - # as UTF-8. - if self._encoding is not None and self._encoding.lower() != "utf-8": - raise BadRequest( - "HTTP requests with the 'application/x-www-form-urlencoded' " - "content type must be UTF-8 encoded." - ) - self._post = QueryDict(self.body, encoding="utf-8") - self._files = MultiValueDict() + raise e else: self._post, self._files = ( QueryDict(encoding=self._encoding), diff --git a/tests/requests_tests/tests.py b/tests/requests_tests/tests.py index eb158bc862..95ddf9bb27 100644 --- a/tests/requests_tests/tests.py +++ b/tests/requests_tests/tests.py @@ -387,20 +387,20 @@ class RequestsTests(SimpleTestCase): def test_non_utf8_charset_POST_bad_request(self): payload = FakePayload(urlencode({"key": "EspaƱa".encode("latin-1")})) - request = WSGIRequest( - { - "REQUEST_METHOD": "POST", - "CONTENT_LENGTH": len(payload), - "CONTENT_TYPE": "application/x-www-form-urlencoded; charset=iso-8859-1", - "wsgi.input": payload, - } - ) + environ = { + "REQUEST_METHOD": "POST", + "CONTENT_LENGTH": len(payload), + "CONTENT_TYPE": "application/x-www-form-urlencoded; charset=iso-8859-1", + "wsgi.input": payload, + } + request = WSGIRequest(environ) msg = ( "HTTP requests with the 'application/x-www-form-urlencoded' content type " "must be UTF-8 encoded." ) with self.assertRaisesMessage(BadRequest, msg): request.POST + request = WSGIRequest(environ) with self.assertRaisesMessage(BadRequest, msg): request.FILES