| 1 | from __future__ import annotations |
| 2 | |
| 3 | from typing import Any, Dict, Mapping |
| 4 | |
| 5 | # RFC6265 token 分隔符与空白字符 |
| 6 | INVALID_COOKIE_NAME_CHARS = set('()<>@,;:\\"/[]?={} \t\r\n') |
| 7 | |
| 8 | |
| 9 | def is_valid_cookie_name(name: str) -> bool: |
| 10 | if not name or not isinstance(name, str): |
| 11 | return False |
| 12 | if any(ord(ch) < 33 or ord(ch) > 126 for ch in name): |
| 13 | return False |
| 14 | if any(ch in INVALID_COOKIE_NAME_CHARS for ch in name): |
| 15 | return False |
| 16 | return True |
| 17 | |
| 18 | |
| 19 | def sanitize_cookies(cookies: Mapping[Any, Any]) -> Dict[str, str]: |
| 20 | sanitized: Dict[str, str] = {} |
| 21 | for raw_key, raw_value in (cookies or {}).items(): |
| 22 | if not isinstance(raw_key, str): |
| 23 | continue |
| 24 | key = raw_key.strip() |
| 25 | if not is_valid_cookie_name(key): |
| 26 | continue |
| 27 | value = "" if raw_value is None else str(raw_value).strip() |
| 28 | sanitized[key] = value |
| 29 | return sanitized |
| 30 | |
| 31 | |
| 32 | def parse_cookie_header(cookie_header: str) -> Dict[str, str]: |
| 33 | if not cookie_header: |
| 34 | return {} |
| 35 | parsed: Dict[str, str] = {} |
| 36 | for item in cookie_header.split(";"): |
| 37 | item = item.strip() |
| 38 | if not item or "=" not in item: |
| 39 | continue |
| 40 | key, value = item.split("=", 1) |
| 41 | key = key.strip() |
| 42 | if not is_valid_cookie_name(key): |
| 43 | continue |
| 44 | parsed[key] = value.strip() |
| 45 | return parsed |
| 46 |