From 105c010218e0f2b6bac8e1e15503d8fa737c6bc5 Mon Sep 17 00:00:00 2001 From: Lauro Foletti <25608942+lfoletti@users.noreply.github.com> Date: Wed, 4 Nov 2026 20:44:20 +0100 Subject: [PATCH 1/3] =?UTF-8?q?diag:=20en-t=C3=AAtes=20envoy=C3=A9s=20par?= =?UTF-8?q?=20curl=5Fcffi=200.17=20(temporaire)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- tests/test_diag_curl_cffi.py | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/test_diag_curl_cffi.py diff --git a/pyproject.toml b/pyproject.toml index 767db31..4e9519e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ inspect = [ ] # Empreinte TLS Chrome (curl_cffi) et décompression br tls = [ - "curl_cffi>=0.15,<0.17", # 0.17 n'envoie plus l'en-tête User-Agent de l'empreinte imitée + "curl_cffi>=0.15,<0.18", "brotli>=1.2.1", ] # Gestes humains et préparation des profils (warmup, gestes) diff --git a/tests/test_diag_curl_cffi.py b/tests/test_diag_curl_cffi.py new file mode 100644 index 0000000..a547276 --- /dev/null +++ b/tests/test_diag_curl_cffi.py @@ -0,0 +1,38 @@ +"""Diagnostic temporaire (branche curl-cffi-017-ua, à retirer) : en-têtes reçus selon la cible et les en-têtes passés.""" + +from __future__ import annotations + +import asyncio +import json + +import pytest + +from accesspath import _http + +from ._localserver import LocalServer, _send + +cc = pytest.importorskip("curl_cffi") + + +@pytest.fixture(autouse=True) +def _allow_local(monkeypatch): + monkeypatch.setenv("ACCESSPATH_ALLOW_PRIVATE", "1") + + +def test_diagnostic(): + out = {"version": cc.__version__} + for target in ("chrome", "chrome150", "chrome146", "chrome152_macos"): + for hdrs in (None, {"Accept": "text/html"}): + seen = {} + + def h(req, seen=seen): + seen.update(req.headers) + _send(req, 200, b"ok", {"Content-Type": "text/plain"}) + + with LocalServer({"/": h}) as s: + try: + asyncio.run(_http.aimpersonate_get(s.url("/"), headers=hdrs, impersonate=target)) + except Exception as e: # noqa: BLE001 + seen["ERR"] = repr(e) + out[f"{target}|{bool(hdrs)}"] = {k: v[:60] for k, v in seen.items()} + pytest.fail(json.dumps(out, indent=1)) From 7b6c3b442186d88eba859be1fd88ec05d026ada4 Mon Sep 17 00:00:00 2001 From: Lauro Foletti <25608942+lfoletti@users.noreply.github.com> Date: Wed, 4 Nov 2026 20:45:04 +0100 Subject: [PATCH 2/3] =?UTF-8?q?diag:=20relev=C3=A9=20seulement=20sous=20cu?= =?UTF-8?q?rl=5Fcffi=200.17?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_diag_curl_cffi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_diag_curl_cffi.py b/tests/test_diag_curl_cffi.py index a547276..0cb5622 100644 --- a/tests/test_diag_curl_cffi.py +++ b/tests/test_diag_curl_cffi.py @@ -35,4 +35,5 @@ def h(req, seen=seen): except Exception as e: # noqa: BLE001 seen["ERR"] = repr(e) out[f"{target}|{bool(hdrs)}"] = {k: v[:60] for k, v in seen.items()} - pytest.fail(json.dumps(out, indent=1)) + if not cc.__version__.startswith("0.16"): # sur la CI seulement : 0.17 donne son relevé + pytest.fail(json.dumps(out, indent=1)) From c89d7c5e647a97b0bea38dc944161cdc19c43e04 Mon Sep 17 00:00:00 2001 From: Lauro Foletti <25608942+lfoletti@users.noreply.github.com> Date: Wed, 4 Nov 2026 20:49:18 +0100 Subject: [PATCH 3/3] =?UTF-8?q?tests:=20en-t=C3=AAtes=20re=C3=A7us=20lus?= =?UTF-8?q?=20sans=20la=20casse=20;=20curl=5Fcffi=200.17=20admis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- tests/test_diag_curl_cffi.py | 39 ----------------------------------- tests/test_tls_fingerprint.py | 8 ++++--- 3 files changed, 6 insertions(+), 43 deletions(-) delete mode 100644 tests/test_diag_curl_cffi.py diff --git a/pyproject.toml b/pyproject.toml index 4e9519e..90a378d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ inspect = [ ] # Empreinte TLS Chrome (curl_cffi) et décompression br tls = [ - "curl_cffi>=0.15,<0.18", + "curl_cffi>=0.15,<0.18", # 0.17 : en-têtes de l'empreinte en minuscules (Chrome 152) "brotli>=1.2.1", ] # Gestes humains et préparation des profils (warmup, gestes) diff --git a/tests/test_diag_curl_cffi.py b/tests/test_diag_curl_cffi.py deleted file mode 100644 index 0cb5622..0000000 --- a/tests/test_diag_curl_cffi.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Diagnostic temporaire (branche curl-cffi-017-ua, à retirer) : en-têtes reçus selon la cible et les en-têtes passés.""" - -from __future__ import annotations - -import asyncio -import json - -import pytest - -from accesspath import _http - -from ._localserver import LocalServer, _send - -cc = pytest.importorskip("curl_cffi") - - -@pytest.fixture(autouse=True) -def _allow_local(monkeypatch): - monkeypatch.setenv("ACCESSPATH_ALLOW_PRIVATE", "1") - - -def test_diagnostic(): - out = {"version": cc.__version__} - for target in ("chrome", "chrome150", "chrome146", "chrome152_macos"): - for hdrs in (None, {"Accept": "text/html"}): - seen = {} - - def h(req, seen=seen): - seen.update(req.headers) - _send(req, 200, b"ok", {"Content-Type": "text/plain"}) - - with LocalServer({"/": h}) as s: - try: - asyncio.run(_http.aimpersonate_get(s.url("/"), headers=hdrs, impersonate=target)) - except Exception as e: # noqa: BLE001 - seen["ERR"] = repr(e) - out[f"{target}|{bool(hdrs)}"] = {k: v[:60] for k, v in seen.items()} - if not cc.__version__.startswith("0.16"): # sur la CI seulement : 0.17 donne son relevé - pytest.fail(json.dumps(out, indent=1)) diff --git a/tests/test_tls_fingerprint.py b/tests/test_tls_fingerprint.py index 17c882c..192d1c0 100644 --- a/tests/test_tls_fingerprint.py +++ b/tests/test_tls_fingerprint.py @@ -30,16 +30,18 @@ def test_imitation_lit_une_page(): def test_imitation_envoie_une_empreinte_de_navigateur(): - seen = {} + # Noms d'en-têtes lus sans la casse : depuis curl_cffi 0.17, l'empreinte Chrome 152 + # les envoie en minuscules (« user-agent »). + seen = httpx.Headers() def h(req): - seen.update(req.headers) + seen.update(list(req.headers.items())) _send(req, 200, b"ok", {"Content-Type": "text/plain"}) with LocalServer({"/": h}) as s: asyncio.run(_http.aimpersonate_get(s.url("/"), headers={"Accept": "text/html"})) assert "Chrome" in seen.get("User-Agent", "") - assert seen.get("Accept") == "text/html" + assert seen.get_list("Accept") == ["text/html"] # celui de l'appelant, sans doublon de l'empreinte def test_imitation_garde_chaque_redirection(monkeypatch):