From 549857e4c1435ec9d3cd8f5bbf3c7197e80a3bf3 Mon Sep 17 00:00:00 2001 From: Anthony Moreau Date: Sat, 7 Jun 2025 16:50:37 +0200 Subject: [PATCH 1/3] More explicit error message when redirected to a non websocket uri --- src/websockets/asyncio/client.py | 6 +++++- tests/asyncio/test_client.py | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/websockets/asyncio/client.py b/src/websockets/asyncio/client.py index 63cd2be2e..06a871d85 100644 --- a/src/websockets/asyncio/client.py +++ b/src/websockets/asyncio/client.py @@ -18,6 +18,7 @@ InvalidProxyMessage, InvalidProxyStatus, InvalidStatus, + InvalidURI, ProxyError, SecurityError, ) @@ -493,7 +494,10 @@ def process_redirect(self, exc: Exception) -> Exception | str: old_ws_uri = parse_uri(self.uri) new_uri = urllib.parse.urljoin(self.uri, exc.response.headers["Location"]) - new_ws_uri = parse_uri(new_uri) + try: + new_ws_uri = parse_uri(new_uri) + except InvalidURI as uri_exception: + raise InvalidURI("Redirection URI is invalid", uri_exception) # If connect() received a socket, it is closed and cannot be reused. if self.connection_kwargs.get("sock") is not None: diff --git a/tests/asyncio/test_client.py b/tests/asyncio/test_client.py index 465ea2bdb..e9f5ddcd5 100644 --- a/tests/asyncio/test_client.py +++ b/tests/asyncio/test_client.py @@ -354,6 +354,25 @@ def redirect(connection, request): "cannot follow redirect to ws://invalid/ with a preexisting socket", ) + async def test_not_a_websocket_redirect(self): + """Client raises an explicit error when redirected to an absolute uri that isn't using websocket protocole.""" + + def redirect(connection, request): + response = connection.respond(http.HTTPStatus.FOUND, "") + response.headers["Location"] = "https://not-a-websocket.com" + return response + + async with serve(*args, process_request=redirect) as server: + host, port = get_host_port(server) + with self.assertRaises(InvalidURI) as raised: + async with connect("ws://overridden/", host=host, port=port): + self.fail("did not raise") + + self.assertEqual( + str(raised.exception), + "Redirection URI is invalid isn't a valid URI: https://not-a-websocket.com isn't a valid URI: scheme isn't ws or wss" + ) + async def test_invalid_uri(self): """Client receives an invalid URI.""" with self.assertRaises(InvalidURI): From ab481fbe2682d8d7984702c281d263441f3699c7 Mon Sep 17 00:00:00 2001 From: Anthony Moreau Date: Sat, 7 Jun 2025 18:19:25 +0200 Subject: [PATCH 2/3] Fix file format --- tests/asyncio/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/asyncio/test_client.py b/tests/asyncio/test_client.py index e9f5ddcd5..6edeec401 100644 --- a/tests/asyncio/test_client.py +++ b/tests/asyncio/test_client.py @@ -370,7 +370,7 @@ def redirect(connection, request): self.assertEqual( str(raised.exception), - "Redirection URI is invalid isn't a valid URI: https://not-a-websocket.com isn't a valid URI: scheme isn't ws or wss" + "Redirection URI is invalid isn't a valid URI: https://not-a-websocket.com isn't a valid URI: scheme isn't ws or wss", ) async def test_invalid_uri(self): From 72c5750364651ce2158ba8766fe2b97da9f053e0 Mon Sep 17 00:00:00 2001 From: Anthony Moreau Date: Sun, 29 Jun 2025 11:29:27 +0200 Subject: [PATCH 3/3] Subclass InvalidURI exception with a better message --- src/websockets/asyncio/client.py | 3 ++- src/websockets/exceptions.py | 10 ++++++++++ tests/asyncio/test_client.py | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/websockets/asyncio/client.py b/src/websockets/asyncio/client.py index 06a871d85..684cc8b49 100644 --- a/src/websockets/asyncio/client.py +++ b/src/websockets/asyncio/client.py @@ -19,6 +19,7 @@ InvalidProxyStatus, InvalidStatus, InvalidURI, + InvalidRedirectURI, ProxyError, SecurityError, ) @@ -497,7 +498,7 @@ def process_redirect(self, exc: Exception) -> Exception | str: try: new_ws_uri = parse_uri(new_uri) except InvalidURI as uri_exception: - raise InvalidURI("Redirection URI is invalid", uri_exception) + raise InvalidRedirectURI(uri_exception.uri, uri_exception.msg) # If connect() received a socket, it is closed and cannot be reused. if self.connection_kwargs.get("sock") is not None: diff --git a/src/websockets/exceptions.py b/src/websockets/exceptions.py index a88deaa66..4a1fc3522 100644 --- a/src/websockets/exceptions.py +++ b/src/websockets/exceptions.py @@ -177,6 +177,16 @@ def __str__(self) -> str: return f"{self.uri} isn't a valid URI: {self.msg}" +class InvalidRedirectURI(InvalidURI): + """ + Raised when redirected to a URI that isn't a valid WebSocket URI. + + """ + + def __str__(self) -> str: + return f"Redirection URI {self.uri} isn't a valid URI: {self.msg}" + + class InvalidProxy(WebSocketException): """ Raised when connecting via a proxy that isn't valid. diff --git a/tests/asyncio/test_client.py b/tests/asyncio/test_client.py index 6edeec401..a12bca189 100644 --- a/tests/asyncio/test_client.py +++ b/tests/asyncio/test_client.py @@ -355,7 +355,7 @@ def redirect(connection, request): ) async def test_not_a_websocket_redirect(self): - """Client raises an explicit error when redirected to an absolute uri that isn't using websocket protocole.""" + """Client raises an explicit error when redirected to an absolute URI that isn't using websocket protocole.""" def redirect(connection, request): response = connection.respond(http.HTTPStatus.FOUND, "") @@ -370,7 +370,7 @@ def redirect(connection, request): self.assertEqual( str(raised.exception), - "Redirection URI is invalid isn't a valid URI: https://not-a-websocket.com isn't a valid URI: scheme isn't ws or wss", + "Redirection URI https://not-a-websocket.com isn't a valid URI: scheme isn't ws or wss", ) async def test_invalid_uri(self):