From 9a2f146a12e3525b43e96723ef41584bf9cf784e Mon Sep 17 00:00:00 2001 From: Cycloctane Date: Sun, 21 Sep 2025 17:01:43 +0800 Subject: [PATCH] Ignore partitioned attributes in cookies if it is not supported by http.cookie (#11529) --- CHANGES/11523.bugfix.rst | 2 ++ aiohttp/_cookie_helpers.py | 8 ++------ tests/test_cookie_helpers.py | 33 +++++++++++++++++++++++++++++++++ tests/test_cookiejar.py | 13 +++++++++++++ 4 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 CHANGES/11523.bugfix.rst diff --git a/CHANGES/11523.bugfix.rst b/CHANGES/11523.bugfix.rst new file mode 100644 index 00000000000..7b70bcf6e76 --- /dev/null +++ b/CHANGES/11523.bugfix.rst @@ -0,0 +1,2 @@ +Fix saved ``CookieJar`` fail to be loaded if cookies have ``partitioned`` flag when +``http.cookie`` does not have partitioned cookies supports. -- by :user:`Cycloctane`. diff --git a/aiohttp/_cookie_helpers.py b/aiohttp/_cookie_helpers.py index 4e9fc968814..9e80b6065d7 100644 --- a/aiohttp/_cookie_helpers.py +++ b/aiohttp/_cookie_helpers.py @@ -6,7 +6,6 @@ """ import re -import sys from http.cookies import Morsel from typing import List, Optional, Sequence, Tuple, cast @@ -270,11 +269,8 @@ def parse_set_cookie_headers(headers: Sequence[str]) -> List[Tuple[str, Morsel[s break if lower_key in _COOKIE_BOOL_ATTRS: # Boolean attribute with any value should be True - if current_morsel is not None: - if lower_key == "partitioned" and sys.version_info < (3, 14): - dict.__setitem__(current_morsel, lower_key, True) - else: - current_morsel[lower_key] = True + if current_morsel is not None and current_morsel.isReservedKey(key): + current_morsel[lower_key] = True elif value is None: # Invalid cookie string - non-boolean attribute without value break diff --git a/tests/test_cookie_helpers.py b/tests/test_cookie_helpers.py index 6deef6544c2..0ec393e2b79 100644 --- a/tests/test_cookie_helpers.py +++ b/tests/test_cookie_helpers.py @@ -1,5 +1,6 @@ """Tests for internal cookie helper functions.""" +import sys from http.cookies import ( CookieError, Morsel, @@ -427,6 +428,10 @@ def test_parse_set_cookie_headers_boolean_attrs() -> None: assert morsel.get("httponly") is True, f"{name} should have httponly=True" +@pytest.mark.skipif( + sys.version_info < (3, 14), + reason="Partitioned cookies support requires Python 3.14+", +) def test_parse_set_cookie_headers_boolean_attrs_with_partitioned() -> None: """Test that boolean attributes including partitioned work correctly.""" # Test secure attribute variations @@ -482,6 +487,10 @@ def test_parse_set_cookie_headers_encoded_values() -> None: assert result[2][1].value == "%21%40%23%24%25%5E%26*%28%29" +@pytest.mark.skipif( + sys.version_info < (3, 14), + reason="Partitioned cookies support requires Python 3.14+", +) def test_parse_set_cookie_headers_partitioned() -> None: """ Test that parse_set_cookie_headers handles partitioned attribute correctly. @@ -518,6 +527,10 @@ def test_parse_set_cookie_headers_partitioned() -> None: assert result[4][1].get("path") == "/" +@pytest.mark.skipif( + sys.version_info < (3, 14), + reason="Partitioned cookies support requires Python 3.14+", +) def test_parse_set_cookie_headers_partitioned_case_insensitive() -> None: """Test that partitioned attribute is recognized case-insensitively.""" headers = [ @@ -555,6 +568,26 @@ def test_parse_set_cookie_headers_partitioned_not_set() -> None: # Tests that don't require partitioned support in SimpleCookie +@pytest.mark.skipif( + sys.version_info >= (3, 14), + reason="Python 3.14+ has built-in partitioned cookie support", +) +def test_parse_set_cookie_headers_partitioned_not_set_if_no_support() -> None: + headers = [ + "cookie1=value1; Partitioned", + "cookie2=value2; Partitioned=", + "cookie3=value3; Partitioned=true", + ] + + result = parse_set_cookie_headers(headers) + + assert len(result) == 3 + for i, (_, morsel) in enumerate(result): + assert ( + morsel.get("partitioned") is None + ), f"Cookie {i+1} should not have partitioned flag" + + def test_parse_set_cookie_headers_partitioned_with_other_attrs_manual() -> None: """ Test parsing logic for partitioned cookies combined with all other attributes. diff --git a/tests/test_cookiejar.py b/tests/test_cookiejar.py index b3774140c63..25d9cc731e3 100644 --- a/tests/test_cookiejar.py +++ b/tests/test_cookiejar.py @@ -206,6 +206,19 @@ def test_save_load( assert jar_test == cookies_to_receive +def test_save_load_partitioned_cookies(tmp_path: Path) -> None: + file_path = Path(str(tmp_path)) / "aiohttp.test2.cookie" + # export cookie jar + jar_save = CookieJar() + jar_save.update_cookies_from_headers( + ["session=cookie; Partitioned"], URL("https://example.com/") + ) + jar_save.save(file_path=file_path) + jar_load = CookieJar() + jar_load.load(file_path=file_path) + assert jar_save._cookies == jar_load._cookies + + async def test_update_cookie_with_unicode_domain() -> None: cookies = ( "idna-domain-first=first; Domain=xn--9caa.com; Path=/;",