0
0
mirror of https://github.com/django/django.git synced 2024-11-24 20:07:01 +01:00

Added __init__ to BaseParsers and subclasses.

This commit is contained in:
David Smith 2023-12-15 11:36:45 +00:00
parent e9a18b4a2a
commit 95d94515b6
6 changed files with 92 additions and 40 deletions

View File

@ -113,9 +113,9 @@ class ASGIRequest(HttpRequest):
# Other bits.
self.resolver_match = None
self._parsers = [
parsers.FormParser(),
parsers.MultiPartParser(),
parsers.JSONParser(),
parsers.FormParser,
parsers.MultiPartParser,
parsers.JSONParser,
]
@cached_property

View File

@ -79,9 +79,9 @@ class WSGIRequest(HttpRequest):
self._read_started = False
self.resolver_match = None
self._parsers = [
parsers.FormParser(),
parsers.MultiPartParser(),
parsers.JSONParser(),
parsers.FormParser,
parsers.MultiPartParser,
parsers.JSONParser,
]
def _get_scheme(self):

View File

@ -10,39 +10,50 @@ class BaseParser:
media_type = None
parsers = None
def can_handle(self, media_type):
return media_type == self.media_type
def __init__(self, request):
self.request = request
def parse(self, data, request=None):
@classmethod
def can_handle(cls, media_type):
return media_type == cls.media_type
def parse(self, data):
pass
class FormParser(BaseParser):
media_type = "application/x-www-form-urlencoded"
def parse(self, request):
from django.http import QueryDict
def __init__(self, request):
super().__init__(request)
# 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":
if (
self.request._encoding is not None
and self.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()
def parse(self, data):
from django.http import QueryDict
return QueryDict(data, encoding="utf-8"), MultiValueDict()
class MultiPartParser(BaseParser):
media_type = "multipart/form-data"
def parse(self, request):
def parse(self, data):
request = self.request
if hasattr(request, "_body"):
# Use already read data
data = BytesIO(request._body)
request_data = BytesIO(request._body)
else:
data = request
request_data = request
# TODO - POST and data can be called on the same request. This parser can be
# called multiple times on the same request. While `_post` `_data` are different
@ -56,7 +67,11 @@ class MultiPartParser(BaseParser):
),
)
parser = _MultiPartParser(
request.META, data, request.upload_handlers, request.encoding, self.parsers
request.META,
request_data,
request.upload_handlers,
request.encoding,
self.parsers,
)
# TODO _post could also be _data
_post, _files = parser.parse()
@ -66,15 +81,10 @@ class MultiPartParser(BaseParser):
class JSONParser(BaseParser):
media_type = "application/json"
# TODO rename request -- it's not always one.
def parse(self, request):
from django.http import HttpRequest
def parse(self, data):
def strict_constant(o):
raise ValueError(
"Out of range float values are not JSON compliant: " + repr(o)
)
if isinstance(request, HttpRequest):
request = request.body
return json.loads(request, parse_constant=strict_constant), MultiValueDict()
return json.loads(data, parse_constant=strict_constant), MultiValueDict()

View File

@ -70,9 +70,9 @@ class HttpRequest:
self.content_type = None
self.content_params = None
self._parsers = [
parsers.FormParser(),
parsers.MultiPartParser(),
parsers.JSONParser(),
parsers.FormParser,
parsers.MultiPartParser,
parsers.JSONParser,
]
def __repr__(self):
@ -366,7 +366,7 @@ class HttpRequest:
return
if parser_list is None:
parser_list = [parsers.FormParser(), parsers.MultiPartParser()]
parser_list = [parsers.FormParser, parsers.MultiPartParser]
selected_parser = None
for parser in parser_list:
if parser.can_handle(self.content_type):
@ -374,9 +374,13 @@ class HttpRequest:
break
if selected_parser:
selected_parser.parsers = parser_list
parser = selected_parser(self)
try:
data, self._files = parser.parse(self)
if self.content_type == "multipart/form-data":
parser.parsers = (parser(self) for parser in parser_list)
data, self._files = parser.parse(None)
else:
data, self._files = parser.parse(self.body)
setattr(self, data_attr, data)
except Exception as e:
# TODO 'application/x-www-form-urlencoded' didn't do this.

View File

@ -8,11 +8,11 @@ from django.utils.http import urlencode
class TestParsers(SimpleTestCase):
def test_can_handle(self):
parser = MultiPartParser()
parser = MultiPartParser(HttpRequest())
self.assertIs(parser.can_handle("multipart/form-data"), True)
self.assertIs(parser.can_handle("application/json"), False)
parser = FormParser()
parser = FormParser(HttpRequest())
self.assertIs(parser.can_handle("application/x-www-form-urlencoded"), True)
self.assertIs(parser.can_handle("multipart/form-data"), False)
@ -24,7 +24,7 @@ class TestParsers(SimpleTestCase):
main_type, sub_type = media_type.split("/")
return main_type == "text"
parser = CustomParser()
parser = CustomParser(None)
self.assertIs(parser.can_handle("application/json"), False)
self.assertTrue(parser.can_handle("text/*"), True)
self.assertTrue(parser.can_handle("text/csv"), True)
@ -32,16 +32,16 @@ class TestParsers(SimpleTestCase):
def test_request_parser_no_setting(self):
request = HttpRequest()
form, multipart, json = request.parsers
self.assertIsInstance(form, FormParser)
self.assertIsInstance(multipart, MultiPartParser)
self.assertIsInstance(json, JSONParser)
self.assertIs(form, FormParser)
self.assertIs(multipart, MultiPartParser)
self.assertIs(json, JSONParser)
def test_set_parser(self):
request = HttpRequest()
request.parsers = [FormParser()]
request.parsers = [FormParser]
self.assertEqual(len(request.parsers), 1)
self.assertIsInstance(request.parsers[0], FormParser)
self.assertIs(request.parsers[0], FormParser)
def test_set_parsers_following_files_access(self):
payload = FakePayload(urlencode({"key": "value"}))
@ -62,7 +62,7 @@ class TestParsers(SimpleTestCase):
request.parsers = []
def test_json_strict(self):
parser = JSONParser()
parser = JSONParser(None)
msg_base = "Out of range float values are not JSON compliant: '%s'"
for value in ["Infinity", "-Infinity", "NaN"]:

View File

@ -673,6 +673,34 @@ class RequestsTests(SimpleTestCase):
},
)
def test_data_form_data_json(self):
payload = FakePayload(
"\r\n".join([f"--{BOUNDARY}", *self._json_payload, f"--{BOUNDARY}--"])
)
request = WSGIRequest(
{
"REQUEST_METHOD": "POST",
"CONTENT_TYPE": MULTIPART_CONTENT,
"CONTENT_LENGTH": len(payload),
"wsgi.input": payload,
}
)
self.assertEqual(
request.data,
{
"JSON": [
{
"pk": 1,
"model": "store.book",
"fields": {
"name": "Mostly Harmless",
"author": ["Douglas", "Adams"],
},
}
],
},
)
def test_POST_multipart_json(self):
payload = FakePayload(
"\r\n".join(
@ -1036,6 +1064,7 @@ class RequestsTests(SimpleTestCase):
)
request.body # evaluate
self.assertEqual(request.POST, {"name": ["value"]})
self.assertEqual(request.data, {"name": ["value"]})
def test_multipart_post_field_with_invalid_base64(self):
payload = FakePayload(
@ -1061,6 +1090,7 @@ class RequestsTests(SimpleTestCase):
)
request.body # evaluate
self.assertEqual(request.POST, {"name": ["123"]})
self.assertEqual(request.data, {"name": ["123"]})
def test_POST_after_body_read_and_stream_read_multipart(self):
"""
@ -1090,6 +1120,7 @@ class RequestsTests(SimpleTestCase):
# Consume enough data to mess up the parsing:
self.assertEqual(request.read(13), b"--boundary\r\nC")
self.assertEqual(request.POST, {"name": ["value"]})
self.assertEqual(request.data, {"name": ["value"]})
def test_POST_immutable_for_multipart(self):
"""
@ -1115,6 +1146,7 @@ class RequestsTests(SimpleTestCase):
}
)
self.assertFalse(request.POST._mutable)
self.assertFalse(request.data._mutable)
def test_multipart_without_boundary(self):
request = WSGIRequest(
@ -1129,6 +1161,10 @@ class RequestsTests(SimpleTestCase):
MultiPartParserError, "Invalid boundary in multipart: None"
):
request.POST
with self.assertRaisesMessage(
MultiPartParserError, "Invalid boundary in multipart: None"
):
request.data
def test_multipart_non_ascii_content_type(self):
request = WSGIRequest(
@ -1145,6 +1181,8 @@ class RequestsTests(SimpleTestCase):
)
with self.assertRaisesMessage(MultiPartParserError, msg):
request.POST
with self.assertRaisesMessage(MultiPartParserError, msg):
request.data
def test_multipart_with_header_fields_too_large(self):
payload = FakePayload(