stable-diffusion-implementation
/
main
/myenv
/lib
/python3.10
/site-packages
/aiohttp
/_cookie_helpers.py
""" | |
Internal cookie handling helpers. | |
This module contains internal utilities for cookie parsing and manipulation. | |
These are not part of the public API and may change without notice. | |
""" | |
import re | |
import sys | |
from http.cookies import Morsel | |
from typing import List, Optional, Sequence, Tuple, cast | |
from .log import internal_logger | |
__all__ = ( | |
"parse_set_cookie_headers", | |
"parse_cookie_header", | |
"preserve_morsel_with_coded_value", | |
) | |
# Cookie parsing constants | |
# Allow more characters in cookie names to handle real-world cookies | |
# that don't strictly follow RFC standards (fixes #2683) | |
# RFC 6265 defines cookie-name token as per RFC 2616 Section 2.2, | |
# but many servers send cookies with characters like {} [] () etc. | |
# This makes the cookie parser more tolerant of real-world cookies | |
# while still providing some validation to catch obviously malformed names. | |
_COOKIE_NAME_RE = re.compile(r"^[!#$%&\'()*+\-./0-9:<=>?@A-Z\[\]^_`a-z{|}~]+$") | |
_COOKIE_KNOWN_ATTRS = frozenset( # AKA Morsel._reserved | |
( | |
"path", | |
"domain", | |
"max-age", | |
"expires", | |
"secure", | |
"httponly", | |
"samesite", | |
"partitioned", | |
"version", | |
"comment", | |
) | |
) | |
_COOKIE_BOOL_ATTRS = frozenset( # AKA Morsel._flags | |
("secure", "httponly", "partitioned") | |
) | |
# SimpleCookie's pattern for parsing cookies with relaxed validation | |
# Based on http.cookies pattern but extended to allow more characters in cookie names | |
# to handle real-world cookies (fixes #2683) | |
_COOKIE_PATTERN = re.compile( | |
r""" | |
\s* # Optional whitespace at start of cookie | |
(?P<key> # Start of group 'key' | |
# aiohttp has extended to include [] for compatibility with real-world cookies | |
[\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=\[\]]+? # Any word of at least one letter | |
) # End of group 'key' | |
( # Optional group: there may not be a value. | |
\s*=\s* # Equal Sign | |
(?P<val> # Start of group 'val' | |
"(?:[^\\"]|\\.)*" # Any double-quoted string (properly closed) | |
| # or | |
"[^";]* # Unmatched opening quote (differs from SimpleCookie - issue #7993) | |
| # or | |
# Special case for "expires" attr - RFC 822, RFC 850, RFC 1036, RFC 1123 | |
(\w{3,6}day|\w{3}),\s # Day of the week or abbreviated day (with comma) | |
[\w\d\s-]{9,11}\s[\d:]{8}\s # Date and time in specific format | |
(GMT|[+-]\d{4}) # Timezone: GMT or RFC 2822 offset like -0000, +0100 | |
# NOTE: RFC 2822 timezone support is an aiohttp extension | |
# for issue #4493 - SimpleCookie does NOT support this | |
| # or | |
# ANSI C asctime() format: "Wed Jun 9 10:18:14 2021" | |
# NOTE: This is an aiohttp extension for issue #4327 - SimpleCookie does NOT support this format | |
\w{3}\s+\w{3}\s+[\s\d]\d\s+\d{2}:\d{2}:\d{2}\s+\d{4} | |
| # or | |
[\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=\[\]]* # Any word or empty string | |
) # End of group 'val' | |
)? # End of optional value group | |
\s* # Any number of spaces. | |
(\s+|;|$) # Ending either at space, semicolon, or EOS. | |
""", | |
re.VERBOSE | re.ASCII, | |
) | |
def preserve_morsel_with_coded_value(cookie: Morsel[str]) -> Morsel[str]: | |
""" | |
Preserve a Morsel's coded_value exactly as received from the server. | |
This function ensures that cookie encoding is preserved exactly as sent by | |
the server, which is critical for compatibility with old servers that have | |
strict requirements about cookie formats. | |
This addresses the issue described in https://github.com/aio-libs/aiohttp/pull/1453 | |
where Python's SimpleCookie would re-encode cookies, breaking authentication | |
with certain servers. | |
Args: | |
cookie: A Morsel object from SimpleCookie | |
Returns: | |
A Morsel object with preserved coded_value | |
""" | |
mrsl_val = cast("Morsel[str]", cookie.get(cookie.key, Morsel())) | |
# We use __setstate__ instead of the public set() API because it allows us to | |
# bypass validation and set already validated state. This is more stable than | |
# setting protected attributes directly and unlikely to change since it would | |
# break pickling. | |
mrsl_val.__setstate__( # type: ignore[attr-defined] | |
{"key": cookie.key, "value": cookie.value, "coded_value": cookie.coded_value} | |
) | |
return mrsl_val | |
_unquote_sub = re.compile(r"\\(?:([0-3][0-7][0-7])|(.))").sub | |
def _unquote_replace(m: re.Match[str]) -> str: | |
""" | |
Replace function for _unquote_sub regex substitution. | |
Handles escaped characters in cookie values: | |
- Octal sequences are converted to their character representation | |
- Other escaped characters are unescaped by removing the backslash | |
""" | |
if m[1]: | |
return chr(int(m[1], 8)) | |
return m[2] | |
def _unquote(value: str) -> str: | |
""" | |
Unquote a cookie value. | |
Vendored from http.cookies._unquote to ensure compatibility. | |
Note: The original implementation checked for None, but we've removed | |
that check since all callers already ensure the value is not None. | |
""" | |
# If there aren't any doublequotes, | |
# then there can't be any special characters. See RFC 2109. | |
if len(value) < 2: | |
return value | |
if value[0] != '"' or value[-1] != '"': | |
return value | |
# We have to assume that we must decode this string. | |
# Down to work. | |
# Remove the "s | |
value = value[1:-1] | |
# Check for special sequences. Examples: | |
# \012 --> \n | |
# \" --> " | |
# | |
return _unquote_sub(_unquote_replace, value) | |
def parse_cookie_header(header: str) -> List[Tuple[str, Morsel[str]]]: | |
""" | |
Parse a Cookie header according to RFC 6265 Section 5.4. | |
Cookie headers contain only name-value pairs separated by semicolons. | |
There are no attributes in Cookie headers - even names that match | |
attribute names (like 'path' or 'secure') should be treated as cookies. | |
This parser uses the same regex-based approach as parse_set_cookie_headers | |
to properly handle quoted values that may contain semicolons. | |
Args: | |
header: The Cookie header value to parse | |
Returns: | |
List of (name, Morsel) tuples for compatibility with SimpleCookie.update() | |
""" | |
if not header: | |
return [] | |
cookies: List[Tuple[str, Morsel[str]]] = [] | |
i = 0 | |
n = len(header) | |
while i < n: | |
# Use the same pattern as parse_set_cookie_headers to find cookies | |
match = _COOKIE_PATTERN.match(header, i) | |
if not match: | |
break | |
key = match.group("key") | |
value = match.group("val") or "" | |
i = match.end(0) | |
# Validate the name | |
if not key or not _COOKIE_NAME_RE.match(key): | |
internal_logger.warning("Can not load cookie: Illegal cookie name %r", key) | |
continue | |
# Create new morsel | |
morsel: Morsel[str] = Morsel() | |
# Preserve the original value as coded_value (with quotes if present) | |
# We use __setstate__ instead of the public set() API because it allows us to | |
# bypass validation and set already validated state. This is more stable than | |
# setting protected attributes directly and unlikely to change since it would | |
# break pickling. | |
morsel.__setstate__( # type: ignore[attr-defined] | |
{"key": key, "value": _unquote(value), "coded_value": value} | |
) | |
cookies.append((key, morsel)) | |
return cookies | |
def parse_set_cookie_headers(headers: Sequence[str]) -> List[Tuple[str, Morsel[str]]]: | |
""" | |
Parse cookie headers using a vendored version of SimpleCookie parsing. | |
This implementation is based on SimpleCookie.__parse_string to ensure | |
compatibility with how SimpleCookie parses cookies, including handling | |
of malformed cookies with missing semicolons. | |
This function is used for both Cookie and Set-Cookie headers in order to be | |
forgiving. Ideally we would have followed RFC 6265 Section 5.2 (for Cookie | |
headers) and RFC 6265 Section 4.2.1 (for Set-Cookie headers), but the | |
real world data makes it impossible since we need to be a bit more forgiving. | |
NOTE: This implementation differs from SimpleCookie in handling unmatched quotes. | |
SimpleCookie will stop parsing when it encounters a cookie value with an unmatched | |
quote (e.g., 'cookie="value'), causing subsequent cookies to be silently dropped. | |
This implementation handles unmatched quotes more gracefully to prevent cookie loss. | |
See https://github.com/aio-libs/aiohttp/issues/7993 | |
""" | |
parsed_cookies: List[Tuple[str, Morsel[str]]] = [] | |
for header in headers: | |
if not header: | |
continue | |
# Parse cookie string using SimpleCookie's algorithm | |
i = 0 | |
n = len(header) | |
current_morsel: Optional[Morsel[str]] = None | |
morsel_seen = False | |
while 0 <= i < n: | |
# Start looking for a cookie | |
match = _COOKIE_PATTERN.match(header, i) | |
if not match: | |
# No more cookies | |
break | |
key, value = match.group("key"), match.group("val") | |
i = match.end(0) | |
lower_key = key.lower() | |
if key[0] == "$": | |
if not morsel_seen: | |
# We ignore attributes which pertain to the cookie | |
# mechanism as a whole, such as "$Version". | |
continue | |
# Process as attribute | |
if current_morsel is not None: | |
attr_lower_key = lower_key[1:] | |
if attr_lower_key in _COOKIE_KNOWN_ATTRS: | |
current_morsel[attr_lower_key] = value or "" | |
elif lower_key in _COOKIE_KNOWN_ATTRS: | |
if not morsel_seen: | |
# Invalid cookie string - attribute before cookie | |
break | |
if lower_key in _COOKIE_BOOL_ATTRS: | |
# Boolean attribute with any value should be True | |
if current_morsel is not None: | |
if lower_key == "partitioned" and sys.version_info < (3, 14): | |
dict.__setitem__(current_morsel, lower_key, True) | |
else: | |
current_morsel[lower_key] = True | |
elif value is None: | |
# Invalid cookie string - non-boolean attribute without value | |
break | |
elif current_morsel is not None: | |
# Regular attribute with value | |
current_morsel[lower_key] = _unquote(value) | |
elif value is not None: | |
# This is a cookie name=value pair | |
# Validate the name | |
if key in _COOKIE_KNOWN_ATTRS or not _COOKIE_NAME_RE.match(key): | |
internal_logger.warning( | |
"Can not load cookies: Illegal cookie name %r", key | |
) | |
current_morsel = None | |
else: | |
# Create new morsel | |
current_morsel = Morsel() | |
# Preserve the original value as coded_value (with quotes if present) | |
# We use __setstate__ instead of the public set() API because it allows us to | |
# bypass validation and set already validated state. This is more stable than | |
# setting protected attributes directly and unlikely to change since it would | |
# break pickling. | |
current_morsel.__setstate__( # type: ignore[attr-defined] | |
{"key": key, "value": _unquote(value), "coded_value": value} | |
) | |
parsed_cookies.append((key, current_morsel)) | |
morsel_seen = True | |
else: | |
# Invalid cookie string - no value for non-attribute | |
break | |
return parsed_cookies | |