Skip to content

Commit 45da614

Browse files
committed
Omit default port from WebSocket handshake Host header (Fix #2480)
The WebSocket upgrade request always appended ":port" to the Host header, violating RFC 6455 Section 4.1 which says the port should be included only when it is not the default (80 for ws, 443 for wss). Some CDNs alter routing when the Host header carries an explicit default port. Build the Host header with detail::make_host_and_port_string, which also brackets IPv6 literal hosts correctly.
1 parent f5c8c98 commit 45da614

2 files changed

Lines changed: 52 additions & 4 deletions

File tree

httplib.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8905,7 +8905,8 @@ inline bool is_field_value(const std::string &s) { return is_field_content(s); }
89058905
} // namespace fields
89068906

89078907
inline bool perform_websocket_handshake(Stream &strm, const std::string &host,
8908-
int port, const std::string &path,
8908+
int port, bool is_ssl,
8909+
const std::string &path,
89098910
const Headers &headers,
89108911
std::string &selected_subprotocol) {
89118912
// Validate path and host
@@ -8931,7 +8932,7 @@ inline bool perform_websocket_handshake(Stream &strm, const std::string &host,
89318932

89328933
// Build upgrade request
89338934
std::string req_str = "GET " + path + " HTTP/1.1\r\n";
8934-
req_str += "Host: " + host + ":" + std::to_string(port) + "\r\n";
8935+
req_str += "Host: " + make_host_and_port_string(host, port, is_ssl) + "\r\n";
89358936
req_str += "Upgrade: websocket\r\n";
89368937
req_str += "Connection: Upgrade\r\n";
89378938
req_str += "Sec-WebSocket-Key: " + client_key + "\r\n";
@@ -20570,9 +20571,15 @@ inline bool WebSocketClient::connect() {
2057020571
return false;
2057120572
}
2057220573

20574+
#ifdef CPPHTTPLIB_SSL_ENABLED
20575+
auto is_ssl = is_ssl_;
20576+
#else
20577+
auto is_ssl = false;
20578+
#endif
20579+
2057320580
std::string selected_subprotocol;
20574-
if (!detail::perform_websocket_handshake(*strm, host_, port_, path_, headers_,
20575-
selected_subprotocol)) {
20581+
if (!detail::perform_websocket_handshake(*strm, host_, port_, is_ssl, path_,
20582+
headers_, selected_subprotocol)) {
2057620583
shutdown_and_close();
2057720584
return false;
2057820585
}

test/test.cc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18975,6 +18975,47 @@ TEST(WebSocketTest, QueryStringInHandshake) {
1897518975
t.join();
1897618976
}
1897718977

18978+
TEST(WebSocketTest, HostHeaderInHandshake) {
18979+
Server svr;
18980+
18981+
std::mutex mtx;
18982+
std::string received_host;
18983+
18984+
svr.WebSocket("/ws", [&](const Request &req, ws::WebSocket &ws) {
18985+
{
18986+
std::lock_guard<std::mutex> lock(mtx);
18987+
received_host = req.get_header_value("Host");
18988+
}
18989+
std::string msg;
18990+
while (ws.read(msg)) {
18991+
ws.send(msg);
18992+
}
18993+
});
18994+
18995+
auto port = svr.bind_to_any_port("localhost");
18996+
std::thread t([&]() { svr.listen_after_bind(); });
18997+
svr.wait_until_ready();
18998+
18999+
ws::WebSocketClient client("ws://localhost:" + std::to_string(port) + "/ws");
19000+
ASSERT_TRUE(client.connect());
19001+
// Round-trip ensures the handler has run and captured the request.
19002+
ASSERT_TRUE(client.send("hello"));
19003+
std::string msg;
19004+
ASSERT_TRUE(client.read(msg));
19005+
client.close();
19006+
19007+
{
19008+
std::lock_guard<std::mutex> lock(mtx);
19009+
// Non-default port must be present in the Host header. Default ports
19010+
// (80/443) are omitted; that logic is covered by
19011+
// MakeHostAndPortStringTest.
19012+
EXPECT_EQ("localhost:" + std::to_string(port), received_host);
19013+
}
19014+
19015+
svr.stop();
19016+
t.join();
19017+
}
19018+
1897819019
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1897919020
class WebSocketSSLIntegrationTest : public ::testing::Test {
1898019021
protected:

0 commit comments

Comments
 (0)