From e71f7b23b5bf5fed729ac8adafda212aded14ad2 Mon Sep 17 00:00:00 2001 From: Wleeaf <179125857+wleeaf@users.noreply.github.com> Date: Sun, 25 Oct 2026 19:30:45 +0000 Subject: [PATCH] End server handshake error responses and answer 426 for other versions sendErrorResponse wrote the status line and the Server header but not the empty line that ends the header section, so every rejected handshake produced an incomplete HTTP response that a client could only end by waiting for the connection to close. A client asking for a WebSocket version other than 13 got 400 without a Sec-WebSocket-Version header, which RFC 6455 (section 4.2.2) requires so the client knows which version to use. Error responses now end with an empty line, and an unsupported version is answered with 426 and Sec-WebSocket-Version: 13. --- ixwebsocket/IXWebSocketHandshake.cpp | 12 +++++-- ixwebsocket/IXWebSocketHandshake.h | 4 ++- test/IXWebSocketServerTest.cpp | 54 ++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/ixwebsocket/IXWebSocketHandshake.cpp b/ixwebsocket/IXWebSocketHandshake.cpp index 986b270c..39476cab 100644 --- a/ixwebsocket/IXWebSocketHandshake.cpp +++ b/ixwebsocket/IXWebSocketHandshake.cpp @@ -63,7 +63,9 @@ namespace ix return s; } - WebSocketInitResult WebSocketHandshake::sendErrorResponse(int code, const std::string& reason) + WebSocketInitResult WebSocketHandshake::sendErrorResponse(int code, + const std::string& reason, + const std::string& extraHeaders) { std::stringstream ss; ss << "HTTP/1.1 "; @@ -72,6 +74,8 @@ namespace ix ss << reason; ss << "\r\n"; ss << "Server: " << userAgent() << "\r\n"; + ss << extraHeaders; + ss << "\r\n"; // Socket write can only be cancelled through a timeout here, not manually. static std::atomic requestInitCancellation(false); @@ -354,10 +358,12 @@ namespace ix if (version != 13) { - return sendErrorResponse(400, + // Tell the client which version is supported (RFC 6455 section 4.2.2) + return sendErrorResponse(426, "Invalid Sec-WebSocket-Version, " "need 13, got " + - ss.str()); + ss.str(), + "Sec-WebSocket-Version: 13\r\n"); } } diff --git a/ixwebsocket/IXWebSocketHandshake.h b/ixwebsocket/IXWebSocketHandshake.h index a6bf2a15..40a33e9f 100644 --- a/ixwebsocket/IXWebSocketHandshake.h +++ b/ixwebsocket/IXWebSocketHandshake.h @@ -45,7 +45,9 @@ namespace ix std::string genRandomString(const int len); // Parse HTTP headers - WebSocketInitResult sendErrorResponse(int code, const std::string& reason); + WebSocketInitResult sendErrorResponse(int code, + const std::string& reason, + const std::string& extraHeaders = std::string()); bool insensitiveStringCompare(const std::string& a, const std::string& b); diff --git a/test/IXWebSocketServerTest.cpp b/test/IXWebSocketServerTest.cpp index 88394900..e02f989c 100644 --- a/test/IXWebSocketServerTest.cpp +++ b/test/IXWebSocketServerTest.cpp @@ -155,6 +155,60 @@ TEST_CASE("Websocket_server", "[websocket_server]") REQUIRE(server.getClients().size() == 0); } + SECTION("Send a request with an unsupported version. Should return 426 with the supported " + "version and end the response headers") + { + int port = getFreePort(); + ix::WebSocketServer server(port); + std::string connectionId; + REQUIRE(startServer(server, connectionId)); + + std::string errMsg; + bool tls = false; + SocketTLSOptions tlsOptions; + std::shared_ptr socket = createSocket(tls, -1, errMsg, tlsOptions); + std::string host("127.0.0.1"); + auto isCancellationRequested = []() -> bool { return false; }; + bool success = socket->connect(host, port, errMsg, isCancellationRequested); + REQUIRE(success); + + socket->writeBytes("GET / HTTP/1.1\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Version: 8\r\n" + "Sec-WebSocket-Key: foobar\r\n" + "\r\n", + isCancellationRequested); + + auto lineResult = socket->readLine(isCancellationRequested); + REQUIRE(lineResult.first); + + int status = -1; + REQUIRE(sscanf(lineResult.second.c_str(), "HTTP/1.1 %d", &status) == 1); + REQUIRE(status == 426); + + bool supportedVersionSent = false; + bool headersEnded = false; + while (true) + { + lineResult = socket->readLine(isCancellationRequested); + if (!lineResult.first) break; + if (lineResult.second == "\r\n") + { + headersEnded = true; + break; + } + if (lineResult.second == "Sec-WebSocket-Version: 13\r\n") + { + supportedVersionSent = true; + } + } + REQUIRE(supportedVersionSent); + REQUIRE(headersEnded); + + server.stop(); + REQUIRE(server.getClients().size() == 0); + } + SECTION("Connect to the server. Send GET request with correct header") { int port = getFreePort();