LibWebSocket: Add a new websocket library

This library currently contains a basic WebSocket client that can
handle both standard TCP websockets and TLS websockets.
This commit is contained in:
DexesTTP 2021-04-17 17:20:24 +02:00 committed by Andreas Kling
parent b30f5dc8b5
commit d3a89ce737
Notes: sideshowbarker 2024-07-18 19:25:05 +09:00
13 changed files with 1407 additions and 0 deletions

View file

@ -37,4 +37,5 @@ add_subdirectory(LibTLS)
add_subdirectory(LibTTF)
add_subdirectory(LibVT)
add_subdirectory(LibWeb)
add_subdirectory(LibWebSocket)
add_subdirectory(LibX86)

View file

@ -0,0 +1,10 @@
set(SOURCES
ConnectionInfo.cpp
Impl/AbstractWebSocketImpl.cpp
Impl/TCPWebSocketConnectionImpl.cpp
Impl/TLSv12WebSocketConnectionImpl.cpp
WebSocket.cpp
)
serenity_lib(LibWebSocket websocket)
target_link_libraries(LibWebSocket LibCore LibCrypto LibTLS)

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2021, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <LibWebSocket/ConnectionInfo.h>
namespace WebSocket {
ConnectionInfo::ConnectionInfo(URL url)
: m_url(move(url))
{
}
bool ConnectionInfo::is_secure() const
{
// RFC 6455 Section 3 :
// The URI is called "secure" if the scheme component matches "wss" case-insensitively.
return m_url.protocol().equals_ignoring_case("wss"sv);
}
String ConnectionInfo::resource_name() const
{
// RFC 6455 Section 3 :
// The "resource-name" can be constructed by concatenating the following:
StringBuilder builder;
// "/" if the path component is empty
if (m_url.path().is_empty())
builder.append("/");
// The path component
builder.append(m_url.path());
// "?" if the query component is non-empty
if (!m_url.query().is_empty())
builder.append("?");
// the query component
builder.append(m_url.query());
return builder.to_string();
}
}

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2021, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/URL.h>
#include <AK/Vector.h>
#include <LibCore/Object.h>
#include <LibCore/TCPSocket.h>
#include <LibTLS/TLSv12.h>
#include <LibWebSocket/Message.h>
namespace WebSocket {
class ConnectionInfo final {
public:
ConnectionInfo(URL);
URL const& url() const { return m_url; }
String const& origin() const { return m_origin; }
void set_origin(String origin) { m_origin = move(origin); }
Vector<String> const& protocols() const { return m_protocols; }
void set_protocols(Vector<String> protocols) { m_protocols = move(protocols); }
Vector<String> const& extensions() const { return m_extensions; }
void set_extensions(Vector<String> extensions) { m_extensions = move(extensions); }
struct Header {
String name;
String value;
};
Vector<Header> const& headers() const { return m_headers; }
void set_headers(Vector<Header> headers) { m_headers = move(headers); }
// secure flag - defined in RFC 6455 Section 3
bool is_secure() const;
// "resource-name" or "/resource name/" - defined in RFC 6455 Section 3
String resource_name() const;
private:
URL m_url;
String m_origin;
Vector<String> m_protocols {};
Vector<String> m_extensions {};
Vector<Header> m_headers {};
};
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2021, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <LibWebSocket/Impl/AbstractWebSocketImpl.h>
namespace WebSocket {
AbstractWebSocketImpl::AbstractWebSocketImpl(Core::Object* parent)
: Object(parent)
{
}
AbstractWebSocketImpl::~AbstractWebSocketImpl()
{
}
}

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2021, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/Span.h>
#include <AK/String.h>
#include <LibCore/Object.h>
#include <LibWebSocket/ConnectionInfo.h>
namespace WebSocket {
class AbstractWebSocketImpl : public Core::Object {
C_OBJECT_ABSTRACT(AbstractWebSocketImpl);
public:
virtual ~AbstractWebSocketImpl() override;
explicit AbstractWebSocketImpl(Core::Object* parent = nullptr);
virtual void connect(ConnectionInfo const&) = 0;
virtual bool can_read_line() = 0;
virtual String read_line(size_t size) = 0;
virtual bool can_read() = 0;
virtual ByteBuffer read(int max_size) = 0;
virtual bool send(ReadonlyBytes) = 0;
virtual bool eof() = 0;
virtual void discard_connection() = 0;
Function<void()> on_connected;
Function<void()> on_connection_error;
Function<void()> on_ready_to_read;
};
}

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2021, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <LibWebSocket/Impl/TCPWebSocketConnectionImpl.h>
namespace WebSocket {
TCPWebSocketConnectionImpl::TCPWebSocketConnectionImpl(Core::Object* parent)
: AbstractWebSocketImpl(parent)
{
}
TCPWebSocketConnectionImpl::~TCPWebSocketConnectionImpl()
{
discard_connection();
}
void TCPWebSocketConnectionImpl::connect(ConnectionInfo const& connection)
{
VERIFY(!m_socket);
VERIFY(on_connected);
VERIFY(on_connection_error);
VERIFY(on_ready_to_read);
m_socket = Core::TCPSocket::construct(this);
m_notifier = Core::Notifier::construct(m_socket->fd(), Core::Notifier::Read);
m_notifier->on_ready_to_read = [this] {
on_ready_to_read();
};
m_socket->on_connected = [this] {
on_connected();
};
bool success = m_socket->connect(connection.url().host(), connection.url().port());
if (!success) {
deferred_invoke([this](auto&) {
on_connection_error();
});
}
}
bool TCPWebSocketConnectionImpl::send(ReadonlyBytes data)
{
return m_socket->write(data);
}
bool TCPWebSocketConnectionImpl::can_read_line()
{
return m_socket->can_read_line();
}
String TCPWebSocketConnectionImpl::read_line(size_t size)
{
return m_socket->read_line(size);
}
bool TCPWebSocketConnectionImpl::can_read()
{
return m_socket->can_read();
}
ByteBuffer TCPWebSocketConnectionImpl::read(int max_size)
{
return m_socket->read(max_size);
}
bool TCPWebSocketConnectionImpl::eof()
{
return m_socket->eof();
}
void TCPWebSocketConnectionImpl::discard_connection()
{
if (!m_socket)
return;
m_socket->on_ready_to_read = nullptr;
remove_child(*m_socket);
m_socket = nullptr;
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2021, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/Span.h>
#include <AK/String.h>
#include <LibCore/Notifier.h>
#include <LibCore/Object.h>
#include <LibCore/TCPSocket.h>
#include <LibWebSocket/ConnectionInfo.h>
#include <LibWebSocket/Impl/AbstractWebSocketImpl.h>
namespace WebSocket {
class TCPWebSocketConnectionImpl final : public AbstractWebSocketImpl {
C_OBJECT(TCPWebSocketConnectionImpl);
public:
virtual ~TCPWebSocketConnectionImpl() override;
explicit TCPWebSocketConnectionImpl(Core::Object* parent = nullptr);
virtual void connect(ConnectionInfo const& connection) override;
virtual bool can_read_line() override;
virtual String read_line(size_t size) override;
virtual bool can_read() override;
virtual ByteBuffer read(int max_size) override;
virtual bool send(ReadonlyBytes data) override;
virtual bool eof() override;
virtual void discard_connection() override;
private:
RefPtr<Core::Notifier> m_notifier;
RefPtr<Core::TCPSocket> m_socket;
};
}

View file

@ -0,0 +1,118 @@
/*
* Copyright (c) 2021, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <LibWebSocket/Impl/TLSv12WebSocketConnectionImpl.h>
namespace WebSocket {
TLSv12WebSocketConnectionImpl::TLSv12WebSocketConnectionImpl(Core::Object* parent)
: AbstractWebSocketImpl(parent)
{
}
TLSv12WebSocketConnectionImpl::~TLSv12WebSocketConnectionImpl()
{
discard_connection();
}
void TLSv12WebSocketConnectionImpl::connect(ConnectionInfo const& connection)
{
VERIFY(!m_socket);
VERIFY(on_connected);
VERIFY(on_connection_error);
VERIFY(on_ready_to_read);
m_socket = TLS::TLSv12::construct(this);
m_notifier = Core::Notifier::construct(m_socket->fd(), Core::Notifier::Read);
m_notifier->on_ready_to_read = [this] {
on_ready_to_read();
};
m_socket->set_root_certificates(DefaultRootCACertificates::the().certificates());
m_socket->on_tls_error = [this](TLS::AlertDescription) {
on_connection_error();
};
m_socket->on_tls_ready_to_write = [this] {
on_connected();
};
m_socket->on_tls_finished = [this] {
on_connection_error();
};
m_socket->on_tls_certificate_request = [this](auto&) {
// FIXME : Once we handle TLS certificate requests, handle it here as well.
};
bool success = m_socket->connect(connection.url().host(), connection.url().port());
if (!success) {
deferred_invoke([this](auto&) {
on_connection_error();
});
}
}
bool TLSv12WebSocketConnectionImpl::send(ReadonlyBytes data)
{
return m_socket->write(data);
}
bool TLSv12WebSocketConnectionImpl::can_read_line()
{
return m_socket->can_read_line();
}
String TLSv12WebSocketConnectionImpl::read_line(size_t size)
{
return m_socket->read_line(size);
}
bool TLSv12WebSocketConnectionImpl::can_read()
{
return m_socket->can_read();
}
ByteBuffer TLSv12WebSocketConnectionImpl::read(int max_size)
{
return m_socket->read(max_size);
}
bool TLSv12WebSocketConnectionImpl::eof()
{
return m_socket->eof();
}
void TLSv12WebSocketConnectionImpl::discard_connection()
{
if (!m_socket)
return;
m_socket->on_tls_connected = nullptr;
m_socket->on_tls_error = nullptr;
m_socket->on_tls_finished = nullptr;
m_socket->on_tls_certificate_request = nullptr;
m_socket->on_ready_to_read = nullptr;
remove_child(*m_socket);
m_socket = nullptr;
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2021, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/Span.h>
#include <AK/String.h>
#include <LibCore/Object.h>
#include <LibTLS/TLSv12.h>
#include <LibWebSocket/ConnectionInfo.h>
#include <LibWebSocket/Impl/AbstractWebSocketImpl.h>
namespace WebSocket {
class TLSv12WebSocketConnectionImpl final : public AbstractWebSocketImpl {
C_OBJECT(TLSv12WebSocketConnectionImpl);
public:
virtual ~TLSv12WebSocketConnectionImpl() override;
explicit TLSv12WebSocketConnectionImpl(Core::Object* parent = nullptr);
void connect(ConnectionInfo const& connection) override;
virtual bool can_read_line() override;
virtual String read_line(size_t size) override;
virtual bool can_read() override;
virtual ByteBuffer read(int max_size) override;
virtual bool send(ReadonlyBytes data) override;
virtual bool eof() override;
virtual void discard_connection() override;
private:
RefPtr<Core::Notifier> m_notifier;
RefPtr<TLS::TLSv12> m_socket;
};
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2021, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/Optional.h>
namespace WebSocket {
class Message {
public:
explicit Message(String const& data)
: m_is_text(true)
, m_data(ByteBuffer::copy(data.bytes()))
{
}
explicit Message(ByteBuffer data, bool is_text)
: m_is_text(is_text)
, m_data(move(data))
{
}
explicit Message(ByteBuffer const&& data, bool is_text)
: m_is_text(is_text)
, m_data(move(data))
{
}
bool is_text() const { return m_is_text; }
ByteBuffer const& data() const { return m_data; }
private:
bool m_is_text { false };
ByteBuffer m_data;
};
}

View file

@ -0,0 +1,613 @@
/*
* Copyright (c) 2021, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/Base64.h>
#include <AK/Debug.h>
#include <AK/Random.h>
#include <LibCrypto/Hash/HashManager.h>
#include <LibWebSocket/Impl/TCPWebSocketConnectionImpl.h>
#include <LibWebSocket/Impl/TLSv12WebSocketConnectionImpl.h>
#include <LibWebSocket/WebSocket.h>
#include <stdio.h>
#include <unistd.h>
namespace WebSocket {
// Note : The websocket protocol is defined by RFC 6455, found at https://tools.ietf.org/html/rfc6455
// In this file, section numbers will refer to the RFC 6455
NonnullRefPtr<WebSocket> WebSocket::create(ConnectionInfo connection)
{
return adopt(*new WebSocket(connection));
}
WebSocket::WebSocket(ConnectionInfo connection)
: m_connection(connection)
{
}
WebSocket::~WebSocket()
{
}
void WebSocket::start()
{
VERIFY(m_state == WebSocket::InternalState::NotStarted);
VERIFY(!m_impl);
if (m_connection.is_secure())
m_impl = TLSv12WebSocketConnectionImpl::construct();
else
m_impl = TCPWebSocketConnectionImpl::construct();
m_impl->on_connection_error = [this] {
dbgln("WebSocket: Connection error (underlying socket)");
fatal_error(WebSocket::Error::CouldNotEstablishConnection);
};
m_impl->on_connected = [this] {
if (m_state != WebSocket::InternalState::EstablishingProtocolConnection)
return;
m_state = WebSocket::InternalState::SendingClientHandshake;
send_client_handshake();
drain_read();
};
m_impl->on_ready_to_read = [this] {
drain_read();
};
m_state = WebSocket::InternalState::EstablishingProtocolConnection;
m_impl->connect(m_connection);
}
ReadyState WebSocket::ready_state()
{
switch (m_state) {
case WebSocket::InternalState::NotStarted:
case WebSocket::InternalState::EstablishingProtocolConnection:
case WebSocket::InternalState::SendingClientHandshake:
case WebSocket::InternalState::WaitingForServerHandshake:
return ReadyState::Connecting;
case WebSocket::InternalState::Open:
return ReadyState::Open;
case WebSocket::InternalState::Closing:
return ReadyState::Closing;
case WebSocket::InternalState::Closed:
case WebSocket::InternalState::Errored:
return ReadyState::Closed;
default:
VERIFY_NOT_REACHED();
return ReadyState::Closed;
}
}
void WebSocket::send(Message message)
{
// Calling send on a socket that is not opened is not allowed
VERIFY(m_state == WebSocket::InternalState::Open);
VERIFY(m_impl);
if (message.is_text())
send_frame(WebSocket::OpCode::Text, message.data(), true);
else
send_frame(WebSocket::OpCode::Binary, message.data(), true);
}
void WebSocket::close(u16 code, String message)
{
// Calling close on a socket that is not opened is not allowed
VERIFY(m_state == WebSocket::InternalState::Open);
VERIFY(m_impl);
auto message_bytes = message.bytes();
auto close_payload = ByteBuffer::create_uninitialized(message_bytes.size() + 2);
close_payload.overwrite(0, (u8*)&code, 2);
close_payload.overwrite(2, message_bytes.data(), message_bytes.size());
send_frame(WebSocket::OpCode::ConnectionClose, close_payload, true);
}
void WebSocket::drain_read()
{
if (m_impl->eof()) {
// The connection got closed by the server
m_state = WebSocket::InternalState::Closed;
notify_close(m_last_close_code, m_last_close_message, true);
discard_connection();
return;
}
while (m_impl->can_read()) {
if (m_state == WebSocket::InternalState::WaitingForServerHandshake) {
read_server_handshake();
return;
}
if (m_state == WebSocket::InternalState::Open) {
read_frame();
return;
}
if (m_state == WebSocket::InternalState::Closing) {
read_frame();
return;
}
}
}
// The client handshake message is defined in the second list of section 4.1
void WebSocket::send_client_handshake()
{
VERIFY(m_impl);
VERIFY(m_state == WebSocket::InternalState::SendingClientHandshake);
StringBuilder builder;
// 2. and 3. GET /resource name/ HTTP 1.1
builder.appendff("GET {} HTTP/1.1\r\n", m_connection.resource_name());
// 4. Host
auto url = m_connection.url();
builder.appendff("Host: {}", url.host());
if (!m_connection.is_secure() && url.port() != 80)
builder.appendff(":{}", url.port());
else if (m_connection.is_secure() && url.port() != 443)
builder.appendff(":{}", url.port());
builder.append("\r\n");
// 5. and 6. Connection Upgrade
builder.append("Upgrade: websocket\r\n");
builder.append("Connection: Upgrade\r\n");
// 7. 16-byte nonce encoded as Base64
u8 nonce_data[16];
fill_with_random(nonce_data, 16);
m_websocket_key = encode_base64(ReadonlyBytes(nonce_data, 16));
builder.appendff("Sec-WebSocket-Key: {}\r\n", m_websocket_key);
// 8. Origin (optional field)
if (!m_connection.origin().is_empty()) {
builder.appendff("Origin: {}\r\n", m_connection.origin());
}
// 9. Websocket version
builder.append("Sec-WebSocket-Version: 13\r\n");
// 10. Websocket protocol (optional field)
if (!m_connection.protocols().is_empty()) {
builder.append("Sec-WebSocket-Protocol: ");
builder.join(",", m_connection.protocols());
builder.append("\r\n");
}
// 11. Websocket extensions (optional field)
if (!m_connection.extensions().is_empty()) {
builder.append("Sec-WebSocket-Extensions: ");
builder.join(",", m_connection.extensions());
builder.append("\r\n");
}
// 12. Additional headers
for (auto& header : m_connection.headers()) {
builder.appendff("{}: {}\r\n", header.name, header.value);
}
builder.append("\r\n");
m_state = WebSocket::InternalState::WaitingForServerHandshake;
auto success = m_impl->send(builder.to_string().bytes());
VERIFY(success);
}
// 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);
// Read the server handshake
if (!m_impl->can_read_line())
return;
if (!m_has_read_server_handshake_first_line) {
auto header = m_impl->read_line(PAGE_SIZE);
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();
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();
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 authentification request, which we do not handle.
dbgln("WebSocket: Server HTTP Handshake return status {} which isn't supported", parts[1]);
fatal_error(WebSocket::Error::ConnectionUpgradeFailed);
return;
}
m_has_read_server_handshake_first_line = true;
}
// Read the rest of the reply until we find an empty line
while (m_impl->can_read_line()) {
auto line = m_impl->read_line(PAGE_SIZE);
if (line.is_whitespace()) {
// We're done with the HTTP headers.
// 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);
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);
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);
return;
}
m_state = WebSocket::InternalState::Open;
notify_open();
return;
}
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);
return;
}
auto header_name = parts[0];
if (header_name.equals_ignoring_case("Upgrade")) {
// 2. |Upgrade| should be case-insensitive "websocket"
if (!parts[1].trim_whitespace().equals_ignoring_case("websocket")) {
dbgln("WebSocket: Server HTTP Handshake Header |Upgrade| should be 'websocket', got '{}'. Failing connection.", parts[1]);
fatal_error(WebSocket::Error::ConnectionUpgradeFailed);
return;
}
m_has_read_server_handshake_upgrade = true;
continue;
}
if (header_name.equals_ignoring_case("Connection")) {
// 3. |Connection| should be case-insensitive "Upgrade"
if (!parts[1].trim_whitespace().equals_ignoring_case("Upgrade")) {
dbgln("WebSocket: Server HTTP Handshake Header |Connection| should be 'Upgrade', got '{}'. Failing connection.", parts[1]);
return;
}
m_has_read_server_handshake_connection = true;
continue;
}
if (header_name.equals_ignoring_case("Sec-WebSocket-Accept")) {
// 4. |Sec-WebSocket-Accept| should be base64(SHA1(|Sec-WebSocket-Key| + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
auto expected_content = String::formatted("{}258EAFA5-E914-47DA-95CA-C5AB0DC85B11", m_websocket_key);
Crypto::Hash::Manager hash;
hash.initialize(Crypto::Hash::HashKind::SHA1);
hash.update(expected_content);
auto expected_sha1 = hash.digest();
auto expected_sha1_string = encode_base64(ReadonlyBytes(expected_sha1.immutable_data(), expected_sha1.data_length()));
if (!parts[1].trim_whitespace().equals_ignoring_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);
return;
}
m_has_read_server_handshake_accept = true;
continue;
}
if (header_name.equals_ignoring_case("Sec-WebSocket-Extensions")) {
// 5. |Sec-WebSocket-Extensions| should not contain an extension that doesn't appear in m_connection->extensions()
auto server_extensions = parts[1].split(',');
for (auto extension : server_extensions) {
auto trimmed_extension = extension.trim_whitespace();
bool found_extension = false;
for (auto supported_extension : m_connection.extensions()) {
if (trimmed_extension.equals_ignoring_case(supported_extension)) {
found_extension = true;
}
}
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);
return;
}
}
continue;
}
if (header_name.equals_ignoring_case("Sec-WebSocket-Protocol")) {
// 6. |Sec-WebSocket-Protocol| should not contain an extension that doesn't appear in m_connection->protocols()
auto server_protocols = parts[1].split(',');
for (auto protocol : server_protocols) {
auto trimmed_protocol = protocol.trim_whitespace();
bool found_protocol = false;
for (auto supported_protocol : m_connection.protocols()) {
if (trimmed_protocol.equals_ignoring_case(supported_protocol)) {
found_protocol = true;
}
}
if (!found_protocol) {
dbgln("WebSocket: Server HTTP Handshake Header |Sec-WebSocket-Protocol| contains '{}', which is not supported by the client. Failing connection.", trimmed_protocol);
fatal_error(WebSocket::Error::ConnectionUpgradeFailed);
return;
}
}
continue;
}
}
// If needed, we will keep reading the header on the next drain_read call
}
void WebSocket::read_frame()
{
VERIFY(m_impl);
VERIFY(m_state == WebSocket::InternalState::Open || m_state == WebSocket::InternalState::Closing);
auto head_bytes = m_impl->read(2);
if (head_bytes.size() == 0) {
// The connection got closed.
m_state = WebSocket::InternalState::Closed;
notify_close(m_last_close_code, m_last_close_message, true);
discard_connection();
return;
}
VERIFY(head_bytes.size() == 2);
bool is_final_frame = head_bytes[0] & 0x80;
if (!is_final_frame) {
// FIXME: Support fragmented frames
TODO();
}
auto op_code = (WebSocket::OpCode)(head_bytes[0] & 0x0f);
bool is_masked = head_bytes[1] & 0x80;
// Parse the payload length.
size_t payload_length;
auto payload_length_bits = head_bytes[1] & 0x7f;
if (payload_length_bits == 127) {
// A code of 127 means that the next 8 bytes contains the payload length
auto actual_bytes = m_impl->read(8);
VERIFY(actual_bytes.size() == 8);
u64 full_payload_length = (u64)((u64)(actual_bytes[0] & 0xff) << 56)
| (u64)((u64)(actual_bytes[1] & 0xff) << 48)
| (u64)((u64)(actual_bytes[2] & 0xff) << 40)
| (u64)((u64)(actual_bytes[3] & 0xff) << 32)
| (u64)((u64)(actual_bytes[4] & 0xff) << 24)
| (u64)((u64)(actual_bytes[5] & 0xff) << 16)
| (u64)((u64)(actual_bytes[6] & 0xff) << 8)
| (u64)((u64)(actual_bytes[7] & 0xff) << 0);
VERIFY(full_payload_length <= NumericLimits<size_t>::max());
payload_length = (size_t)full_payload_length;
} else if (payload_length_bits == 126) {
// A code of 126 means that the next 2 bytes contains the payload length
auto actual_bytes = m_impl->read(2);
VERIFY(actual_bytes.size() == 2);
payload_length = (size_t)((size_t)(actual_bytes[0] & 0xff) << 8)
| (size_t)((size_t)(actual_bytes[1] & 0xff) << 0);
} else {
payload_length = (size_t)payload_length_bits;
}
// Parse the mask, if it exists.
// Note : this is technically non-conformant with Section 5.1 :
// > A server MUST NOT mask any frames that it sends to the client.
// > A client MUST close a connection if it detects a masked frame.
// > (These rules might be relaxed in a future specification.)
// But because it doesn't cost much, we can support receiving masked frames anyways.
u8 masking_key[4];
if (is_masked) {
auto masking_key_data = m_impl->read(4);
VERIFY(masking_key_data.size() == 4);
masking_key[0] = masking_key_data[0];
masking_key[1] = masking_key_data[1];
masking_key[2] = masking_key_data[2];
masking_key[3] = masking_key_data[3];
}
auto payload = ByteBuffer::create_uninitialized(payload_length);
u64 read_length = 0;
while (read_length < payload_length) {
auto payload_part = m_impl->read(payload_length - read_length);
if (payload_part.size() == 0) {
// We got disconnected, somehow.
dbgln("Websocket: Server disconnected while sending payload ({} bytes read out of {})", read_length, payload_length);
fatal_error(WebSocket::Error::ServerClosedSocket);
return;
}
// We read at most "actual_length - read" bytes, so this is safe to do.
payload.overwrite(read_length, payload_part.data(), payload_part.size());
read_length -= payload_part.size();
}
if (is_masked) {
// Unmask the payload
for (size_t i = 0; i < payload.size(); ++i) {
payload[i] = payload[i] ^ (masking_key[i % 4]);
}
}
if (op_code == WebSocket::OpCode::ConnectionClose) {
if (payload.size() > 1) {
m_last_close_code = (((u16)(payload[0] & 0xff) << 8) | ((u16)(payload[1] & 0xff)));
m_last_close_message = String(ReadonlyBytes(payload.offset_pointer(2), payload.size() - 2));
}
m_state = WebSocket::InternalState::Closing;
return;
}
if (op_code == WebSocket::OpCode::Ping) {
// Immediately send a pong frame as a reply, with the given payload.
send_frame(WebSocket::OpCode::Pong, payload, true);
return;
}
if (op_code == WebSocket::OpCode::Pong) {
// We can safely ignore the pong
return;
}
if (op_code == WebSocket::OpCode::Continuation) {
// FIXME: Support fragmented frames
TODO();
return;
}
if (op_code == WebSocket::OpCode::Text) {
notify_message(Message(payload, true));
return;
}
if (op_code == WebSocket::OpCode::Binary) {
notify_message(Message(payload, false));
return;
}
dbgln("Websocket: Found unknown opcode {}", (u8)op_code);
}
void WebSocket::send_frame(WebSocket::OpCode op_code, ReadonlyBytes payload, bool is_final)
{
VERIFY(m_impl);
VERIFY(m_state == WebSocket::InternalState::Open);
u8 frame_head[1] = { (u8)((is_final ? 0x80 : 0x00) | ((u8)(op_code)&0xf)) };
m_impl->send(ReadonlyBytes(frame_head, 1));
// Section 5.1 : a client MUST mask all frames that it sends to the server
bool has_mask = true;
if (payload.size() > NumericLimits<u64>::max()) {
// FIXME: We can technically stream this via non-final packets.
TODO();
} else if (payload.size() > NumericLimits<u16>::max()) {
// Send (the 'mask' flag + 127) + the 8-byte payload length
if constexpr (sizeof(size_t) >= 64) {
u8 payload_length[9] = {
(u8)((has_mask ? 0x80 : 0x00) | 127),
(u8)((payload.size() >> 56) & 0xff),
(u8)((payload.size() >> 48) & 0xff),
(u8)((payload.size() >> 40) & 0xff),
(u8)((payload.size() >> 32) & 0xff),
(u8)((payload.size() >> 24) & 0xff),
(u8)((payload.size() >> 16) & 0xff),
(u8)((payload.size() >> 8) & 0xff),
(u8)((payload.size() >> 0) & 0xff),
};
m_impl->send(ReadonlyBytes(payload_length, 9));
} else {
u8 payload_length[9] = {
(u8)((has_mask ? 0x80 : 0x00) | 127),
0,
0,
0,
0,
(u8)((payload.size() >> 24) & 0xff),
(u8)((payload.size() >> 16) & 0xff),
(u8)((payload.size() >> 8) & 0xff),
(u8)((payload.size() >> 0) & 0xff),
};
m_impl->send(ReadonlyBytes(payload_length, 9));
}
} else if (payload.size() >= 126) {
// Send (the 'mask' flag + 126) + the 2-byte payload length
u8 payload_length[3] = {
(u8)((has_mask ? 0x80 : 0x00) | 126),
(u8)((payload.size() >> 8) & 0xff),
(u8)((payload.size() >> 0) & 0xff),
};
m_impl->send(ReadonlyBytes(payload_length, 3));
} else {
// Send the mask flag + the payload in a single byte
u8 payload_length[1] = {
(u8)((has_mask ? 0x80 : 0x00) | (u8)(payload.size() & 0x7f)),
};
m_impl->send(ReadonlyBytes(payload_length, 1));
}
if (has_mask) {
// Section 10.3 :
// > Clients MUST choose a new masking key for each frame, using an algorithm
// > that cannot be predicted by end applications that provide data
u8 masking_key[4];
fill_with_random(masking_key, 4);
m_impl->send(ReadonlyBytes(masking_key, 4));
// Mask the payload
auto masked_payload = ByteBuffer::create_uninitialized(payload.size());
for (size_t i = 0; i < payload.size(); ++i) {
masked_payload[i] = payload[i] ^ (masking_key[i % 4]);
}
m_impl->send(masked_payload);
} else {
m_impl->send(payload);
}
}
void WebSocket::fatal_error(WebSocket::Error error)
{
m_state = WebSocket::InternalState::Errored;
notify_error(error);
discard_connection();
}
void WebSocket::discard_connection()
{
VERIFY(m_impl);
m_impl->discard_connection();
m_impl = nullptr;
}
void WebSocket::notify_open()
{
if (!on_open)
return;
on_open();
}
void WebSocket::notify_close(u16 code, String reason, bool was_clean)
{
if (!on_close)
return;
on_close(code, reason, was_clean);
}
void WebSocket::notify_error(WebSocket::Error error)
{
if (!on_error)
return;
on_error(error);
}
void WebSocket::notify_message(Message message)
{
if (!on_message)
return;
on_message(message);
}
}

View file

@ -0,0 +1,130 @@
/*
* Copyright (c) 2021, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/Span.h>
#include <LibCore/Object.h>
#include <LibWebSocket/ConnectionInfo.h>
#include <LibWebSocket/Impl/AbstractWebSocketImpl.h>
#include <LibWebSocket/Message.h>
namespace WebSocket {
enum class ReadyState {
Connecting = 0,
Open = 1,
Closing = 2,
Closed = 3,
};
class WebSocket final : public Core::Object {
C_OBJECT(WebSocket)
public:
static NonnullRefPtr<WebSocket> create(ConnectionInfo);
virtual ~WebSocket() override;
URL const& url() const { return m_connection.url(); }
ReadyState ready_state();
// Call this to start the WebSocket connection.
void start();
// This can only be used if the `ready_state` is `ReadyState::Open`
void send(Message);
// This can only be used if the `ready_state` is `ReadyState::Open`
void close(u16 code = 1005, String reason = {});
Function<void()> on_open;
Function<void(u16 code, String reason, bool was_clean)> on_close;
Function<void(Message message)> on_message;
enum class Error {
CouldNotEstablishConnection,
ConnectionUpgradeFailed,
ServerClosedSocket,
};
Function<void(Error)> on_error;
private:
explicit WebSocket(ConnectionInfo);
// As defined in section 5.2
enum class OpCode : u8 {
Continuation = 0x0,
Text = 0x1,
Binary = 0x2,
ConnectionClose = 0x8,
Ping = 0x9,
Pong = 0xA,
};
void drain_read();
void send_client_handshake();
void read_server_handshake();
void read_frame();
void send_frame(OpCode, ReadonlyBytes, bool is_final);
void notify_open();
void notify_close(u16 code, String reason, bool was_clean);
void notify_error(Error);
void notify_message(Message);
void fatal_error(Error);
void discard_connection();
enum class InternalState {
NotStarted,
EstablishingProtocolConnection,
SendingClientHandshake,
WaitingForServerHandshake,
Open,
Closing,
Closed,
Errored,
};
InternalState m_state { InternalState::NotStarted };
String m_websocket_key;
bool m_has_read_server_handshake_first_line { false };
bool m_has_read_server_handshake_upgrade { false };
bool m_has_read_server_handshake_connection { false };
bool m_has_read_server_handshake_accept { false };
u16 m_last_close_code { 1005 };
String m_last_close_message;
ConnectionInfo m_connection;
RefPtr<AbstractWebSocketImpl> m_impl;
};
}