blob: 54bfe95481e74bd36ba431c2cf606a00494476bd [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/webapps/isolated_web_apps/url_loading/url_loader.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/types/expected.h"
#include "components/web_package/web_bundle_utils.h"
#include "components/webapps/isolated_web_apps/reading/response_reader.h"
#include "components/webapps/isolated_web_apps/reading/response_reader_registry.h"
#include "components/webapps/isolated_web_apps/reading/response_reader_registry_factory.h"
#include "components/webapps/isolated_web_apps/url_loading/utils.h"
#include "content/public/browser/browser_context.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "net/base/net_errors.h"
#include "net/http/http_status_code.h"
#include "services/network/public/cpp/constants.h"
#include "services/network/public/cpp/loading_params.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_loader_completion_status.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
namespace web_app {
namespace {
class IsolatedWebAppURLLoaderImpl : public network::mojom::URLLoader {
public:
IsolatedWebAppURLLoaderImpl(
const base::FilePath& web_bundle_path,
bool dev_mode,
const web_package::SignedWebBundleId& web_bundle_id,
mojo::PendingRemote<network::mojom::URLLoaderClient> loader_client,
const network::ResourceRequest& resource_request,
std::optional<content::FrameTreeNodeId> frame_tree_node_id)
: loader_client_(std::move(loader_client)),
resource_request_(resource_request),
frame_tree_node_id_(frame_tree_node_id),
web_bundle_path_(web_bundle_path),
dev_mode_(dev_mode),
web_bundle_id_(web_bundle_id) {}
IsolatedWebAppURLLoaderImpl(const IsolatedWebAppURLLoaderImpl&) = delete;
IsolatedWebAppURLLoaderImpl& operator=(const IsolatedWebAppURLLoaderImpl&) =
delete;
~IsolatedWebAppURLLoaderImpl() override = default;
void Start(content::BrowserContext* browser_context) {
IsolatedWebAppReaderRegistryFactory::Get(browser_context)
->ReadResponse(
web_bundle_path_, dev_mode_, web_bundle_id_, resource_request_,
base::BindOnce(&IsolatedWebAppURLLoaderImpl::OnResponseRead,
weak_factory_.GetWeakPtr()));
}
private:
void OnResponseRead(
base::expected<IsolatedWebAppResponseReader::Response,
IsolatedWebAppReaderRegistry::ReadResponseError>
response) {
if (!loader_client_.is_connected()) {
return;
}
if (!response.has_value()) {
LogErrorMessageToConsole(frame_tree_node_id_, response.error().message);
switch (response.error().type) {
case IsolatedWebAppReaderRegistry::ReadResponseError::Type::kOtherError:
loader_client_->OnComplete(
network::URLLoaderCompletionStatus(net::ERR_INVALID_WEB_BUNDLE));
return;
case IsolatedWebAppReaderRegistry::ReadResponseError::Type::
kResponseNotFound:
// Return a synthetic 404 response.
CompleteWithGeneratedResponse(std::move(loader_client_),
net::HTTP_NOT_FOUND);
return;
}
}
// TODO(crbug.com/41474458): For the initial implementation, we allow only
// net::HTTP_OK, but we should clarify acceptable status code in the spec.
if (response->head()->response_code != net::HTTP_OK) {
LogErrorMessageToConsole(
frame_tree_node_id_,
base::StringPrintf(
"Failed to read response from Signed Web Bundle: The response "
"has an unsupported HTTP status code: %d (only status code %d is "
"allowed).",
response->head()->response_code, net::HTTP_OK));
loader_client_->OnComplete(
network::URLLoaderCompletionStatus(net::ERR_INVALID_WEB_BUNDLE));
return;
}
std::string header_string =
web_package::CreateHeaderString(response->head());
auto response_head =
web_package::CreateResourceResponseFromHeaderString(header_string);
response_head->content_length = response->head()->payload_length;
mojo::ScopedDataPipeProducerHandle producer_handle;
mojo::ScopedDataPipeConsumerHandle consumer_handle;
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes =
std::min(uint64_t{network::GetDataPipeDefaultAllocationSize()},
response->head()->payload_length);
auto result =
mojo::CreateDataPipe(&options, producer_handle, consumer_handle);
if (result != MOJO_RESULT_OK) {
loader_client_->OnComplete(
network::URLLoaderCompletionStatus(net::ERR_INSUFFICIENT_RESOURCES));
return;
}
header_length_ = header_string.size();
body_length_ = response_head->content_length;
loader_client_->OnReceiveResponse(std::move(response_head),
std::move(consumer_handle), std::nullopt);
response->ReadBody(
std::move(producer_handle),
base::BindOnce(&IsolatedWebAppURLLoaderImpl::FinishReadingBody,
weak_factory_.GetWeakPtr()));
}
void FinishReadingBody(net::Error net_error) {
if (!loader_client_.is_connected()) {
return;
}
network::URLLoaderCompletionStatus status(net_error);
// For these values we use the same `body_length_` as we don't currently
// provide encoding in Web Bundles.
status.encoded_data_length = body_length_ + header_length_;
status.encoded_body_length = body_length_;
status.decoded_body_length = body_length_;
loader_client_->OnComplete(status);
}
// network::mojom::URLLoader implementation
void FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const std::optional<GURL>& new_url) override {
NOTREACHED();
}
void SetPriority(net::RequestPriority priority,
int intra_priority_value) override {}
mojo::Remote<network::mojom::URLLoaderClient> loader_client_;
int64_t header_length_;
int64_t body_length_;
const network::ResourceRequest resource_request_;
std::optional<content::FrameTreeNodeId> frame_tree_node_id_;
const base::FilePath web_bundle_path_;
const bool dev_mode_;
const web_package::SignedWebBundleId web_bundle_id_;
base::WeakPtrFactory<IsolatedWebAppURLLoaderImpl> weak_factory_{this};
};
} // namespace
// static
void IsolatedWebAppURLLoader::CreateAndStart(
content::BrowserContext* browser_context,
const base::FilePath& web_bundle_path,
bool dev_mode,
const web_package::SignedWebBundleId& web_bundle_id,
mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver,
mojo::PendingRemote<network::mojom::URLLoaderClient> loader_client,
const network::ResourceRequest& resource_request,
const std::optional<content::FrameTreeNodeId>& frame_tree_node_id) {
// `browser_context` is not stored in the class to prevent having to deal with
// dangling pointers (as this is a self-owned class without a shutdown
// notifier).
auto loader = std::make_unique<IsolatedWebAppURLLoaderImpl>(
web_bundle_path, dev_mode, web_bundle_id, std::move(loader_client),
resource_request, frame_tree_node_id);
auto* raw_loader = loader.get();
mojo::MakeSelfOwnedReceiver(std::move(loader), std::move(loader_receiver));
raw_loader->Start(browser_context);
}
} // namespace web_app