Skip to content

Commit

Permalink
Merge pull request tornadoweb#2411 from bdarnell/multipart-filename
Browse files Browse the repository at this point in the history
httputil: Support non-ascii filenames in multipart uploads
  • Loading branch information
bdarnell committed Jun 3, 2018
2 parents 2a420ea + e9785a3 commit 2595586
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 9 deletions.
31 changes: 22 additions & 9 deletions tornado/httputil.py
Expand Up @@ -891,7 +891,8 @@ def parse_response_start_line(line):
# The original 2.7 version of this code did not correctly support some
# combinations of semicolons and double quotes.
# It has also been modified to support valueless parameters as seen in
# websocket extension negotiations.
# websocket extension negotiations, and to support non-ascii values in
# RFC 2231/5987 format.


def _parseparam(s):
Expand All @@ -908,25 +909,37 @@ def _parseparam(s):


def _parse_header(line):
"""Parse a Content-type like header.
r"""Parse a Content-type like header.
Return the main content-type and a dictionary of options.
>>> d = "form-data; foo=\"b\\\\a\\\"r\"; file*=utf-8''T%C3%A4st"
>>> ct, d = _parse_header(d)
>>> ct
'form-data'
>>> d['file'] == r'T\u00e4st'.encode('ascii').decode('unicode_escape')
True
>>> d['foo']
'b\\a"r'
"""
parts = _parseparam(';' + line)
key = next(parts)
pdict = {}
# decode_params treats first argument special, but we already stripped key
params = [('Dummy', 'value')]
for p in parts:
i = p.find('=')
if i >= 0:
name = p[:i].strip().lower()
value = p[i + 1:].strip()
if len(value) >= 2 and value[0] == value[-1] == '"':
value = value[1:-1]
value = value.replace('\\\\', '\\').replace('\\"', '"')
pdict[name] = value
else:
pdict[p] = None
params.append((name, native_str(value)))
params = email.utils.decode_params(params)
params.pop(0) # get rid of the dummy again
pdict = {}
for name, value in params:
value = email.utils.collapse_rfc2231_value(value)
if len(value) >= 2 and value[0] == '"' and value[-1] == '"':
value = value[1:-1]
pdict[name] = value
return key, pdict


Expand Down
14 changes: 14 additions & 0 deletions tornado/test/httputil_test.py
Expand Up @@ -176,6 +176,20 @@ def test_special_filenames(self):
self.assertEqual(file["filename"], filename)
self.assertEqual(file["body"], b"Foo")

def test_non_ascii_filename(self):
data = b"""\
--1234
Content-Disposition: form-data; name="files"; filename="ab.txt"; filename*=UTF-8''%C3%A1b.txt
Foo
--1234--""".replace(b"\n", b"\r\n")
args = {}
files = {}
parse_multipart_form_data(b"1234", data, args, files)
file = files["files"][0]
self.assertEqual(file["filename"], u"áb.txt")
self.assertEqual(file["body"], b"Foo")

def test_boundary_starts_and_ends_with_quotes(self):
data = b'''\
--1234
Expand Down

0 comments on commit 2595586

Please sign in to comment.