diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 99014a20b50..ebb9ede885c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -121,7 +121,7 @@ repos: exclude: >- ^CHANGES\.rst$ - repo: https://github.com/codespell-project/codespell - rev: v2.4.2 + rev: v2.4.3 hooks: - id: codespell additional_dependencies: diff --git a/CHANGES.rst b/CHANGES.rst index f54a948c5dd..8318be40b40 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,239 @@ .. towncrier release notes start +3.14.2 (2026-07-20) +=================== + +Bug fixes +--------- + +- Fixed :py:attr:`~aiohttp.web.StreamResponse.last_modified` rounding a + :class:`datetime.datetime` with a fractional second down. + + + *Related issues and pull requests on GitHub:* + :issue:`5303`. + + + +- Fixed resolving ``localhost`` on Windows to fall back without ``AI_ADDRCONFIG`` + when the first lookup fails, so ``localhost`` still works without an active + network. + + + *Related issues and pull requests on GitHub:* + :issue:`5357`. + + + +- Rejected multipart body parts whose ``Content-Length`` header is not a + plain sequence of digits (e.g. ``+5``, ``-1``, ``1_0``), matching the + strictness of the main request parser per :rfc:`9110#section-8.6` + -- by :user:`dxbjavid`. + + + *Related issues and pull requests on GitHub:* + :issue:`12794`. + + + +- Fixed ``GunicornWebWorker`` endlessly reloading when app fails during startup -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`12879`. + + + +- Fixed some inconsistent case sensitivity on request methods -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`12931`. + + + +- Fixed ``IndexError: string index out of range`` in ``parse_content_disposition`` + when a header parameter has an empty value (e.g. ``filename=``). + -- by :user:`JSap0914`. + + + *Related issues and pull requests on GitHub:* + :issue:`12948`. + + + +- Fixed the ``sock_read`` timeout being re-armed on a keep-alive connection after + it had been returned to the pool. An idle pooled connection could be left with a + pending read timeout that fired and poisoned it, so the next request reusing the + connection failed immediately with :exc:`aiohttp.SocketTimeoutError`. The read + timeout is now only rescheduled when resuming a transport that was actually + paused -- by :user:`daragok`. + + + *Related issues and pull requests on GitHub:* + :issue:`12953`, :issue:`12954`. + + + +- Fixed the client decompressing frames when ``permessage-deflate`` was not negotiated -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`12976`. + + + +- Fixed ``DigestAuthMiddleware`` raising an ``IndexError`` on empty domain -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`12983`. + + + +- Fixed :class:`~aiohttp.DigestAuthMiddleware` corrupting the ``Digest`` + challenge when a ``WWW-Authenticate`` response offered more than one + authentication scheme -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`12984`. + + + +- Fixed client not closing cleanly after an exception -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`12985`. + + + +- Fixed control frames breaking fragmented WebSocket messages -- by :user:`arshsmith1`. + + + *Related issues and pull requests on GitHub:* + :issue:`12988`. + + + +- 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`. + + + *Related issues and pull requests on GitHub:* + :issue:`12996`. + + + +- Fixed an :exc:`IndexError` in the pure-Python HTTP parser -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`13001`. + + + +- Fixed parsing optional whitespace in Content-Disposition -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`13002`. + + + +- Fixed request body not being read on rejected WebSocket upgrades -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`13016`. + + + +- Fixed :exc:`LookupError` (and an unguarded :exc:`UnicodeDecodeError`) escaping + ``Content-Disposition`` parsing when a multipart part supplies an extended + parameter with an unknown charset + -- by :user:`arshsmith1`. + + + *Related issues and pull requests on GitHub:* + :issue:`13042`. + + + +- Fixed ``escape_quotes`` in the Digest authentication middleware not escaping + backslashes, so a ``WWW-Authenticate`` challenge value containing a backslash + could break out of its quoted-string in the generated ``Authorization`` header + -- by :user:`dxbjavid`. + + + *Related issues and pull requests on GitHub:* + :issue:`13054`. + + + +- Fixed Python parser not rejecting a bare ``LF`` in the request line -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`13136`. + + + +- Fixed the C HTTP parser folding the fragment into the query string for an + origin-form request target with an empty query (e.g. ``/path?#frag``), + which diverged from the pure-Python parser -- by :user:`GiulioDER`. + + + *Related issues and pull requests on GitHub:* + :issue:`13171`. + + + +- Fixed the C parser reporting newer HTTP methods such as ``QUERY`` as ````; + the method table is now derived from the vendored llhttp instead of a hand-maintained count + -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`13174`. + + + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Upgraded ``llhttp`` to v9.4.2 -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`12956`. + + + + +Contributor-facing changes +-------------------------- + +- Added admin documentation on incident response and on running reproducer code + safely, covering security vulnerability handling and supply-chain, account, and + CI/infrastructure compromise -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`12914`. + + + + +---- + + 3.14.1 (2026-06-07) =================== diff --git a/CHANGES/12794.bugfix.rst b/CHANGES/12794.bugfix.rst deleted file mode 100644 index ec72efc1f3c..00000000000 --- a/CHANGES/12794.bugfix.rst +++ /dev/null @@ -1,4 +0,0 @@ -Rejected multipart body parts whose ``Content-Length`` header is not a -plain sequence of digits (e.g. ``+5``, ``-1``, ``1_0``), matching the -strictness of the main request parser per :rfc:`9110#section-8.6` --- by :user:`dxbjavid`. diff --git a/CHANGES/12879.bugfix.rst b/CHANGES/12879.bugfix.rst deleted file mode 100644 index 9dc8057a64d..00000000000 --- a/CHANGES/12879.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed ``GunicornWebWorker`` endlessly reloading when app fails during startup -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/12914.contrib.rst b/CHANGES/12914.contrib.rst deleted file mode 100644 index 5ef030c2786..00000000000 --- a/CHANGES/12914.contrib.rst +++ /dev/null @@ -1,3 +0,0 @@ -Added admin documentation on incident response and on running reproducer code -safely, covering security vulnerability handling and supply-chain, account, and -CI/infrastructure compromise -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/12931.bugfix.rst b/CHANGES/12931.bugfix.rst deleted file mode 100644 index 6b62e87dac7..00000000000 --- a/CHANGES/12931.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed some inconsistent case sensitivity on request methods -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/12948.bugfix.rst b/CHANGES/12948.bugfix.rst deleted file mode 100644 index 8cd00cfe0a6..00000000000 --- a/CHANGES/12948.bugfix.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fixed ``IndexError: string index out of range`` in ``parse_content_disposition`` -when a header parameter has an empty value (e.g. ``filename=``). --- by :user:`JSap0914`. diff --git a/CHANGES/12953.bugfix.rst b/CHANGES/12953.bugfix.rst deleted file mode 100644 index 20edc2dda87..00000000000 --- a/CHANGES/12953.bugfix.rst +++ /dev/null @@ -1,6 +0,0 @@ -Fixed the ``sock_read`` timeout being re-armed on a keep-alive connection after -it had been returned to the pool. An idle pooled connection could be left with a -pending read timeout that fired and poisoned it, so the next request reusing the -connection failed immediately with :exc:`aiohttp.SocketTimeoutError`. The read -timeout is now only rescheduled when resuming a transport that was actually -paused -- by :user:`daragok`. diff --git a/CHANGES/12954.bugfix.rst b/CHANGES/12954.bugfix.rst deleted file mode 120000 index baccfb94cc8..00000000000 --- a/CHANGES/12954.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -12953.bugfix.rst \ No newline at end of file diff --git a/CHANGES/12956.packaging.rst b/CHANGES/12956.packaging.rst deleted file mode 100644 index 7ea0cd11ac6..00000000000 --- a/CHANGES/12956.packaging.rst +++ /dev/null @@ -1 +0,0 @@ -Upgraded ``llhttp`` to v9.4.2 -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/12976.bugfix.rst b/CHANGES/12976.bugfix.rst deleted file mode 100644 index 008095a3351..00000000000 --- a/CHANGES/12976.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed the client decompressing frames when ``permessage-deflate`` was not negotiated -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/12983.bugfix.rst b/CHANGES/12983.bugfix.rst deleted file mode 100644 index 03f4083d83a..00000000000 --- a/CHANGES/12983.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed ``DigestAuthMiddleware`` raising an ``IndexError`` on empty domain -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/12984.bugfix.rst b/CHANGES/12984.bugfix.rst deleted file mode 100644 index a156216397b..00000000000 --- a/CHANGES/12984.bugfix.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fixed :class:`~aiohttp.DigestAuthMiddleware` corrupting the ``Digest`` -challenge when a ``WWW-Authenticate`` response offered more than one -authentication scheme -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/12985.bugfix.rst b/CHANGES/12985.bugfix.rst deleted file mode 100644 index 055b8572e42..00000000000 --- a/CHANGES/12985.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed client not closing cleanly after an exception -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/12988.bugfix.rst b/CHANGES/12988.bugfix.rst deleted file mode 100644 index ff07271fa31..00000000000 --- a/CHANGES/12988.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed control frames breaking fragmented WebSocket messages -- by :user:`arshsmith1`. diff --git a/CHANGES/12996.bugfix.rst b/CHANGES/12996.bugfix.rst deleted file mode 100644 index c43d973fb77..00000000000 --- a/CHANGES/12996.bugfix.rst +++ /dev/null @@ -1,5 +0,0 @@ -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/CHANGES/13001.bugfix.rst b/CHANGES/13001.bugfix.rst deleted file mode 100644 index 67511e3c95e..00000000000 --- a/CHANGES/13001.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed an :exc:`IndexError` in the pure-Python HTTP parser -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/13002.bugfix.rst b/CHANGES/13002.bugfix.rst deleted file mode 100644 index 1ac16a04fa7..00000000000 --- a/CHANGES/13002.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed parsing optional whitespace in Content-Disposition -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/13016.bugfix.rst b/CHANGES/13016.bugfix.rst deleted file mode 100644 index a984f64e333..00000000000 --- a/CHANGES/13016.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed request body not being read on rejected WebSocket upgrades -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/13042.bugfix.rst b/CHANGES/13042.bugfix.rst deleted file mode 100644 index ecda7970b60..00000000000 --- a/CHANGES/13042.bugfix.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fixed :exc:`LookupError` (and an unguarded :exc:`UnicodeDecodeError`) escaping -``Content-Disposition`` parsing when a multipart part supplies an extended -parameter with an unknown charset, e.g. ``filename*=unknown-8bit''...`` --- by :user:`arshsmith1`. diff --git a/CHANGES/13054.bugfix.rst b/CHANGES/13054.bugfix.rst deleted file mode 100644 index 75196b9bfe6..00000000000 --- a/CHANGES/13054.bugfix.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fixed ``escape_quotes`` in the Digest authentication middleware not escaping -backslashes, so a ``WWW-Authenticate`` challenge value containing a backslash -could break out of its quoted-string in the generated ``Authorization`` header --- by :user:`dxbjavid`. diff --git a/CHANGES/13136.bugfix.rst b/CHANGES/13136.bugfix.rst deleted file mode 100644 index 55fdd4228ca..00000000000 --- a/CHANGES/13136.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed Python parser not rejecting a bare ``LF`` in the request line -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/13171.bugfix.rst b/CHANGES/13171.bugfix.rst deleted file mode 100644 index fead0685a8e..00000000000 --- a/CHANGES/13171.bugfix.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fixed the C HTTP parser folding the fragment into the query string for an -origin-form request target with an empty query (e.g. ``/path?#frag``), -which diverged from the pure-Python parser -- by :user:`GiulioDER`. diff --git a/CHANGES/13174.bugfix.rst b/CHANGES/13174.bugfix.rst deleted file mode 100644 index 56cf18eea32..00000000000 --- a/CHANGES/13174.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed the C parser reporting newer HTTP methods such as ``QUERY`` as ````; the method table is now derived from the vendored llhttp instead of a hand-maintained count -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/13180.bugfix.rst b/CHANGES/13180.bugfix.rst new file mode 100644 index 00000000000..d1e676e5be8 --- /dev/null +++ b/CHANGES/13180.bugfix.rst @@ -0,0 +1,2 @@ +Fixed the client dropping only the first ``Authorization``, ``Cookie`` and +``Proxy-Authorization`` header when a redirect crosses an origin -- by :user:`arshsmith1`. diff --git a/CHANGES/5303.bugfix.rst b/CHANGES/5303.bugfix.rst deleted file mode 100644 index fcb1e7c8ae9..00000000000 --- a/CHANGES/5303.bugfix.rst +++ /dev/null @@ -1,7 +0,0 @@ -Fixed :py:attr:`~aiohttp.web.StreamResponse.last_modified` rounding a -:class:`datetime.datetime` with a fractional second down while rounding an -equivalent :class:`float` timestamp up, so the two produced different -``Last-Modified`` headers for the same instant. Both now round up to the -next whole second, consistent with how :py:class:`~aiohttp.web.FileResponse` -relies on this rounding to avoid falsely reporting a resource as -unmodified. diff --git a/CHANGES/5357.bugfix.rst b/CHANGES/5357.bugfix.rst deleted file mode 100644 index dd0125cb9ba..00000000000 --- a/CHANGES/5357.bugfix.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fixed resolving ``localhost`` on Windows to fall back without ``AI_ADDRCONFIG`` -when the first lookup fails, so ``localhost`` still works without an active -network. diff --git a/aiohttp/client.py b/aiohttp/client.py index 464b8c89b30..481663128fd 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -837,9 +837,9 @@ async def _request( if url.origin() != redirect_origin: cookies = None - headers.pop(hdrs.AUTHORIZATION, None) - headers.pop(hdrs.COOKIE, None) - headers.pop(hdrs.PROXY_AUTHORIZATION, None) + headers.popall(hdrs.AUTHORIZATION, None) + headers.popall(hdrs.COOKIE, None) + headers.popall(hdrs.PROXY_AUTHORIZATION, None) url = parsed_redirect_url params = {} diff --git a/tests/test_client_functional.py b/tests/test_client_functional.py index e06cd019fa5..e8d3cbe7515 100644 --- a/tests/test_client_functional.py +++ b/tests/test_client_functional.py @@ -37,7 +37,7 @@ ZstdCompressor = None # type: ignore[assignment,misc] # pragma: no cover import pytest -from multidict import MultiDict +from multidict import CIMultiDict, MultiDict from pytest_aiohttp import AiohttpClient, AiohttpServer from pytest_mock import MockerFixture from yarl import URL, Query @@ -3576,6 +3576,51 @@ async def close(self) -> None: assert resp.status == 200 +async def test_drop_duplicate_auth_headers_on_redirect_to_other_host( + aiohttp_server: AiohttpServer, +) -> None: + """Every copy of a credential header is dropped, not just the first one.""" + + async def srv_from(request: web.Request) -> NoReturn: + assert list(request.headers.getall("Authorization")) == [ + "Basic first", + "Basic second", + ] + raise web.HTTPFound(server_to.make_url("/path2")) + + async def srv_to(request: web.Request) -> web.Response: + assert "Authorization" not in request.headers, "Header wasn't dropped" + assert "Proxy-Authorization" not in request.headers + assert "Cookie" not in request.headers + return web.Response() + + app_from = web.Application() + app_from.router.add_get("/path1", srv_from) + server_from = await aiohttp_server(app_from) + + app_to = web.Application() + app_to.router.add_get("/path2", srv_to) + server_to = await aiohttp_server(app_to) + + # Two servers on the same host but different ports are different origins, + # so following the redirect must strip the credential headers. + headers = CIMultiDict( + ( + ("Authorization", "Basic first"), + ("Authorization", "Basic second"), + ("Proxy-Authorization", "Basic first"), + ("Proxy-Authorization", "Basic second"), + ("Cookie", "a=b"), + ("Cookie", "c=d"), + ) + ) + + url = server_from.make_url("/path1") + async with aiohttp.ClientSession() as client: + async with client.get(url, headers=headers) as resp: + assert resp.status == 200 + + async def test_drop_session_authorization_header_on_redirect_to_other_host( create_server_for_url_and_handler: Callable[[URL, Handler], Awaitable[TestServer]], ) -> None: