|
12 | 12 |
|
13 | 13 | from .log import internal_logger
|
14 | 14 |
|
15 |
| -__all__ = ("parse_cookie_headers", "preserve_morsel_with_coded_value") |
| 15 | +__all__ = ( |
| 16 | + "parse_set_cookie_headers", |
| 17 | + "parse_cookie_header", |
| 18 | + "preserve_morsel_with_coded_value", |
| 19 | +) |
16 | 20 |
|
17 | 21 | # Cookie parsing constants
|
18 | 22 | # Allow more characters in cookie names to handle real-world cookies
|
@@ -153,7 +157,62 @@ def _unquote(value: str) -> str:
|
153 | 157 | return _unquote_sub(_unquote_replace, value)
|
154 | 158 |
|
155 | 159 |
|
156 |
| -def parse_cookie_headers(headers: Sequence[str]) -> List[Tuple[str, Morsel[str]]]: |
| 160 | +def parse_cookie_header(header: str) -> List[Tuple[str, Morsel[str]]]: |
| 161 | + """ |
| 162 | + Parse a Cookie header according to RFC 6265 Section 5.4. |
| 163 | +
|
| 164 | + Cookie headers contain only name-value pairs separated by semicolons. |
| 165 | + There are no attributes in Cookie headers - even names that match |
| 166 | + attribute names (like 'path' or 'secure') should be treated as cookies. |
| 167 | +
|
| 168 | + This parser uses the same regex-based approach as parse_set_cookie_headers |
| 169 | + to properly handle quoted values that may contain semicolons. |
| 170 | +
|
| 171 | + Args: |
| 172 | + header: The Cookie header value to parse |
| 173 | +
|
| 174 | + Returns: |
| 175 | + List of (name, Morsel) tuples for compatibility with SimpleCookie.update() |
| 176 | + """ |
| 177 | + if not header: |
| 178 | + return [] |
| 179 | + |
| 180 | + cookies: List[Tuple[str, Morsel[str]]] = [] |
| 181 | + i = 0 |
| 182 | + n = len(header) |
| 183 | + |
| 184 | + while i < n: |
| 185 | + # Use the same pattern as parse_set_cookie_headers to find cookies |
| 186 | + match = _COOKIE_PATTERN.match(header, i) |
| 187 | + if not match: |
| 188 | + break |
| 189 | + |
| 190 | + key = match.group("key") |
| 191 | + value = match.group("val") or "" |
| 192 | + i = match.end(0) |
| 193 | + |
| 194 | + # Validate the name |
| 195 | + if not key or not _COOKIE_NAME_RE.match(key): |
| 196 | + internal_logger.warning("Can not load cookie: Illegal cookie name %r", key) |
| 197 | + continue |
| 198 | + |
| 199 | + # Create new morsel |
| 200 | + morsel: Morsel[str] = Morsel() |
| 201 | + # Preserve the original value as coded_value (with quotes if present) |
| 202 | + # We use __setstate__ instead of the public set() API because it allows us to |
| 203 | + # bypass validation and set already validated state. This is more stable than |
| 204 | + # setting protected attributes directly and unlikely to change since it would |
| 205 | + # break pickling. |
| 206 | + morsel.__setstate__( # type: ignore[attr-defined] |
| 207 | + {"key": key, "value": _unquote(value), "coded_value": value} |
| 208 | + ) |
| 209 | + |
| 210 | + cookies.append((key, morsel)) |
| 211 | + |
| 212 | + return cookies |
| 213 | + |
| 214 | + |
| 215 | +def parse_set_cookie_headers(headers: Sequence[str]) -> List[Tuple[str, Morsel[str]]]: |
157 | 216 | """
|
158 | 217 | Parse cookie headers using a vendored version of SimpleCookie parsing.
|
159 | 218 |
|
|
0 commit comments