143 lines
4.1 KiB
Python
143 lines
4.1 KiB
Python
"""HTTP transport sessions used by the login protocol client."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Callable
|
|
|
|
|
|
OKHTTP4_ANDROID10_JA3 = ",".join(
|
|
[
|
|
"771",
|
|
"4865-4866-4867-49195-49196-52393-49199-49200-52392-49171-49172-156-157-47-53",
|
|
"0-23-65281-10-11-35-16-5-13-51-45-43-21",
|
|
"29-23-24",
|
|
"0",
|
|
]
|
|
)
|
|
OKHTTP4_ANDROID10_AKAMAI = "4:16777216|16711681|0|m,p,a,s"
|
|
OKHTTP4_ANDROID10_EXTRA_FP = {
|
|
"tls_signature_algorithms": [
|
|
"ecdsa_secp256r1_sha256",
|
|
"rsa_pss_rsae_sha256",
|
|
"rsa_pkcs1_sha256",
|
|
"ecdsa_secp384r1_sha384",
|
|
"rsa_pss_rsae_sha384",
|
|
"rsa_pkcs1_sha384",
|
|
"rsa_pss_rsae_sha512",
|
|
"rsa_pkcs1_sha512",
|
|
"rsa_pkcs1_sha1",
|
|
]
|
|
}
|
|
|
|
|
|
class _CurlCookieAdapter:
|
|
"""Accept Playwright cookie attributes not represented by curl_cffi."""
|
|
|
|
def __init__(self, cookies: Any) -> None:
|
|
self._cookies = cookies
|
|
|
|
def set(
|
|
self,
|
|
name: str,
|
|
value: str,
|
|
*,
|
|
domain: str = "",
|
|
path: str = "/",
|
|
secure: bool = False,
|
|
expires: int | None = None,
|
|
**_: Any,
|
|
) -> None:
|
|
del expires
|
|
self._cookies.set(
|
|
name,
|
|
value,
|
|
domain=domain,
|
|
path=path,
|
|
secure=secure,
|
|
)
|
|
|
|
def __iter__(self):
|
|
return iter(self._cookies)
|
|
|
|
def __getattr__(self, name: str) -> Any:
|
|
return getattr(self._cookies, name)
|
|
|
|
|
|
class OkHttp4Android10Session:
|
|
"""Requests-like session with the documented OkHttp 4 Android 10 profile."""
|
|
|
|
def __init__(self, session: Any) -> None:
|
|
self._session = session
|
|
self.cookies = _CurlCookieAdapter(session.cookies)
|
|
|
|
def request(self, method: str, url: str, **kwargs: Any) -> Any:
|
|
kwargs.setdefault("http_version", "v2")
|
|
kwargs.setdefault("default_headers", False)
|
|
# The protocol client supplies its own Accept-Encoding header.
|
|
kwargs.setdefault("accept_encoding", None)
|
|
headers = kwargs.get("headers")
|
|
if headers is not None:
|
|
# HTTP/2 represents connection state at the framing layer.
|
|
kwargs["headers"] = {
|
|
str(name): value
|
|
for name, value in headers.items()
|
|
if str(name).lower() != "connection"
|
|
}
|
|
return self._session.request(method, url, **kwargs)
|
|
|
|
def get(self, url: str, **kwargs: Any) -> Any:
|
|
return self.request("GET", url, **kwargs)
|
|
|
|
def post(self, url: str, **kwargs: Any) -> Any:
|
|
return self.request("POST", url, **kwargs)
|
|
|
|
def close(self) -> None:
|
|
self._session.close()
|
|
|
|
|
|
def create_http_session(
|
|
transport: str,
|
|
*,
|
|
curl_session: Any | None = None,
|
|
curl_session_factory: Callable[..., Any] | None = None,
|
|
) -> Any:
|
|
"""Create a shared requests-compatible session for one transport profile."""
|
|
|
|
if transport == "requests":
|
|
import requests
|
|
|
|
session = requests.Session()
|
|
# OkHttp does not add requests' default ``Accept: */*`` header.
|
|
headers = getattr(session, "headers", None)
|
|
if headers is not None:
|
|
headers.pop("Accept", None)
|
|
return session
|
|
|
|
if transport != "okhttp4-android10":
|
|
raise ValueError(f"unsupported HTTP transport: {transport}")
|
|
|
|
if curl_session is None:
|
|
if curl_session_factory is None:
|
|
try:
|
|
from curl_cffi.requests import Session as curl_session_factory
|
|
except ImportError as exc:
|
|
raise RuntimeError(
|
|
"okhttp4-android10 transport requires the curl-cffi dependency"
|
|
) from exc
|
|
curl_session = curl_session_factory(
|
|
ja3=OKHTTP4_ANDROID10_JA3,
|
|
akamai=OKHTTP4_ANDROID10_AKAMAI,
|
|
extra_fp=OKHTTP4_ANDROID10_EXTRA_FP,
|
|
default_headers=False,
|
|
)
|
|
return OkHttp4Android10Session(curl_session)
|
|
|
|
|
|
__all__ = [
|
|
"OKHTTP4_ANDROID10_AKAMAI",
|
|
"OKHTTP4_ANDROID10_EXTRA_FP",
|
|
"OKHTTP4_ANDROID10_JA3",
|
|
"OkHttp4Android10Session",
|
|
"create_http_session",
|
|
]
|