From 852c081068046240174e17ef0876046f89bbefc3 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Wed, 24 Mar 2021 21:35:39 +0000 Subject: [PATCH 1/2] deserialize data form media type --- .../deserializing/media_types/factories.py | 7 +++- .../deserializing/media_types/util.py | 17 +++++++- .../test_media_types_deserializers.py | 42 ++++++++++++++++++- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/openapi_core/deserializing/media_types/factories.py b/openapi_core/deserializing/media_types/factories.py index b174bef..f44a5c0 100644 --- a/openapi_core/deserializing/media_types/factories.py +++ b/openapi_core/deserializing/media_types/factories.py @@ -1,4 +1,6 @@ -from openapi_core.deserializing.media_types.util import json_loads, form_loads +from openapi_core.deserializing.media_types.util import ( + json_loads, urlencoded_form_loads, data_form_loads, +) from openapi_core.deserializing.media_types.deserializers import ( PrimitiveDeserializer, @@ -9,7 +11,8 @@ class MediaTypeDeserializersFactory(object): MEDIA_TYPE_DESERIALIZERS = { 'application/json': json_loads, - 'application/x-www-form-urlencoded': form_loads, + 'application/x-www-form-urlencoded': urlencoded_form_loads, + 'multipart/form-data': data_form_loads, } def __init__(self, custom_deserializers=None): diff --git a/openapi_core/deserializing/media_types/util.py b/openapi_core/deserializing/media_types/util.py index bc5a88f..e4d3100 100644 --- a/openapi_core/deserializing/media_types/util.py +++ b/openapi_core/deserializing/media_types/util.py @@ -1,3 +1,4 @@ +from email.parser import BytesParser from json import loads from six import binary_type @@ -11,5 +12,19 @@ def json_loads(value): return loads(value) -def form_loads(value): +def urlencoded_form_loads(value): return dict(parse_qsl(value)) + + +def data_form_loads(value): + if issubclass(type(value), str): + value = value.encode() + parser = BytesParser() + parts = parser.parsebytes(value) + return dict( + ( + part.get_param('name', header='content-disposition'), + part.get_payload(decode=True), + ) + for part in parts.get_payload() + ) diff --git a/tests/unit/deserializing/test_media_types_deserializers.py b/tests/unit/deserializing/test_media_types_deserializers.py index 435feb4..683a3b4 100644 --- a/tests/unit/deserializing/test_media_types_deserializers.py +++ b/tests/unit/deserializing/test_media_types_deserializers.py @@ -1,3 +1,6 @@ +from email.mime.multipart import MIMEMultipart +from email.mime.nonmultipart import MIMENonMultipart + import pytest from openapi_core.deserializing.exceptions import DeserializeError @@ -7,6 +10,24 @@ from openapi_core.deserializing.media_types.factories import ( from openapi_core.schema.media_types.models import MediaType +class MIMEFormdata(MIMENonMultipart): + def __init__(self, keyname, *args, **kwargs): + super(MIMEFormdata, self).__init__(*args, **kwargs) + self.add_header( + "Content-Disposition", "form-data; name=\"%s\"" % keyname) + + +def encode_multipart_formdata(fields): + m = MIMEMultipart("form-data") + + for field, value in fields.items(): + data = MIMEFormdata(field, "text", "plain") + data.set_payload(value) + m.attach(data) + + return m + + class TestMediaTypeDeserializer(object): @pytest.fixture @@ -31,7 +52,7 @@ class TestMediaTypeDeserializer(object): assert result == {} - def test_form_urlencoded_empty(self, deserializer_factory): + def test_urlencoded_form_empty(self, deserializer_factory): media_type = MediaType('application/x-www-form-urlencoded') value = '' @@ -39,7 +60,7 @@ class TestMediaTypeDeserializer(object): assert result == {} - def test_form_urlencoded_simple(self, deserializer_factory): + def test_urlencoded_form_simple(self, deserializer_factory): media_type = MediaType('application/x-www-form-urlencoded') value = 'param1=test' @@ -47,6 +68,23 @@ class TestMediaTypeDeserializer(object): assert result == {'param1': 'test'} + @pytest.mark.parametrize('value', [b'', '']) + def test_data_form_empty(self, deserializer_factory, value): + media_type = MediaType('multipart/form-data') + + result = deserializer_factory(media_type)(value) + + assert result == {} + + def test_data_form_simple(self, deserializer_factory): + media_type = MediaType('multipart/form-data') + formdata = encode_multipart_formdata({'param1': 'test'}) + value = str(formdata) + + result = deserializer_factory(media_type)(value) + + assert result == {'param1': b'test'} + def test_custom_simple(self, deserializer_factory): custom_mimetype = 'application/custom' media_type = MediaType(custom_mimetype) From 5f62c6293730c5e1c59b335fa2a2e5861cced7f0 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Wed, 24 Mar 2021 21:55:09 +0000 Subject: [PATCH 2/2] deserialize data form media type py27 --- .../deserializing/media_types/util.py | 10 ++--- .../test_media_types_deserializers.py | 38 +++++++------------ 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/openapi_core/deserializing/media_types/util.py b/openapi_core/deserializing/media_types/util.py index e4d3100..f1e6762 100644 --- a/openapi_core/deserializing/media_types/util.py +++ b/openapi_core/deserializing/media_types/util.py @@ -1,4 +1,4 @@ -from email.parser import BytesParser +from email.parser import Parser from json import loads from six import binary_type @@ -17,10 +17,10 @@ def urlencoded_form_loads(value): def data_form_loads(value): - if issubclass(type(value), str): - value = value.encode() - parser = BytesParser() - parts = parser.parsebytes(value) + if issubclass(type(value), binary_type): + value = value.decode('ASCII', errors='surrogateescape') + parser = Parser() + parts = parser.parsestr(value, headersonly=False) return dict( ( part.get_param('name', header='content-disposition'), diff --git a/tests/unit/deserializing/test_media_types_deserializers.py b/tests/unit/deserializing/test_media_types_deserializers.py index 683a3b4..40ea100 100644 --- a/tests/unit/deserializing/test_media_types_deserializers.py +++ b/tests/unit/deserializing/test_media_types_deserializers.py @@ -1,8 +1,7 @@ -from email.mime.multipart import MIMEMultipart -from email.mime.nonmultipart import MIMENonMultipart - import pytest +from six import b, u + from openapi_core.deserializing.exceptions import DeserializeError from openapi_core.deserializing.media_types.factories import ( MediaTypeDeserializersFactory, @@ -10,24 +9,6 @@ from openapi_core.deserializing.media_types.factories import ( from openapi_core.schema.media_types.models import MediaType -class MIMEFormdata(MIMENonMultipart): - def __init__(self, keyname, *args, **kwargs): - super(MIMEFormdata, self).__init__(*args, **kwargs) - self.add_header( - "Content-Disposition", "form-data; name=\"%s\"" % keyname) - - -def encode_multipart_formdata(fields): - m = MIMEMultipart("form-data") - - for field, value in fields.items(): - data = MIMEFormdata(field, "text", "plain") - data.set_payload(value) - m.attach(data) - - return m - - class TestMediaTypeDeserializer(object): @pytest.fixture @@ -68,7 +49,7 @@ class TestMediaTypeDeserializer(object): assert result == {'param1': 'test'} - @pytest.mark.parametrize('value', [b'', '']) + @pytest.mark.parametrize('value', [b(''), u('')]) def test_data_form_empty(self, deserializer_factory, value): media_type = MediaType('multipart/form-data') @@ -78,12 +59,19 @@ class TestMediaTypeDeserializer(object): def test_data_form_simple(self, deserializer_factory): media_type = MediaType('multipart/form-data') - formdata = encode_multipart_formdata({'param1': 'test'}) - value = str(formdata) + value = b( + 'Content-Type: multipart/form-data; boundary="' + '===============2872712225071193122=="\n' + 'MIME-Version: 1.0\n\n' + '--===============2872712225071193122==\n' + 'Content-Type: text/plain\nMIME-Version: 1.0\n' + 'Content-Disposition: form-data; name="param1"\n\ntest\n' + '--===============2872712225071193122==--\n' + ) result = deserializer_factory(media_type)(value) - assert result == {'param1': b'test'} + assert result == {'param1': b('test')} def test_custom_simple(self, deserializer_factory): custom_mimetype = 'application/custom'