Skip to content

Commit b6061d4

Browse files
committed
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 aio-libs#903
1 parent f180199 commit b6061d4

File tree

3 files changed

+21
-4
lines changed

3 files changed

+21
-4
lines changed

aiohttp/helpers.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,12 @@ class FormData:
8585
"""Helper class for multipart/form-data and
8686
application/x-www-form-urlencoded body generation."""
8787

88-
def __init__(self, fields=()):
88+
def __init__(self, fields=(), quote_fields=True):
8989
from . import multipart
9090
self._writer = multipart.MultipartWriter('form-data')
9191
self._fields = []
9292
self._is_multipart = False
93+
self._quote_fields = quote_fields
9394

9495
if isinstance(fields, dict):
9596
fields = list(fields.items())
@@ -181,7 +182,9 @@ def _gen_form_data(self, *args, **kwargs):
181182
for dispparams, headers, value in self._fields:
182183
part = self._writer.append(value, headers)
183184
if dispparams:
184-
part.set_content_disposition('form-data', **dispparams)
185+
part.set_content_disposition(
186+
'form-data', quote_fields=self._quote_fields, **dispparams
187+
)
185188
# FIXME cgi.FieldStorage doesn't likes body parts with
186189
# Content-Length which were sent via chunked transfer encoding
187190
part.headers.pop(hdrs.CONTENT_LENGTH, None)

aiohttp/multipart.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -851,7 +851,7 @@ def _apply_content_transfer_encoding(self, stream):
851851
raise RuntimeError('unknown content transfer encoding: {}'
852852
''.format(encoding))
853853

854-
def set_content_disposition(self, disptype, **params):
854+
def set_content_disposition(self, disptype, quote_fields=True, **params):
855855
"""Sets ``Content-Disposition`` header.
856856
857857
:param str disptype: Disposition type: inline, attachment, form-data.
@@ -868,7 +868,7 @@ def set_content_disposition(self, disptype, **params):
868868
if not key or not (TOKEN > set(key)):
869869
raise ValueError('bad content disposition parameter'
870870
' {!r}={!r}'.format(key, val))
871-
qval = quote(val, '')
871+
qval = quote(val, '') if quote_fields else val
872872
lparams.append((key, '"%s"' % qval))
873873
if key == 'filename':
874874
lparams.append(('filename*', "utf-8''" + qval))

tests/test_helpers.py

+14
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,20 @@ def test_invalid_formdata_content_transfer_encoding():
131131
content_transfer_encoding=invalid_val)
132132

133133

134+
def test_formdata_field_name_is_quoted():
135+
form = helpers.FormData()
136+
form.add_field("emails[]", "[email protected]", content_type="multipart/form-data")
137+
res = b"".join(form("ascii"))
138+
assert b'name="emails%5B%5D"' in res
139+
140+
141+
def test_formdata_field_name_is_not_quoted():
142+
form = helpers.FormData(quote_fields=False)
143+
form.add_field("emails[]", "[email protected]", content_type="multipart/form-data")
144+
res = b"".join(form("ascii"))
145+
assert b'name="emails[]"' in res
146+
147+
134148
def test_access_logger_format():
135149
log_format = '%T {%{SPAM}e} "%{ETag}o" %X {X} %%P'
136150
mock_logger = mock.Mock()

0 commit comments

Comments
 (0)