From b6061d44ea209e14bd8779e686f7f1695ba6b344 Mon Sep 17 00:00:00 2001 From: Lukasz Madon Date: Mon, 6 Jun 2016 16:23:11 +0200 Subject: [PATCH] Add optional flag for quoting fields RFC stanard requires all fields to be US-ASCII. Additional `quote` may provide extra security (RFC-1806 p. 2.3), but does not allow for interoperability with API that requires ASCII characters outside `quote` set e.g. emails[] becomes emails%5B%5D fixes #903 --- aiohttp/helpers.py | 7 +++++-- aiohttp/multipart.py | 4 ++-- tests/test_helpers.py | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py index 4f3f5c4cf0e..173c9c89342 100644 --- a/aiohttp/helpers.py +++ b/aiohttp/helpers.py @@ -85,11 +85,12 @@ class FormData: """Helper class for multipart/form-data and application/x-www-form-urlencoded body generation.""" - def __init__(self, fields=()): + def __init__(self, fields=(), quote_fields=True): from . import multipart self._writer = multipart.MultipartWriter('form-data') self._fields = [] self._is_multipart = False + self._quote_fields = quote_fields if isinstance(fields, dict): fields = list(fields.items()) @@ -181,7 +182,9 @@ def _gen_form_data(self, *args, **kwargs): for dispparams, headers, value in self._fields: part = self._writer.append(value, headers) if dispparams: - part.set_content_disposition('form-data', **dispparams) + part.set_content_disposition( + 'form-data', quote_fields=self._quote_fields, **dispparams + ) # FIXME cgi.FieldStorage doesn't likes body parts with # Content-Length which were sent via chunked transfer encoding part.headers.pop(hdrs.CONTENT_LENGTH, None) diff --git a/aiohttp/multipart.py b/aiohttp/multipart.py index 45dff953244..1b2f04e0af9 100644 --- a/aiohttp/multipart.py +++ b/aiohttp/multipart.py @@ -851,7 +851,7 @@ def _apply_content_transfer_encoding(self, stream): raise RuntimeError('unknown content transfer encoding: {}' ''.format(encoding)) - def set_content_disposition(self, disptype, **params): + def set_content_disposition(self, disptype, quote_fields=True, **params): """Sets ``Content-Disposition`` header. :param str disptype: Disposition type: inline, attachment, form-data. @@ -868,7 +868,7 @@ def set_content_disposition(self, disptype, **params): if not key or not (TOKEN > set(key)): raise ValueError('bad content disposition parameter' ' {!r}={!r}'.format(key, val)) - qval = quote(val, '') + qval = quote(val, '') if quote_fields else val lparams.append((key, '"%s"' % qval)) if key == 'filename': lparams.append(('filename*', "utf-8''" + qval)) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index a90fe76a2aa..02e84a503e0 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -131,6 +131,20 @@ def test_invalid_formdata_content_transfer_encoding(): content_transfer_encoding=invalid_val) +def test_formdata_field_name_is_quoted(): + form = helpers.FormData() + form.add_field("emails[]", "xxx@x.co", content_type="multipart/form-data") + res = b"".join(form("ascii")) + assert b'name="emails%5B%5D"' in res + + +def test_formdata_field_name_is_not_quoted(): + form = helpers.FormData(quote_fields=False) + form.add_field("emails[]", "xxx@x.co", content_type="multipart/form-data") + res = b"".join(form("ascii")) + assert b'name="emails[]"' in res + + def test_access_logger_format(): log_format = '%T {%{SPAM}e} "%{ETag}o" %X {X} %%P' mock_logger = mock.Mock()