Skip to content

Commit

Permalink
Add optional flag for quoting fields
Browse files Browse the repository at this point in the history
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 aio-libs#903
  • Loading branch information
lukasz-madon committed Jun 6, 2016
1 parent f180199 commit b6061d4
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 4 deletions.
7 changes: 5 additions & 2 deletions aiohttp/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions aiohttp/multipart.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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))
Expand Down
14 changes: 14 additions & 0 deletions tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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[]", "[email protected]", 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[]", "[email protected]", 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()
Expand Down

0 comments on commit b6061d4

Please sign in to comment.