diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index c018dba6177..f9f99c7d09f 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -75,7 +75,7 @@ jobs: with: python-version: 3.11 - name: Cache PyPI - uses: actions/cache@v6.1.0 + uses: actions/cache@v5.0.5 with: key: pip-lint-${{ hashFiles('requirements/*.txt') }} path: ~/.cache/pip @@ -158,7 +158,7 @@ jobs: with: submodules: true - name: Cache llhttp generated files - uses: actions/cache@v6.1.0 + uses: actions/cache@v5.0.5 id: cache with: key: llhttp-${{ hashFiles('vendor/llhttp/package*.json', 'vendor/llhttp/src/**/*') }} diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py index 1a45a060ff1..b89d64312c0 100644 --- a/aiohttp/helpers.py +++ b/aiohttp/helpers.py @@ -334,8 +334,21 @@ def parse_mimetype(mimetype: str) -> MimeType: for item in parts[1:]: if not item: continue - key, _, value = item.partition("=") - params.add(key.lower().strip(), value.strip(' "')) + # Per RFC 9110 section 5.6.5 a parameter is `token "=" token` (or quoted-string) + # and both sides must be non-empty. A bare `;` (already skipped by the + # empty check above) or a `;=value` / `key=` token (empty key) is not a + # valid parameter. Skip it instead of producing `{"": "value"}` which + # downstream callers like StringPayload read with `.get("charset")` and + # silently get an empty key back, defaulting to utf-8 without warning. + key, sep, value = item.partition("=") + if not sep: + # `text/html; charset` is also malformed (no '='). Skip the segment + # rather than treating the whole token as a key with empty value. + continue + key = key.strip() + if not key: + continue + params.add(key.lower(), value.strip(' "')) fulltype = parts[0].strip().lower() if fulltype == "*": diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 4d6b9e34e1d..a3059bbedb5 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -69,10 +69,8 @@ helpers.MimeType("application", "rss", "xml", MultiDictProxy(MultiDict())), ), ( - "text/plain;base64", - helpers.MimeType( - "text", "plain", "", MultiDictProxy(MultiDict({"base64": ""})) - ), + "text/plain;", + helpers.MimeType("text", "plain", "", MultiDictProxy(MultiDict())), ), ], ) @@ -83,6 +81,14 @@ def test_parse_mimetype(mimetype: str, expected: helpers.MimeType) -> None: assert result == expected +def test_parse_mimetype_skips_empty_param_key_and_missing_equals() -> None: + # Trailing `;` should not insert a `''` key into the params dict + # and the real parameter before it should still parse. + result = helpers.parse_mimetype("application/json; charset=utf-8;") + assert "" not in result.parameters + assert result.parameters.get("charset") == "utf-8" + + # ------------------- parse_content_type ------------------------------