diff --git a/Ladybird/CMakeLists.txt b/Ladybird/CMakeLists.txt index e801b2c46b..d8f1a6ca2d 100644 --- a/Ladybird/CMakeLists.txt +++ b/Ladybird/CMakeLists.txt @@ -30,19 +30,20 @@ include(${Lagom_SOURCE_DIR}/../CMake/lagom_compile_options.cmake) add_compile_options(-Wno-expansion-to-defined) set(CMAKE_AUTOMOC ON) -find_package(Qt6 REQUIRED COMPONENTS Core Widgets) +find_package(Qt6 REQUIRED COMPONENTS Core Widgets Network) # FIXME: Stop using deprecated declarations from QT :^) add_compile_options(-Wno-deprecated-declarations) set(SOURCES BrowserWindow.cpp + RequestManagerQt.cpp main.cpp WebView.cpp ) add_executable(ladybird ${SOURCES}) -target_link_libraries(ladybird PRIVATE Qt6::Widgets Lagom::Web Lagom::HTTP Lagom::WebSocket Lagom::Main) +target_link_libraries(ladybird PRIVATE Qt6::Widgets Qt::Network Lagom::Web Lagom::WebSocket Lagom::Main) get_filename_component( SERENITY_SOURCE_DIR "${Lagom_SOURCE_DIR}/../.." diff --git a/Ladybird/RequestManagerQt.cpp b/Ladybird/RequestManagerQt.cpp new file mode 100644 index 0000000000..2594c84440 --- /dev/null +++ b/Ladybird/RequestManagerQt.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "RequestManagerQt.h" +#include + +RequestManagerQt::RequestManagerQt() +{ + m_qnam = new QNetworkAccessManager(this); + + QObject::connect(m_qnam, &QNetworkAccessManager::finished, this, &RequestManagerQt::reply_finished); +} + +void RequestManagerQt::reply_finished(QNetworkReply* reply) +{ + auto request = m_pending.get(reply).value(); + m_pending.remove(reply); + request->did_finish(); +} + +RefPtr RequestManagerQt::start_request(String const& method, AK::URL const& url, HashMap const& request_headers, ReadonlyBytes request_body, Core::ProxyData const& proxy) +{ + if (!url.protocol().is_one_of_ignoring_case("http"sv, "https"sv)) { + return nullptr; + } + auto request_or_error = Request::create(*m_qnam, method, url, request_headers, request_body, proxy); + if (request_or_error.is_error()) { + return nullptr; + } + auto request = request_or_error.release_value(); + m_pending.set(&request->reply(), *request); + return request; +} + +ErrorOr> RequestManagerQt::Request::create(QNetworkAccessManager& qnam, String const& method, AK::URL const& url, HashMap const& request_headers, ReadonlyBytes request_body, Core::ProxyData const&) +{ + QNetworkRequest request { QString(url.to_string().characters()) }; + + QNetworkReply* reply = nullptr; + + if (method.equals_ignoring_case("head"sv)) { + reply = qnam.head(request); + } else if (method.equals_ignoring_case("get"sv)) { + reply = qnam.get(request); + } else if (method.equals_ignoring_case("post"sv)) { + reply = qnam.post(request, QByteArray((char const*)request_body.data(), request_body.size())); + } + + for (auto& it : request_headers) { + request.setRawHeader(it.key.characters(), it.value.characters()); + } + + return adopt_ref(*new Request(*reply)); +} + +RequestManagerQt::Request::Request(QNetworkReply& reply) + : m_reply(reply) +{ +} + +RequestManagerQt::Request::~Request() = default; + +void RequestManagerQt::Request::did_finish() +{ + bool success = m_reply.error() == QNetworkReply::NetworkError::NoError; + auto buffer = m_reply.readAll(); + auto http_status_code = m_reply.attribute(QNetworkRequest::Attribute::HttpStatusCodeAttribute).toInt(); + HashMap response_headers; + Vector set_cookie_headers; + for (auto& it : m_reply.rawHeaderPairs()) { + auto name = String(it.first.data(), it.first.length()); + auto value = String(it.second.data(), it.second.length()); + if (name.equals_ignoring_case("set-cookie")) { + set_cookie_headers.append(value); + } else { + response_headers.set(name, value); + } + } + if (!set_cookie_headers.is_empty()) { + response_headers.set("set-cookie", JsonArray { set_cookie_headers }.to_string()); + } + on_buffered_request_finish(success, buffer.length(), response_headers, http_status_code, ReadonlyBytes { buffer.data(), (size_t)buffer.size() }); +} diff --git a/Ladybird/RequestManagerQt.h b/Ladybird/RequestManagerQt.h new file mode 100644 index 0000000000..3882b4e057 --- /dev/null +++ b/Ladybird/RequestManagerQt.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#define AK_DONT_REPLACE_STD + +#include +#include +#include + +class RequestManagerQt + : public QObject + , public Web::ResourceLoaderConnector { + Q_OBJECT +public: + static NonnullRefPtr create() + { + return adopt_ref(*new RequestManagerQt()); + } + + virtual ~RequestManagerQt() override { } + + virtual void prefetch_dns(AK::URL const&) override { } + virtual void preconnect(AK::URL const&) override { } + + virtual RefPtr start_request(String const& method, AK::URL const&, HashMap const& request_headers, ReadonlyBytes request_body, Core::ProxyData const&) override; + +private slots: + void reply_finished(QNetworkReply*); + +private: + RequestManagerQt(); + + class Request + : public Web::ResourceLoaderConnectorRequest { + public: + static ErrorOr> create(QNetworkAccessManager& qnam, String const& method, AK::URL const& url, HashMap const& request_headers, ReadonlyBytes request_body, Core::ProxyData const&); + + virtual ~Request() override; + + virtual void set_should_buffer_all_input(bool) override { } + virtual bool stop() override { return false; } + virtual void stream_into(Core::Stream::Stream&) override { } + + void did_finish(); + + QNetworkReply& reply() { return m_reply; } + + private: + Request(QNetworkReply&); + + QNetworkReply& m_reply; + }; + + HashMap> m_pending; + QNetworkAccessManager* m_qnam { nullptr }; +}; diff --git a/Ladybird/WebView.cpp b/Ladybird/WebView.cpp index 0fcf205068..f81a3cbb37 100644 --- a/Ladybird/WebView.cpp +++ b/Ladybird/WebView.cpp @@ -8,6 +8,7 @@ #define AK_DONT_REPLACE_STD #include "WebView.h" +#include "RequestManagerQt.h" #include #include #include @@ -23,18 +24,11 @@ #include #include #include -#include -#include -#include #include #include #include #include #include -#include -#include -#include -#include #include #include #include @@ -440,274 +434,6 @@ private: explicit HeadlessImageDecoderClient() = default; }; -static HashTable> s_all_requests; - -class HeadlessRequestServer : public Web::ResourceLoaderConnector { -public: - class HTTPHeadlessRequest - : public Web::ResourceLoaderConnectorRequest - , public Weakable { - public: - static ErrorOr> create(String const& method, AK::URL const& url, HashMap const& request_headers, ReadonlyBytes request_body, Core::ProxyData const&) - { - auto stream_backing_buffer = TRY(ByteBuffer::create_uninitialized(1 * MiB)); - auto underlying_socket = TRY(Core::Stream::TCPSocket::connect(url.host(), url.port().value_or(80))); - TRY(underlying_socket->set_blocking(false)); - auto socket = TRY(Core::Stream::BufferedSocket::create(move(underlying_socket))); - - HTTP::HttpRequest request; - if (method.equals_ignoring_case("head"sv)) - request.set_method(HTTP::HttpRequest::HEAD); - else if (method.equals_ignoring_case("get"sv)) - request.set_method(HTTP::HttpRequest::GET); - else if (method.equals_ignoring_case("post"sv)) - request.set_method(HTTP::HttpRequest::POST); - else - request.set_method(HTTP::HttpRequest::Invalid); - request.set_url(move(url)); - request.set_headers(request_headers); - request.set_body(TRY(ByteBuffer::copy(request_body))); - - return adopt_ref(*new HTTPHeadlessRequest(move(request), move(socket), move(stream_backing_buffer))); - } - - virtual ~HTTPHeadlessRequest() override - { - } - - virtual void set_should_buffer_all_input(bool) override - { - } - - virtual bool stop() override - { - return false; - } - - virtual void stream_into(Core::Stream::Stream&) override - { - } - - private: - HTTPHeadlessRequest(HTTP::HttpRequest&& request, NonnullOwnPtr socket, ByteBuffer&& stream_backing_buffer) - : m_stream_backing_buffer(move(stream_backing_buffer)) - , m_output_stream(Core::Stream::MemoryStream::construct(m_stream_backing_buffer.bytes()).release_value_but_fixme_should_propagate_errors()) - , m_socket(move(socket)) - , m_job(HTTP::Job::construct(move(request), *m_output_stream)) - { - m_job->on_headers_received = [weak_this = make_weak_ptr()](auto& response_headers, auto response_code) mutable { - if (auto strong_this = weak_this.strong_ref()) { - strong_this->m_response_code = response_code; - for (auto& header : response_headers) { - strong_this->m_response_headers.set(header.key, header.value); - } - } - }; - m_job->on_finish = [weak_this = make_weak_ptr()](bool success) mutable { - Core::deferred_invoke([weak_this, success]() mutable { - if (auto strong_this = weak_this.strong_ref()) { - ReadonlyBytes response_bytes { strong_this->m_output_stream->bytes().data(), strong_this->m_output_stream->offset() }; - auto response_buffer = ByteBuffer::copy(response_bytes).release_value_but_fixme_should_propagate_errors(); - strong_this->on_buffered_request_finish(success, strong_this->m_output_stream->offset(), strong_this->m_response_headers, strong_this->m_response_code, response_buffer); - } }); - }; - m_job->start(*m_socket); - } - - Optional m_response_code; - ByteBuffer m_stream_backing_buffer; - NonnullOwnPtr m_output_stream; - NonnullOwnPtr m_socket; - NonnullRefPtr m_job; - HashMap m_response_headers; - }; - - class HTTPSHeadlessRequest - : public Web::ResourceLoaderConnectorRequest - , public Weakable { - public: - static ErrorOr> create(String const& method, AK::URL const& url, HashMap const& request_headers, ReadonlyBytes request_body, Core::ProxyData const&) - { - auto stream_backing_buffer = TRY(ByteBuffer::create_uninitialized(1 * MiB)); - auto underlying_socket = TRY(TLS::TLSv12::connect(url.host(), url.port().value_or(443))); - TRY(underlying_socket->set_blocking(false)); - auto socket = TRY(Core::Stream::BufferedSocket::create(move(underlying_socket))); - - HTTP::HttpRequest request; - if (method.equals_ignoring_case("head"sv)) - request.set_method(HTTP::HttpRequest::HEAD); - else if (method.equals_ignoring_case("get"sv)) - request.set_method(HTTP::HttpRequest::GET); - else if (method.equals_ignoring_case("post"sv)) - request.set_method(HTTP::HttpRequest::POST); - else - request.set_method(HTTP::HttpRequest::Invalid); - request.set_url(move(url)); - request.set_headers(request_headers); - request.set_body(TRY(ByteBuffer::copy(request_body))); - - return adopt_ref(*new HTTPSHeadlessRequest(move(request), move(socket), move(stream_backing_buffer))); - } - - virtual ~HTTPSHeadlessRequest() override - { - } - - virtual void set_should_buffer_all_input(bool) override - { - } - - virtual bool stop() override - { - return false; - } - - virtual void stream_into(Core::Stream::Stream&) override - { - } - - private: - HTTPSHeadlessRequest(HTTP::HttpRequest&& request, NonnullOwnPtr socket, ByteBuffer&& stream_backing_buffer) - : m_stream_backing_buffer(move(stream_backing_buffer)) - , m_output_stream(Core::Stream::MemoryStream::construct(m_stream_backing_buffer.bytes()).release_value_but_fixme_should_propagate_errors()) - , m_socket(move(socket)) - , m_job(HTTP::HttpsJob::construct(move(request), *m_output_stream)) - { - m_job->on_headers_received = [weak_this = make_weak_ptr()](auto& response_headers, auto response_code) mutable { - if (auto strong_this = weak_this.strong_ref()) { - strong_this->m_response_code = response_code; - for (auto& header : response_headers) { - strong_this->m_response_headers.set(header.key, header.value); - } - } - }; - m_job->on_finish = [weak_this = make_weak_ptr()](bool success) mutable { - Core::deferred_invoke([weak_this, success]() mutable { - if (auto strong_this = weak_this.strong_ref()) { - ReadonlyBytes response_bytes { strong_this->m_output_stream->bytes().data(), strong_this->m_output_stream->offset() }; - auto response_buffer = ByteBuffer::copy(response_bytes).release_value_but_fixme_should_propagate_errors(); - strong_this->on_buffered_request_finish(success, strong_this->m_output_stream->offset(), strong_this->m_response_headers, strong_this->m_response_code, response_buffer); - } }); - }; - m_job->start(*m_socket); - } - - Optional m_response_code; - ByteBuffer m_stream_backing_buffer; - NonnullOwnPtr m_output_stream; - NonnullOwnPtr m_socket; - NonnullRefPtr m_job; - HashMap m_response_headers; - }; - - class GeminiHeadlessRequest - : public Web::ResourceLoaderConnectorRequest - , public Weakable { - public: - static ErrorOr> create(String const&, AK::URL const& url, HashMap const&, ReadonlyBytes, Core::ProxyData const&) - { - auto stream_backing_buffer = TRY(ByteBuffer::create_uninitialized(1 * MiB)); - auto underlying_socket = TRY(Core::Stream::TCPSocket::connect(url.host(), url.port().value_or(80))); - TRY(underlying_socket->set_blocking(false)); - auto socket = TRY(Core::Stream::BufferedSocket::create(move(underlying_socket))); - - Gemini::GeminiRequest request; - request.set_url(url); - - return adopt_ref(*new GeminiHeadlessRequest(move(request), move(socket), move(stream_backing_buffer))); - } - - virtual ~GeminiHeadlessRequest() override - { - } - - virtual void set_should_buffer_all_input(bool) override - { - } - - virtual bool stop() override - { - return false; - } - - virtual void stream_into(Core::Stream::Stream&) override - { - } - - private: - GeminiHeadlessRequest(Gemini::GeminiRequest&& request, NonnullOwnPtr socket, ByteBuffer&& stream_backing_buffer) - : m_stream_backing_buffer(move(stream_backing_buffer)) - , m_output_stream(Core::Stream::MemoryStream::construct(m_stream_backing_buffer.bytes()).release_value_but_fixme_should_propagate_errors()) - , m_socket(move(socket)) - , m_job(Gemini::Job::construct(move(request), *m_output_stream)) - { - m_job->on_headers_received = [weak_this = make_weak_ptr()](auto& response_headers, auto response_code) mutable { - if (auto strong_this = weak_this.strong_ref()) { - strong_this->m_response_code = response_code; - for (auto& header : response_headers) { - strong_this->m_response_headers.set(header.key, header.value); - } - } - }; - m_job->on_finish = [weak_this = make_weak_ptr()](bool success) mutable { - Core::deferred_invoke([weak_this, success]() mutable { - if (auto strong_this = weak_this.strong_ref()) { - ReadonlyBytes response_bytes { strong_this->m_output_stream->bytes().data(), strong_this->m_output_stream->offset() }; - auto response_buffer = ByteBuffer::copy(response_bytes).release_value_but_fixme_should_propagate_errors(); - strong_this->on_buffered_request_finish(success, strong_this->m_output_stream->offset(), strong_this->m_response_headers, strong_this->m_response_code, response_buffer); - } }); - }; - m_job->start(*m_socket); - } - - Optional m_response_code; - ByteBuffer m_stream_backing_buffer; - NonnullOwnPtr m_output_stream; - NonnullOwnPtr m_socket; - NonnullRefPtr m_job; - HashMap m_response_headers; - }; - - static NonnullRefPtr create() - { - return adopt_ref(*new HeadlessRequestServer()); - } - - virtual ~HeadlessRequestServer() override { } - - virtual void prefetch_dns(AK::URL const&) override { } - virtual void preconnect(AK::URL const&) override { } - - virtual RefPtr start_request(String const& method, AK::URL const& url, HashMap const& request_headers, ReadonlyBytes request_body, Core::ProxyData const& proxy) override - { - RefPtr request; - if (url.protocol().equals_ignoring_case("http"sv)) { - auto request_or_error = HTTPHeadlessRequest::create(method, url, request_headers, request_body, proxy); - if (request_or_error.is_error()) - return {}; - request = request_or_error.release_value(); - } - if (url.protocol().equals_ignoring_case("https"sv)) { - auto request_or_error = HTTPSHeadlessRequest::create(method, url, request_headers, request_body, proxy); - if (request_or_error.is_error()) - return {}; - request = request_or_error.release_value(); - } - if (url.protocol().equals_ignoring_case("gemini"sv)) { - auto request_or_error = GeminiHeadlessRequest::create(method, url, request_headers, request_body, proxy); - if (request_or_error.is_error()) - return {}; - request = request_or_error.release_value(); - } - if (request) - s_all_requests.set(request); - return request; - } - -private: - HeadlessRequestServer() { } -}; - class HeadlessWebSocketClientManager : public Web::WebSockets::WebSocketClientManager { public: class HeadlessWebSocket @@ -823,7 +549,7 @@ private: void initialize_web_engine() { Web::ImageDecoding::Decoder::initialize(HeadlessImageDecoderClient::create()); - Web::ResourceLoader::initialize(HeadlessRequestServer::create()); + Web::ResourceLoader::initialize(RequestManagerQt::create()); Web::WebSockets::WebSocketClientManager::initialize(HeadlessWebSocketClientManager::create()); Web::FrameLoader::set_default_favicon_path(String::formatted("{}/res/icons/16x16/app-browser.png", s_serenity_resource_root));