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

1""" 

2HTTP-helpers (cookie logic, charset, Playwright ↔ Curl adapters). 

3""" 

4 

5from __future__ import annotations 

6 

7from http.cookies import SimpleCookie 

8from typing import Any, Iterable, Mapping, Tuple 

9from urllib.parse import SplitResult 

10 

11from ..abstraction.cookies import Cookie 

12 

13# ───────────────────── RFC 6265 helpers ────────────────────────────── 

14 

15 

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) 

23 

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) 

31 

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 ) 

37 

38 

39# ───────────────────── charset helper ──────────────────────────────── 

40 

41 

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" 

47 

48 

49# ───────────────────── Cookie → Header ─────────────────────────────── 

50 

51 

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"], [] 

60 

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) 

67 

68 return ("; ".join(kv) if kv else "", sent) 

69 

70 

71# ───────────────────── Set-Cookie → Cookie objects ─────────────────── 

72 

73 

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 

85 

86 

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