Coverage for human_requests/tools/http_utils.py: 92%
52 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-13 21:41 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-13 21:41 +0000
1"""
2HTTP-helpers (cookie logic, charset, Playwright ↔ Curl adapters).
3"""
5from __future__ import annotations
7from http.cookies import SimpleCookie
8from typing import Any, Iterable, Mapping, Tuple
9from urllib.parse import SplitResult
11from ..abstraction.cookies import Cookie
13# ───────────────────── RFC 6265 helpers ──────────────────────────────
16def cookie_matches(url_parts: SplitResult, cookie: Cookie) -> bool: # noqa: ANN001
17 def domain_match(host: str, cookie_domain: str | None) -> bool:
18 if not cookie_domain:
19 return True
20 host = host.split(":", 1)[0].lower()
21 cd = cookie_domain.lstrip(".").lower()
22 return host == cd or host.endswith("." + cd)
24 def path_match(req_path: str, cookie_path: str | None) -> bool:
25 if not cookie_path:
26 return True
27 if not req_path.endswith("/"):
28 req_path += "/"
29 cp = cookie_path if cookie_path.endswith("/") else cookie_path + "/"
30 return req_path.startswith(cp)
32 return (
33 domain_match(url_parts.hostname or "", cookie.domain)
34 and path_match(url_parts.path or "/", cookie.path)
35 and (not cookie.secure or url_parts.scheme == "https")
36 )
39# ───────────────────── charset helper ────────────────────────────────
42def guess_encoding(headers: Mapping[str, str]) -> str:
43 ctype = headers.get("content-type", "")
44 if "charset=" in ctype:
45 return ctype.split("charset=", 1)[1].split(";", 1)[0].strip(" \"'") or "utf-8"
46 return "utf-8"
49# ───────────────────── Cookie → Header ───────────────────────────────
52def compose_cookie_header(
53 url_parts: SplitResult,
54 current_headers: Mapping[str, str],
55 jar: Iterable[Cookie],
56) -> Tuple[str, list[Cookie]]:
57 """Returns (header string, [cookie list, actually sent])."""
58 if "cookie" in current_headers:
59 return current_headers["cookie"], []
61 kv: list[str] = []
62 sent: list[Cookie] = []
63 for c in jar:
64 if cookie_matches(url_parts, c):
65 kv.append(f"{c.name}={c.value}")
66 sent.append(c)
68 return ("; ".join(kv) if kv else "", sent)
71# ───────────────────── Set-Cookie → Cookie objects ───────────────────
74def collect_set_cookie_headers(headers: Mapping[str, Any]) -> list[str]:
75 """curl_cffi.Headers→list[str] всех *Set-Cookie*."""
76 out: list[str] = []
77 for k, v in headers.items():
78 if k.lower() != "set-cookie":
79 continue
80 if isinstance(v, (list, tuple)):
81 out.extend(v)
82 else:
83 out.extend(p.strip() for p in str(v).split(",") if p.strip())
84 return out
87def parse_set_cookie(raw_headers: list[str], default_domain: str) -> list[Cookie]:
88 out: list[Cookie] = []
89 for raw in raw_headers:
90 jar = SimpleCookie()
91 jar.load(raw)
92 for m in jar.values():
93 out.append(
94 Cookie(
95 name=m.key,
96 value=m.value,
97 domain=(m["domain"] or default_domain).lower(),
98 path=m["path"] or "/",
99 secure=bool(m["secure"]),
100 http_only=bool(m["httponly"]),
101 )
102 )
103 return out