From 6d1049462d75ac3bba6909a3ef325a428c58e825 Mon Sep 17 00:00:00 2001 From: yhirose Date: Sun, 4 Oct 2026 18:34:21 -0400 Subject: [PATCH] Ignore a subprotocol the WebSocket client did not offer A SubProtocolSelector could return a value outside the client's Sec-WebSocket-Protocol list, and the server sent it back as is. The server now treats such a value as no selection. --- README-websocket.md | 2 +- httplib.h | 6 ++++++ test/test.cc | 24 ++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/README-websocket.md b/README-websocket.md index 3629a59..7a3c892 100644 --- a/README-websocket.md +++ b/README-websocket.md @@ -119,7 +119,7 @@ using SubProtocolSelector = std::function &protocols)>; ``` -The `SubProtocolSelector` receives the list of subprotocols proposed by the client (from the `Sec-WebSocket-Protocol` header) and returns the selected one. Return an empty string to decline all proposed subprotocols. +The `SubProtocolSelector` receives the list of subprotocols proposed by the client (from the `Sec-WebSocket-Protocol` header) and returns the selected one. Return an empty string to decline all proposed subprotocols. A returned value that the client did not propose is ignored. ### WebSocket (Server-side) diff --git a/httplib.h b/httplib.h index 5b4b22c..a9b12c7 100644 --- a/httplib.h +++ b/httplib.h @@ -14692,6 +14692,12 @@ Server::process_request(Stream &strm, const std::string &remote_addr, protocols.emplace_back(b, e); }); selected_subprotocol = entry.sub_protocol_selector(protocols); + + // Ignore a selection the client did not offer (RFC 6455 4.2.2) + if (std::find(protocols.begin(), protocols.end(), + selected_subprotocol) == protocols.end()) { + selected_subprotocol.clear(); + } } } diff --git a/test/test.cc b/test/test.cc index feb6990..f187b09 100644 --- a/test/test.cc +++ b/test/test.cc @@ -24061,6 +24061,18 @@ protected: } return ""; }); + + server_->WebSocket( + "/ws-subprotocol-unoffered", + [](const Request &, ws::WebSocket &ws) { + std::string msg; + while (ws.read(msg)) { + ws.send(msg); + } + }, + [](const std::vector &) -> std::string { + return "admin"; + }); } void start_server() { @@ -24403,6 +24415,18 @@ TEST_F(WebSocketIntegrationTest, SubProtocolNoMatch) { client.close(); } +TEST_F(WebSocketIntegrationTest, SubProtocolSelectorReturnsUnoffered) { + Headers headers = {{"Sec-WebSocket-Protocol", "chat"}}; + ws::WebSocketClient client("ws://localhost:" + std::to_string(port_) + + "/ws-subprotocol-unoffered", + headers); + ASSERT_TRUE(client.connect()); + + EXPECT_TRUE(client.subprotocol().empty()); + + client.close(); +} + TEST_F(WebSocketIntegrationTest, SubProtocolNotRequested) { // Connect without requesting any subprotocol ws::WebSocketClient client("ws://localhost:" + std::to_string(port_) +