mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-26 12:24:46 +00:00
Match the Upgrade websocket token rather than the whole field value
RFC 9110 7.8 defines Upgrade as a comma-separated list of protocols and asks
recipients to match each protocol-name case-insensitively; RFC 6455 4.2.1 asks
for a header field containing the value "websocket". Both handshake checks
instead read occurrence zero and required the whole field value to be exactly
"websocket", so a client offering "websocket, HTTP/3.0" -- or naming websocket
on a second Upgrade field line -- was answered 404 rather than 101.
This is the defect ffe2a1c fixed for Connection two lines below, and
has_header_token() is already called in both of these functions.
The client-side check loosens what we accept back from a server, which is the
same reading: a server answering 101 may name websocket alongside another
protocol, and rejecting that handshake was ours to get wrong.
This commit is contained in:
1 parent
8b19c288e8
commit
0151b3e23e
2 files changed
+44
-9
No files matched your search
@@ -5296,11 +5296,11 @@ inline std::string websocket_accept_key(const std::string &client_key) {
|
|||||||
inline bool is_websocket_upgrade(const Request &req) {
|
inline bool is_websocket_upgrade(const Request &req) {
|
||||||
if (req.method != "GET") { return false; }
|
if (req.method != "GET") { return false; }
|
||||||
|
|
||||||
// Check Upgrade: websocket (case-insensitive)
|
// Check Upgrade: websocket. RFC 9110 7.8 defines Upgrade as a comma-separated
|
||||||
auto upgrade_it = req.headers.find("Upgrade");
|
// list of protocols and asks recipients to match each name
|
||||||
if (upgrade_it == req.headers.end()) { return false; }
|
// case-insensitively, so look for the token rather than compare the whole
|
||||||
auto upgrade_val = case_ignore::to_lower(upgrade_it->second);
|
// field value.
|
||||||
if (upgrade_val != "websocket") { return false; }
|
if (!has_header_token(req.headers, "Upgrade", "websocket")) { return false; }
|
||||||
|
|
||||||
// Check Connection: Upgrade
|
// Check Connection: Upgrade
|
||||||
if (!has_header_token(req.headers, "Connection", "upgrade")) { return false; }
|
if (!has_header_token(req.headers, "Connection", "upgrade")) { return false; }
|
||||||
@@ -7912,10 +7912,8 @@ inline bool read_websocket_upgrade_response(Stream &strm,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify Upgrade: websocket (case-insensitive)
|
// Verify Upgrade: websocket (a comma-separated list, matched per token)
|
||||||
auto upgrade_it = headers.find("Upgrade");
|
if (!has_header_token(headers, "Upgrade", "websocket")) {
|
||||||
if (upgrade_it == headers.end() ||
|
|
||||||
case_ignore::to_lower(upgrade_it->second) != "websocket") {
|
|
||||||
upgrade.error = Error::WebSocketHandshake;
|
upgrade.error = Error::WebSocketHandshake;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21561,6 +21561,43 @@ TEST(WebSocketTest, ConnectionHeaderNeedsCompleteUpgradeToken) {
|
|||||||
EXPECT_FALSE(detail::is_websocket_upgrade(make_request({})));
|
EXPECT_FALSE(detail::is_websocket_upgrade(make_request({})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(WebSocketTest, UpgradeHeaderNeedsCompleteWebsocketToken) {
|
||||||
|
// RFC 9110 7.8 defines Upgrade as a comma-separated list of protocols and
|
||||||
|
// asks recipients to match each protocol-name case-insensitively, and RFC
|
||||||
|
// 6455 4.2.1 asks for a header field containing the value "websocket". A
|
||||||
|
// client naming websocket alongside another protocol, or on a second field
|
||||||
|
// line, is offering websocket; a value that merely contains "websocket" as a
|
||||||
|
// substring is a different protocol name and is not.
|
||||||
|
auto make_request = [](const std::vector<std::string> &upgrade_values) {
|
||||||
|
Request req;
|
||||||
|
req.method = "GET";
|
||||||
|
for (const auto &value : upgrade_values) {
|
||||||
|
req.headers.emplace("Upgrade", value);
|
||||||
|
}
|
||||||
|
req.headers.emplace("Connection", "Upgrade");
|
||||||
|
req.headers.emplace("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==");
|
||||||
|
req.headers.emplace("Sec-WebSocket-Version", "13");
|
||||||
|
return req;
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_TRUE(detail::is_websocket_upgrade(make_request({"websocket"})));
|
||||||
|
EXPECT_TRUE(detail::is_websocket_upgrade(make_request({"WebSocket"})));
|
||||||
|
EXPECT_TRUE(
|
||||||
|
detail::is_websocket_upgrade(make_request({"websocket, HTTP/3.0"})));
|
||||||
|
EXPECT_TRUE(
|
||||||
|
detail::is_websocket_upgrade(make_request({"HTTP/3.0 , websocket"})));
|
||||||
|
EXPECT_TRUE(
|
||||||
|
detail::is_websocket_upgrade(make_request({"HTTP/3.0", "websocket"})));
|
||||||
|
|
||||||
|
EXPECT_FALSE(detail::is_websocket_upgrade(make_request({"notwebsocket"})));
|
||||||
|
EXPECT_FALSE(detail::is_websocket_upgrade(make_request({"websocket-2"})));
|
||||||
|
EXPECT_FALSE(detail::is_websocket_upgrade(make_request({"xwebsocket"})));
|
||||||
|
EXPECT_FALSE(
|
||||||
|
detail::is_websocket_upgrade(make_request({"HTTP/3.0, notwebsocket"})));
|
||||||
|
EXPECT_FALSE(detail::is_websocket_upgrade(make_request({"h2c"})));
|
||||||
|
EXPECT_FALSE(detail::is_websocket_upgrade(make_request({})));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(WebSocketTest, ServerRejectsHandshakeWithoutUpgradeToken) {
|
TEST(WebSocketTest, ServerRejectsHandshakeWithoutUpgradeToken) {
|
||||||
Server svr;
|
Server svr;
|
||||||
svr.WebSocket("/ws", [](const Request &, ws::WebSocket &) {});
|
svr.WebSocket("/ws", [](const Request &, ws::WebSocket &) {});
|
||||||
|
|||||||
Reference in new issue
Block a user