mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-20 19:45:12 +00:00
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:
parent
b30f5dc8b5
commit
d3a89ce737
Notes:
sideshowbarker
2024-07-18 19:25:05 +09:00
Author: https://github.com/Dexesttp Commit: https://github.com/SerenityOS/serenity/commit/d3a89ce7372 Pull-request: https://github.com/SerenityOS/serenity/pull/6420 Reviewed-by: https://github.com/alimpfard Reviewed-by: https://github.com/linusg ✅
13 changed files with 1407 additions and 0 deletions
|
@ -37,4 +37,5 @@ add_subdirectory(LibTLS)
|
|||
add_subdirectory(LibTTF)
|
||||
add_subdirectory(LibVT)
|
||||
add_subdirectory(LibWeb)
|
||||
add_subdirectory(LibWebSocket)
|
||||
add_subdirectory(LibX86)
|
||||
|
|
10
Userland/Libraries/LibWebSocket/CMakeLists.txt
Normal file
10
Userland/Libraries/LibWebSocket/CMakeLists.txt
Normal 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)
|
61
Userland/Libraries/LibWebSocket/ConnectionInfo.cpp
Normal file
61
Userland/Libraries/LibWebSocket/ConnectionInfo.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
74
Userland/Libraries/LibWebSocket/ConnectionInfo.h
Normal file
74
Userland/Libraries/LibWebSocket/ConnectionInfo.h
Normal 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 {};
|
||||
};
|
||||
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
63
Userland/Libraries/LibWebSocket/Impl/AbstractWebSocketImpl.h
Normal file
63
Userland/Libraries/LibWebSocket/Impl/AbstractWebSocketImpl.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
62
Userland/Libraries/LibWebSocket/Message.h
Normal file
62
Userland/Libraries/LibWebSocket/Message.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
613
Userland/Libraries/LibWebSocket/WebSocket.cpp
Normal file
613
Userland/Libraries/LibWebSocket/WebSocket.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
130
Userland/Libraries/LibWebSocket/WebSocket.h
Normal file
130
Userland/Libraries/LibWebSocket/WebSocket.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue