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

View file

@ -22,6 +22,20 @@ enum class ReadyState {
Closed = 3, 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 { class WebSocket final : public Core::EventReceiver {
C_OBJECT(WebSocket) C_OBJECT(WebSocket)
public: public:
@ -101,6 +115,8 @@ private:
void set_state(InternalState); 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_subprotocol_in_use { ByteString::empty() };
ByteString m_websocket_key; ByteString m_websocket_key;