LibWebSocket: Ensure connection is failed on invalid opening handshake

This commit is contained in:
Tim Ledbetter 2024-10-15 13:18:28 +01:00 committed by Andreas Kling
commit 0b365061d1
Notes: github-actions[bot] 2024-10-16 06:33:43 +00:00
2 changed files with 41 additions and 25 deletions

View file

@ -223,11 +223,24 @@ void WebSocket::send_client_handshake()
VERIFY(success);
}
void WebSocket::fail_connection(u16 close_status_code, WebSocket::Error error_code, ByteString const& reason)
{
dbgln("WebSocket: {}", reason);
set_state(WebSocket::InternalState::Closed);
fatal_error(error_code);
notify_close(close_status_code, reason, false);
}
// The server handshake message is defined in the third list of section 4.1
void WebSocket::read_server_handshake()
{
VERIFY(m_impl);
VERIFY(m_state == WebSocket::InternalState::WaitingForServerHandshake);
auto fail_opening_handshake = [&](ByteString const& reason, CloseStatusCode close_status_code = CloseStatusCode::AbnormalClosure) {
fail_connection(to_underlying(close_status_code), WebSocket::Error::ConnectionUpgradeFailed, reason);
};
// Read the server handshake
if (!m_impl->can_read_line())
return;
@ -236,22 +249,17 @@ void WebSocket::read_server_handshake()
auto header = m_impl->read_line(PAGE_SIZE).release_value_but_fixme_should_propagate_errors();
auto parts = header.split(' ');
if (parts.size() < 2) {
dbgln("WebSocket: Server HTTP Handshake contained HTTP header was malformed");
fatal_error(WebSocket::Error::ConnectionUpgradeFailed);
discard_connection();
fail_opening_handshake("Server HTTP Handshake contained HTTP header was malformed");
return;
}
if (parts[0] != "HTTP/1.1") {
dbgln("WebSocket: Server HTTP Handshake contained HTTP header {} which isn't supported", parts[0]);
fatal_error(WebSocket::Error::ConnectionUpgradeFailed);
discard_connection();
fail_opening_handshake(ByteString::formatted("Server HTTP Handshake contained HTTP header {} which isn't supported", parts[0]));
return;
}
if (parts[1] != "101") {
// 1. If the status code is not 101, handle as per HTTP procedures.
// FIXME : This could be a redirect or a 401 authentication request, which we do not handle.
dbgln("WebSocket: Server HTTP Handshake return status {} which isn't supported", parts[1]);
fatal_error(WebSocket::Error::ConnectionUpgradeFailed);
fail_opening_handshake(ByteString::formatted("Server HTTP Handshake return status {} which isn't supported", parts[1]));
return;
}
m_has_read_server_handshake_first_line = true;
@ -265,20 +273,17 @@ void WebSocket::read_server_handshake()
// Fail the connection if we're missing any of the following:
if (!m_has_read_server_handshake_upgrade) {
// 2. |Upgrade| should be present
dbgln("WebSocket: Server HTTP Handshake didn't contain an |Upgrade| header");
fatal_error(WebSocket::Error::ConnectionUpgradeFailed);
fail_opening_handshake("Server HTTP Handshake didn't contain an |Upgrade| header");
return;
}
if (!m_has_read_server_handshake_connection) {
// 2. |Connection| should be present
dbgln("WebSocket: Server HTTP Handshake didn't contain a |Connection| header");
fatal_error(WebSocket::Error::ConnectionUpgradeFailed);
fail_opening_handshake("Server HTTP Handshake didn't contain a |Connection| header");
return;
}
if (!m_has_read_server_handshake_accept) {
// 2. |Sec-WebSocket-Accept| should be present
dbgln("WebSocket: Server HTTP Handshake didn't contain a |Sec-WebSocket-Accept| header");
fatal_error(WebSocket::Error::ConnectionUpgradeFailed);
fail_opening_handshake("Server HTTP Handshake didn't contain a |Sec-WebSocket-Accept| header");
return;
}
@ -290,8 +295,7 @@ void WebSocket::read_server_handshake()
auto parts = line.split(':');
if (parts.size() < 2) {
// The header field is not valid
dbgln("WebSocket: Got invalid header line {} in the Server HTTP handshake", line);
fatal_error(WebSocket::Error::ConnectionUpgradeFailed);
fail_opening_handshake(ByteString::formatted("Got invalid header line {} in the Server HTTP handshake", line));
return;
}
@ -300,8 +304,7 @@ void WebSocket::read_server_handshake()
if (header_name.equals_ignoring_ascii_case("Upgrade"sv)) {
// 2. |Upgrade| should be case-insensitive "websocket"
if (!parts[1].trim_whitespace().equals_ignoring_ascii_case("websocket"sv)) {
dbgln("WebSocket: Server HTTP Handshake Header |Upgrade| should be 'websocket', got '{}'. Failing connection.", parts[1]);
fatal_error(WebSocket::Error::ConnectionUpgradeFailed);
fail_opening_handshake(ByteString::formatted("Server HTTP Handshake Header |Upgrade| should be 'websocket', got '{}'. Failing connection.", parts[1]));
return;
}
@ -312,7 +315,7 @@ void WebSocket::read_server_handshake()
if (header_name.equals_ignoring_ascii_case("Connection"sv)) {
// 3. |Connection| should be case-insensitive "Upgrade"
if (!parts[1].trim_whitespace().equals_ignoring_ascii_case("Upgrade"sv)) {
dbgln("WebSocket: Server HTTP Handshake Header |Connection| should be 'Upgrade', got '{}'. Failing connection.", parts[1]);
fail_opening_handshake(ByteString::formatted("Server HTTP Handshake Header |Connection| should be 'Upgrade', got '{}'. Failing connection.", parts[1]));
return;
}
@ -331,8 +334,7 @@ void WebSocket::read_server_handshake()
// FIXME: change to TRY() and make method fallible
auto expected_sha1_string = MUST(encode_base64({ expected_sha1.immutable_data(), expected_sha1.data_length() }));
if (!parts[1].trim_whitespace().equals_ignoring_ascii_case(expected_sha1_string)) {
dbgln("WebSocket: Server HTTP Handshake Header |Sec-Websocket-Accept| should be '{}', got '{}'. Failing connection.", expected_sha1_string, parts[1]);
fatal_error(WebSocket::Error::ConnectionUpgradeFailed);
fail_opening_handshake(ByteString::formatted("Server HTTP Handshake Header |Sec-Websocket-Accept| should be '{}', got '{}'. Failing connection.", expected_sha1_string, parts[1]));
return;
}
@ -352,8 +354,7 @@ void WebSocket::read_server_handshake()
}
}
if (!found_extension) {
dbgln("WebSocket: Server HTTP Handshake Header |Sec-WebSocket-Extensions| contains '{}', which is not supported by the client. Failing connection.", trimmed_extension);
fatal_error(WebSocket::Error::ConnectionUpgradeFailed);
fail_opening_handshake(ByteString::formatted("Server HTTP Handshake Header |Sec-WebSocket-Extensions| contains '{}', which is not supported by the client. Failing connection.", trimmed_extension));
return;
}
}
@ -371,8 +372,7 @@ void WebSocket::read_server_handshake()
}
}
if (!found_protocol) {
dbgln("WebSocket: Server HTTP Handshake Header |Sec-WebSocket-Protocol| contains '{}', which is not supported by the client. Failing connection.", server_protocol);
fatal_error(WebSocket::Error::ConnectionUpgradeFailed);
fail_opening_handshake(ByteString::formatted("Server HTTP Handshake Header |Sec-WebSocket-Protocol| contains '{}', which is not supported by the client. Failing connection.", server_protocol));
return;
}
m_subprotocol_in_use = server_protocol;

View file

@ -22,6 +22,20 @@ enum class ReadyState {
Closed = 3,
};
// https://datatracker.ietf.org/doc/html/rfc6455#section-7.4.1
enum class CloseStatusCode : u16 {
Normal = 1000,
GoingAway = 1001,
ProtocolError = 1002,
UnsupportedData = 1003,
AbnormalClosure = 1006,
InvalidPayload = 1007,
PolicyViolation = 1008,
MessageTooBig = 1009,
MissingExtension = 1010,
UnexpectedCondition = 1011,
};
class WebSocket final : public Core::EventReceiver {
C_OBJECT(WebSocket)
public:
@ -101,6 +115,8 @@ private:
void set_state(InternalState);
void fail_connection(u16 close_status_code, WebSocket::Error, ByteString const& reason);
ByteString m_subprotocol_in_use { ByteString::empty() };
ByteString m_websocket_key;