1from __future__ import annotations
2
3from http import cookies
4
5from plain.runtime import settings
6from plain.signing import BadSignature, TimestampSigner
7from plain.utils.encoding import force_bytes
8
9
10def parse_cookie(cookie: str) -> dict[str, str]:
11 """
12 Return a dictionary parsed from a `Cookie:` header string.
13 """
14 cookiedict = {}
15 for chunk in cookie.split(";"):
16 if "=" in chunk:
17 key, val = chunk.split("=", 1)
18 else:
19 # Assume an empty name per
20 # https://bugzilla.mozilla.org/show_bug.cgi?id=169091
21 key, val = "", chunk
22 key, val = key.strip(), val.strip()
23 if key or val:
24 # unquote using Python's algorithm.
25 cookiedict[key] = cookies._unquote(val)
26 return cookiedict
27
28
29def _cookie_key(key: str) -> bytes:
30 """
31 Generate a key for cookie signing that matches the pattern used by
32 set_signed_cookie and get_signed_cookie.
33 """
34 return b"plain.http.cookies" + force_bytes(key)
35
36
37def get_signed_cookie_signer(key: str, salt: str = "") -> TimestampSigner:
38 """
39 Create a TimestampSigner for signed cookies with the same configuration
40 used by both set_signed_cookie and get_signed_cookie.
41 """
42 return TimestampSigner(
43 key=_cookie_key(settings.SECRET_KEY).decode(),
44 fallback_keys=[_cookie_key(k).decode() for k in settings.SECRET_KEY_FALLBACKS],
45 salt=key + salt,
46 )
47
48
49def sign_cookie_value(key: str, value: str, salt: str = "") -> str:
50 """
51 Sign a cookie value using the standard Plain cookie signing approach.
52 """
53 signer = get_signed_cookie_signer(key, salt)
54 return signer.sign(value)
55
56
57def unsign_cookie_value(
58 key: str,
59 signed_value: str,
60 salt: str = "",
61 max_age: int | None = None,
62 default: str | None = None,
63) -> str | None:
64 """
65 Unsign a cookie value using the standard Plain cookie signing approach.
66 Returns the default value if the signature is invalid or the cookie has expired.
67 """
68 signer = get_signed_cookie_signer(key, salt)
69 try:
70 return signer.unsign(signed_value, max_age=max_age)
71 except BadSignature:
72 return default