Skip to content

Commit e28ced2

Browse files
committed
Normalize cookies before building TLS request payload
- Convert dict-based cookies into the list-of-dicts schema expected by tls-client, inferring the domain from the request URL to avoid `TLSClientException: json: cannot unmarshal object into Go struct field RequestInput.requestCookies`. - Update request option type hints so both dict and list cookie inputs are supported.
1 parent 11bee12 commit e28ced2

File tree

2 files changed

+45
-25
lines changed

2 files changed

+45
-25
lines changed

async_tls_client/session/request_payload_builder.py

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from base64 import b64encode
2-
from typing import Any, Dict, Optional, TYPE_CHECKING, Tuple, Union
3-
from urllib.parse import urlencode
42
from json import dumps as json_dumps
3+
from typing import Any, Dict, Optional, TYPE_CHECKING, Tuple, Union
4+
from urllib.parse import urlencode, urlparse
55

66
if TYPE_CHECKING:
77
from .session import AsyncSession
@@ -55,9 +55,29 @@ def _merge_headers(
5555
return merged
5656

5757

58-
def _prepare_cookies(cookies: Optional[Dict[str, str]]) -> Dict[str, str]:
59-
"""Formats cookies into the expected backend format (name=value dict)."""
60-
return cookies or {}
58+
def _prepare_cookies(request_url: str,
59+
cookies: Optional[Union[Dict[str, str], list[Dict[str, str]]]]) -> list[Dict[str, str]]:
60+
"""
61+
Convert cookies to a list of dicts and infer the domain from the URL for mapping input.
62+
63+
:param request_url: URL used to extract the cookie domain (hostname).
64+
:param cookies: Either a mapping of cookie names to values or a list of cookie dicts.
65+
If a mapping is provided, it will be transformed into
66+
[{"name": k, "value": v, "domain": <hostname>}].
67+
If a list is provided, it is returned unchanged.
68+
69+
:returns: A list of cookie dicts. For mapping input, each item has "name", "value", and "domain".
70+
"""
71+
if not cookies:
72+
return []
73+
74+
parsed = urlparse(request_url)
75+
inferred_domain = parsed.hostname or (parsed.netloc.split(":")[0] if parsed.netloc else "")
76+
77+
if isinstance(cookies, dict):
78+
return [{"name": key, "value": value, "domain": inferred_domain} for key, value in cookies.items()]
79+
80+
return cookies
6181

6282

6383
def _prepare_proxy(proxy: Optional[Union[Dict[str, Any], str]]) -> Optional[str]:
@@ -97,23 +117,23 @@ def _prepare_proxy(proxy: Optional[Union[Dict[str, Any], str]]) -> Optional[str]
97117

98118

99119
def build_payload(
100-
session: "AsyncSession",
101-
method: str,
102-
url: str,
103-
params: Optional[dict[str, Any]] = None,
104-
data: Optional[Union[str, bytes, dict]] = None,
105-
headers: Optional[dict[str, str]] = None,
106-
cookies: Optional[dict[str, str]] = None,
107-
json: Optional[Union[dict, list, str]] = None,
108-
allow_redirects: bool = False,
109-
insecure_skip_verify: bool = False,
110-
timeout_seconds: Optional[int] = None,
111-
timeout_milliseconds: Optional[int] = None,
112-
proxy: Optional[Union[dict, str]] = None,
113-
request_host_override: Optional[str] = None,
114-
stream_output_path: Optional[str] = None,
115-
stream_output_block_size: Optional[int] = None,
116-
stream_output_eof_symbol: Optional[str] = None
120+
session: "AsyncSession",
121+
method: str,
122+
url: str,
123+
params: Optional[dict[str, Any]] = None,
124+
data: Optional[Union[str, bytes, dict]] = None,
125+
headers: Optional[dict[str, str]] = None,
126+
cookies: Optional[Union[dict[str, str], list[dict[str, str]]]] = None,
127+
json: Optional[Union[dict, list, str]] = None,
128+
allow_redirects: bool = False,
129+
insecure_skip_verify: bool = False,
130+
timeout_seconds: Optional[int] = None,
131+
timeout_milliseconds: Optional[int] = None,
132+
proxy: Optional[Union[dict, str]] = None,
133+
request_host_override: Optional[str] = None,
134+
stream_output_path: Optional[str] = None,
135+
stream_output_block_size: Optional[int] = None,
136+
stream_output_eof_symbol: Optional[str] = None
117137
) -> dict:
118138
final_url = url
119139
if params:
@@ -123,7 +143,7 @@ def build_payload(
123143

124144
merged_headers = _merge_headers(session.headers, headers, content_type)
125145

126-
request_cookies = _prepare_cookies(cookies)
146+
request_cookies = _prepare_cookies(url, cookies)
127147

128148
final_proxy = _prepare_proxy(proxy)
129149

@@ -236,4 +256,4 @@ def build_payload(
236256
payload["tlsClientIdentifier"] = session.client_identifier
237257
payload["withRandomTLSExtensionOrder"] = session.random_tls_extension_order
238258

239-
return payload
259+
return payload

async_tls_client/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class RequestOptions(TypedDict, total=False):
8080
params: Optional[dict[str, Any]]
8181
data: Optional[Union[str, bytes, dict[str, Any]]]
8282
headers: Optional[dict[str, str]]
83-
cookies: Optional[dict[str, str]]
83+
cookies: Optional[Union[dict[str, str], list[dict[str, str]]]]
8484
json: Optional[Union[dict[str, Any], list[Any], str]]
8585
allow_redirects: Optional[bool]
8686
insecure_skip_verify: Optional[bool]

0 commit comments

Comments
 (0)