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