Skip to content

Commit 7eb4a2a

Browse files
authored
Merge commit from fork
1 parent f05b132 commit 7eb4a2a

4 files changed

Lines changed: 69 additions & 1 deletion

File tree

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
- Fixed a security issue where restricting the maximum number of followed
55
redirects at the ``urllib3.PoolManager`` level via the ``retries`` parameter
66
did not work.
7+
- Made the Node.js runtime respect redirect parameters such as ``retries``
8+
and ``redirects``.
79
- TODO: add other entries in the release PR.
810

911

docs/reference/contrib/emscripten.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ Features which are usable with Emscripten support are:
6565
* Timeouts
6666
* Retries
6767
* Streaming (with Web Workers and Cross-Origin Isolation)
68-
* Redirects (determined by browser/runtime, not restrictable with urllib3)
68+
* Redirects (urllib3 controls redirects in Node.js but not in browsers where behavior is determined by runtime)
6969
* Decompressing response bodies
7070

7171
Features which don't work with Emscripten:

src/urllib3/contrib/emscripten/fetch.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,11 @@ def send_jspi_request(
573573
"method": request.method,
574574
"signal": js_abort_controller.signal,
575575
}
576+
# Node.js returns the whole response (unlike opaqueredirect in browsers),
577+
# so urllib3 can set `redirect: manual` to control redirects itself.
578+
# https://stackoverflow.com/a/78524615
579+
if _is_node_js():
580+
fetch_data["redirect"] = "manual"
576581
# Call JavaScript fetch (async api, returns a promise)
577582
fetcher_promise_js = js.fetch(request.url, _obj_from_dict(fetch_data))
578583
# Now suspend WebAssembly until we resolve that promise
@@ -693,6 +698,21 @@ def has_jspi() -> bool:
693698
return False
694699

695700

701+
def _is_node_js() -> bool:
702+
"""
703+
Check if we are in Node.js.
704+
705+
:return: True if we are in Node.js.
706+
:rtype: bool
707+
"""
708+
return (
709+
hasattr(js, "process")
710+
and hasattr(js.process, "release")
711+
# According to the Node.js documentation, the release name is always "node".
712+
and js.process.release.name == "node"
713+
)
714+
715+
696716
def streaming_ready() -> bool | None:
697717
if _fetcher:
698718
return _fetcher.streaming_ready

test/contrib/emscripten/test_emscripten.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,52 @@ def pyodide_test(selenium_coverage: typing.Any, host: str, port: int) -> None:
960960
)
961961

962962

963+
@pytest.mark.with_jspi
964+
def test_disabled_redirects(
965+
selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
966+
) -> None:
967+
"""
968+
Test that urllib3 can control redirects in Node.js.
969+
"""
970+
971+
@run_in_pyodide # type: ignore[misc]
972+
def pyodide_test(selenium_coverage: typing.Any, host: str, port: int) -> None:
973+
import pytest
974+
975+
from urllib3 import PoolManager, request
976+
from urllib3.contrib.emscripten.fetch import _is_node_js
977+
from urllib3.exceptions import MaxRetryError
978+
979+
if not _is_node_js():
980+
pytest.skip("urllib3 does not control redirects in browsers.")
981+
982+
redirect_url = f"http://{host}:{port}/redirect"
983+
984+
with PoolManager(retries=0) as http:
985+
with pytest.raises(MaxRetryError):
986+
http.request("GET", redirect_url)
987+
988+
response = http.request("GET", redirect_url, redirect=False)
989+
assert response.status == 303
990+
991+
with PoolManager(retries=False) as http:
992+
response = http.request("GET", redirect_url)
993+
assert response.status == 303
994+
995+
with pytest.raises(MaxRetryError):
996+
request("GET", redirect_url, retries=0)
997+
998+
response = request("GET", redirect_url, redirect=False)
999+
assert response.status == 303
1000+
1001+
response = request("GET", redirect_url, retries=0, redirect=False)
1002+
assert response.status == 303
1003+
1004+
pyodide_test(
1005+
selenium_coverage, testserver_http.http_host, testserver_http.http_port
1006+
)
1007+
1008+
9631009
def test_insecure_requests_warning(
9641010
selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
9651011
) -> None:

0 commit comments

Comments
 (0)