Fix host-only state on cookie expiration (#13674) (#13675) · aio-libs/aiohttp@b477d8c · GitHub
Skip to content

Commit b477d8c

Browse files
Fix host-only state on cookie expiration (#13674) (#13675)
(cherry picked from commit 9332972)
1 parent bc24dc1 commit b477d8c

6 files changed

Lines changed: 89 additions & 19 deletions

File tree

‎CHANGES/13674.bugfix.rst‎

Lines changed: 1 addition & 0 deletions

‎aiohttp/abc.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def cookies(self) -> MappingProxyType[tuple[str, str], SimpleCookie]:
181181

182182
@property
183183
@abstractmethod
184-
def host_only_cookies(self) -> frozenset[tuple[str, str]]:
184+
def host_only_cookies(self) -> frozenset[tuple[str, str, str]]:
185185
"""Return the host-only cookies stored in this jar."""
186186

187187
@abstractmethod

‎aiohttp/cookiejar.py‎

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ def __init__(
136136
self._morsel_cache: defaultdict[tuple[str, str], dict[str, Morsel[str]]] = (
137137
defaultdict(dict)
138138
)
139-
self._host_only_cookies: set[tuple[str, str]] = set()
139+
# Cookie identity is (domain, path, name).
140+
self._host_only_cookies: set[tuple[str, str, str]] = set()
140141
self._unsafe = unsafe
141142
self._quote_cookie = quote_cookie
142143
if treat_as_secure_origin is None:
@@ -168,7 +169,7 @@ def cookies(self) -> MappingProxyType[tuple[str, str], SimpleCookie]:
168169
return MappingProxyType(self._cookies)
169170

170171
@property
171-
def host_only_cookies(self) -> frozenset[tuple[str, str]]:
172+
def host_only_cookies(self) -> frozenset[tuple[str, str, str]]:
172173
"""Return the host-only cookies stored in this jar."""
173174
return frozenset(self._host_only_cookies)
174175

@@ -197,7 +198,7 @@ def save(self, file_path: PathLike) -> None:
197198
if attr_val:
198199
morsel_data[attr] = attr_val
199200
# Persist or it reloads as a domain cookie and leaks to subdomains.
200-
if (domain, name) in self._host_only_cookies:
201+
if (domain, path, name) in self._host_only_cookies:
201202
morsel_data["host_only"] = True
202203
if (exp := self._expirations.get((domain, path, name))) is not None:
203204
morsel_data["expires_timestamp"] = exp
@@ -360,7 +361,7 @@ def _do_expiration(self) -> None:
360361

361362
def _delete_cookies(self, to_del: list[tuple[str, str, str]]) -> None:
362363
for domain, path, name in to_del:
363-
self._host_only_cookies.discard((domain, name))
364+
self._host_only_cookies.discard((domain, path, name))
364365
self._cookies[(domain, path)].pop(name, None)
365366
self._morsel_cache[(domain, path)].pop(name, None)
366367
self._expirations.pop((domain, path, name), None)
@@ -414,18 +415,12 @@ def _update_cookies(
414415
domain = ""
415416
del cookie["domain"]
416417

417-
if not domain and hostname is not None:
418-
# Set the cookie's domain to the response hostname
419-
# and set its host-only-flag
420-
self._host_only_cookies.add((hostname, name))
421-
domain = cookie["domain"] = hostname
422-
423418
if domain and domain[0] == ".":
424419
# Remove leading dot
425420
domain = domain[1:]
426421
cookie["domain"] = domain
427422

428-
if hostname and not self._is_domain_match(domain, hostname):
423+
if domain and hostname and not self._is_domain_match(domain, hostname):
429424
# Setting cookies for different domains is not allowed
430425
continue
431426

@@ -441,6 +436,14 @@ def _update_cookies(
441436
cookie["path"] = path
442437
path = path.rstrip("/")
443438

439+
if not domain and hostname is not None:
440+
self._host_only_cookies.add((hostname, path, name))
441+
domain = cookie["domain"] = hostname
442+
else:
443+
# A cookie with an explicit Domain attribute replaces any
444+
# host-only cookie with the same (domain, path, name) identity.
445+
self._host_only_cookies.discard((domain, path, name))
446+
444447
if max_age := cookie["max-age"]:
445448
try:
446449
delta_seconds = int(max_age)
@@ -530,7 +533,7 @@ def filter_cookies(self, request_url: URL = URL()) -> "BaseCookie[str]":
530533
for name, cookie in self._cookies[p].items():
531534
domain = cookie["domain"]
532535

533-
if (domain, name) in self._host_only_cookies and domain != hostname:
536+
if domain != hostname and p + (name,) in self._host_only_cookies:
534537
continue
535538

536539
# Skip edge case when the cookie has a trailing slash but request doesn't.
@@ -680,7 +683,7 @@ def cookies(self) -> MappingProxyType[tuple[str, str], SimpleCookie]:
680683
return MappingProxyType({})
681684

682685
@property
683-
def host_only_cookies(self) -> frozenset[tuple[str, str]]:
686+
def host_only_cookies(self) -> frozenset[tuple[str, str, str]]:
684687
"""Return an empty frozenset."""
685688
return frozenset()
686689

‎docs/client_reference.rst‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2651,11 +2651,17 @@ Utilities
26512651

26522652
.. attribute:: host_only_cookies
26532653

2654-
A :class:`frozenset` of ``(domain, name)`` tuples indicating which
2655-
cookies are host-only (not sent to subdomains).
2654+
A :class:`frozenset` of ``(domain, path, name)`` tuples indicating
2655+
which cookies are host-only (not sent to subdomains).
26562656

26572657
.. versionadded:: 3.14
26582658

2659+
.. versionchanged:: 3.14.4
2660+
2661+
The tuples gained the *path* element; host-only state is tracked
2662+
per ``(domain, path, name)`` cookie identity so that same-named
2663+
cookies on other paths cannot affect it.
2664+
26592665

26602666
.. class:: DummyCookieJar(*, loop=None)
26612667

‎tests/test_client_session.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ def cookies(self) -> MappingProxyType[tuple[str, str], SimpleCookie]:
759759
return MappingProxyType({})
760760

761761
@property
762-
def host_only_cookies(self) -> frozenset[tuple[str, str]]:
762+
def host_only_cookies(self) -> frozenset[tuple[str, str, str]]:
763763
return frozenset()
764764

765765
def clear(self, predicate: abc.ClearCookiePredicate | None = None) -> None:

‎tests/test_cookiejar.py‎

Lines changed: 62 additions & 2 deletions

0 commit comments

Comments
 (0)