2020-01-18 09:38:21 +01:00
|
|
|
/*
|
2024-11-11 18:05:09 +01:00
|
|
|
* Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org>
|
2022-04-30 12:06:30 +02:00
|
|
|
* Copyright (c) 2022, Dex♪ <dexes.ttp@gmail.com>
|
2020-01-18 09:38:21 +01:00
|
|
|
*
|
2021-04-22 01:24:48 -07:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-01-18 09:38:21 +01:00
|
|
|
*/
|
|
|
|
|
|
2021-01-17 18:17:00 +01:00
|
|
|
#include <AK/Debug.h>
|
2023-08-13 22:35:35 +02:00
|
|
|
#include <LibCore/Directory.h>
|
2023-03-22 23:54:23 +01:00
|
|
|
#include <LibCore/MimeData.h>
|
2023-12-23 15:42:05 -05:00
|
|
|
#include <LibCore/Resource.h>
|
2025-02-22 20:43:52 +13:00
|
|
|
#include <LibGC/Function.h>
|
2024-10-07 16:07:48 -04:00
|
|
|
#include <LibRequests/Request.h>
|
|
|
|
|
#include <LibRequests/RequestClient.h>
|
2025-02-22 20:43:52 +13:00
|
|
|
#include <LibURL/Parser.h>
|
2022-12-30 22:57:58 +00:00
|
|
|
#include <LibWeb/Cookie/Cookie.h>
|
|
|
|
|
#include <LibWeb/Cookie/ParsedCookie.h>
|
2024-03-24 08:37:33 -04:00
|
|
|
#include <LibWeb/Fetch/Infrastructure/URL.h>
|
2021-01-05 18:12:29 +01:00
|
|
|
#include <LibWeb/Loader/ContentFilter.h>
|
2023-09-21 17:32:31 +02:00
|
|
|
#include <LibWeb/Loader/GeneratedPagesLoader.h>
|
2020-06-01 21:58:29 +02:00
|
|
|
#include <LibWeb/Loader/LoadRequest.h>
|
2022-04-08 01:46:47 +04:30
|
|
|
#include <LibWeb/Loader/ProxyMappings.h>
|
2020-06-01 20:42:50 +02:00
|
|
|
#include <LibWeb/Loader/ResourceLoader.h>
|
2024-10-07 16:07:48 -04:00
|
|
|
#include <LibWeb/Page/Page.h>
|
2022-09-07 20:30:31 +02:00
|
|
|
#include <LibWeb/Platform/EventLoopPlugin.h>
|
|
|
|
|
#include <LibWeb/Platform/Timer.h>
|
2022-04-30 12:09:19 +02:00
|
|
|
|
2020-03-07 10:27:02 +01:00
|
|
|
namespace Web {
|
|
|
|
|
|
2022-04-30 12:06:30 +02:00
|
|
|
static RefPtr<ResourceLoader> s_resource_loader;
|
|
|
|
|
|
2024-11-15 04:01:23 +13:00
|
|
|
void ResourceLoader::initialize(GC::Heap& heap, NonnullRefPtr<Requests::RequestClient> request_client)
|
2019-10-08 19:37:15 +02:00
|
|
|
{
|
2024-10-31 02:38:57 +13:00
|
|
|
s_resource_loader = adopt_ref(*new ResourceLoader(heap, move(request_client)));
|
2019-10-08 19:37:15 +02:00
|
|
|
}
|
|
|
|
|
|
2022-04-30 12:06:30 +02:00
|
|
|
ResourceLoader& ResourceLoader::the()
|
2022-01-14 13:12:49 +00:00
|
|
|
{
|
2022-04-30 12:06:30 +02:00
|
|
|
if (!s_resource_loader) {
|
|
|
|
|
dbgln("Web::ResourceLoader was not initialized");
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
return *s_resource_loader;
|
|
|
|
|
}
|
2022-01-14 13:12:49 +00:00
|
|
|
|
2024-11-15 04:01:23 +13:00
|
|
|
ResourceLoader::ResourceLoader(GC::Heap& heap, NonnullRefPtr<Requests::RequestClient> request_client)
|
2024-10-31 02:38:57 +13:00
|
|
|
: m_heap(heap)
|
|
|
|
|
, m_request_client(move(request_client))
|
2023-11-20 13:44:27 +13:00
|
|
|
, m_user_agent(MUST(String::from_utf8(default_user_agent)))
|
|
|
|
|
, m_platform(MUST(String::from_utf8(default_platform)))
|
2024-07-23 21:10:24 +01:00
|
|
|
, m_preferred_languages({ "en-US"_string })
|
2024-07-02 01:04:40 +01:00
|
|
|
, m_navigator_compatibility_mode(default_navigator_compatibility_mode)
|
2019-11-24 14:24:09 +01:00
|
|
|
{
|
2025-08-09 13:07:57 -04:00
|
|
|
m_request_client->on_request_server_died = [this]() {
|
|
|
|
|
m_request_client = nullptr;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ResourceLoader::set_client(NonnullRefPtr<Requests::RequestClient> request_client)
|
|
|
|
|
{
|
|
|
|
|
m_request_client = move(request_client);
|
|
|
|
|
m_request_client->on_request_server_died = [this]() {
|
|
|
|
|
m_request_client = nullptr;
|
|
|
|
|
};
|
2019-11-24 14:24:09 +01:00
|
|
|
}
|
|
|
|
|
|
2024-03-18 16:22:27 +13:00
|
|
|
void ResourceLoader::prefetch_dns(URL::URL const& url)
|
2021-09-28 00:08:29 +03:30
|
|
|
{
|
2024-03-26 11:39:45 +01:00
|
|
|
if (url.scheme().is_one_of("file"sv, "data"sv))
|
|
|
|
|
return;
|
|
|
|
|
|
2022-06-10 11:53:43 +01:00
|
|
|
if (ContentFilter::the().is_filtered(url)) {
|
|
|
|
|
dbgln("ResourceLoader: Refusing to prefetch DNS for '{}': \033[31;1mURL was filtered\033[0m", url);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-09 13:07:57 -04:00
|
|
|
// FIXME: We could put this request in a queue until the client connection is re-established.
|
|
|
|
|
if (m_request_client)
|
|
|
|
|
m_request_client->ensure_connection(url, RequestServer::CacheLevel::ResolveOnly);
|
2021-09-28 00:08:29 +03:30
|
|
|
}
|
|
|
|
|
|
2024-03-18 16:22:27 +13:00
|
|
|
void ResourceLoader::preconnect(URL::URL const& url)
|
2021-09-28 00:08:29 +03:30
|
|
|
{
|
2023-12-28 21:33:56 +01:00
|
|
|
if (url.scheme().is_one_of("file"sv, "data"sv))
|
|
|
|
|
return;
|
|
|
|
|
|
2022-06-10 11:53:43 +01:00
|
|
|
if (ContentFilter::the().is_filtered(url)) {
|
|
|
|
|
dbgln("ResourceLoader: Refusing to pre-connect to '{}': \033[31;1mURL was filtered\033[0m", url);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-09 13:07:57 -04:00
|
|
|
// FIXME: We could put this request in a queue until the client connection is re-established.
|
|
|
|
|
if (m_request_client)
|
|
|
|
|
m_request_client->ensure_connection(url, RequestServer::CacheLevel::CreateConnection);
|
2021-09-28 00:08:29 +03:30
|
|
|
}
|
|
|
|
|
|
2024-03-18 16:22:27 +13:00
|
|
|
static ByteString sanitized_url_for_logging(URL::URL const& url)
|
2021-09-16 00:52:39 +02:00
|
|
|
{
|
2022-09-29 01:30:58 +02:00
|
|
|
if (url.scheme() == "data"sv)
|
AK: Decode data URLs to separate class (and parse like every other URL)
Parsing 'data:' URLs took it's own route. It never set standard URL
fields like path, query or fragment (except for scheme) and instead
gave us separate methods called `data_payload()`, `data_mime_type()`,
and `data_payload_is_base64()`.
Because parsing 'data:' didn't use standard fields, running the
following JS code:
new URL('#a', 'data:text/plain,hello').toString()
not only cleared the path as URLParser doesn't check for data from
data_payload() function (making the result be 'data:#a'), but it also
crashes the program because we forbid having an empty MIME type when we
serialize to string.
With this change, 'data:' URLs will be parsed like every other URLs.
To decode the 'data:' URL contents, one needs to call process_data_url()
on a URL, which will return a struct containing MIME type with already
decoded data! :^)
2023-07-06 19:11:58 +02:00
|
|
|
return "[data URL]"sv;
|
2023-12-16 17:49:34 +03:30
|
|
|
return url.to_byte_string();
|
2021-09-16 00:52:39 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-09 13:27:58 +02:00
|
|
|
static void store_response_cookies(Page& page, URL::URL const& url, ByteString const& set_cookie_entry)
|
2022-12-30 22:57:58 +00:00
|
|
|
{
|
2024-09-18 12:41:53 -04:00
|
|
|
auto cookie = Cookie::parse_cookie(url, set_cookie_entry);
|
2024-06-09 13:27:58 +02:00
|
|
|
if (!cookie.has_value())
|
|
|
|
|
return;
|
|
|
|
|
page.client().page_did_set_cookie(url, cookie.value(), Cookie::Source::Http); // FIXME: Determine cookie source correctly
|
2022-12-30 22:57:58 +00:00
|
|
|
}
|
|
|
|
|
|
2024-06-09 11:28:37 +02:00
|
|
|
static HTTP::HeaderMap response_headers_for_file(StringView path, Optional<time_t> const& modified_time)
|
2023-12-23 15:42:05 -05:00
|
|
|
{
|
|
|
|
|
// For file:// and resource:// URLs, we have to guess the MIME type, since there's no HTTP header to tell us what
|
|
|
|
|
// it is. We insert a fake Content-Type header here, so that clients can use it to learn the MIME type.
|
|
|
|
|
auto mime_type = Core::guess_mime_type_based_on_filename(path);
|
|
|
|
|
|
2024-06-09 11:28:37 +02:00
|
|
|
HTTP::HeaderMap response_headers;
|
2025-04-23 11:14:15 -04:00
|
|
|
response_headers.set("Access-Control-Allow-Origin"sv, "null"sv);
|
2023-12-23 15:42:05 -05:00
|
|
|
response_headers.set("Content-Type"sv, mime_type);
|
|
|
|
|
|
2024-04-01 21:28:06 +02:00
|
|
|
if (modified_time.has_value()) {
|
2025-06-19 21:05:42 +02:00
|
|
|
auto const datetime = AK::UnixDateTime::from_seconds_since_epoch(modified_time.value());
|
2025-06-25 16:32:24 -04:00
|
|
|
response_headers.set("Last-Modified"sv, datetime.to_byte_string("%a, %d %b %Y %H:%M:%S GMT"sv, AK::UnixDateTime::LocalTime::No));
|
2024-04-01 21:28:06 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-23 15:42:05 -05:00
|
|
|
return response_headers;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-26 07:52:39 -04:00
|
|
|
static void log_request_start(LoadRequest const& request)
|
2019-10-08 19:37:15 +02:00
|
|
|
{
|
2025-04-19 16:59:36 +12:00
|
|
|
auto url_for_logging = sanitized_url_for_logging(*request.url());
|
2021-09-11 21:17:04 -07:00
|
|
|
|
2023-12-08 14:08:30 -07:00
|
|
|
dbgln_if(SPAM_DEBUG, "ResourceLoader: Starting load of: \"{}\"", url_for_logging);
|
2024-05-26 07:52:39 -04:00
|
|
|
}
|
2022-03-24 09:45:30 +01:00
|
|
|
|
2024-05-26 07:52:39 -04:00
|
|
|
static void log_success(LoadRequest const& request)
|
|
|
|
|
{
|
2025-04-19 16:59:36 +12:00
|
|
|
auto url_for_logging = sanitized_url_for_logging(*request.url());
|
2024-05-26 07:52:39 -04:00
|
|
|
auto load_time_ms = request.load_time().to_milliseconds();
|
|
|
|
|
|
|
|
|
|
dbgln_if(SPAM_DEBUG, "ResourceLoader: Finished load of: \"{}\", Duration: {}ms", url_for_logging, load_time_ms);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<typename ErrorType>
|
|
|
|
|
static void log_failure(LoadRequest const& request, ErrorType const& error)
|
|
|
|
|
{
|
2025-04-19 16:59:36 +12:00
|
|
|
auto url_for_logging = sanitized_url_for_logging(*request.url());
|
2024-05-26 07:52:39 -04:00
|
|
|
auto load_time_ms = request.load_time().to_milliseconds();
|
|
|
|
|
|
|
|
|
|
dbgln("ResourceLoader: Failed load of: \"{}\", \033[31;1mError: {}\033[0m, Duration: {}ms", url_for_logging, error, load_time_ms);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-04 14:27:37 -05:00
|
|
|
static void log_filtered_request(LoadRequest const& request)
|
|
|
|
|
{
|
2025-04-19 16:59:36 +12:00
|
|
|
auto url_for_logging = sanitized_url_for_logging(*request.url());
|
2024-07-04 14:27:37 -05:00
|
|
|
dbgln("ResourceLoader: Filtered request to: \"{}\"", url_for_logging);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-26 07:52:39 -04:00
|
|
|
static bool should_block_request(LoadRequest const& request)
|
|
|
|
|
{
|
2025-04-19 16:59:36 +12:00
|
|
|
auto const& url = request.url().value();
|
2021-09-11 21:17:04 -07:00
|
|
|
|
2024-05-26 07:52:39 -04:00
|
|
|
auto is_port_blocked = [](int port) {
|
|
|
|
|
static constexpr auto ports = to_array({ 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42,
|
|
|
|
|
43, 53, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 139,
|
|
|
|
|
143, 179, 389, 465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 556, 563, 587, 601, 636,
|
|
|
|
|
993, 995, 2049, 3659, 4045, 6000, 6379, 6665, 6666, 6667, 6668, 6669 });
|
|
|
|
|
|
|
|
|
|
return ports.first_index_of(port).has_value();
|
2021-09-11 21:17:04 -07:00
|
|
|
};
|
2021-01-05 18:12:29 +01:00
|
|
|
|
2024-05-26 07:52:39 -04:00
|
|
|
if (is_port_blocked(url.port_or_default())) {
|
|
|
|
|
log_failure(request, ByteString::formatted("Port #{} is blocked", url.port_or_default()));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ContentFilter::the().is_filtered(url)) {
|
2024-07-04 14:27:37 -05:00
|
|
|
log_filtered_request(request);
|
2024-05-26 07:52:39 -04:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-15 03:17:18 +01:00
|
|
|
template<typename FileHandler, typename ErrorHandler>
|
|
|
|
|
void ResourceLoader::handle_file_load_request(LoadRequest& request, FileHandler on_file, ErrorHandler on_error)
|
|
|
|
|
{
|
|
|
|
|
auto page = request.page();
|
|
|
|
|
if (!page) {
|
|
|
|
|
auto const error_message = ByteString("INTERNAL ERROR: No Page for file scheme request"sv);
|
|
|
|
|
on_error(error_message);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto const& url = request.url().value();
|
|
|
|
|
|
|
|
|
|
FileRequest file_request(url.file_path(), [this, request, on_file, on_error, url](ErrorOr<i32> file_or_error) mutable {
|
|
|
|
|
--m_pending_loads;
|
|
|
|
|
if (on_load_counter_change)
|
|
|
|
|
on_load_counter_change();
|
|
|
|
|
|
|
|
|
|
if (file_or_error.is_error()) {
|
|
|
|
|
auto const message = ByteString::formatted("{}", file_or_error.error());
|
|
|
|
|
on_error(message);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto const fd = file_or_error.value();
|
|
|
|
|
|
|
|
|
|
auto maybe_is_valid_directory = Core::Directory::is_valid_directory(fd);
|
|
|
|
|
if (!maybe_is_valid_directory.is_error() && maybe_is_valid_directory.value()) {
|
|
|
|
|
auto maybe_response = load_file_directory_page(url);
|
|
|
|
|
if (maybe_response.is_error()) {
|
|
|
|
|
auto const message = ByteString::formatted("{}", maybe_response.error());
|
|
|
|
|
on_error(message);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FileLoadResult load_result;
|
|
|
|
|
load_result.data = maybe_response.value().bytes();
|
|
|
|
|
load_result.response_headers.set("Content-Type"sv, "text/html"sv);
|
|
|
|
|
on_file(load_result);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto st_or_error = Core::System::fstat(fd);
|
|
|
|
|
if (st_or_error.is_error()) {
|
|
|
|
|
on_error(ByteString::formatted("{}", st_or_error.error()));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto maybe_file = Core::File::adopt_fd(fd, Core::File::OpenMode::Read);
|
|
|
|
|
if (maybe_file.is_error()) {
|
|
|
|
|
on_error(ByteString::formatted("{}", maybe_file.error()));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto file = maybe_file.release_value();
|
|
|
|
|
auto maybe_data = file->read_until_eof();
|
|
|
|
|
if (maybe_data.is_error()) {
|
|
|
|
|
on_error(ByteString::formatted("{}", maybe_data.error()));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FileLoadResult load_result;
|
|
|
|
|
load_result.data = maybe_data.value().bytes();
|
|
|
|
|
load_result.response_headers = response_headers_for_file(request.url()->file_path(), st_or_error.value().st_mtime);
|
|
|
|
|
on_file(load_result);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
page->client().request_file(move(file_request));
|
|
|
|
|
|
|
|
|
|
++m_pending_loads;
|
|
|
|
|
if (on_load_counter_change)
|
|
|
|
|
on_load_counter_change();
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-16 18:38:53 +01:00
|
|
|
template<typename Callback>
|
|
|
|
|
void ResourceLoader::handle_about_load_request(LoadRequest const& request, Callback callback)
|
|
|
|
|
{
|
|
|
|
|
auto const& url = request.url().value();
|
|
|
|
|
|
|
|
|
|
dbgln_if(SPAM_DEBUG, "Loading about: URL {}", url);
|
|
|
|
|
|
|
|
|
|
HTTP::HeaderMap response_headers;
|
|
|
|
|
response_headers.set("Content-Type"sv, "text/html; charset=UTF-8"sv);
|
|
|
|
|
|
|
|
|
|
// FIXME: Implement timing info for about requests.
|
|
|
|
|
Requests::RequestTimingInfo timing_info {};
|
|
|
|
|
|
|
|
|
|
auto serialized_path = URL::percent_decode(url.serialize_path());
|
|
|
|
|
|
|
|
|
|
// About version page
|
|
|
|
|
if (serialized_path == "version") {
|
|
|
|
|
auto version_page = MUST(load_about_version_page());
|
|
|
|
|
callback(version_page.bytes(), timing_info, response_headers);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Other about static HTML pages
|
|
|
|
|
auto target_file = ByteString::formatted("{}.html", serialized_path);
|
|
|
|
|
|
|
|
|
|
auto about_directory = MUST(Core::Resource::load_from_uri("resource://ladybird/about-pages"_string));
|
|
|
|
|
if (about_directory->children().contains_slow(target_file.view())) {
|
|
|
|
|
auto resource = Core::Resource::load_from_uri(ByteString::formatted("resource://ladybird/about-pages/{}", target_file));
|
|
|
|
|
if (!resource.is_error()) {
|
|
|
|
|
auto const& buffer = resource.value()->data();
|
|
|
|
|
ReadonlyBytes data(buffer.data(), buffer.size());
|
|
|
|
|
callback(data, timing_info, response_headers);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(
|
|
|
|
|
m_heap,
|
|
|
|
|
[callback, timing_info, response_headers = move(response_headers)]() mutable {
|
|
|
|
|
auto buffer = ByteString::empty().to_byte_buffer();
|
|
|
|
|
callback(buffer.bytes(), timing_info, response_headers);
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-17 01:57:14 +01:00
|
|
|
template<typename ResourceHandler, typename ErrorHandler>
|
|
|
|
|
void ResourceLoader::handle_resource_load_request(LoadRequest const& request, ResourceHandler on_resource, ErrorHandler on_error)
|
2024-05-26 07:52:39 -04:00
|
|
|
{
|
2025-04-19 16:59:36 +12:00
|
|
|
auto const& url = request.url().value();
|
2024-05-26 07:52:39 -04:00
|
|
|
|
2025-11-17 01:57:14 +01:00
|
|
|
auto resource = Core::Resource::load_from_uri(url.serialize());
|
|
|
|
|
if (resource.is_error()) {
|
|
|
|
|
on_error(ByteString::formatted("{}", resource.error()));
|
2024-05-26 07:52:39 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-17 01:57:14 +01:00
|
|
|
auto resource_value = resource.release_value();
|
2025-02-26 13:28:21 +00:00
|
|
|
|
2025-11-17 01:57:14 +01:00
|
|
|
// When resource URI is a directory use file directory loader to generate response
|
|
|
|
|
if (resource_value->is_directory()) {
|
|
|
|
|
auto directory_url = URL::Parser::basic_parse(resource_value->file_url());
|
|
|
|
|
VERIFY(directory_url.has_value());
|
|
|
|
|
|
|
|
|
|
auto maybe_response = load_file_directory_page(directory_url.release_value());
|
2023-12-26 10:12:35 +01:00
|
|
|
if (maybe_response.is_error()) {
|
2025-11-17 01:57:14 +01:00
|
|
|
on_error(ByteString::formatted("{}", maybe_response.error()));
|
2023-12-26 10:12:35 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-17 01:57:14 +01:00
|
|
|
FileLoadResult load_result;
|
2025-11-25 18:06:48 +01:00
|
|
|
load_result.data = maybe_response.value().bytes();
|
2025-11-17 01:57:14 +01:00
|
|
|
load_result.response_headers.set("Content-Type"sv, "text/html"sv);
|
|
|
|
|
on_resource(load_result);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto const& buffer = resource_value->data();
|
|
|
|
|
auto response_headers = response_headers_for_file(url.file_path(), resource_value->modified_time());
|
|
|
|
|
|
|
|
|
|
// FIXME: Implement timing info for resource requests.
|
|
|
|
|
Requests::RequestTimingInfo timing_info {};
|
|
|
|
|
|
|
|
|
|
FileLoadResult load_result;
|
|
|
|
|
load_result.data = buffer;
|
|
|
|
|
load_result.response_headers = move(response_headers);
|
|
|
|
|
load_result.timing_info = timing_info;
|
|
|
|
|
on_resource(load_result);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-17 03:35:51 +01:00
|
|
|
void ResourceLoader::load(LoadRequest& request, GC::Root<OnHeadersReceived> on_headers_received, GC::Root<OnDataReceived> on_data_received, GC::Root<OnComplete> on_complete)
|
2020-04-26 22:08:03 +02:00
|
|
|
{
|
2025-04-19 16:59:36 +12:00
|
|
|
auto const& url = request.url().value();
|
2024-05-26 07:52:39 -04:00
|
|
|
|
|
|
|
|
log_request_start(request);
|
|
|
|
|
request.start_timer();
|
|
|
|
|
|
|
|
|
|
if (should_block_request(request)) {
|
2025-02-26 13:28:21 +00:00
|
|
|
on_complete->function()(false, {}, "Request was blocked"sv);
|
2024-05-26 07:52:39 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-16 18:38:53 +01:00
|
|
|
if (url.scheme() == "about"sv) {
|
|
|
|
|
handle_about_load_request(request, [on_headers_received, on_data_received, on_complete, request](ReadonlyBytes data, Requests::RequestTimingInfo const& timing_info, HTTP::HeaderMap const& response_headers) {
|
|
|
|
|
log_success(request);
|
|
|
|
|
on_headers_received->function()(response_headers, {}, {});
|
|
|
|
|
on_data_received->function()(data);
|
|
|
|
|
on_complete->function()(true, timing_info, {});
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-17 01:57:14 +01:00
|
|
|
if (url.scheme() == "resource"sv) {
|
|
|
|
|
handle_resource_load_request(
|
|
|
|
|
request,
|
|
|
|
|
[on_headers_received, on_data_received, on_complete](FileLoadResult const& load_result) {
|
|
|
|
|
on_headers_received->function()(load_result.response_headers, {}, {});
|
|
|
|
|
on_data_received->function()(load_result.data);
|
|
|
|
|
on_complete->function()(true, load_result.timing_info, {});
|
|
|
|
|
},
|
|
|
|
|
[on_complete](ByteString const& message) {
|
|
|
|
|
Requests::RequestTimingInfo fixme_implement_timing_info {};
|
|
|
|
|
on_complete->function()(false, fixme_implement_timing_info, StringView(message));
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-15 03:17:18 +01:00
|
|
|
if (url.scheme() == "file"sv) {
|
|
|
|
|
handle_file_load_request(
|
|
|
|
|
request,
|
|
|
|
|
[request, on_headers_received, on_data_received, on_complete](FileLoadResult const& load_result) {
|
|
|
|
|
log_success(request);
|
|
|
|
|
on_headers_received->function()(load_result.response_headers, {}, {});
|
|
|
|
|
on_data_received->function()(load_result.data);
|
|
|
|
|
on_complete->function()(true, load_result.timing_info, {});
|
|
|
|
|
},
|
|
|
|
|
[on_complete, request](ByteString const& message) {
|
|
|
|
|
log_failure(request, message);
|
|
|
|
|
on_complete->function()(false, {}, StringView(message));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-04 08:00:13 +02:00
|
|
|
if (!url.scheme().is_one_of("http"sv, "https"sv)) {
|
2025-04-17 07:58:24 -04:00
|
|
|
auto not_implemented_error = ByteString::formatted("Protocol not implemented: {}", url.scheme());
|
|
|
|
|
log_failure(request, not_implemented_error);
|
|
|
|
|
on_complete->function()(false, {}, not_implemented_error);
|
2024-05-26 07:52:39 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto protocol_request = start_network_request(request);
|
|
|
|
|
if (!protocol_request) {
|
2025-02-26 13:28:21 +00:00
|
|
|
on_complete->function()(false, {}, "Failed to start network request"sv);
|
2024-05-26 07:52:39 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-23 16:46:26 -05:00
|
|
|
auto protocol_headers_received = [this, on_headers_received, request](auto const& response_headers, auto status_code, auto const& reason_phrase) {
|
2024-05-26 07:52:39 -04:00
|
|
|
handle_network_response_headers(request, response_headers);
|
2024-10-23 16:46:26 -05:00
|
|
|
on_headers_received->function()(response_headers, move(status_code), reason_phrase);
|
2024-05-26 07:52:39 -04:00
|
|
|
};
|
|
|
|
|
|
2024-10-31 06:12:14 +13:00
|
|
|
auto protocol_data_received = [on_data_received](auto data) {
|
|
|
|
|
on_data_received->function()(data);
|
2024-05-26 07:52:39 -04:00
|
|
|
};
|
|
|
|
|
|
2025-02-26 13:28:21 +00:00
|
|
|
auto protocol_complete = [this, on_complete, request, &protocol_request = *protocol_request](u64, Requests::RequestTimingInfo const& timing_info, Optional<Requests::NetworkError> const& network_error) {
|
2024-05-26 07:52:39 -04:00
|
|
|
finish_network_request(protocol_request);
|
|
|
|
|
|
2024-10-09 17:01:34 -05:00
|
|
|
if (!network_error.has_value()) {
|
2024-05-26 07:52:39 -04:00
|
|
|
log_success(request);
|
2025-02-26 13:28:21 +00:00
|
|
|
on_complete->function()(true, timing_info, {});
|
2024-05-26 07:52:39 -04:00
|
|
|
} else {
|
|
|
|
|
log_failure(request, "Request finished with error"sv);
|
2025-02-26 13:28:21 +00:00
|
|
|
on_complete->function()(false, timing_info, "Request finished with error"sv);
|
2024-05-26 07:52:39 -04:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
protocol_request->set_unbuffered_request_callbacks(move(protocol_headers_received), move(protocol_data_received), move(protocol_complete));
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-07 16:07:48 -04:00
|
|
|
RefPtr<Requests::Request> ResourceLoader::start_network_request(LoadRequest const& request)
|
2024-05-26 07:52:39 -04:00
|
|
|
{
|
2025-04-19 16:59:36 +12:00
|
|
|
auto proxy = ProxyMappings::the().proxy_for_url(request.url().value());
|
2024-05-26 07:52:39 -04:00
|
|
|
|
2024-06-09 13:46:04 +02:00
|
|
|
HTTP::HeaderMap headers;
|
2024-05-26 07:52:39 -04:00
|
|
|
|
|
|
|
|
for (auto const& it : request.headers()) {
|
|
|
|
|
headers.set(it.key, it.value);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-19 08:08:06 +02:00
|
|
|
if (!headers.contains("User-Agent"))
|
|
|
|
|
headers.set("User-Agent", m_user_agent.to_byte_string());
|
|
|
|
|
|
2025-08-09 13:07:57 -04:00
|
|
|
// FIXME: We could put this request in a queue until the client connection is re-established.
|
|
|
|
|
if (!m_request_client) {
|
|
|
|
|
log_failure(request, "RequestServer is currently unavailable"sv);
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-19 16:59:36 +12:00
|
|
|
auto protocol_request = m_request_client->start_request(request.method(), request.url().value(), headers, request.body(), proxy);
|
2024-05-26 07:52:39 -04:00
|
|
|
if (!protocol_request) {
|
|
|
|
|
log_failure(request, "Failed to initiate load"sv);
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-07 16:07:48 -04:00
|
|
|
protocol_request->on_certificate_requested = []() -> Requests::Request::CertificateAndKey {
|
2024-05-26 07:52:39 -04:00
|
|
|
return {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
++m_pending_loads;
|
|
|
|
|
if (on_load_counter_change)
|
|
|
|
|
on_load_counter_change();
|
|
|
|
|
|
|
|
|
|
m_active_requests.set(*protocol_request);
|
|
|
|
|
return protocol_request;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-09 11:28:37 +02:00
|
|
|
void ResourceLoader::handle_network_response_headers(LoadRequest const& request, HTTP::HeaderMap const& response_headers)
|
2024-05-26 07:52:39 -04:00
|
|
|
{
|
|
|
|
|
if (!request.page())
|
|
|
|
|
return;
|
|
|
|
|
|
2025-08-23 03:49:58 -04:00
|
|
|
if (request.store_set_cookie_headers()) {
|
|
|
|
|
// From https://fetch.spec.whatwg.org/#concept-http-network-fetch:
|
|
|
|
|
// 15. If includeCredentials is true, then the user agent should parse and store response
|
|
|
|
|
// `Set-Cookie` headers given request and response.
|
|
|
|
|
for (auto const& [header, value] : response_headers.headers()) {
|
|
|
|
|
if (header.equals_ignoring_ascii_case("Set-Cookie"sv)) {
|
|
|
|
|
store_response_cookies(*request.page(), request.url().value(), value);
|
|
|
|
|
}
|
2024-06-09 13:27:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-05-26 07:52:39 -04:00
|
|
|
}
|
|
|
|
|
|
2024-11-11 18:05:09 +01:00
|
|
|
void ResourceLoader::finish_network_request(NonnullRefPtr<Requests::Request> protocol_request)
|
2024-05-26 07:52:39 -04:00
|
|
|
{
|
|
|
|
|
--m_pending_loads;
|
|
|
|
|
if (on_load_counter_change)
|
|
|
|
|
on_load_counter_change();
|
|
|
|
|
|
2024-11-11 18:05:09 +01:00
|
|
|
deferred_invoke([this, protocol_request = move(protocol_request)] {
|
|
|
|
|
auto did_remove = m_active_requests.remove(protocol_request);
|
|
|
|
|
VERIFY(did_remove);
|
|
|
|
|
});
|
2020-04-12 04:01:34 +00:00
|
|
|
}
|
|
|
|
|
|
2020-03-07 10:27:02 +01:00
|
|
|
}
|