diff --git a/CHANGES/12996.bugfix.rst b/CHANGES/12996.bugfix.rst new file mode 100644 index 00000000000..c43d973fb77 --- /dev/null +++ b/CHANGES/12996.bugfix.rst @@ -0,0 +1,5 @@ +Fixed ``parse_content_disposition`` rejecting otherwise-valid +``Content-Disposition`` header values that contain optional whitespace (OWS) +around the disposition type (e.g. ``"form-data ; name=\"field\""``). +The disposition type is now stripped before token validation, consistent with +how parameter keys are already handled -- by :user:`JSap0914`. diff --git a/aiohttp/multipart.py b/aiohttp/multipart.py index 7e63e324779..190f2e2b4cc 100644 --- a/aiohttp/multipart.py +++ b/aiohttp/multipart.py @@ -102,7 +102,9 @@ def unescape(text: str, *, chars: str = "".join(map(re.escape, CHAR))) -> str: if not header: return None, {} + # https://www.rfc-editor.org/info/rfc9110/#section-5.6.6-2 disptype, *parts = header.split(";") + disptype = disptype.strip() if not is_token(disptype): warnings.warn(BadContentDispositionHeader(header)) return None, {} diff --git a/tests/test_multipart_helpers.py b/tests/test_multipart_helpers.py index 3548feacd40..67bbc86c223 100644 --- a/tests/test_multipart_helpers.py +++ b/tests/test_multipart_helpers.py @@ -659,6 +659,16 @@ def test_empty_param_value_multiple(self) -> None: assert disptype is None assert {} == params + def test_disptype_with_trailing_space_before_semicolon(self) -> None: + disptype, params = parse_content_disposition('form-data ; name="field"') + assert disptype == "form-data" + assert params == {"name": "field"} + + def test_disptype_with_trailing_space_no_params(self) -> None: + disptype, params = parse_content_disposition("inline ") + assert disptype == "inline" + assert params == {} + class TestContentDispositionFilename: # http://greenbytes.de/tech/tc2231/