Repository: pallets/werkzeug (python)

Issue:
WebSocket requests from Firefox fail with a 400 error
Firefox issues their WebSocket requests with the following connection header:

    Connection: keep-alive, Upgrade

While Chrome and other browsers do:

    Connection: Upgrade

The WebSocket routing logic needs to be made more robust to accept the general format of the Connection header. I will work on this fix and submit a PR in the next few days.

Environment:

- Python version: Any
- Werkzeug version: 2.0.0


Pull request description:
Handle multiple tokens in Connection header when routing WebSocket
Parse the `Connection` header, in case it comes with multiple tokens (as seen in WebSocket requests sent by Firefox).

<!--
Link to relevant issues or previous PRs, one per line. Use "fixes" to
automatically close an issue.
-->

- fixes #2131 

<!--
Ensure each step in CONTRIBUTING.rst is complete by adding an "x" to
each box below.

If only docs were changed, these aren't relevant and can be removed.
-->

Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [ ] Add or update relevant docs, in the docs folder and in code.
- [x] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [ ] Add `.. versionchanged::` entries in any relevant code docs.
- [x] Run `pre-commit` hooks and fix any issues.
- [x] Run `pytest` and `tox`, no tests failed.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 1efd2847..deda16b8 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,6 +5,9 @@ Version 2.0.2
 
 Unreleased
 
+-   Handle multiple tokens in ``Connection`` header when routing
+    WebSocket requests. :issue:`2131`
+
 
 Version 2.0.1
 -------------
diff --git a/src/werkzeug/routing.py b/src/werkzeug/routing.py
index 10438759..345871ee 100644
--- a/src/werkzeug/routing.py
+++ b/src/werkzeug/routing.py
@@ -1650,11 +1650,12 @@ class Map:
         environ = _get_environ(environ)
         wsgi_server_name = get_host(environ).lower()
         scheme = environ["wsgi.url_scheme"]
+        upgrade = any(
+            v.strip() == "upgrade"
+            for v in environ.get("HTTP_CONNECTION", "").lower().split(",")
+        )
 
-        if (
-            environ.get("HTTP_CONNECTION", "").lower() == "upgrade"
-            and environ.get("HTTP_UPGRADE", "").lower() == "websocket"
-        ):
+        if upgrade and environ.get("HTTP_UPGRADE", "").lower() == "websocket":
             scheme = "wss" if scheme == "https" else "ws"
 
         if server_name is None:


Test patch:
diff --git a/tests/test_routing.py b/tests/test_routing.py
index 4a88f2f4..1d4cef90 100644
--- a/tests/test_routing.py
+++ b/tests/test_routing.py
@@ -73,6 +73,17 @@ def test_basic_routing():
     with pytest.raises(r.WebsocketMismatch):
         adapter.match("/ws", websocket=False)
 
+    adapter = map.bind_to_environ(
+        create_environ(
+            "/ws?foo=bar",
+            "http://example.org/",
+            headers=[("Connection", "keep-alive, Upgrade"), ("upgrade", "websocket")],
+        )
+    )
+    assert adapter.match("/ws") == ("ws", {})
+    with pytest.raises(r.WebsocketMismatch):
+        adapter.match("/ws", websocket=False)
+
 
 def test_merge_slashes_match():
     url_map = r.Map(