From 3bede8cc3f2192739fbf1a7bc2743f205dcdcbca Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Thu, 11 Jun 2026 22:59:20 +0100 Subject: [PATCH] HttpServer: match the Upgrade header value case-insensitively RFC 6455 section 4.2.1 requires the server to look for "an |Upgrade| header field containing the value 'websocket', treated as an ASCII case-insensitive value"; RFC 9110 section 7.8 likewise defines Upgrade protocol names as case-insensitive. HttpServer::handleConnection compared the value case-sensitively with "websocket", so a client spelling it differently was routed to the plain HTTP callback instead of being upgraded. Chrome's remote debugging proxy (what chrome://inspect attaches through) sends "Upgrade: WebSocket", so an HttpServer-fronted DevTools target answered the attach with a 404 from the HTTP handler instead of completing the websocket handshake. WebSocketHandshake::insensitiveStringCompare had a related defect: it implemented equality as CaseInsensitiveLess::cmp(a, b) == 0, but cmp is a lexicographical less-than, so the expression accepted any value that sorts at or above the expected one. Derive equivalence from the strict weak ordering properly: !cmp(a, b) && !cmp(b, a). --- ixwebsocket/IXHttpServer.cpp | 7 ++++++- ixwebsocket/IXWebSocketHandshake.cpp | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ixwebsocket/IXHttpServer.cpp b/ixwebsocket/IXHttpServer.cpp index 00456629..633b8768 100644 --- a/ixwebsocket/IXHttpServer.cpp +++ b/ixwebsocket/IXHttpServer.cpp @@ -9,6 +9,7 @@ #include "IXGzipCodec.h" #include "IXNetSystem.h" #include "IXSocketConnect.h" +#include "IXStrCaseCompare.h" #include "IXUserAgent.h" #include #include @@ -98,7 +99,11 @@ namespace ix { auto request = std::get<2>(ret); std::shared_ptr response; - if (request->headers["Upgrade"] == "websocket") + // The Upgrade header value is case-insensitive (RFC 6455 4.2.1); + // e.g. Chrome's remote-debugging proxy sends "Upgrade: WebSocket". + const std::string& upgradeHeader = request->headers["Upgrade"]; + if (!CaseInsensitiveLess::cmp(upgradeHeader, "websocket") && + !CaseInsensitiveLess::cmp("websocket", upgradeHeader)) { WebSocketServer::handleUpgrade(std::move(socket), connectionState, request); } diff --git a/ixwebsocket/IXWebSocketHandshake.cpp b/ixwebsocket/IXWebSocketHandshake.cpp index 9ac86b61..986b270c 100644 --- a/ixwebsocket/IXWebSocketHandshake.cpp +++ b/ixwebsocket/IXWebSocketHandshake.cpp @@ -36,7 +36,9 @@ namespace ix bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b) { - return CaseInsensitiveLess::cmp(a, b) == 0; + // Equivalence under the case-insensitive strict weak ordering: neither + // string sorts below the other. (cmp(a, b) == 0 only checked a >= b.) + return !CaseInsensitiveLess::cmp(a, b) && !CaseInsensitiveLess::cmp(b, a); } std::string WebSocketHandshake::genRandomString(const int len)