From 05cea5360a5156e2fb2399e2a97702ea5eb29021 Mon Sep 17 00:00:00 2001 From: Alexander Shorin Date: Wed, 14 Sep 2016 15:14:27 +0300 Subject: [PATCH 1/2] Support Content-Transfer-Encoding binary This fixes #1168 --- aiohttp/multipart.py | 6 +++++- tests/test_multipart.py | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/aiohttp/multipart.py b/aiohttp/multipart.py index 6c3a8a1a1ec..093c85603a7 100644 --- a/aiohttp/multipart.py +++ b/aiohttp/multipart.py @@ -443,7 +443,7 @@ def decode(self, data): Supports ``gzip``, ``deflate`` and ``identity`` encodings for `Content-Encoding` header. - Supports ``base64``, ``quoted-printable`` encodings for + Supports ``base64``, ``quoted-printable``, ``binary`` encodings for `Content-Transfer-Encoding` header. :param bytearray data: Data to decode. @@ -477,6 +477,8 @@ def _decode_content_transfer(self, data): return base64.b64decode(data) elif encoding == 'quoted-printable': return binascii.a2b_qp(data) + elif encoding == 'binary': + return data else: raise RuntimeError('unknown content transfer encoding: {}' ''.format(encoding)) @@ -854,6 +856,8 @@ def _apply_content_transfer_encoding(self, stream): elif encoding == 'quoted-printable': for chunk in stream: yield binascii.b2a_qp(chunk) + elif encoding == 'binary': + yield from stream else: raise RuntimeError('unknown content transfer encoding: {}' ''.format(encoding)) diff --git a/tests/test_multipart.py b/tests/test_multipart.py index 9f067769913..37397c2e887 100644 --- a/tests/test_multipart.py +++ b/tests/test_multipart.py @@ -339,6 +339,15 @@ def test_read_with_content_transfer_encoding_quoted_printable(self): self.assertEqual(b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82,' b' \xd0\xbc\xd0\xb8\xd1\x80!', result) + def test_read_with_content_transfer_encoding_binary(self): + obj = aiohttp.multipart.BodyPartReader( + self.boundary, {CONTENT_TRANSFER_ENCODING: 'binary'}, + Stream(b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82,' + b' \xd0\xbc\xd0\xb8\xd1\x80!\r\n--:--')) + result = yield from obj.read(decode=True) + self.assertEqual(b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82,' + b' \xd0\xbc\xd0\xb8\xd1\x80!', result) + def test_read_with_content_transfer_encoding_unknown(self): obj = aiohttp.multipart.BodyPartReader( self.boundary, {CONTENT_TRANSFER_ENCODING: 'unknown'}, @@ -941,6 +950,21 @@ def test_serialize_with_content_transfer_encoding_quote_printable(self): self.assertEqual(b'\r\n', next(stream)) self.assertIsNone(next(stream, None)) + def test_serialize_with_content_transfer_encoding_binary(self): + part = aiohttp.multipart.BodyPartWriter( + 'Привет, мир!'.encode('utf-8'), + {CONTENT_TRANSFER_ENCODING: 'binary'}) + stream = part.serialize() + self.assertEqual(b'Content-Transfer-Encoding: binary\r\n' + b'Content-Type: application/octet-stream', + next(stream)) + self.assertEqual(b'\r\n\r\n', next(stream)) + + self.assertEqual(b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82,' + b' \xd0\xbc\xd0\xb8\xd1\x80!', next(stream)) + self.assertEqual(b'\r\n', next(stream)) + self.assertIsNone(next(stream, None)) + def test_serialize_with_content_transfer_encoding_unknown(self): part = aiohttp.multipart.BodyPartWriter( 'Time to Relax!', {CONTENT_TRANSFER_ENCODING: 'unknown'}) From 8dc33539c012acae97489d4957d580e75030246f Mon Sep 17 00:00:00 2001 From: Alexander Shorin Date: Fri, 16 Sep 2016 07:05:18 +0300 Subject: [PATCH 2/2] Add additional test following the feedback --- tests/test_web_functional.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_web_functional.py b/tests/test_web_functional.py index 48ceabdea52..bdd98ae677c 100644 --- a/tests/test_web_functional.py +++ b/tests/test_web_functional.py @@ -178,6 +178,38 @@ def handler(request): yield from resp.release() +@asyncio.coroutine +def test_multipart_content_transfer_encoding(loop, test_client): + """For issue #1168""" + with multipart.MultipartWriter() as writer: + writer.append(b'\x00' * 10, + headers={'Content-Transfer-Encoding': 'binary'}) + + @asyncio.coroutine + def handler(request): + reader = yield from request.multipart() + assert isinstance(reader, multipart.MultipartReader) + + part = yield from reader.next() + assert isinstance(part, multipart.BodyPartReader) + assert part.headers['Content-Transfer-Encoding'] == 'binary' + thing = yield from part.read() + assert thing == b'\x00' * 10 + + resp = web.Response() + resp.content_type = 'application/json' + resp.body = b'' + return resp + + app = web.Application(loop=loop) + app.router.add_post('/', handler) + client = yield from test_client(app) + + resp = yield from client.post('/', data=writer, headers=writer.headers) + assert 200 == resp.status + yield from resp.release() + + @asyncio.coroutine def test_render_redirect(loop, test_client):