From 62ed26164bd160387c80522dfee5db2891b4c0dc Mon Sep 17 00:00:00 2001 From: DexesTTP Date: Sat, 24 Apr 2021 01:46:49 +0200 Subject: Services: Add a WebSocket service The WebSocket service isolates communication with a WebSocket to its own isolated process. Similar to other isolating services, it has its own user and group. --- Userland/Libraries/LibProtocol/CMakeLists.txt | 4 + Userland/Libraries/LibProtocol/WebSocket.cpp | 70 +++++++++++++++ Userland/Libraries/LibProtocol/WebSocket.h | 78 ++++++++++++++++ Userland/Libraries/LibProtocol/WebSocketClient.cpp | 100 +++++++++++++++++++++ Userland/Libraries/LibProtocol/WebSocketClient.h | 45 ++++++++++ Userland/Libraries/LibWebSocket/WebSocket.cpp | 3 + 6 files changed, 300 insertions(+) create mode 100644 Userland/Libraries/LibProtocol/WebSocket.cpp create mode 100644 Userland/Libraries/LibProtocol/WebSocket.h create mode 100644 Userland/Libraries/LibProtocol/WebSocketClient.cpp create mode 100644 Userland/Libraries/LibProtocol/WebSocketClient.h (limited to 'Userland/Libraries') diff --git a/Userland/Libraries/LibProtocol/CMakeLists.txt b/Userland/Libraries/LibProtocol/CMakeLists.txt index 7c2e59d45d..a17b5e728b 100644 --- a/Userland/Libraries/LibProtocol/CMakeLists.txt +++ b/Userland/Libraries/LibProtocol/CMakeLists.txt @@ -1,11 +1,15 @@ set(SOURCES Client.cpp Download.cpp + WebSocket.cpp + WebSocketClient.cpp ) set(GENERATED_SOURCES ../../Services/ProtocolServer/ProtocolClientEndpoint.h ../../Services/ProtocolServer/ProtocolServerEndpoint.h + ../../Services/WebSocket/WebSocketClientEndpoint.h + ../../Services/WebSocket/WebSocketServerEndpoint.h ) serenity_lib(LibProtocol protocol) diff --git a/Userland/Libraries/LibProtocol/WebSocket.cpp b/Userland/Libraries/LibProtocol/WebSocket.cpp new file mode 100644 index 0000000000..5d497e1a18 --- /dev/null +++ b/Userland/Libraries/LibProtocol/WebSocket.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021, Dex♪ + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Protocol { + +WebSocket::WebSocket(WebSocketClient& client, i32 connection_id) + : m_client(client) + , m_connection_id(connection_id) +{ +} + +WebSocket::ReadyState WebSocket::ready_state() +{ + return (WebSocket::ReadyState)m_client->ready_state({}, *this); +} + +void WebSocket::send(ByteBuffer binary_or_text_message, bool is_text) +{ + m_client->send({}, *this, move(binary_or_text_message), is_text); +} + +void WebSocket::send(StringView text_message) +{ + send(ByteBuffer::copy(text_message.bytes()), true); +} + +void WebSocket::close(u16 code, String reason) +{ + m_client->close({}, *this, code, move(reason)); +} + +void WebSocket::did_open(Badge) +{ + if (on_open) + on_open(); +} + +void WebSocket::did_receive(Badge, ByteBuffer data, bool is_text) +{ + if (on_message) + on_message(WebSocket::Message { move(data), is_text }); +} + +void WebSocket::did_error(Badge, i32 error_code) +{ + if (on_error) + on_error((WebSocket::Error)error_code); +} + +void WebSocket::did_close(Badge, u16 code, String reason, bool was_clean) +{ + if (on_close) + on_close(code, move(reason), was_clean); +} + +void WebSocket::did_request_certificates(Badge) +{ + if (on_certificate_requested) { + auto result = on_certificate_requested(); + if (!m_client->set_certificate({}, *this, result.certificate, result.key)) + dbgln("WebSocket: set_certificate failed"); + } +} +} diff --git a/Userland/Libraries/LibProtocol/WebSocket.h b/Userland/Libraries/LibProtocol/WebSocket.h new file mode 100644 index 0000000000..c0f079f5c1 --- /dev/null +++ b/Userland/Libraries/LibProtocol/WebSocket.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021, Dex♪ + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Protocol { + +class WebSocketClient; + +class WebSocket : public RefCounted { +public: + struct CertificateAndKey { + String certificate; + String key; + }; + + struct Message { + ByteBuffer data; + bool is_text { false }; + }; + + enum class Error { + CouldNotEstablishConnection, + ConnectionUpgradeFailed, + ServerClosedSocket, + }; + + enum class ReadyState { + Connecting = 0, + Open = 1, + Closing = 2, + Closed = 3, + }; + + static NonnullRefPtr create_from_id(Badge, WebSocketClient& client, i32 connection_id) + { + return adopt_ref(*new WebSocket(client, connection_id)); + } + + int id() const { return m_connection_id; } + + ReadyState ready_state(); + + void send(ByteBuffer binary_or_text_message, bool is_text); + void send(StringView text_message); + void close(u16 code = 1005, String reason = {}); + + Function on_open; + Function on_message; + Function on_error; + Function on_close; + Function on_certificate_requested; + + void did_open(Badge); + void did_receive(Badge, ByteBuffer, bool); + void did_error(Badge, i32); + void did_close(Badge, u16, String, bool); + void did_request_certificates(Badge); + +private: + explicit WebSocket(WebSocketClient&, i32 connection_id); + WeakPtr m_client; + int m_connection_id { -1 }; +}; + +} diff --git a/Userland/Libraries/LibProtocol/WebSocketClient.cpp b/Userland/Libraries/LibProtocol/WebSocketClient.cpp new file mode 100644 index 0000000000..728f05a3a1 --- /dev/null +++ b/Userland/Libraries/LibProtocol/WebSocketClient.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2021, Dex♪ + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Protocol { + +WebSocketClient::WebSocketClient() + : IPC::ServerConnection(*this, "/tmp/portal/websocket") +{ + handshake(); +} + +void WebSocketClient::handshake() +{ + send_sync(); +} + +RefPtr WebSocketClient::connect(const URL& url, const String& origin, const Vector& protocols, const Vector& extensions, const HashMap& request_headers) +{ + IPC::Dictionary header_dictionary; + for (auto& it : request_headers) + header_dictionary.add(it.key, it.value); + auto response = send_sync(url, origin, protocols, extensions, header_dictionary); + auto connection_id = response->connection_id(); + if (connection_id < 0) + return nullptr; + auto connection = WebSocket::create_from_id({}, *this, connection_id); + m_connections.set(connection_id, connection); + return connection; +} + +u32 WebSocketClient::ready_state(Badge, WebSocket& connection) +{ + if (!m_connections.contains(connection.id())) + return (u32)WebSocket::ReadyState::Closed; + return send_sync(connection.id())->ready_state(); +} + +void WebSocketClient::send(Badge, WebSocket& connection, ByteBuffer data, bool is_text) +{ + if (!m_connections.contains(connection.id())) + return; + post_message(Messages::WebSocketServer::Send(connection.id(), is_text, move(data))); +} + +void WebSocketClient::close(Badge, WebSocket& connection, u16 code, String message) +{ + if (!m_connections.contains(connection.id())) + return; + post_message(Messages::WebSocketServer::Close(connection.id(), code, move(message))); +} + +bool WebSocketClient::set_certificate(Badge, WebSocket& connection, String certificate, String key) +{ + if (!m_connections.contains(connection.id())) + return false; + return send_sync(connection.id(), move(certificate), move(key))->success(); +} + +void WebSocketClient::handle(const Messages::WebSocketClient::Connected& message) +{ + auto maybe_connection = m_connections.get(message.connection_id()); + if (maybe_connection.has_value()) + maybe_connection.value()->did_open({}); +} + +void WebSocketClient::handle(const Messages::WebSocketClient::Received& message) +{ + auto maybe_connection = m_connections.get(message.connection_id()); + if (maybe_connection.has_value()) + maybe_connection.value()->did_receive({}, message.data(), message.is_text()); +} + +void WebSocketClient::handle(const Messages::WebSocketClient::Errored& message) +{ + auto maybe_connection = m_connections.get(message.connection_id()); + if (maybe_connection.has_value()) + maybe_connection.value()->did_error({}, message.message()); +} + +void WebSocketClient::handle(const Messages::WebSocketClient::Closed& message) +{ + auto maybe_connection = m_connections.get(message.connection_id()); + if (maybe_connection.has_value()) + maybe_connection.value()->did_close({}, message.code(), message.reason(), message.clean()); +} + +void WebSocketClient::handle(const Messages::WebSocketClient::CertificateRequested& message) +{ + auto maybe_connection = m_connections.get(message.connection_id()); + if (maybe_connection.has_value()) + maybe_connection.value()->did_request_certificates({}); +} + +} diff --git a/Userland/Libraries/LibProtocol/WebSocketClient.h b/Userland/Libraries/LibProtocol/WebSocketClient.h new file mode 100644 index 0000000000..07db4de425 --- /dev/null +++ b/Userland/Libraries/LibProtocol/WebSocketClient.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021, Dex♪ + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Protocol { + +class WebSocket; + +class WebSocketClient + : public IPC::ServerConnection + , public WebSocketClientEndpoint { + C_OBJECT(WebSocketClient); + +public: + virtual void handshake() override; + + RefPtr connect(const URL&, const String& origin = {}, const Vector& protocols = {}, const Vector& extensions = {}, const HashMap& request_headers = {}); + + u32 ready_state(Badge, WebSocket&); + void send(Badge, WebSocket&, ByteBuffer, bool is_text); + void close(Badge, WebSocket&, u16 code, String reason); + bool set_certificate(Badge, WebSocket&, String, String); + +private: + WebSocketClient(); + + virtual void handle(const Messages::WebSocketClient::Connected&) override; + virtual void handle(const Messages::WebSocketClient::Received&) override; + virtual void handle(const Messages::WebSocketClient::Errored&) override; + virtual void handle(const Messages::WebSocketClient::Closed&) override; + virtual void handle(const Messages::WebSocketClient::CertificateRequested&) override; + + HashMap> m_connections; +}; + +} diff --git a/Userland/Libraries/LibWebSocket/WebSocket.cpp b/Userland/Libraries/LibWebSocket/WebSocket.cpp index 42351fc28a..03184aa503 100644 --- a/Userland/Libraries/LibWebSocket/WebSocket.cpp +++ b/Userland/Libraries/LibWebSocket/WebSocket.cpp @@ -559,6 +559,9 @@ void WebSocket::discard_connection() { VERIFY(m_impl); m_impl->discard_connection(); + m_impl->on_connection_error = nullptr; + m_impl->on_connected = nullptr; + m_impl->on_ready_to_read = nullptr; m_impl = nullptr; } -- cgit v1.2.3