diff --git a/CHANGES.rst b/CHANGES.rst index 97689f035a0..f54a948c5dd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,112 @@ .. towncrier release notes start +3.14.1 (2026-06-07) +=================== + +Bug fixes +--------- + +- Fixed a race condition in :py:class:`~aiohttp.TCPConnector` where closing the connector while a DNS resolution was in-flight could raise :py:exc:`AttributeError` instead of :py:exc:`~aiohttp.ClientConnectionError` -- by :user:`goingforstudying-ctrl`. + + + *Related issues and pull requests on GitHub:* + :issue:`12497`. + + + +- Fixed ``CancelledError`` not closing a connection -- by :user:`aiolibsbot`. + + + *Related issues and pull requests on GitHub:* + :issue:`12795`. + + + +- Tightened up some websocket parser checks -- by :user:`Dreamsorcerer`. + + + *Related issues and pull requests on GitHub:* + :issue:`12817`. + + + +- Fixed :class:`~aiohttp.CookieJar` dropping the host-only flag of cookies when persisted with :meth:`~aiohttp.CookieJar.save` and reloaded with :meth:`~aiohttp.CookieJar.load`, so a cookie set without a ``Domain`` attribute is again scoped to the exact host that set it after a reload; the absolute expiration deadline is now persisted as well, so a reloaded cookie keeps its original lifetime instead of being rescheduled from the load time. :meth:`~aiohttp.CookieJar.load` now replaces the jar contents rather than merging onto prior state, and loaded cookies pass through the same acceptance rules as :meth:`~aiohttp.CookieJar.update_cookies`, so a cookie for an IP-address host is dropped when loaded into a jar created without ``unsafe=True`` -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`12824`. + + + +- Scoped :class:`~aiohttp.DigestAuthMiddleware` credentials to the origin of the first request it handles, so a redirect to a different origin no longer triggers a digest response computed from the configured credentials; a challenge from another origin is only answered when that origin falls within a protection space advertised by the anchor origin through the RFC 7616 ``domain`` directive -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`12825`. + + + +- Fixed the C HTTP parser not enforcing ``max_line_size`` on a request target or response reason phrase that is split across multiple reads; each fragment was checked on its own, so an accumulated line could exceed the limit without raising ``LineTooLong``. The accumulated length is now checked, matching the pure-Python parser -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`12826`. + + + +- Changed :class:`~aiohttp.TCPConnector` to reject legacy non-canonical numeric IPv4 host forms such as ``2130706433``, ``017700000001`` and ``127.1`` with :exc:`~aiohttp.InvalidUrlClientError`; only canonical dotted-quad IPv4 literals are now treated as IP address literals, while every other host is sent through the configured resolver -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`12827`. + + + +- Fixed :meth:`~aiohttp.StreamReader.readany` and :meth:`~aiohttp.StreamReader.read_nowait` joining data fed back into the buffer during the call (when draining below the low water mark resumes reading) into a single unbounded :class:`bytes`; a call now returns only the chunks that were buffered when it started, keeping the drain of an unread auto-decompressed request body bounded by the read buffer -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`12828`. + + + +- Bounded the number of parsed-but-unhandled pipelined HTTP/1 requests buffered per connection on the server; once the queue reaches an internal limit the parser stops emitting and the transport is paused, resuming as the request handler drains the queue, so a client keeping one handler busy can no longer accumulate an unbounded backlog of pipelined requests -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`12830`. + + + +- Fixed :meth:`aiohttp.web.Response.write_eof` skipping ``Payload.close()`` when the body write was interrupted by an error or cancellation, for example when a client disconnects mid-response; the payload close hook now runs in a ``finally`` so a :class:`~aiohttp.payload.Payload` body always releases its resources -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`12831`. + + + +- Fixed the pure-Python HTTP parser not enforcing ``max_line_size`` on a chunk-size line when the whole line arrived in a single read; the limit was only applied to chunk-size metadata split across reads. The complete-line case is now checked too, matching the split-line behavior -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`12832`. + + + +- Included the per-request ``server_hostname`` override in the :class:`~aiohttp.TCPConnector` connection pool key, so a pooled TLS connection is no longer reused for a request that sets ``server_hostname`` to a different value -- by :user:`bdraco`. + + + *Related issues and pull requests on GitHub:* + :issue:`12835`. + + + + +---- + + 3.14.0 (2026-06-01) =================== diff --git a/CHANGES/12497.bugfix.rst b/CHANGES/12497.bugfix.rst deleted file mode 100644 index 7fd5883ccbd..00000000000 --- a/CHANGES/12497.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a race condition in :py:class:`~aiohttp.TCPConnector` where closing the connector while a DNS resolution was in-flight could raise :py:exc:`AttributeError` instead of :py:exc:`~aiohttp.ClientConnectionError` -- by :user:`goingforstudying-ctrl`. diff --git a/CHANGES/12795.bugfix.rst b/CHANGES/12795.bugfix.rst deleted file mode 100644 index d08ea779287..00000000000 --- a/CHANGES/12795.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed ``CancelledError`` not closing a connection -- by :user:`aiolibsbot`. diff --git a/CHANGES/12817.bugfix.rst b/CHANGES/12817.bugfix.rst deleted file mode 100644 index c8a35e309a0..00000000000 --- a/CHANGES/12817.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Tightened up some websocket parser checks -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/12824.bugfix.rst b/CHANGES/12824.bugfix.rst deleted file mode 100644 index f8dbd169c31..00000000000 --- a/CHANGES/12824.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed :class:`~aiohttp.CookieJar` dropping the host-only flag of cookies when persisted with :meth:`~aiohttp.CookieJar.save` and reloaded with :meth:`~aiohttp.CookieJar.load`, so a cookie set without a ``Domain`` attribute is again scoped to the exact host that set it after a reload; the absolute expiration deadline is now persisted as well, so a reloaded cookie keeps its original lifetime instead of being rescheduled from the load time. :meth:`~aiohttp.CookieJar.load` now replaces the jar contents rather than merging onto prior state, and loaded cookies pass through the same acceptance rules as :meth:`~aiohttp.CookieJar.update_cookies`, so a cookie for an IP-address host is dropped when loaded into a jar created without ``unsafe=True`` -- by :user:`bdraco`. diff --git a/CHANGES/12825.bugfix.rst b/CHANGES/12825.bugfix.rst deleted file mode 100644 index 8bfd90bff65..00000000000 --- a/CHANGES/12825.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Scoped :class:`~aiohttp.client_middleware_digest_auth.DigestAuthMiddleware` credentials to the origin of the first request it handles, so a redirect to a different origin no longer triggers a digest response computed from the configured credentials; a challenge from another origin is only answered when that origin falls within a protection space advertised by the anchor origin through the RFC 7616 ``domain`` directive -- by :user:`bdraco`. diff --git a/CHANGES/12826.bugfix.rst b/CHANGES/12826.bugfix.rst deleted file mode 100644 index 7e095615d84..00000000000 --- a/CHANGES/12826.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed the C HTTP parser not enforcing ``max_line_size`` on a request target or response reason phrase that is split across multiple reads; each fragment was checked on its own, so an accumulated line could exceed the limit without raising ``LineTooLong``. The accumulated length is now checked, matching the pure-Python parser -- by :user:`bdraco`. diff --git a/CHANGES/12827.bugfix.rst b/CHANGES/12827.bugfix.rst deleted file mode 100644 index 9442867d363..00000000000 --- a/CHANGES/12827.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Changed :class:`~aiohttp.TCPConnector` to reject legacy non-canonical numeric IPv4 host forms such as ``2130706433``, ``017700000001`` and ``127.1`` with :exc:`~aiohttp.InvalidUrlClientError`; only canonical dotted-quad IPv4 literals are now treated as IP address literals, while every other host is sent through the configured resolver -- by :user:`bdraco`. diff --git a/CHANGES/12828.bugfix.rst b/CHANGES/12828.bugfix.rst deleted file mode 100644 index 9893577a587..00000000000 --- a/CHANGES/12828.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed :meth:`~aiohttp.StreamReader.readany` and :meth:`~aiohttp.StreamReader.read_nowait` joining data fed back into the buffer during the call (when draining below the low water mark resumes reading) into a single unbounded :class:`bytes`; a call now returns only the chunks that were buffered when it started, keeping the drain of an unread auto-decompressed request body bounded by the read buffer -- by :user:`bdraco`. diff --git a/CHANGES/12830.bugfix.rst b/CHANGES/12830.bugfix.rst deleted file mode 100644 index d44d76da404..00000000000 --- a/CHANGES/12830.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Bounded the number of parsed-but-unhandled pipelined HTTP/1 requests buffered per connection on the server; once the queue reaches an internal limit the parser stops emitting and the transport is paused, resuming as the request handler drains the queue, so a client keeping one handler busy can no longer accumulate an unbounded backlog of pipelined requests -- by :user:`bdraco`. diff --git a/CHANGES/12831.bugfix.rst b/CHANGES/12831.bugfix.rst deleted file mode 100644 index bf460ffccac..00000000000 --- a/CHANGES/12831.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed :meth:`aiohttp.web.Response.write_eof` skipping ``Payload.close()`` when the body write was interrupted by an error or cancellation, for example when a client disconnects mid-response; the payload close hook now runs in a ``finally`` so a :class:`~aiohttp.payload.Payload` body always releases its resources -- by :user:`bdraco`. diff --git a/CHANGES/12832.bugfix.rst b/CHANGES/12832.bugfix.rst deleted file mode 100644 index 00562fecd61..00000000000 --- a/CHANGES/12832.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed the pure-Python HTTP parser not enforcing ``max_line_size`` on a chunk-size line when the whole line arrived in a single read; the limit was only applied to chunk-size metadata split across reads. The complete-line case is now checked too, matching the split-line behavior -- by :user:`bdraco`. diff --git a/CHANGES/12835.bugfix.rst b/CHANGES/12835.bugfix.rst deleted file mode 100644 index 84a8ae00677..00000000000 --- a/CHANGES/12835.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Included the per-request ``server_hostname`` override in the :class:`~aiohttp.TCPConnector` connection pool key, so a pooled TLS connection is no longer reused for a request that sets ``server_hostname`` to a different value -- by :user:`bdraco`. diff --git a/aiohttp/streams.py b/aiohttp/streams.py index 8d089fb8e1d..72d26e607d7 100644 --- a/aiohttp/streams.py +++ b/aiohttp/streams.py @@ -549,7 +549,7 @@ def _read_nowait(self, n: int) -> bytes: count = len(self._buffer) if count == 1: return self._read_nowait_chunk(-1) - return b"".join(self._read_nowait_chunk(-1) for _ in range(count)) + return b"".join([self._read_nowait_chunk(-1) for _ in range(count)]) chunks: list[bytes] = [] while self._buffer: diff --git a/aiohttp/web_protocol.py b/aiohttp/web_protocol.py index 96cd9401b5d..3a5be452090 100644 --- a/aiohttp/web_protocol.py +++ b/aiohttp/web_protocol.py @@ -664,7 +664,7 @@ async def start(self) -> None: # pipelining keeps flowing while this request is handled. # no branch: _parser is only None after connection_lost, whose path # exits this loop, so the None case is not reachably exercisable. - if self._parser is not None: # pragma: no branch + if self._parser is not None: self._parser.message_consumed() if ( self._msg_queue_paused