From 6af24ca574b203447fa25bfaac4aa5384364eb12 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sun, 22 May 2022 22:00:28 +0200 Subject: [PATCH] Raise `MultiPartException` when missing "name" field on `Content-Disposition` header --- setup.cfg | 1 + starlette/formparsers.py | 8 +++++++- tests/test_formparsers.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 734fa818c..8dad329c7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,7 @@ filterwarnings= error ignore: run_until_first_complete is deprecated and will be removed in a future version.:DeprecationWarning ignore: starlette\.middleware\.wsgi is deprecated and will be removed in a future release\.*:DeprecationWarning + ignore: Async generator 'starlette\.requests\.Request\.stream' was garbage collected before it had been exhausted.*:ResourceWarning [coverage:run] source_pkgs = starlette, tests diff --git a/starlette/formparsers.py b/starlette/formparsers.py index 4cde71b67..53538c814 100644 --- a/starlette/formparsers.py +++ b/starlette/formparsers.py @@ -220,7 +220,13 @@ async def parse(self) -> FormData: header_value = b"" elif message_type == MultiPartMessage.HEADERS_FINISHED: disposition, options = parse_options_header(content_disposition) - field_name = _user_safe_decode(options[b"name"], charset) + try: + field_name = _user_safe_decode(options[b"name"], charset) + except KeyError: + raise MultiPartException( + 'The Content-Disposition header field "name" must be ' + "provided." + ) if b"filename" in options: filename = _user_safe_decode(options[b"filename"], charset) file = UploadFile( diff --git a/tests/test_formparsers.py b/tests/test_formparsers.py index 671059529..7418595cf 100644 --- a/tests/test_formparsers.py +++ b/tests/test_formparsers.py @@ -418,3 +418,35 @@ def test_missing_boundary_parameter( ) assert res.status_code == 400 assert res.text == "Missing boundary in multipart." + + +@pytest.mark.parametrize( + "app,expectation", + [ + (app, pytest.raises(MultiPartException)), + (Starlette(routes=[Mount("/", app=app)]), does_not_raise()), + ], +) +def test_missing_name_parameter_on_content_disposition( + app, expectation, test_client_factory: typing.Callable[..., TestClient] +): + client = test_client_factory(app) + with expectation: + res = client.post( + "/", + data=( + # data + b"--a7f7ac8d4e2e437c877bb7b8d7cc549c\r\n" + b'Content-Disposition: form-data; ="field0"\r\n\r\n' + b"value0\r\n" + ), + headers={ + "Content-Type": ( + "multipart/form-data; boundary=a7f7ac8d4e2e437c877bb7b8d7cc549c" + ) + }, + ) + assert res.status_code == 400 + assert ( + res.text == 'The Content-Disposition header field "name" must be provided.' + )