Skip to content

Commit 4f1b98a

Browse files
committed
fix: clear expired websocket cookies
Signed-off-by: Emre K <110906681+kocaemre@users.noreply.github.com>
1 parent c1004f8 commit 4f1b98a

2 files changed

Lines changed: 44 additions & 7 deletions

File tree

‎src/acp/_cookies.py‎

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
``Set-Cookie`` headers from the upgrade response and echo them back as a
66
``Cookie`` request header for the socket lifetime.
77
8-
This is intentionally minimal: it stores name→value pairs without attribute
9-
parsing (domain/path/expiry), matching the affinity-only use case in the RFD.
8+
This is intentionally minimal: it stores name→value pairs and only honors
9+
expiration attributes that remove a cookie, matching the affinity-only use case
10+
in the RFD.
1011
"""
1112

1213
from __future__ import annotations
@@ -23,16 +24,23 @@ def __init__(self) -> None:
2324
def store_set_cookie(self, header_value: str) -> None:
2425
"""Ingest a single ``Set-Cookie`` header value.
2526
26-
Only the leading ``name=value`` pair is retained; cookie attributes
27-
(``; Path=/``, ``; HttpOnly`` etc.) are ignored.
27+
Only the leading ``name=value`` pair is retained; most cookie attributes
28+
(``; Path=/``, ``; HttpOnly`` etc.) are ignored. Expiration attributes
29+
that explicitly clear a cookie (``Max-Age=0`` or an epoch ``Expires``
30+
value) remove any stored cookie with the same name.
2831
"""
29-
first = header_value.split(";", 1)[0].strip()
32+
parts = [part.strip() for part in header_value.split(";")]
33+
first = parts[0]
3034
if not first or "=" not in first:
3135
return
3236
name, _, value = first.partition("=")
3337
name = name.strip()
34-
if name:
35-
self._cookies[name] = value.strip()
38+
if not name:
39+
return
40+
if _is_deletion_cookie(parts[1:]):
41+
self._cookies.pop(name, None)
42+
return
43+
self._cookies[name] = value.strip()
3644

3745
def store_set_cookies(self, header_values: list[str]) -> None:
3846
"""Ingest multiple ``Set-Cookie`` header values."""
@@ -51,3 +59,17 @@ def clear(self) -> None:
5159

5260
def __len__(self) -> int:
5361
return len(self._cookies)
62+
63+
64+
def _is_deletion_cookie(attributes: list[str]) -> bool:
65+
for attribute in attributes:
66+
key, separator, value = attribute.partition("=")
67+
if not separator:
68+
continue
69+
key = key.strip().lower()
70+
value = value.strip().lower()
71+
if key == "max-age" and value == "0":
72+
return True
73+
if key == "expires" and value in {"thu, 01 jan 1970 00:00:00 gmt", "0"}:
74+
return True
75+
return False

‎tests/http/test_cookies.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,21 @@ def test_later_value_overwrites_same_name() -> None:
2323
assert len(store) == 1
2424

2525

26+
def test_expiring_cookie_removes_stored_value() -> None:
27+
store = MemoryAcpCookieStore()
28+
store.store_set_cookie("affinity=abc123; Path=/")
29+
store.store_set_cookie("affinity=; Max-Age=0; Path=/")
30+
assert store.cookie_header() is None
31+
assert len(store) == 0
32+
33+
34+
def test_epoch_expires_cookie_removes_stored_value() -> None:
35+
store = MemoryAcpCookieStore()
36+
store.store_set_cookie("affinity=abc123; Path=/")
37+
store.store_set_cookie("affinity=deleted; Expires=Thu, 01 Jan 1970 00:00:00 GMT")
38+
assert store.cookie_header() is None
39+
40+
2641
def test_empty_store_returns_none() -> None:
2742
store = MemoryAcpCookieStore()
2843
assert store.cookie_header() is None

0 commit comments

Comments
 (0)