Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES/11523.bugfix.rst
Original file line number Diff line number Diff line change
@@ -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`.
8 changes: 2 additions & 6 deletions aiohttp/_cookie_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"""

import re
import sys
from http.cookies import Morsel
from typing import List, Optional, Sequence, Tuple, cast

Expand Down Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions tests/test_cookie_helpers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Tests for internal cookie helper functions."""

import sys
from http.cookies import (
CookieError,
Morsel,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -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.
Expand Down
13 changes: 13 additions & 0 deletions tests/test_cookiejar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=/;",
Expand Down
Loading