blob: 46314386ae29b2117ccd53c0eece435fc5fd8191 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/loader/prefetch_url_loader_service.h"
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/time/default_tick_clock.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/loader/prefetch_url_loader.h"
#include "content/browser/loader/url_loader_throttles.h"
#include "content/browser/url_loader_factory_getter.h"
#include "content/browser/web_package/prefetched_signed_exchange_cache.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/base/load_flags.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
#include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
#include "third_party/blink/public/mojom/renderer_preferences.mojom.h"
namespace content {
struct PrefetchURLLoaderService::BindContext {
BindContext(int frame_tree_node_id,
scoped_refptr<network::SharedURLLoaderFactory> factory,
base::WeakPtr<RenderFrameHostImpl> render_frame_host,
scoped_refptr<PrefetchedSignedExchangeCache>
prefetched_signed_exchange_cache)
: frame_tree_node_id(frame_tree_node_id),
factory(factory),
render_frame_host(std::move(render_frame_host)),
cross_origin_factory(nullptr),
prefetched_signed_exchange_cache(
std::move(prefetched_signed_exchange_cache)) {}
explicit BindContext(const std::unique_ptr<BindContext>& other)
: frame_tree_node_id(other->frame_tree_node_id),
factory(other->factory),
render_frame_host(other->render_frame_host),
cross_origin_factory(other->cross_origin_factory),
prefetched_signed_exchange_cache(
other->prefetched_signed_exchange_cache),
prefetch_network_isolation_keys(
other->prefetch_network_isolation_keys) {}
~BindContext() = default;
const int frame_tree_node_id;
scoped_refptr<network::SharedURLLoaderFactory> factory;
base::WeakPtr<RenderFrameHostImpl> render_frame_host;
// This member is lazily initialized by EnsureCrossOriginFactory().
scoped_refptr<network::SharedURLLoaderFactory> cross_origin_factory;
scoped_refptr<PrefetchedSignedExchangeCache> prefetched_signed_exchange_cache;
// This maps recursive prefetch tokens to NetworkIsolationKeys that they
// should be fetched with.
std::map<base::UnguessableToken, net::NetworkIsolationKey>
prefetch_network_isolation_keys;
// This must be the last member.
base::WeakPtrFactory<PrefetchURLLoaderService::BindContext> weak_ptr_factory{
this};
};
PrefetchURLLoaderService::PrefetchURLLoaderService(
BrowserContext* browser_context)
: browser_context_(browser_context),
signed_exchange_prefetch_metric_recorder_(
base::MakeRefCounted<SignedExchangePrefetchMetricRecorder>(
base::DefaultTickClock::GetInstance())) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
accept_langs_ =
GetContentClient()->browser()->GetAcceptLangs(browser_context);
// Create a RendererPreferenceWatcher to observe updates in the preferences.
GetContentClient()->browser()->RegisterRendererPreferenceWatcher(
browser_context, preference_watcher_receiver_.BindNewPipeAndPassRemote());
}
void PrefetchURLLoaderService::GetFactory(
mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver,
int frame_tree_node_id,
std::unique_ptr<network::PendingSharedURLLoaderFactory> factories,
base::WeakPtr<RenderFrameHostImpl> render_frame_host,
scoped_refptr<PrefetchedSignedExchangeCache>
prefetched_signed_exchange_cache) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto factory_bundle =
network::SharedURLLoaderFactory::Create(std::move(factories));
loader_factory_receivers_.Add(
this, std::move(receiver),
std::make_unique<BindContext>(
frame_tree_node_id, factory_bundle, std::move(render_frame_host),
std::move(prefetched_signed_exchange_cache)));
}
void PrefetchURLLoaderService::CreateLoaderAndStart(
mojo::PendingReceiver<network::mojom::URLLoader> receiver,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& resource_request_in,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Make a copy of |resource_request_in|, because we may need to modify the
// request.
network::ResourceRequest resource_request = resource_request_in;
DCHECK_EQ(static_cast<int>(blink::mojom::ResourceType::kPrefetch),
resource_request.resource_type);
auto& current_context = *loader_factory_receivers_.current_context();
if (!current_context.render_frame_host) {
mojo::Remote<network::mojom::URLLoaderClient>(std::move(client))
->OnComplete(network::URLLoaderCompletionStatus(net::ERR_ABORTED));
return;
}
scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory_to_use =
current_context.factory;
if (resource_request.load_flags & net::LOAD_RESTRICTED_PREFETCH) {
// The renderer has marked this prefetch as restricted, meaning it is a
// cross-origin prefetch intended for top-leve navigation reuse. We must
// verify that the request meets the necessary security requirements, and
// populate |resource_request|'s NetworkIsolationKey appropriately.
EnsureCrossOriginFactory();
DCHECK(current_context.cross_origin_factory);
// An invalid request could indicate a compromised renderer inappropriately
// modifying the request, so we immediately complete it with an error.
if (!IsValidCrossOriginPrefetch(resource_request)) {
mojo::Remote<network::mojom::URLLoaderClient>(std::move(client))
->OnComplete(
network::URLLoaderCompletionStatus(net::ERR_INVALID_ARGUMENT));
return;
}
// Use the trusted cross-origin prefetch loader factory, and set the
// request's NetworkIsolationKey suitable for the cross-origin prefetch.
network_loader_factory_to_use = current_context.cross_origin_factory;
url::Origin destination_origin = url::Origin::Create(resource_request.url);
resource_request.trusted_params = network::ResourceRequest::TrustedParams();
resource_request.trusted_params->network_isolation_key =
net::NetworkIsolationKey(destination_origin, destination_origin);
}
// Recursive prefetch from a cross-origin main resource prefetch.
if (resource_request.recursive_prefetch_token) {
// A request's |recursive_prefetch_token| is only provided if the request is
// a recursive prefetch. This means it is expected that the current
// context's |cross_origin_factory| was already created.
DCHECK(current_context.cross_origin_factory);
// Resurrect the request's NetworkIsolationKey from the current context's
// map, and use it for this request.
auto nik_iterator = current_context.prefetch_network_isolation_keys.find(
resource_request.recursive_prefetch_token.value());
// An unexpected token could indicate a compromised renderer trying to fetch
// a request in a special way. We'll cancel the request.
if (nik_iterator == current_context.prefetch_network_isolation_keys.end()) {
mojo::Remote<network::mojom::URLLoaderClient>(std::move(client))
->OnComplete(
network::URLLoaderCompletionStatus(net::ERR_INVALID_ARGUMENT));
return;
}
resource_request.trusted_params = network::ResourceRequest::TrustedParams();
resource_request.trusted_params->network_isolation_key =
nik_iterator->second;
network_loader_factory_to_use = current_context.cross_origin_factory;
}
if (prefetch_load_callback_for_testing_)
prefetch_load_callback_for_testing_.Run();
scoped_refptr<PrefetchedSignedExchangeCache> prefetched_signed_exchange_cache;
if (loader_factory_receivers_.current_context()) {
prefetched_signed_exchange_cache =
loader_factory_receivers_.current_context()
->prefetched_signed_exchange_cache;
}
// For now we make self owned receiver for the loader to the request, while we
// can also possibly make the new loader owned by the factory so that they can
// live longer than the client (i.e. run in detached mode).
// TODO(kinuko): Revisit this.
mojo::MakeSelfOwnedReceiver(
std::make_unique<PrefetchURLLoader>(
routing_id, request_id, options, current_context.frame_tree_node_id,
resource_request, std::move(client), traffic_annotation,
std::move(network_loader_factory_to_use),
base::BindRepeating(
&PrefetchURLLoaderService::CreateURLLoaderThrottles, this,
resource_request, current_context.frame_tree_node_id),
browser_context_, signed_exchange_prefetch_metric_recorder_,
std::move(prefetched_signed_exchange_cache), blob_storage_context_,
accept_langs_,
base::BindOnce(
&PrefetchURLLoaderService::GenerateRecursivePrefetchToken, this,
current_context.weak_ptr_factory.GetWeakPtr())),
std::move(receiver));
}
PrefetchURLLoaderService::~PrefetchURLLoaderService() = default;
// This method is used to determine whether it is safe to set the
// NetworkIsolationKey of a cross-origin prefetch request coming from the
// renderer, so that it can be cached correctly.
bool PrefetchURLLoaderService::IsValidCrossOriginPrefetch(
const network::ResourceRequest& resource_request) {
// The request is expected to be cross-origin. Same-origin prefetches do not
// need a special NetworkIsolationKey, and therefore must not be marked for
// restricted use.
url::Origin destination_origin = url::Origin::Create(resource_request.url);
// TODO(domfarolino): We want to check that the request's initiator is
// equivalent to the frame's last committed origin. If they are not equal, we
// must cancel the request.
if (!resource_request.request_initiator ||
resource_request.request_initiator->IsSameOriginWith(
destination_origin)) {
return false;
}
// If the PrefetchPrivacyChanges feature is enabled, the request's redirect
// mode must be |kError|.
if (base::FeatureList::IsEnabled(blink::features::kPrefetchPrivacyChanges) &&
resource_request.redirect_mode != network::mojom::RedirectMode::kError) {
return false;
}
// This prefetch request must not be able to reuse restricted prefetches from
// the prefetch cache. This is because it is possible that another origin
// prefetched the same resource, which should only be reused for top-level
// navigations.
if (resource_request.load_flags & net::LOAD_CAN_USE_RESTRICTED_PREFETCH)
return false;
// The request must not already have its |trusted_params| initialized.
if (resource_request.trusted_params)
return false;
return true;
}
void PrefetchURLLoaderService::EnsureCrossOriginFactory() {
DCHECK(base::FeatureList::IsEnabled(
network::features::kPrefetchMainResourceNetworkIsolationKey));
auto& current_context = *loader_factory_receivers_.current_context();
// If the factory has already been created, don't re-create it.
if (current_context.cross_origin_factory)
return;
DCHECK(current_context.render_frame_host);
std::unique_ptr<network::PendingSharedURLLoaderFactory> factories =
current_context.render_frame_host
->CreateCrossOriginPrefetchLoaderFactoryBundle();
current_context.cross_origin_factory =
network::SharedURLLoaderFactory::Create(std::move(factories));
}
void PrefetchURLLoaderService::Clone(
mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
loader_factory_receivers_.Add(
this, std::move(receiver),
std::make_unique<BindContext>(
loader_factory_receivers_.current_context()));
}
void PrefetchURLLoaderService::NotifyUpdate(
blink::mojom::RendererPreferencesPtr new_prefs) {
SetAcceptLanguages(new_prefs->accept_languages);
}
base::UnguessableToken PrefetchURLLoaderService::GenerateRecursivePrefetchToken(
base::WeakPtr<BindContext> current_context,
const network::ResourceRequest& request) {
// If the relevant frame has gone away before this method is called
// asynchronously, we cannot generate and store a
// {token, NetworkIsolationKey} pair in the frame's
// |prefetch_network_isolation_keys| map, so we'll create and return a dummy
// token that will not get used.
if (!current_context)
return base::UnguessableToken::Create();
// Create NetworkIsolationKey.
url::Origin destination_origin = url::Origin::Create(request.url);
net::NetworkIsolationKey preload_nik =
net::NetworkIsolationKey(destination_origin, destination_origin);
// Generate token.
base::UnguessableToken return_token = base::UnguessableToken::Create();
// Associate the two, and return the token.
current_context->prefetch_network_isolation_keys.insert(
{return_token, preload_nik});
return return_token;
}
std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
PrefetchURLLoaderService::CreateURLLoaderThrottles(
const network::ResourceRequest& request,
int frame_tree_node_id) {
return CreateContentBrowserURLLoaderThrottles(
request, browser_context_,
base::BindRepeating(&WebContents::FromFrameTreeNodeId,
frame_tree_node_id),
nullptr /* navigation_ui_data */, frame_tree_node_id);
}
} // namespace content