v0.165.0
 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