Skip to content

Commit

Permalink
Recognizes non-ASCII filenames in RFC 2231, and suport filename lengt…
Browse files Browse the repository at this point in the history
…h is zero for multipart/form-data. (#1497)

* suport filename length is 0

* 1. suport filename length is zero for multipart/form-data.
2. Now recognizes non-ASCII filenames in RFC 2231, "filename*" format
3. Add some test cases in tests/test_requests.py::test_request_multipart_files.

* reformat sanic/request.py
  • Loading branch information
PWZER authored and sjsadowski committed Feb 28, 2019
1 parent 52deeba commit 8a59907
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 22 deletions.
26 changes: 17 additions & 9 deletions sanic/request.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import asyncio
import email.utils
import json
import sys

from cgi import parse_header
from collections import namedtuple
from http.cookies import SimpleCookie
from urllib.parse import parse_qs, urlunparse
from urllib.parse import parse_qs, unquote, urlunparse

from httptools import parse_url

Expand Down Expand Up @@ -356,28 +357,35 @@ def parse_multipart_form(body, boundary):
)

if form_header_field == "content-disposition":
file_name = form_parameters.get("filename")
field_name = form_parameters.get("name")
file_name = form_parameters.get("filename")

# non-ASCII filenames in RFC2231, "filename*" format
if file_name is None and form_parameters.get("filename*"):
encoding, _, value = email.utils.decode_rfc2231(
form_parameters["filename*"]
)
file_name = unquote(value, encoding=encoding)
elif form_header_field == "content-type":
content_type = form_header_value
content_charset = form_parameters.get("charset", "utf-8")

if field_name:
post_data = form_part[line_index:-4]
if file_name:
if file_name is None:
value = post_data.decode(content_charset)
if field_name in fields:
fields[field_name].append(value)
else:
fields[field_name] = [value]
else:
form_file = File(
type=content_type, name=file_name, body=post_data
)
if field_name in files:
files[field_name].append(form_file)
else:
files[field_name] = [form_file]
else:
value = post_data.decode(content_charset)
if field_name in fields:
fields[field_name].append(value)
else:
fields[field_name] = [value]
else:
logger.debug(
"Form-data field does not have a 'name' parameter "
Expand Down
46 changes: 33 additions & 13 deletions tests/test_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,29 +432,49 @@ async def get(request):


@pytest.mark.parametrize(
"payload",
"payload,filename",
[
"------sanic\r\n"
'Content-Disposition: form-data; filename="filename"; name="test"\r\n'
"\r\n"
"OK\r\n"
"------sanic--\r\n",
"------sanic\r\n"
'content-disposition: form-data; filename="filename"; name="test"\r\n'
"\r\n"
'content-type: application/json; {"field": "value"}\r\n'
"------sanic--\r\n",
("------sanic\r\n"
'Content-Disposition: form-data; filename="filename"; name="test"\r\n'
"\r\n"
"OK\r\n"
"------sanic--\r\n", "filename"),
("------sanic\r\n"
'content-disposition: form-data; filename="filename"; name="test"\r\n'
"\r\n"
'content-type: application/json; {"field": "value"}\r\n'
"------sanic--\r\n", "filename"),
("------sanic\r\n"
'Content-Disposition: form-data; filename=""; name="test"\r\n'
"\r\n"
"OK\r\n"
"------sanic--\r\n", ""),
("------sanic\r\n"
'content-disposition: form-data; filename=""; name="test"\r\n'
"\r\n"
'content-type: application/json; {"field": "value"}\r\n'
"------sanic--\r\n", ""),
("------sanic\r\n"
'Content-Disposition: form-data; filename*="utf-8\'\'filename_%C2%A0_test"; name="test"\r\n'
"\r\n"
"OK\r\n"
"------sanic--\r\n", "filename_\u00A0_test"),
("------sanic\r\n"
'content-disposition: form-data; filename*="utf-8\'\'filename_%C2%A0_test"; name="test"\r\n'
"\r\n"
'content-type: application/json; {"field": "value"}\r\n'
"------sanic--\r\n", "filename_\u00A0_test"),
],
)
def test_request_multipart_files(app, payload):
def test_request_multipart_files(app, payload, filename):
@app.route("/", methods=["POST"])
async def post(request):
return text("OK")

headers = {"content-type": "multipart/form-data; boundary=----sanic"}

request, _ = app.test_client.post(data=payload, headers=headers)
assert request.files.get("test").name == "filename"
assert request.files.get("test").name == filename


def test_request_multipart_file_with_json_content_type(app):
Expand Down

0 comments on commit 8a59907

Please sign in to comment.