From 7eb4a2aafe49a279c29b6d1f0ed0f42e9736194f Mon Sep 17 00:00:00 2001 From: Illia Volochii Date: Wed, 18 Jun 2025 16:30:35 +0300 Subject: [PATCH] Merge commit from fork CVE: CVE-2025-50182 Upstream-Status: Backport [https://github.com/urllib3/urllib3/commit/7eb4a2aafe49a279c29b6d1f0ed0f42e9736194f] Signed-off-by: Yogita Urade --- docs/reference/contrib/emscripten.rst | 2 +- src/urllib3/contrib/emscripten/fetch.py | 20 ++++++++++ test/contrib/emscripten/test_emscripten.py | 46 ++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/docs/reference/contrib/emscripten.rst b/docs/reference/contrib/emscripten.rst index a8f1cda..4670757 100644 --- a/docs/reference/contrib/emscripten.rst +++ b/docs/reference/contrib/emscripten.rst @@ -65,7 +65,7 @@ Features which are usable with Emscripten support are: * Timeouts * Retries * Streaming (with Web Workers and Cross-Origin Isolation) -* Redirects (determined by browser/runtime, not restrictable with urllib3) +* Redirects (urllib3 controls redirects in Node.js but not in browsers where behavior is determined by runtime) * Decompressing response bodies Features which don't work with Emscripten: diff --git a/src/urllib3/contrib/emscripten/fetch.py b/src/urllib3/contrib/emscripten/fetch.py index a514306..6695821 100644 --- a/src/urllib3/contrib/emscripten/fetch.py +++ b/src/urllib3/contrib/emscripten/fetch.py @@ -573,6 +573,11 @@ def send_jspi_request( "method": request.method, "signal": js_abort_controller.signal, } + # Node.js returns the whole response (unlike opaqueredirect in browsers), + # so urllib3 can set `redirect: manual` to control redirects itself. + # https://stackoverflow.com/a/78524615 + if _is_node_js(): + fetch_data["redirect"] = "manual" # Call JavaScript fetch (async api, returns a promise) fetcher_promise_js = js.fetch(request.url, _obj_from_dict(fetch_data)) # Now suspend WebAssembly until we resolve that promise @@ -693,6 +698,21 @@ def has_jspi() -> bool: return False +def _is_node_js() -> bool: + """ + Check if we are in Node.js. + + :return: True if we are in Node.js. + :rtype: bool + """ + return ( + hasattr(js, "process") + and hasattr(js.process, "release") + # According to the Node.js documentation, the release name is always "node". + and js.process.release.name == "node" + ) + + def streaming_ready() -> bool | None: if _fetcher: return _fetcher.streaming_ready diff --git a/test/contrib/emscripten/test_emscripten.py b/test/contrib/emscripten/test_emscripten.py index 5eaa674..fbf89fc 100644 --- a/test/contrib/emscripten/test_emscripten.py +++ b/test/contrib/emscripten/test_emscripten.py @@ -960,6 +960,52 @@ def test_redirects( ) +@pytest.mark.with_jspi +def test_disabled_redirects( + selenium_coverage: typing.Any, testserver_http: PyodideServerInfo +) -> None: + """ + Test that urllib3 can control redirects in Node.js. + """ + + @run_in_pyodide # type: ignore[misc] + def pyodide_test(selenium_coverage: typing.Any, host: str, port: int) -> None: + import pytest + + from urllib3 import PoolManager, request + from urllib3.contrib.emscripten.fetch import _is_node_js + from urllib3.exceptions import MaxRetryError + + if not _is_node_js(): + pytest.skip("urllib3 does not control redirects in browsers.") + + redirect_url = f"http://{host}:{port}/redirect" + + with PoolManager(retries=0) as http: + with pytest.raises(MaxRetryError): + http.request("GET", redirect_url) + + response = http.request("GET", redirect_url, redirect=False) + assert response.status == 303 + + with PoolManager(retries=False) as http: + response = http.request("GET", redirect_url) + assert response.status == 303 + + with pytest.raises(MaxRetryError): + request("GET", redirect_url, retries=0) + + response = request("GET", redirect_url, redirect=False) + assert response.status == 303 + + response = request("GET", redirect_url, retries=0, redirect=False) + assert response.status == 303 + + pyodide_test( + selenium_coverage, testserver_http.http_host, testserver_http.http_port + ) + + def test_insecure_requests_warning( selenium_coverage: typing.Any, testserver_http: PyodideServerInfo ) -> None: -- 2.40.0