Rebrand the Rust client crate (coven/ → hh/, package+binary "hack-house"), README, CLI strings, and branch (coven → hack-house). Gitea repo renamed cmd-chat → hack-house to match. Crypto/server logic unchanged; selftest + golden-vector test still green, binary is now `hack-house`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
160 lines
4.9 KiB
Python
160 lines
4.9 KiB
Python
import re
|
|
|
|
from typing import Any
|
|
|
|
from sanic.cookies.response import Cookie
|
|
from sanic.request.parameters import RequestParameters
|
|
|
|
|
|
COOKIE_NAME_RESERVED_CHARS = re.compile(
|
|
'[\x00-\x1f\x7f-\xff()<>@,;:\\\\"/[\\]?={} \x09]'
|
|
)
|
|
OCTAL_PATTERN = re.compile(r"\\[0-3][0-7][0-7]")
|
|
QUOTE_PATTERN = re.compile(r"[\\].")
|
|
|
|
|
|
def _unquote(str): # no cov
|
|
if str is None or len(str) < 2:
|
|
return str
|
|
if str[0] != '"' or str[-1] != '"':
|
|
return str
|
|
|
|
str = str[1:-1]
|
|
|
|
i = 0
|
|
n = len(str)
|
|
res = []
|
|
while 0 <= i < n:
|
|
o_match = OCTAL_PATTERN.search(str, i)
|
|
q_match = QUOTE_PATTERN.search(str, i)
|
|
if not o_match and not q_match:
|
|
res.append(str[i:])
|
|
break
|
|
# else:
|
|
j = k = -1
|
|
if o_match:
|
|
j = o_match.start(0)
|
|
if q_match:
|
|
k = q_match.start(0)
|
|
if q_match and (not o_match or k < j):
|
|
res.append(str[i:k])
|
|
res.append(str[k + 1])
|
|
i = k + 2
|
|
else:
|
|
res.append(str[i:j])
|
|
res.append(chr(int(str[j + 1 : j + 4], 8))) # noqa: E203
|
|
i = j + 4
|
|
return "".join(res)
|
|
|
|
|
|
def parse_cookie(raw: str) -> dict[str, list[str]]:
|
|
"""Parses a raw cookie string into a dictionary.
|
|
|
|
The function takes a raw cookie string (usually from HTTP headers) and
|
|
returns a dictionary where each key is a cookie name and the value is a
|
|
list of values for that cookie. The function handles quoted values and
|
|
skips invalid cookie names.
|
|
|
|
Args:
|
|
raw (str): The raw cookie string to be parsed.
|
|
|
|
Returns:
|
|
Dict[str, List[str]]: A dictionary containing the cookie names as keys
|
|
and a list of values for each cookie.
|
|
|
|
Example:
|
|
```python
|
|
raw = 'name1=value1; name2="value2"; name3=value3'
|
|
cookies = parse_cookie(raw)
|
|
# cookies will be {'name1': ['value1'], 'name2': ['value2'], 'name3': ['value3']}
|
|
```
|
|
""" # noqa: E501
|
|
cookies: dict[str, list[str]] = {}
|
|
|
|
for token in raw.split(";"):
|
|
name, sep, value = token.partition("=")
|
|
name = name.strip()
|
|
value = value.strip()
|
|
|
|
# Support cookies =value or plain value with no name
|
|
# https://github.com/httpwg/http-extensions/issues/159
|
|
if not sep:
|
|
if not name:
|
|
# Empty value like ;; or a cookie header with no value
|
|
continue
|
|
name, value = "", name
|
|
|
|
if COOKIE_NAME_RESERVED_CHARS.search(name): # no cov
|
|
continue
|
|
|
|
if len(value) > 2 and value[0] == '"' and value[-1] == '"': # no cov
|
|
value = _unquote(value)
|
|
|
|
if name in cookies:
|
|
cookies[name].append(value)
|
|
else:
|
|
cookies[name] = [value]
|
|
|
|
return cookies
|
|
|
|
|
|
class CookieRequestParameters(RequestParameters):
|
|
"""A container for accessing single and multiple cookie values.
|
|
|
|
Because the HTTP standard allows for multiple cookies with the same name,
|
|
a standard dictionary cannot be used to access cookie values. This class
|
|
provides a way to access cookie values in a way that is similar to a
|
|
dictionary, but also allows for accessing multiple values for a single
|
|
cookie name when necessary.
|
|
|
|
Args:
|
|
cookies (Dict[str, List[str]]): A dictionary containing the cookie
|
|
names as keys and a list of values for each cookie.
|
|
|
|
Example:
|
|
```python
|
|
raw = 'name1=value1; name2="value2"; name3=value3'
|
|
cookies = parse_cookie(raw)
|
|
# cookies will be {'name1': ['value1'], 'name2': ['value2'], 'name3': ['value3']}
|
|
|
|
request_cookies = CookieRequestParameters(cookies)
|
|
request_cookies['name1'] # 'value1'
|
|
request_cookies.get('name1') # 'value1'
|
|
request_cookies.getlist('name1') # ['value1']
|
|
```
|
|
""" # noqa: E501
|
|
|
|
def __getitem__(self, key: str) -> str | None:
|
|
try:
|
|
value = self._get_prefixed_cookie(key)
|
|
except KeyError:
|
|
value = super().__getitem__(key)
|
|
return value
|
|
|
|
def __getattr__(self, key: str) -> str:
|
|
if key.startswith("_"):
|
|
return self.__getattribute__(key)
|
|
key = key.rstrip("_").replace("_", "-")
|
|
return str(self.get(key, ""))
|
|
|
|
def get(self, name: str, default: Any | None = None) -> Any | None:
|
|
try:
|
|
return self._get_prefixed_cookie(name)[0]
|
|
except KeyError:
|
|
return super().get(name, default)
|
|
|
|
def getlist(
|
|
self, name: str, default: list[Any] | None = None
|
|
) -> list[Any]:
|
|
try:
|
|
return self._get_prefixed_cookie(name)
|
|
except KeyError:
|
|
return super().getlist(name, default)
|
|
|
|
def _get_prefixed_cookie(self, name: str) -> Any:
|
|
getitem = super().__getitem__
|
|
try:
|
|
return getitem(f"{Cookie.HOST_PREFIX}{name}")
|
|
except KeyError:
|
|
return getitem(f"{Cookie.SECURE_PREFIX}{name}")
|