python-tls-client 1.0.3__py3-none-any.whl → 1.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_tls_client-1.0.4.dist-info/METADATA +364 -0
- python_tls_client-1.0.4.dist-info/RECORD +22 -0
- tls_client/__init__.py +8 -1
- tls_client/__version__.py +1 -1
- tls_client/cffi.py +13 -1
- tls_client/dependencies/tls-client-32.dll +0 -0
- tls_client/dependencies/tls-client-64.dll +0 -0
- tls_client/dependencies/tls-client-amd64.so +0 -0
- tls_client/dependencies/tls-client-arm64.dylib +0 -0
- tls_client/dependencies/tls-client-arm64.so +0 -0
- tls_client/dependencies/tls-client-x86.dylib +0 -0
- tls_client/dependencies/tls-client-x86.so +0 -0
- tls_client/exceptions.py +49 -1
- tls_client/sessions.py +65 -10
- tls_client/settings.py +42 -3
- python_tls_client-1.0.3.dist-info/METADATA +0 -195
- python_tls_client-1.0.3.dist-info/RECORD +0 -22
- {python_tls_client-1.0.3.dist-info → python_tls_client-1.0.4.dist-info}/WHEEL +0 -0
- {python_tls_client-1.0.3.dist-info → python_tls_client-1.0.4.dist-info}/licenses/LICENSE +0 -0
- {python_tls_client-1.0.3.dist-info → python_tls_client-1.0.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-tls-client
|
|
3
|
+
Version: 1.0.4
|
|
4
|
+
Summary: Advanced Python HTTP Client with TLS fingerprint spoofing (fork with binary-response fix and certificate pinning)
|
|
5
|
+
Author: Emanuele Scarlata
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Fenix46/Python-Tls-Client
|
|
8
|
+
Project-URL: Source, https://github.com/Fenix46/Python-Tls-Client
|
|
9
|
+
Project-URL: Original, https://github.com/FlorianREGAZ/Python-Tls-Client
|
|
10
|
+
Classifier: Environment :: Web Environment
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Operating System :: Unix
|
|
14
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
15
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
24
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
26
|
+
Requires-Python: >=3.8
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: typing-extensions
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# Python-TLS-Client
|
|
33
|
+
|
|
34
|
+
Advanced Python HTTP client with TLS fingerprint spoofing, built on
|
|
35
|
+
[bogdanfinn/tls-client](https://github.com/bogdanfinn/tls-client) and inspired
|
|
36
|
+
by [requests](https://github.com/psf/requests).
|
|
37
|
+
|
|
38
|
+
> **Fork notice:** this is a fork of
|
|
39
|
+
> [Python-Tls-Client](https://github.com/FlorianREGAZ/Python-Tls-Client) by
|
|
40
|
+
> Florian Zager, published on PyPI under a different name
|
|
41
|
+
> (`python-tls-client`) because the original name `tls-client` is already
|
|
42
|
+
> taken by an unrelated project. The importable module is unchanged:
|
|
43
|
+
> `import tls_client`.
|
|
44
|
+
>
|
|
45
|
+
> Changes on top of the original:
|
|
46
|
+
> - Fixed corruption of binary response bodies (protobuf, images, ...) — see [Changelog](#changelog) 1.0.2 / 1.0.3
|
|
47
|
+
> - Certificate pinning — see [Changelog](#changelog) 1.0.1
|
|
48
|
+
> - Typed exceptions, safer session lifecycle, clearer native-library load errors — see [Changelog](#changelog) 1.0.4
|
|
49
|
+
> - Native binaries updated to `bogdanfinn/tls-client` v1.16.0, fixing a musl/glibc mismatch on Linux (Chrome 150/152, Firefox 147/148, Brave 146, HTTP/3 over SOCKS5, session ticket control, ...) — see [Changelog](#changelog) 1.0.4
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install python-tls-client
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Quick start
|
|
58
|
+
|
|
59
|
+
The API is intentionally close to [requests](https://github.com/psf/requests),
|
|
60
|
+
so most of what you already know carries over directly.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import tls_client
|
|
64
|
+
|
|
65
|
+
session = tls_client.Session(
|
|
66
|
+
client_identifier="chrome_150",
|
|
67
|
+
random_tls_extension_order=True,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
response = session.get(
|
|
71
|
+
"https://www.example.com/",
|
|
72
|
+
headers={"key1": "value1"},
|
|
73
|
+
proxy="http://user:password@host:port",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
print(response.status_code, response.text)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Available `client_identifier` presets (see `tls_client/settings.py` for the
|
|
80
|
+
full, up-to-date list — kept in sync with the
|
|
81
|
+
[`MappedTLSClients`](https://github.com/bogdanfinn/tls-client/blob/v1.16.0/profiles/profiles.go)
|
|
82
|
+
map of the native library version shipped in `tls_client/dependencies/`,
|
|
83
|
+
currently v1.16.0):
|
|
84
|
+
|
|
85
|
+
| Browser | Identifiers |
|
|
86
|
+
|----------|-------------|
|
|
87
|
+
| Chrome | `chrome_103`...`chrome_112`, `chrome_116_PSK`, `chrome_116_PSK_PQ`, `chrome_117`, `chrome_120`, `chrome_124`, `chrome_130_PSK`, `chrome_131(_PSK)`, `chrome_133(_PSK)`, `chrome_144(_PSK)`, `chrome_146(_PSK)`, `chrome_150(_PSK)`, `chrome_152(_PSK)` |
|
|
88
|
+
| Brave | `brave_146`, `brave_146_PSK` |
|
|
89
|
+
| Firefox | `firefox_102`...`firefox_110`, `firefox_117`, `firefox_120`, `firefox_123`, `firefox_132`, `firefox_133`, `firefox_135`, `firefox_146_PSK`, `firefox_147(_PSK)`, `firefox_148` |
|
|
90
|
+
| Opera | `opera_89`, `opera_90`, `opera_91` |
|
|
91
|
+
| Safari | `safari_15_6_1`, `safari_16_0` |
|
|
92
|
+
| iOS | `safari_ios_15_5`, `safari_ios_15_6`, `safari_ios_16_0`, `safari_ios_17_0`, `safari_ios_18_0`, `safari_ios_18_5`, `safari_ios_26_0` |
|
|
93
|
+
| iPadOS | `safari_ipad_15_6` |
|
|
94
|
+
| Android | `okhttp4_android_7` through `okhttp4_android_13` |
|
|
95
|
+
|
|
96
|
+
> Only `chrome_144`, `chrome_144_PSK`, `firefox_147`, `firefox_147_PSK` and
|
|
97
|
+
> `firefox_148` carry an HTTP/3 fingerprint matching the real browser; every
|
|
98
|
+
> other profile sends a minimal, non-representative SETTINGS frame if you
|
|
99
|
+
> end up negotiating HTTP/3. This doesn't affect the TLS or HTTP/2
|
|
100
|
+
> fingerprint. Use `disable_http3=True` (see below) if the HTTP/3 fingerprint
|
|
101
|
+
> matters and you're not using one of those five profiles.
|
|
102
|
+
|
|
103
|
+
## Releasing the session
|
|
104
|
+
|
|
105
|
+
Every `Session` holds native resources (a connection pool) in the underlying
|
|
106
|
+
Go library that must be released explicitly. Always use the context manager,
|
|
107
|
+
or call `.close()` yourself:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
with tls_client.Session(client_identifier="chrome_150") as session:
|
|
111
|
+
response = session.get("https://www.example.com/")
|
|
112
|
+
# session is closed automatically here
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
If you forget, the session emits a `ResourceWarning` and does a best-effort
|
|
116
|
+
cleanup when it is garbage collected — but that isn't guaranteed to run
|
|
117
|
+
promptly (or at all), so don't rely on it in production code.
|
|
118
|
+
|
|
119
|
+
## Custom TLS fingerprint
|
|
120
|
+
|
|
121
|
+
Instead of a `client_identifier` preset, you can fully customize the TLS/HTTP2
|
|
122
|
+
fingerprint:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
import tls_client
|
|
126
|
+
|
|
127
|
+
session = tls_client.Session(
|
|
128
|
+
ja3_string=(
|
|
129
|
+
"771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-"
|
|
130
|
+
"156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0"
|
|
131
|
+
),
|
|
132
|
+
h2_settings={
|
|
133
|
+
"HEADER_TABLE_SIZE": 65536,
|
|
134
|
+
"MAX_CONCURRENT_STREAMS": 1000,
|
|
135
|
+
"INITIAL_WINDOW_SIZE": 6291456,
|
|
136
|
+
"MAX_HEADER_LIST_SIZE": 262144,
|
|
137
|
+
},
|
|
138
|
+
h2_settings_order=[
|
|
139
|
+
"HEADER_TABLE_SIZE",
|
|
140
|
+
"MAX_CONCURRENT_STREAMS",
|
|
141
|
+
"INITIAL_WINDOW_SIZE",
|
|
142
|
+
"MAX_HEADER_LIST_SIZE",
|
|
143
|
+
],
|
|
144
|
+
supported_signature_algorithms=[
|
|
145
|
+
"ECDSAWithP256AndSHA256",
|
|
146
|
+
"PSSWithSHA256",
|
|
147
|
+
"PKCS1WithSHA256",
|
|
148
|
+
"ECDSAWithP384AndSHA384",
|
|
149
|
+
"PSSWithSHA384",
|
|
150
|
+
"PKCS1WithSHA384",
|
|
151
|
+
"PSSWithSHA512",
|
|
152
|
+
"PKCS1WithSHA512",
|
|
153
|
+
],
|
|
154
|
+
supported_versions=["GREASE", "1.3", "1.2"],
|
|
155
|
+
key_share_curves=["GREASE", "X25519"],
|
|
156
|
+
cert_compression_algo="brotli",
|
|
157
|
+
pseudo_header_order=[":method", ":authority", ":scheme", ":path"],
|
|
158
|
+
connection_flow=15663105,
|
|
159
|
+
header_order=["accept", "user-agent", "accept-encoding", "accept-language"],
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
response = session.post(
|
|
163
|
+
"https://www.example.com/",
|
|
164
|
+
headers={"key1": "value1"},
|
|
165
|
+
json={"key1": "key2"},
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Additional fingerprint and connection controls (v1.16.0+)
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
import tls_client
|
|
173
|
+
|
|
174
|
+
session = tls_client.Session(
|
|
175
|
+
client_identifier="chrome_150",
|
|
176
|
+
# Disable TLS session ticket caching/resumption
|
|
177
|
+
disable_session_tickets=True,
|
|
178
|
+
# Force HTTP/2, even for a profile that would otherwise try HTTP/3
|
|
179
|
+
disable_http3=True,
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
For a custom client (`client_identifier=None`), you can additionally set:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
session = tls_client.Session(
|
|
187
|
+
client_identifier=None,
|
|
188
|
+
ja3_string="...",
|
|
189
|
+
alpn_protocols=["h2", "http/1.1"],
|
|
190
|
+
alps_protocols=["h2"],
|
|
191
|
+
# Required if ja3_string lists TLS extension 51764 (trust_anchors)
|
|
192
|
+
trust_anchors_payload="...",
|
|
193
|
+
)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Certificate pinning
|
|
197
|
+
|
|
198
|
+
Restrict which server certificates are accepted for given hosts by passing
|
|
199
|
+
their pinned public key hashes (SPKI, base64-encoded SHA-256 — the same
|
|
200
|
+
format used by HPKP `pin-sha256` and most certificate-pinning tooling):
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
import tls_client
|
|
204
|
+
|
|
205
|
+
session = tls_client.Session(
|
|
206
|
+
client_identifier="chrome_150",
|
|
207
|
+
certificate_pinning={
|
|
208
|
+
"example.com": [
|
|
209
|
+
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
|
210
|
+
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", # backup pin
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
response = session.get("https://example.com/")
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Requests to a pinned host whose certificate doesn't match any of the given
|
|
219
|
+
pins raise `tls_client.TLSClientCertificateError` (see
|
|
220
|
+
[Error handling](#error-handling)). Including a backup pin is recommended so
|
|
221
|
+
rotating the leaf certificate doesn't lock you out.
|
|
222
|
+
|
|
223
|
+
## Error handling
|
|
224
|
+
|
|
225
|
+
Failures originating from the underlying Go client are raised as
|
|
226
|
+
`TLSClientExeption` (kept misspelled for backwards compatibility) or one of
|
|
227
|
+
its more specific subclasses, so you can catch broadly or narrowly:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from tls_client import (
|
|
231
|
+
TLSClientExeption,
|
|
232
|
+
TLSClientTimeoutError,
|
|
233
|
+
TLSClientProxyError,
|
|
234
|
+
TLSClientConnectionError,
|
|
235
|
+
TLSClientCertificateError,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
response = session.get("https://example.com/", timeout_seconds=5)
|
|
240
|
+
except TLSClientTimeoutError:
|
|
241
|
+
... # request exceeded timeout_seconds
|
|
242
|
+
except TLSClientProxyError:
|
|
243
|
+
... # the configured proxy refused/rejected the connection
|
|
244
|
+
except TLSClientConnectionError:
|
|
245
|
+
... # DNS, TCP, or TLS handshake failure
|
|
246
|
+
except TLSClientCertificateError:
|
|
247
|
+
... # response failed certificate pinning validation
|
|
248
|
+
except TLSClientExeption:
|
|
249
|
+
... # any other backend error
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
All four subclasses inherit from `TLSClientExeption`, so existing code that
|
|
253
|
+
only catches the base class keeps working unchanged. Classification is
|
|
254
|
+
best-effort pattern matching on the backend's error message (it doesn't
|
|
255
|
+
expose a structured error code) and falls back to the base exception when
|
|
256
|
+
the message doesn't match a known pattern.
|
|
257
|
+
|
|
258
|
+
## Packaging with PyInstaller / PyArmor
|
|
259
|
+
|
|
260
|
+
The compiled native library ships inside `tls_client/dependencies/` and needs
|
|
261
|
+
to be included explicitly when bundling with PyInstaller or PyArmor.
|
|
262
|
+
|
|
263
|
+
| Platform | `--add-binary` argument |
|
|
264
|
+
|--------------------------|--------------------------|
|
|
265
|
+
| Linux (x86, 32-bit) | `'{path_to_library}/tls_client/dependencies/tls-client-x86.so:tls_client/dependencies'` |
|
|
266
|
+
| Linux (AMD64 / x86_64) | `'{path_to_library}/tls_client/dependencies/tls-client-amd64.so:tls_client/dependencies'` |
|
|
267
|
+
| macOS (Intel) | `'{path_to_library}/tls_client/dependencies/tls-client-x86.dylib:tls_client/dependencies'` |
|
|
268
|
+
| macOS (Apple Silicon) | `'{path_to_library}/tls_client/dependencies/tls-client-arm64.dylib:tls_client/dependencies'` |
|
|
269
|
+
| Windows (64-bit) | `'{path_to_library}/tls_client/dependencies/tls-client-64.dll;tls_client/dependencies'` |
|
|
270
|
+
|
|
271
|
+
## Development
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
pip install -r requirements.txt
|
|
275
|
+
python -m unittest discover -s tests -p "test_unit_*.py" -v
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`tests/test_unit_*.py` are network-free and run in CI on every push and pull
|
|
279
|
+
request. `tests/test_binary_response.py` is a regression suite that hits
|
|
280
|
+
httpbin.org and is meant for manual local runs (`python -m pytest tests/ -v`),
|
|
281
|
+
not CI.
|
|
282
|
+
|
|
283
|
+
## Acknowledgements
|
|
284
|
+
|
|
285
|
+
Big shout out to [Bogdanfinn](https://github.com/bogdanfinn) for open
|
|
286
|
+
sourcing [tls-client](https://github.com/bogdanfinn/tls-client) in Go, and to
|
|
287
|
+
[FlorianREGAZ](https://github.com/FlorianREGAZ) for the original Python
|
|
288
|
+
wrapper this project is forked from. The syntax stays close to
|
|
289
|
+
[requests](https://github.com/psf/requests) since most people already know it.
|
|
290
|
+
|
|
291
|
+
## Changelog
|
|
292
|
+
|
|
293
|
+
### 1.0.4
|
|
294
|
+
- **Fixed**: the Linux `.so` binaries published in `1.0.3` on PyPI
|
|
295
|
+
(`tls-client-amd64.so`, and likely the other Linux binaries from that
|
|
296
|
+
build) were linked against musl libc instead of glibc, causing
|
|
297
|
+
`OSError: ... libc.musl-x86_64.so.1` on any glibc-based distro (Ubuntu,
|
|
298
|
+
Debian, most standard LXC containers — anything that isn't Alpine).
|
|
299
|
+
The v1.16.0 binaries in this update were downloaded from upstream's
|
|
300
|
+
`*-ubuntu-*` release assets and confirmed glibc-linked (checked for
|
|
301
|
+
`GLIBC_*` version symbols and the absence of a `libc.musl` dependency).
|
|
302
|
+
- **Breaking**: `Session`'s default `client_identifier` changed from
|
|
303
|
+
`chrome_120` to `chrome_150` (matching the v1.16.0 native library's own
|
|
304
|
+
default profile). `chrome_120` imitates a browser version no longer in
|
|
305
|
+
circulation, which made it a distinguishing fingerprint rather than a
|
|
306
|
+
neutral one; pin `client_identifier="chrome_120"` explicitly if you
|
|
307
|
+
relied on the old default.
|
|
308
|
+
- **Added**: typed exception hierarchy (`TLSClientTimeoutError`,
|
|
309
|
+
`TLSClientProxyError`, `TLSClientConnectionError`,
|
|
310
|
+
`TLSClientCertificateError`), all subclasses of the existing
|
|
311
|
+
`TLSClientExeption` so current `except` clauses keep working.
|
|
312
|
+
- **Added**: `Session` now warns (`ResourceWarning`) and best-effort
|
|
313
|
+
auto-closes if garbage collected without an explicit `close()` call.
|
|
314
|
+
- **Fixed**: native library load failures now raise a clear `OSError`
|
|
315
|
+
naming the platform, architecture, and resolved library path instead of
|
|
316
|
+
a raw, hard-to-diagnose `ctypes` error.
|
|
317
|
+
- **Fixed**: invalid/misleading type hints in `Session.__init__` (bare
|
|
318
|
+
`Optional` on `bool` parameters, `str` instead of `Optional[str]`).
|
|
319
|
+
- **Added**: network-free unit tests (`tests/test_unit_*.py`) and a CI
|
|
320
|
+
workflow that runs them on every push and pull request.
|
|
321
|
+
- **Updated**: precompiled native binaries bumped from
|
|
322
|
+
`bogdanfinn/tls-client` ~v1.7.x to v1.16.0. Newest previously available
|
|
323
|
+
profile was `chrome_120`/`firefox_120` (obsolete browser versions by
|
|
324
|
+
2026, which made the fingerprint itself a distinguishing signal). Now
|
|
325
|
+
includes Chrome 150/152, Firefox 147/148, Brave 146, and 20+ other new
|
|
326
|
+
profiles — see the [client identifier table](#quick-start) above. Verified
|
|
327
|
+
against a live handshake (`chrome_150` against tls.peet.ws) and against
|
|
328
|
+
the full existing test suite (unit tests + the httpbin-backed binary
|
|
329
|
+
response regression suite) on macOS arm64.
|
|
330
|
+
- **Added**: `Session` parameters `alpn_protocols`, `alps_protocols`,
|
|
331
|
+
`trust_anchors_payload` (custom client only), `disable_session_tickets`,
|
|
332
|
+
`disable_http3`, exposing v1.16.0 request fields. All default to the
|
|
333
|
+
previous behavior, so existing calls are unaffected.
|
|
334
|
+
- **Fixed**: `cert_compression_algo` now sends `certCompressionAlgos`
|
|
335
|
+
(the v1.16.0 field name, plural/list) internally instead of the old
|
|
336
|
+
singular `certCompressionAlgo`, which the new binary silently ignored.
|
|
337
|
+
The public parameter itself is unchanged.
|
|
338
|
+
- **Fixed**: `ClientIdentifiers` type list corrected — `safari_ipad_15_6`
|
|
339
|
+
was previously listed as a duplicate of `safari_ios_15_6`, making the
|
|
340
|
+
real iPadOS identifier inaccessible through the type; `confirmed_android_2`
|
|
341
|
+
removed as it was never a valid identifier in any checked upstream version.
|
|
342
|
+
|
|
343
|
+
### 1.0.3
|
|
344
|
+
- **Fixed**: on Linux, 64-bit x86 machines (`platform.machine() == "x86_64"`)
|
|
345
|
+
were incorrectly matched by the `"x86" in machine()` check in `cffi.py`
|
|
346
|
+
and loaded the 32-bit `tls-client-x86.so` binary instead of the 64-bit
|
|
347
|
+
`tls-client-amd64.so`, causing the native library to fail to load on the
|
|
348
|
+
vast majority of Linux hosts. `machine()` is now matched explicitly
|
|
349
|
+
against `x86_64`/`amd64`/`AMD64` before falling back to the 32-bit binary.
|
|
350
|
+
|
|
351
|
+
### 1.0.2
|
|
352
|
+
- **Fixed**: binary response bodies (protobuf, images, ...) were corrupted
|
|
353
|
+
by a UTF-8 `byteReplacer` (U+FFFD) inside the Go C library. Every byte
|
|
354
|
+
greater than 127 that did not form a valid UTF-8 sequence was replaced by
|
|
355
|
+
`EF BF BD`.
|
|
356
|
+
- `sessions.py` now sends `isByteResponse: True` in the request payload
|
|
357
|
+
so the backend returns the body as a base64 data-URI.
|
|
358
|
+
- `response.py`'s `build_response` decodes the data-URI back to raw
|
|
359
|
+
bytes for `Response.content`; `Response.text` is a UTF-8 view (with
|
|
360
|
+
replacement characters for invalid sequences).
|
|
361
|
+
- Added `tests/test_binary_response.py` (regression suite, needs network).
|
|
362
|
+
|
|
363
|
+
### 1.0.1
|
|
364
|
+
- Certificate pinning.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
python_tls_client-1.0.4.dist-info/licenses/LICENSE,sha256=issbIg7ujiqhtvXsx5mmKhdeZ6zd39ew_nEzvvwImfw,1123
|
|
2
|
+
tls_client/__init__.py,sha256=pa9nyTBgrTRUPcEH7m3A8_U2s69AdTGECp5MfyO9kbs,819
|
|
3
|
+
tls_client/__version__.py,sha256=MTezpEv9Aa9fBY5Q8ToVM6LirC8gjSdxLRjv-NCF1to,445
|
|
4
|
+
tls_client/cffi.py,sha256=BMtpaZp87ZyuPslNF41eVMygmhDI5ilrjJXJc2_fBqE,1570
|
|
5
|
+
tls_client/cookies.py,sha256=5ujSFL6V8bDMCH8Mow6qoj5dP5GjfMJTd8_jWottvpM,15153
|
|
6
|
+
tls_client/exceptions.py,sha256=MELEkIjO410QTYls9UDbUuJlG_PstAv5ysao-WTcDJU,1917
|
|
7
|
+
tls_client/response.py,sha256=5Ji09s3fnrSst3N5n61fJ7motHNF3Jjsk6WBmsZ6iOk,3126
|
|
8
|
+
tls_client/sessions.py,sha256=JlSR5m77OffjY25_aCuEJAZjk-qZm_i1FdCWPaUpDCw,21322
|
|
9
|
+
tls_client/settings.py,sha256=nhpow6IsksoZw-u3yWBFvvQaSM-u23eBWRcCxxoVodo,2376
|
|
10
|
+
tls_client/structures.py,sha256=zb1wflxxWoKFqe8NMT2qEvIh7RA-kN459ZcNhW8p_Uo,2443
|
|
11
|
+
tls_client/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
tls_client/dependencies/tls-client-32.dll,sha256=UgOjb4DqP5zfpDvwcqd1cC0YAqy45DBOeJBSEPZ90q8,24192773
|
|
13
|
+
tls_client/dependencies/tls-client-64.dll,sha256=U9ymNrMtll7m_kVi8535WbIGP-uqPXQO-pJdGHPPEdc,27843480
|
|
14
|
+
tls_client/dependencies/tls-client-amd64.so,sha256=LshTSWY0VF56fqAocVdjlI1Vu92XrKfsqp_qjC67CN8,17379216
|
|
15
|
+
tls_client/dependencies/tls-client-arm64.dylib,sha256=mZhNATkhx1OrKdKHIMsJnv9q32NTg0fhNlLLXP5b3AI,11884770
|
|
16
|
+
tls_client/dependencies/tls-client-arm64.so,sha256=45hiL5nAzo_MtQ_25BTzc7WTKgJ37OkEaKeWmS8K5Rg,16413368
|
|
17
|
+
tls_client/dependencies/tls-client-x86.dylib,sha256=ZGNFfqcTqWs7jJT9nYdG57xRDLZ4T88PS7ZNnIPjJRo,12584208
|
|
18
|
+
tls_client/dependencies/tls-client-x86.so,sha256=XqgTCOU-I22uc7Nb1KCa8SARkcMnE-opaLhbvP6UthY,16210792
|
|
19
|
+
python_tls_client-1.0.4.dist-info/METADATA,sha256=CatC2OhRC7AydypRfQyp4HfEtqUmflhTYB3nloaQQvY,15461
|
|
20
|
+
python_tls_client-1.0.4.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
21
|
+
python_tls_client-1.0.4.dist-info/top_level.txt,sha256=yLrfQtbTRfECzPN62knWdGNmbhcPRev1grCAMuEsEz0,11
|
|
22
|
+
python_tls_client-1.0.4.dist-info/RECORD,,
|
tls_client/__init__.py
CHANGED
|
@@ -12,4 +12,11 @@
|
|
|
12
12
|
# tls-client: https://github.com/bogdanfinn/tls-client
|
|
13
13
|
# requests: https://github.com/psf/requests
|
|
14
14
|
|
|
15
|
-
from .sessions import Session
|
|
15
|
+
from .sessions import Session
|
|
16
|
+
from .exceptions import (
|
|
17
|
+
TLSClientExeption,
|
|
18
|
+
TLSClientTimeoutError,
|
|
19
|
+
TLSClientProxyError,
|
|
20
|
+
TLSClientConnectionError,
|
|
21
|
+
TLSClientCertificateError,
|
|
22
|
+
)
|
tls_client/__version__.py
CHANGED
tls_client/cffi.py
CHANGED
|
@@ -17,7 +17,19 @@ else:
|
|
|
17
17
|
file_ext = '-x86.so'
|
|
18
18
|
|
|
19
19
|
root_dir = os.path.abspath(os.path.dirname(__file__))
|
|
20
|
-
|
|
20
|
+
library_path = f'{root_dir}/dependencies/tls-client{file_ext}'
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
library = ctypes.cdll.LoadLibrary(library_path)
|
|
24
|
+
except OSError as exc:
|
|
25
|
+
raise OSError(
|
|
26
|
+
f"Failed to load the native tls-client library for this platform "
|
|
27
|
+
f"(platform={platform!r}, machine={machine()!r}) from {library_path!r}. "
|
|
28
|
+
"The binary may be missing from the package, incompatible with this "
|
|
29
|
+
"OS/architecture, or blocked by the OS (e.g. macOS Gatekeeper). "
|
|
30
|
+
"If you believe this platform should be supported, please open an "
|
|
31
|
+
"issue at https://github.com/Fenix46/Python-Tls-Client/issues."
|
|
32
|
+
) from exc
|
|
21
33
|
|
|
22
34
|
# extract the exposed request function from the shared package
|
|
23
35
|
request = library.request
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
tls_client/exceptions.py
CHANGED
|
@@ -1,3 +1,51 @@
|
|
|
1
1
|
|
|
2
2
|
class TLSClientExeption(IOError):
|
|
3
|
-
"""General error with the TLS client"""
|
|
3
|
+
"""General error with the TLS client"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TLSClientTimeoutError(TLSClientExeption):
|
|
7
|
+
"""The request did not complete within ``timeout_seconds``."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TLSClientProxyError(TLSClientExeption):
|
|
11
|
+
"""The proxy refused the connection, timed out, or rejected the credentials."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TLSClientConnectionError(TLSClientExeption):
|
|
15
|
+
"""The connection to the target host could not be established (DNS, TCP, TLS handshake)."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TLSClientCertificateError(TLSClientExeption):
|
|
19
|
+
"""The server certificate did not match the configured certificate pin."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Substrings looked up (case-insensitively) in the Go backend's error message
|
|
23
|
+
# to classify it into one of the typed exceptions above. The backend
|
|
24
|
+
# (bogdanfinn/tls-client) does not expose a structured error code, only this
|
|
25
|
+
# free-text message, so this is best-effort pattern matching kept close to
|
|
26
|
+
# the strings the Go client is known to emit.
|
|
27
|
+
_ERROR_PATTERNS = (
|
|
28
|
+
("certificate", TLSClientCertificateError),
|
|
29
|
+
("pin", TLSClientCertificateError),
|
|
30
|
+
("proxy", TLSClientProxyError),
|
|
31
|
+
("timeout", TLSClientTimeoutError),
|
|
32
|
+
("deadline exceeded", TLSClientTimeoutError),
|
|
33
|
+
("no such host", TLSClientConnectionError),
|
|
34
|
+
("connection refused", TLSClientConnectionError),
|
|
35
|
+
("connection reset", TLSClientConnectionError),
|
|
36
|
+
("dial tcp", TLSClientConnectionError),
|
|
37
|
+
("eof", TLSClientConnectionError),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_exception(message: str) -> TLSClientExeption:
|
|
42
|
+
"""Map a Go backend error message to the most specific matching exception.
|
|
43
|
+
|
|
44
|
+
Falls back to the base ``TLSClientExeption`` when nothing matches, so
|
|
45
|
+
callers that only catch the base class keep working unchanged.
|
|
46
|
+
"""
|
|
47
|
+
lowered = (message or "").lower()
|
|
48
|
+
for pattern, exception_cls in _ERROR_PATTERNS:
|
|
49
|
+
if pattern in lowered:
|
|
50
|
+
return exception_cls(message)
|
|
51
|
+
return TLSClientExeption(message)
|
tls_client/sessions.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .cffi import request, freeMemory, destroySession
|
|
2
2
|
from .cookies import cookiejar_from_dict, merge_cookies, extract_cookies_to_jar
|
|
3
|
-
from .exceptions import
|
|
3
|
+
from .exceptions import build_exception
|
|
4
4
|
from .response import build_response, Response
|
|
5
5
|
from .settings import ClientIdentifiers
|
|
6
6
|
from .structures import CaseInsensitiveDict
|
|
@@ -18,7 +18,7 @@ class Session:
|
|
|
18
18
|
|
|
19
19
|
def __init__(
|
|
20
20
|
self,
|
|
21
|
-
client_identifier: ClientIdentifiers = "
|
|
21
|
+
client_identifier: ClientIdentifiers = "chrome_150",
|
|
22
22
|
ja3_string: Optional[str] = None,
|
|
23
23
|
h2_settings: Optional[Dict[str, int]] = None,
|
|
24
24
|
h2_settings_order: Optional[List[str]] = None,
|
|
@@ -26,20 +26,26 @@ class Session:
|
|
|
26
26
|
supported_delegated_credentials_algorithms: Optional[List[str]] = None,
|
|
27
27
|
supported_versions: Optional[List[str]] = None,
|
|
28
28
|
key_share_curves: Optional[List[str]] = None,
|
|
29
|
-
cert_compression_algo: str = None,
|
|
30
|
-
additional_decode: str = None,
|
|
29
|
+
cert_compression_algo: Optional[str] = None,
|
|
30
|
+
additional_decode: Optional[str] = None,
|
|
31
31
|
pseudo_header_order: Optional[List[str]] = None,
|
|
32
32
|
connection_flow: Optional[int] = None,
|
|
33
33
|
priority_frames: Optional[list] = None,
|
|
34
34
|
header_order: Optional[List[str]] = None,
|
|
35
35
|
header_priority: Optional[List[str]] = None,
|
|
36
|
-
random_tls_extension_order:
|
|
37
|
-
force_http1:
|
|
38
|
-
catch_panics:
|
|
39
|
-
debug:
|
|
36
|
+
random_tls_extension_order: bool = False,
|
|
37
|
+
force_http1: bool = False,
|
|
38
|
+
catch_panics: bool = False,
|
|
39
|
+
debug: bool = False,
|
|
40
40
|
certificate_pinning: Optional[Dict[str, List[str]]] = None,
|
|
41
|
+
alpn_protocols: Optional[List[str]] = None,
|
|
42
|
+
alps_protocols: Optional[List[str]] = None,
|
|
43
|
+
trust_anchors_payload: Optional[str] = None,
|
|
44
|
+
disable_session_tickets: bool = False,
|
|
45
|
+
disable_http3: bool = False,
|
|
41
46
|
) -> None:
|
|
42
47
|
self._session_id = str(uuid.uuid4())
|
|
48
|
+
self._closed = False
|
|
43
49
|
# --- Standard Settings ----------------------------------------------------------------------------------------
|
|
44
50
|
|
|
45
51
|
# Case-insensitive dictionary of headers, send on each request
|
|
@@ -278,6 +284,24 @@ class Session:
|
|
|
278
284
|
# debugging
|
|
279
285
|
self.debug = debug
|
|
280
286
|
|
|
287
|
+
# ALPN protocols offered during the TLS handshake for a custom client
|
|
288
|
+
# Example: ["h2", "http/1.1"]
|
|
289
|
+
self.alpn_protocols = alpn_protocols
|
|
290
|
+
|
|
291
|
+
# ALPS protocols (Application-Layer Protocol Settings) for a custom client
|
|
292
|
+
self.alps_protocols = alps_protocols
|
|
293
|
+
|
|
294
|
+
# Trust anchors payload for the TLS 1.3 "trust_anchors" extension.
|
|
295
|
+
# Required when ja3_string lists extension 51764; pass "" (default
|
|
296
|
+
# None here) to keep the previous behaviour.
|
|
297
|
+
self.trust_anchors_payload = trust_anchors_payload
|
|
298
|
+
|
|
299
|
+
# Disable TLS session ticket caching and resumption
|
|
300
|
+
self.disable_session_tickets = disable_session_tickets
|
|
301
|
+
|
|
302
|
+
# Disable HTTP/3, even for profiles that would otherwise use it
|
|
303
|
+
self.disable_http3 = disable_http3
|
|
304
|
+
|
|
281
305
|
def __enter__(self):
|
|
282
306
|
return self
|
|
283
307
|
|
|
@@ -298,9 +322,32 @@ class Session:
|
|
|
298
322
|
destroy_session_response_object = loads(destroy_session_response_string)
|
|
299
323
|
|
|
300
324
|
freeMemory(destroy_session_response_object['id'].encode('utf-8'))
|
|
325
|
+
self._closed = True
|
|
301
326
|
|
|
302
327
|
return destroy_session_response_string
|
|
303
328
|
|
|
329
|
+
def __del__(self):
|
|
330
|
+
# Safety net for callers who forget to call close() or use the `with`
|
|
331
|
+
# statement: the Go backend keeps the session (and its connection
|
|
332
|
+
# pool) alive until destroySession() is called, so a forgotten
|
|
333
|
+
# session leaks native memory for the life of the process.
|
|
334
|
+
if not getattr(self, "_closed", True):
|
|
335
|
+
import warnings
|
|
336
|
+
warnings.warn(
|
|
337
|
+
f"Session {self._session_id} was never closed; call session.close() "
|
|
338
|
+
"or use it as a context manager (`with tls_client.Session() as session`) "
|
|
339
|
+
"to release the native TLS client resources.",
|
|
340
|
+
ResourceWarning,
|
|
341
|
+
stacklevel=2,
|
|
342
|
+
)
|
|
343
|
+
try:
|
|
344
|
+
self.close()
|
|
345
|
+
except Exception:
|
|
346
|
+
# Best-effort cleanup during garbage collection / interpreter
|
|
347
|
+
# shutdown; the native library or event loop it depends on
|
|
348
|
+
# may already be gone.
|
|
349
|
+
pass
|
|
350
|
+
|
|
304
351
|
def execute_request(
|
|
305
352
|
self,
|
|
306
353
|
method: str,
|
|
@@ -409,6 +456,8 @@ class Session:
|
|
|
409
456
|
"requestBody": base64.b64encode(request_body).decode() if is_byte_request else request_body,
|
|
410
457
|
"requestCookies": request_cookies,
|
|
411
458
|
"timeoutSeconds": timeout_seconds,
|
|
459
|
+
"disableSessionTickets": self.disable_session_tickets,
|
|
460
|
+
"disableHttp3": self.disable_http3,
|
|
412
461
|
}
|
|
413
462
|
if certificate_pinning:
|
|
414
463
|
request_payload["certificatePinningHosts"] = certificate_pinning
|
|
@@ -421,11 +470,17 @@ class Session:
|
|
|
421
470
|
"connectionFlow": self.connection_flow,
|
|
422
471
|
"priorityFrames": self.priority_frames,
|
|
423
472
|
"headerPriority": self.header_priority,
|
|
424
|
-
|
|
473
|
+
# certCompressionAlgos (plural) takes a list as of tls-client
|
|
474
|
+
# v1.16.0's CustomTlsClient schema; wrap the single algorithm
|
|
475
|
+
# this wrapper still accepts as a one-element list.
|
|
476
|
+
"certCompressionAlgos": [self.cert_compression_algo] if self.cert_compression_algo else None,
|
|
425
477
|
"supportedVersions": self.supported_versions,
|
|
426
478
|
"supportedSignatureAlgorithms": self.supported_signature_algorithms,
|
|
427
479
|
"supportedDelegatedCredentialsAlgorithms": self.supported_delegated_credentials_algorithms ,
|
|
428
480
|
"keyShareCurves": self.key_share_curves,
|
|
481
|
+
"alpnProtocols": self.alpn_protocols,
|
|
482
|
+
"alpsProtocols": self.alps_protocols,
|
|
483
|
+
"trustAnchorsPayload": self.trust_anchors_payload,
|
|
429
484
|
}
|
|
430
485
|
else:
|
|
431
486
|
request_payload["tlsClientIdentifier"] = self.client_identifier
|
|
@@ -444,7 +499,7 @@ class Session:
|
|
|
444
499
|
# --- Response -------------------------------------------------------------------------------------------------
|
|
445
500
|
# Error handling
|
|
446
501
|
if response_object["status"] == 0:
|
|
447
|
-
raise
|
|
502
|
+
raise build_exception(response_object["body"])
|
|
448
503
|
# Set response cookies
|
|
449
504
|
response_cookie_jar = extract_cookies_to_jar(
|
|
450
505
|
request_url=url,
|
tls_client/settings.py
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
from typing_extensions import Literal, TypeAlias
|
|
2
2
|
|
|
3
|
+
# Keep this list in sync with the `MappedTLSClients` map in
|
|
4
|
+
# bogdanfinn/tls-client's profiles/profiles.go for the version of the
|
|
5
|
+
# native binaries shipped in tls_client/dependencies/ (currently v1.16.0).
|
|
6
|
+
# Passing a string that isn't in that map fails at the native layer, not
|
|
7
|
+
# here, so an outdated list here just breaks autocomplete/typing, not
|
|
8
|
+
# runtime behavior. Source:
|
|
9
|
+
# https://github.com/bogdanfinn/tls-client/blob/v1.16.0/profiles/profiles.go
|
|
3
10
|
ClientIdentifiers: TypeAlias = Literal[
|
|
4
11
|
# Chrome
|
|
5
12
|
"chrome_103",
|
|
@@ -16,6 +23,23 @@ ClientIdentifiers: TypeAlias = Literal[
|
|
|
16
23
|
"chrome_116_PSK_PQ",
|
|
17
24
|
"chrome_117",
|
|
18
25
|
"chrome_120",
|
|
26
|
+
"chrome_124",
|
|
27
|
+
"chrome_130_PSK",
|
|
28
|
+
"chrome_131",
|
|
29
|
+
"chrome_131_PSK",
|
|
30
|
+
"chrome_133",
|
|
31
|
+
"chrome_133_PSK",
|
|
32
|
+
"chrome_144",
|
|
33
|
+
"chrome_144_PSK",
|
|
34
|
+
"chrome_146",
|
|
35
|
+
"chrome_146_PSK",
|
|
36
|
+
"chrome_150",
|
|
37
|
+
"chrome_150_PSK",
|
|
38
|
+
"chrome_152",
|
|
39
|
+
"chrome_152_PSK",
|
|
40
|
+
# Brave
|
|
41
|
+
"brave_146",
|
|
42
|
+
"brave_146_PSK",
|
|
19
43
|
# Safari
|
|
20
44
|
"safari_15_6_1",
|
|
21
45
|
"safari_16_0",
|
|
@@ -23,8 +47,12 @@ ClientIdentifiers: TypeAlias = Literal[
|
|
|
23
47
|
"safari_ios_15_5",
|
|
24
48
|
"safari_ios_15_6",
|
|
25
49
|
"safari_ios_16_0",
|
|
50
|
+
"safari_ios_17_0",
|
|
51
|
+
"safari_ios_18_0",
|
|
52
|
+
"safari_ios_18_5",
|
|
53
|
+
"safari_ios_26_0",
|
|
26
54
|
# iPadOS (Safari)
|
|
27
|
-
"
|
|
55
|
+
"safari_ipad_15_6",
|
|
28
56
|
# FireFox
|
|
29
57
|
"firefox_102",
|
|
30
58
|
"firefox_104",
|
|
@@ -34,6 +62,14 @@ ClientIdentifiers: TypeAlias = Literal[
|
|
|
34
62
|
"firefox_110",
|
|
35
63
|
"firefox_117",
|
|
36
64
|
"firefox_120",
|
|
65
|
+
"firefox_123",
|
|
66
|
+
"firefox_132",
|
|
67
|
+
"firefox_133",
|
|
68
|
+
"firefox_135",
|
|
69
|
+
"firefox_146_PSK",
|
|
70
|
+
"firefox_147",
|
|
71
|
+
"firefox_147_PSK",
|
|
72
|
+
"firefox_148",
|
|
37
73
|
# Opera
|
|
38
74
|
"opera_89",
|
|
39
75
|
"opera_90",
|
|
@@ -51,14 +87,17 @@ ClientIdentifiers: TypeAlias = Literal[
|
|
|
51
87
|
"zalando_android_mobile",
|
|
52
88
|
"nike_ios_mobile",
|
|
53
89
|
"nike_android_mobile",
|
|
90
|
+
"cloudscraper",
|
|
54
91
|
"mms_ios",
|
|
92
|
+
"mms_ios_1",
|
|
55
93
|
"mms_ios_2",
|
|
56
94
|
"mms_ios_3",
|
|
57
95
|
"mesh_ios",
|
|
96
|
+
"mesh_ios_1",
|
|
58
97
|
"mesh_ios_2",
|
|
59
98
|
"mesh_android",
|
|
99
|
+
"mesh_android_1",
|
|
60
100
|
"mesh_android_2",
|
|
61
101
|
"confirmed_ios",
|
|
62
102
|
"confirmed_android",
|
|
63
|
-
|
|
64
|
-
]
|
|
103
|
+
]
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: python-tls-client
|
|
3
|
-
Version: 1.0.3
|
|
4
|
-
Summary: Advanced Python HTTP Client with TLS fingerprint spoofing (fork with binary-response fix and certificate pinning)
|
|
5
|
-
Author: Emanuele Scarlata
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/Fenix46/Python-Tls-Client
|
|
8
|
-
Project-URL: Source, https://github.com/Fenix46/Python-Tls-Client
|
|
9
|
-
Project-URL: Original, https://github.com/FlorianREGAZ/Python-Tls-Client
|
|
10
|
-
Classifier: Environment :: Web Environment
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: Natural Language :: English
|
|
13
|
-
Classifier: Operating System :: Unix
|
|
14
|
-
Classifier: Operating System :: MacOS :: MacOS X
|
|
15
|
-
Classifier: Operating System :: Microsoft :: Windows
|
|
16
|
-
Classifier: Programming Language :: Python
|
|
17
|
-
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
24
|
-
Classifier: Topic :: Internet :: WWW/HTTP
|
|
25
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
26
|
-
Requires-Python: >=3.8
|
|
27
|
-
Description-Content-Type: text/markdown
|
|
28
|
-
License-File: LICENSE
|
|
29
|
-
Requires-Dist: typing-extensions
|
|
30
|
-
Dynamic: license-file
|
|
31
|
-
|
|
32
|
-
# Python-TLS-Client
|
|
33
|
-
|
|
34
|
-
> **Nota:** questo è un fork di [Python-Tls-Client](https://github.com/FlorianREGAZ/Python-Tls-Client)
|
|
35
|
-
> di Florian Zager, pubblicato su PyPI con nome diverso (`python-tls-client`) perché
|
|
36
|
-
> il nome originale `tls-client` è già occupato dal progetto upstream.
|
|
37
|
-
> Il modulo importabile resta invariato: `import tls_client`.
|
|
38
|
-
>
|
|
39
|
-
> Modifiche rispetto all'originale:
|
|
40
|
-
> - Fix corruzione risposte binarie (protobuf, immagini, ...) — vedi Changelog 1.0.2
|
|
41
|
-
> - Certificate pinning (vedi Changelog 1.0.1)
|
|
42
|
-
|
|
43
|
-
Python-TLS-Client is an advanced HTTP library based on requests and tls-client.
|
|
44
|
-
|
|
45
|
-
# Installation
|
|
46
|
-
```
|
|
47
|
-
pip install python-tls-client
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
# Examples
|
|
51
|
-
The syntax is inspired by [requests](https://github.com/psf/requests), so its very similar and there are only very few things that are different.
|
|
52
|
-
|
|
53
|
-
Example 1 - Preset:
|
|
54
|
-
```python
|
|
55
|
-
import tls_client
|
|
56
|
-
|
|
57
|
-
# You can also use the following as `client_identifier`:
|
|
58
|
-
# Chrome --> chrome_103, chrome_104, chrome_105, chrome_106, chrome_107, chrome_108, chrome109, Chrome110,
|
|
59
|
-
# chrome111, chrome112, chrome_116_PSK, chrome_116_PSK_PQ, chrome_117, chrome_120
|
|
60
|
-
# Firefox --> firefox_102, firefox_104, firefox108, Firefox110, firefox_117, firefox_120
|
|
61
|
-
# Opera --> opera_89, opera_90
|
|
62
|
-
# Safari --> safari_15_3, safari_15_6_1, safari_16_0
|
|
63
|
-
# iOS --> safari_ios_15_5, safari_ios_15_6, safari_ios_16_0
|
|
64
|
-
# iPadOS --> safari_ios_15_6
|
|
65
|
-
# Android --> okhttp4_android_7, okhttp4_android_8, okhttp4_android_9, okhttp4_android_10, okhttp4_android_11,
|
|
66
|
-
# okhttp4_android_12, okhttp4_android_13
|
|
67
|
-
#
|
|
68
|
-
# more client identifiers can be found in settings.py
|
|
69
|
-
|
|
70
|
-
session = tls_client.Session(
|
|
71
|
-
client_identifier="chrome112",
|
|
72
|
-
random_tls_extension_order=True
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
res = session.get(
|
|
76
|
-
"https://www.example.com/",
|
|
77
|
-
headers={
|
|
78
|
-
"key1": "value1",
|
|
79
|
-
},
|
|
80
|
-
proxy="http://user:password@host:port"
|
|
81
|
-
)
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Example 2 - Custom:
|
|
85
|
-
```python
|
|
86
|
-
import tls_client
|
|
87
|
-
|
|
88
|
-
session = tls_client.Session(
|
|
89
|
-
ja3_string="771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
|
|
90
|
-
h2_settings={
|
|
91
|
-
"HEADER_TABLE_SIZE": 65536,
|
|
92
|
-
"MAX_CONCURRENT_STREAMS": 1000,
|
|
93
|
-
"INITIAL_WINDOW_SIZE": 6291456,
|
|
94
|
-
"MAX_HEADER_LIST_SIZE": 262144
|
|
95
|
-
},
|
|
96
|
-
h2_settings_order=[
|
|
97
|
-
"HEADER_TABLE_SIZE",
|
|
98
|
-
"MAX_CONCURRENT_STREAMS",
|
|
99
|
-
"INITIAL_WINDOW_SIZE",
|
|
100
|
-
"MAX_HEADER_LIST_SIZE"
|
|
101
|
-
],
|
|
102
|
-
supported_signature_algorithms=[
|
|
103
|
-
"ECDSAWithP256AndSHA256",
|
|
104
|
-
"PSSWithSHA256",
|
|
105
|
-
"PKCS1WithSHA256",
|
|
106
|
-
"ECDSAWithP384AndSHA384",
|
|
107
|
-
"PSSWithSHA384",
|
|
108
|
-
"PKCS1WithSHA384",
|
|
109
|
-
"PSSWithSHA512",
|
|
110
|
-
"PKCS1WithSHA512",
|
|
111
|
-
],
|
|
112
|
-
supported_versions=["GREASE", "1.3", "1.2"],
|
|
113
|
-
key_share_curves=["GREASE", "X25519"],
|
|
114
|
-
cert_compression_algo="brotli",
|
|
115
|
-
pseudo_header_order=[
|
|
116
|
-
":method",
|
|
117
|
-
":authority",
|
|
118
|
-
":scheme",
|
|
119
|
-
":path"
|
|
120
|
-
],
|
|
121
|
-
connection_flow=15663105,
|
|
122
|
-
header_order=[
|
|
123
|
-
"accept",
|
|
124
|
-
"user-agent",
|
|
125
|
-
"accept-encoding",
|
|
126
|
-
"accept-language"
|
|
127
|
-
]
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
res = session.post(
|
|
131
|
-
"https://www.example.com/",
|
|
132
|
-
headers={
|
|
133
|
-
"key1": "value1",
|
|
134
|
-
},
|
|
135
|
-
json={
|
|
136
|
-
"key1": "key2"
|
|
137
|
-
}
|
|
138
|
-
)
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
# Pyinstaller / Pyarmor
|
|
142
|
-
**If you want to pack the library with Pyinstaller or Pyarmor, make sure to add this to your command:**
|
|
143
|
-
|
|
144
|
-
Linux - Ubuntu / x86:
|
|
145
|
-
```
|
|
146
|
-
--add-binary '{path_to_library}/tls_client/dependencies/tls-client-x86.so:tls_client/dependencies'
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
Linux Alpine / AMD64:
|
|
150
|
-
```
|
|
151
|
-
--add-binary '{path_to_library}/tls_client/dependencies/tls-client-amd64.so:tls_client/dependencies'
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
MacOS M1 and older:
|
|
155
|
-
```
|
|
156
|
-
--add-binary '{path_to_library}/tls_client/dependencies/tls-client-x86.dylib:tls_client/dependencies'
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
MacOS M2:
|
|
160
|
-
```
|
|
161
|
-
--add-binary '{path_to_library}/tls_client/dependencies/tls-client-arm64.dylib:tls_client/dependencies'
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
Windows:
|
|
165
|
-
```
|
|
166
|
-
--add-binary '{path_to_library}/tls_client/dependencies/tls-client-64.dll;tls_client/dependencies'
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
# Acknowledgements
|
|
170
|
-
Big shout out to [Bogdanfinn](https://github.com/bogdanfinn) for open sourcing his [tls-client](https://github.com/bogdanfinn/tls-client) in Golang.
|
|
171
|
-
Also I wanted to keep the syntax as similar as possible to [requests](https://github.com/psf/requests), as most people use it and are familiar with it!
|
|
172
|
-
|
|
173
|
-
# Changelog
|
|
174
|
-
|
|
175
|
-
## 1.0.3
|
|
176
|
-
- **Fix**: on Linux, 64-bit x86 machines (`platform.machine() == "x86_64"`) were
|
|
177
|
-
incorrectly matched by the `"x86" in machine()` check in `cffi.py` and loaded
|
|
178
|
-
the 32-bit `tls-client-x86.so` binary instead of the 64-bit
|
|
179
|
-
`tls-client-amd64.so`, causing the native library to fail to load on the vast
|
|
180
|
-
majority of Linux hosts. `machine()` is now matched explicitly against
|
|
181
|
-
`x86_64`/`amd64`/`AMD64` before falling back to the 32-bit binary.
|
|
182
|
-
|
|
183
|
-
## 1.0.2
|
|
184
|
-
- **Fix**: binary response bodies (protobuf, images, ...) were corrupted by a
|
|
185
|
-
UTF-8 `byteReplacer` (U+FFFD) inside the Go C library. Every byte > 127 that
|
|
186
|
-
did not form a valid UTF-8 sequence was replaced by `EF BF BD`.
|
|
187
|
-
- `sessions.py`: now sends `isByteResponse: True` in the request payload so
|
|
188
|
-
the backend returns the body as a base64 data-URI.
|
|
189
|
-
- `response.py`: `build_response` decodes the data-URI back to raw bytes for
|
|
190
|
-
`Response.content`; `Response.text` is a UTF-8 view (with replacement
|
|
191
|
-
chars for invalid sequences).
|
|
192
|
-
- Added `tests/test_binary_response.py` (regression suite, needs network).
|
|
193
|
-
|
|
194
|
-
## 1.0.1
|
|
195
|
-
- Certificate pinning
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
python_tls_client-1.0.3.dist-info/licenses/LICENSE,sha256=issbIg7ujiqhtvXsx5mmKhdeZ6zd39ew_nEzvvwImfw,1123
|
|
2
|
-
tls_client/__init__.py,sha256=ORP-QdcnLxOEs42G3yPr6txUxryz7jUOA_AtHMafpf8,655
|
|
3
|
-
tls_client/__version__.py,sha256=44prGvLt4nYxvVWOCD33Ky2Zb-QVa7TxgYWnW86DWvU,445
|
|
4
|
-
tls_client/cffi.py,sha256=p6arENyBf1-x43CPOsKAT0ADhy2CaAFAEXdyLj2duzs,1018
|
|
5
|
-
tls_client/cookies.py,sha256=5ujSFL6V8bDMCH8Mow6qoj5dP5GjfMJTd8_jWottvpM,15153
|
|
6
|
-
tls_client/exceptions.py,sha256=edxP4P0NVOK-1r0zNRAMt4yh97LArXz8i6SU9oTSYQY,78
|
|
7
|
-
tls_client/response.py,sha256=5Ji09s3fnrSst3N5n61fJ7motHNF3Jjsk6WBmsZ6iOk,3126
|
|
8
|
-
tls_client/sessions.py,sha256=Uwx4YlhwmUkJonuqu6t0rtINiEpxbwPHbuJ4R2fc3Jw,18651
|
|
9
|
-
tls_client/settings.py,sha256=sknNBPsNOujgIDO5Jo4L8RZFaY6gtGuWUvpJkJ0rBog,1289
|
|
10
|
-
tls_client/structures.py,sha256=zb1wflxxWoKFqe8NMT2qEvIh7RA-kN459ZcNhW8p_Uo,2443
|
|
11
|
-
tls_client/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
tls_client/dependencies/tls-client-32.dll,sha256=RYiPCa-Df2FRn1E5A-hqFs-vgkKNq-HyI5BrBZjgj7Q,16512055
|
|
13
|
-
tls_client/dependencies/tls-client-64.dll,sha256=sXd3hlZFX2uEghVCONMjo95NdPKot6Yr2XMlGiWe24c,18685673
|
|
14
|
-
tls_client/dependencies/tls-client-amd64.so,sha256=atLDjHTHV_REcR4XfmtzjeffRnObntzoPzV2GudSJCg,12091312
|
|
15
|
-
tls_client/dependencies/tls-client-arm64.dylib,sha256=9aKdKT0X97KOrXIyi2ilnGDpyi1iIBE8QFaWqxHf8to,10778130
|
|
16
|
-
tls_client/dependencies/tls-client-arm64.so,sha256=MCwijkBgGDvxs9KPpSU53zou73NxzXjmUiZMZwUTpFE,11116584
|
|
17
|
-
tls_client/dependencies/tls-client-x86.dylib,sha256=dEzNbwEuzbc_CWkBW3NyqhBOQY4IJJSMUT_2h5z8LBk,11687888
|
|
18
|
-
tls_client/dependencies/tls-client-x86.so,sha256=xY-FoLKXMRxEJHyb9W1TKXVmuARnSeQw4T8w6cbuHLI,12045392
|
|
19
|
-
python_tls_client-1.0.3.dist-info/METADATA,sha256=ouTIRQ4XKfhMjoplf8POykNnVou4jG5RnLCAsjhjlWA,6784
|
|
20
|
-
python_tls_client-1.0.3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
21
|
-
python_tls_client-1.0.3.dist-info/top_level.txt,sha256=yLrfQtbTRfECzPN62knWdGNmbhcPRev1grCAMuEsEz0,11
|
|
22
|
-
python_tls_client-1.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|