blob: e5256bf291f49592e7852f7ca38bef165b12e041 [file] [log] [blame]
// Copyright 2019 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/prefetched_signed_exchange_cache.h"
#include "base/feature_list.h"
#include "base/strings/stringprintf.h"
#include "content/browser/loader/navigation_loader_interceptor.h"
#include "content/browser/navigation_subresource_loader_params.h"
#include "content/browser/web_package/signed_exchange_utils.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_features.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "net/http/http_util.h"
#include "net/url_request/redirect_util.h"
#include "services/network/public/cpp/constants.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/resource_response.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_impl.h"
#include "storage/browser/blob/mojo_blob_reader.h"
namespace content {
namespace {
// The max number of cached entry per one frame. This limit is intended to
// prevent OOM crash of the browser process.
constexpr size_t kMaxEntrySize = 100u;
void UpdateRequestResponseStartTime(
network::ResourceResponseHead* response_head) {
const base::TimeTicks now_ticks = base::TimeTicks::Now();
const base::Time now = base::Time::Now();
response_head->request_start = now_ticks;
response_head->response_start = now_ticks;
response_head->load_timing.request_start_time = now;
response_head->load_timing.request_start = now_ticks;
}
// A utility subclass of MojoBlobReader::Delegate that calls the passed callback
// in OnComplete().
class MojoBlobReaderDelegate : public storage::MojoBlobReader::Delegate {
public:
explicit MojoBlobReaderDelegate(base::OnceCallback<void(net::Error)> callback)
: callback_(std::move(callback)) {}
private:
// storage::MojoBlobReader::Delegate
RequestSideData DidCalculateSize(uint64_t total_size,
uint64_t content_size) override {
return DONT_REQUEST_SIDE_DATA;
}
void OnComplete(net::Error result, uint64_t total_written_bytes) override {
std::move(callback_).Run(result);
}
base::OnceCallback<void(net::Error)> callback_;
};
// A URLLoader which returns a synthesized redirect response for signed
// exchange's outer URL request.
class RedirectResponseURLLoader : public network::mojom::URLLoader {
public:
RedirectResponseURLLoader(const network::ResourceRequest& url_request,
const GURL& inner_url,
const network::ResourceResponseHead& outer_response,
network::mojom::URLLoaderClientPtr client)
: client_(std::move(client)) {
network::ResourceResponseHead response_head =
signed_exchange_utils::CreateRedirectResponseHead(
outer_response, false /* is_fallback_redirect */);
UpdateRequestResponseStartTime(&response_head);
client_->OnReceiveRedirect(signed_exchange_utils::CreateRedirectInfo(
inner_url, url_request, outer_response,
false /* is_fallback_redirect */),
response_head);
}
~RedirectResponseURLLoader() override {}
private:
// network::mojom::URLLoader overrides:
void FollowRedirect(const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const base::Optional<GURL>& new_url) override {
NOTREACHED();
}
void ProceedWithResponse() override { NOTREACHED(); }
void SetPriority(net::RequestPriority priority,
int intra_priority_value) override {
// There is nothing to do, because this class just calls OnReceiveRedirect.
}
void PauseReadingBodyFromNet() override {
// There is nothing to do, because we don't fetch the resource from the
// network.
}
void ResumeReadingBodyFromNet() override {
// There is nothing to do, because we don't fetch the resource from the
// network.
}
network::mojom::URLLoaderClientPtr client_;
DISALLOW_COPY_AND_ASSIGN(RedirectResponseURLLoader);
};
// A URLLoader which returns the inner response of signed exchange.
class InnerResponseURLLoader : public network::mojom::URLLoader {
public:
InnerResponseURLLoader(
const network::ResourceResponseHead& inner_response,
std::unique_ptr<const storage::BlobDataHandle> blob_data_handle,
const network::URLLoaderCompletionStatus& completion_status,
network::mojom::URLLoaderClientPtr client,
bool is_navigation_request)
: blob_data_handle_(std::move(blob_data_handle)),
completion_status_(completion_status),
client_(std::move(client)),
weak_factory_(this) {
network::ResourceResponseHead response = inner_response;
UpdateRequestResponseStartTime(&response);
response.encoded_data_length = 0;
client_->OnReceiveResponse(response);
// When Network Service is not enabled, we need to wait ProceedWithResponse
// for navigation request.
// See https://crbug.com/791049.
if (is_navigation_request &&
!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
return;
}
SendResponseBody();
}
~InnerResponseURLLoader() override {}
private:
// network::mojom::URLLoader overrides:
void FollowRedirect(const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const base::Optional<GURL>& new_url) override {
NOTREACHED();
}
void ProceedWithResponse() override {
DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService));
SendResponseBody();
}
void SetPriority(net::RequestPriority priority,
int intra_priority_value) override {
// There is nothing to do, because there is no prioritization mechanism for
// reading a blob.
}
void PauseReadingBodyFromNet() override {
// There is nothing to do, because we don't fetch the resource from the
// network.
}
void ResumeReadingBodyFromNet() override {
// There is nothing to do, because we don't fetch the resource from the
// network.
}
void SendResponseBody() {
mojo::ScopedDataPipeProducerHandle pipe_producer_handle;
mojo::ScopedDataPipeConsumerHandle pipe_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 = network::kDataPipeDefaultAllocationSize;
MojoResult rv = mojo::CreateDataPipe(&options, &pipe_producer_handle,
&pipe_consumer_handle);
if (rv != MOJO_RESULT_OK) {
client_->OnComplete(
network::URLLoaderCompletionStatus(net::ERR_INSUFFICIENT_RESOURCES));
return;
}
storage::MojoBlobReader::Create(
blob_data_handle_.get(), net::HttpByteRange(),
std::make_unique<MojoBlobReaderDelegate>(
base::BindOnce(&InnerResponseURLLoader::BlobReaderComplete,
weak_factory_.GetWeakPtr())),
std::move(pipe_producer_handle));
client_->OnStartLoadingResponseBody(std::move(pipe_consumer_handle));
}
void BlobReaderComplete(net::Error result) {
network::URLLoaderCompletionStatus status;
if (result == net::OK) {
status = completion_status_;
status.exists_in_cache = true;
status.completion_time = base::TimeTicks::Now();
status.encoded_data_length = 0;
} else {
status = network::URLLoaderCompletionStatus(status);
}
client_->OnComplete(status);
}
std::unique_ptr<const storage::BlobDataHandle> blob_data_handle_;
const network::URLLoaderCompletionStatus completion_status_;
network::mojom::URLLoaderClientPtr client_;
base::WeakPtrFactory<InnerResponseURLLoader> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(InnerResponseURLLoader);
};
// A URLLoaderFactory which handles a signed exchange subresource request from
// renderer process.
class SubresourceSignedExchangeURLLoaderFactory
: public network::mojom::URLLoaderFactory {
public:
SubresourceSignedExchangeURLLoaderFactory(
network::mojom::URLLoaderFactoryRequest request,
std::unique_ptr<const PrefetchedSignedExchangeCache::Entry> entry)
: entry_(std::move(entry)) {
bindings_.AddBinding(this, std::move(request));
bindings_.set_connection_error_handler(base::BindRepeating(
&SubresourceSignedExchangeURLLoaderFactory::OnConnectionError,
base::Unretained(this)));
}
~SubresourceSignedExchangeURLLoaderFactory() override {}
// network::mojom::URLLoaderFactory implementation.
void CreateLoaderAndStart(network::mojom::URLLoaderRequest loader,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
network::mojom::URLLoaderClientPtr client,
const net::MutableNetworkTrafficAnnotationTag&
traffic_annotation) override {
// TODO(crbug.com/935267): Implement CORS check.
DCHECK_EQ(request.url, entry_->inner_url());
mojo::MakeStrongBinding(std::make_unique<InnerResponseURLLoader>(
*entry_->inner_response(),
std::make_unique<const storage::BlobDataHandle>(
*entry_->blob_data_handle()),
*entry_->completion_status(), std::move(client),
false /* is_navigation_request */),
std::move(loader));
}
void Clone(network::mojom::URLLoaderFactoryRequest request) override {
bindings_.AddBinding(this, std::move(request));
}
private:
void OnConnectionError() {
if (!bindings_.empty())
return;
delete this;
}
std::unique_ptr<const PrefetchedSignedExchangeCache::Entry> entry_;
mojo::BindingSet<network::mojom::URLLoaderFactory> bindings_;
DISALLOW_COPY_AND_ASSIGN(SubresourceSignedExchangeURLLoaderFactory);
};
// A NavigationLoaderInterceptor which handles a request which matches the
// prefetched signed exchange that has been stored to a
// PrefetchedSignedExchangeCache.
class PrefetchedNavigationLoaderInterceptor
: public NavigationLoaderInterceptor {
public:
PrefetchedNavigationLoaderInterceptor(
std::unique_ptr<const PrefetchedSignedExchangeCache::Entry> exchange,
std::vector<PrefetchedSignedExchangeInfo> info_list)
: exchange_(std::move(exchange)),
info_list_(std::move(info_list)),
weak_factory_(this) {}
~PrefetchedNavigationLoaderInterceptor() override {}
void MaybeCreateLoader(
const network::ResourceRequest& tentative_resource_request,
ResourceContext* resource_context,
LoaderCallback callback,
FallbackCallback fallback_callback) override {
// Currently we just check the URL matching. But we should check the Vary
// header (eg: HttpVaryData::MatchesRequest()) and Cache-Control header.
// And also we shuold check the expires parameter of the signed exchange's
// signature. TODO(crbug.com/935267): Implement these checking logic.
if (state_ == State::kInitial &&
tentative_resource_request.url == exchange_->outer_url()) {
state_ = State::kOuterRequestRequested;
std::move(callback).Run(base::BindOnce(
&PrefetchedNavigationLoaderInterceptor::StartRedirectResponse,
weak_factory_.GetWeakPtr()));
return;
}
if (tentative_resource_request.url == exchange_->inner_url()) {
DCHECK_EQ(State::kOuterRequestRequested, state_);
state_ = State::kInnerResponseRequested;
std::move(callback).Run(base::BindOnce(
&PrefetchedNavigationLoaderInterceptor::StartInnerResponse,
weak_factory_.GetWeakPtr()));
return;
}
NOTREACHED();
}
base::Optional<SubresourceLoaderParams> MaybeCreateSubresourceLoaderParams()
override {
if (state_ != State::kInnerResponseRequested)
return base::nullopt;
SubresourceLoaderParams params;
params.prefetched_signed_exchanges = std::move(info_list_);
return base::make_optional(std::move(params));
}
private:
enum class State {
kInitial,
kOuterRequestRequested,
kInnerResponseRequested
};
void StartRedirectResponse(const network::ResourceRequest& resource_request,
network::mojom::URLLoaderRequest request,
network::mojom::URLLoaderClientPtr client) {
mojo::MakeStrongBinding(
std::make_unique<RedirectResponseURLLoader>(
resource_request, exchange_->inner_url(),
*exchange_->outer_response(), std::move(client)),
std::move(request));
}
void StartInnerResponse(const network::ResourceRequest& resource_request,
network::mojom::URLLoaderRequest request,
network::mojom::URLLoaderClientPtr client) {
mojo::MakeStrongBinding(
std::make_unique<InnerResponseURLLoader>(
*exchange_->inner_response(),
std::make_unique<const storage::BlobDataHandle>(
*exchange_->blob_data_handle()),
*exchange_->completion_status(), std::move(client),
true /* is_navigation_request */),
std::move(request));
}
State state_ = State::kInitial;
std::unique_ptr<const PrefetchedSignedExchangeCache::Entry> exchange_;
std::vector<PrefetchedSignedExchangeInfo> info_list_;
base::WeakPtrFactory<PrefetchedNavigationLoaderInterceptor> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(PrefetchedNavigationLoaderInterceptor);
};
} // namespace
PrefetchedSignedExchangeCache::Entry::Entry() = default;
PrefetchedSignedExchangeCache::Entry::~Entry() = default;
void PrefetchedSignedExchangeCache::Entry::SetOuterUrl(const GURL& outer_url) {
outer_url_ = outer_url;
}
void PrefetchedSignedExchangeCache::Entry::SetOuterResponse(
std::unique_ptr<const network::ResourceResponseHead> outer_response) {
outer_response_ = std::move(outer_response);
}
void PrefetchedSignedExchangeCache::Entry::SetHeaderIntegrity(
std::unique_ptr<const net::SHA256HashValue> header_integrity) {
header_integrity_ = std::move(header_integrity);
}
void PrefetchedSignedExchangeCache::Entry::SetInnerUrl(const GURL& inner_url) {
inner_url_ = inner_url;
}
void PrefetchedSignedExchangeCache::Entry::SetInnerResponse(
std::unique_ptr<const network::ResourceResponseHead> inner_response) {
inner_response_ = std::move(inner_response);
}
void PrefetchedSignedExchangeCache::Entry::SetCompletionStatus(
std::unique_ptr<const network::URLLoaderCompletionStatus>
completion_status) {
completion_status_ = std::move(completion_status);
}
void PrefetchedSignedExchangeCache::Entry::SetBlobDataHandle(
std::unique_ptr<const storage::BlobDataHandle> blob_data_handle) {
blob_data_handle_ = std::move(blob_data_handle);
}
std::unique_ptr<const PrefetchedSignedExchangeCache::Entry>
PrefetchedSignedExchangeCache::Entry::Clone() const {
DCHECK(outer_url().is_valid());
DCHECK(outer_response());
DCHECK(header_integrity());
DCHECK(inner_url().is_valid());
DCHECK(inner_response());
DCHECK(completion_status());
DCHECK(blob_data_handle());
std::unique_ptr<Entry> clone = std::make_unique<Entry>();
clone->SetOuterUrl(outer_url_);
clone->SetOuterResponse(
std::make_unique<const network::ResourceResponseHead>(*outer_response_));
clone->SetHeaderIntegrity(
std::make_unique<const net::SHA256HashValue>(*header_integrity_));
clone->SetInnerUrl(inner_url_);
clone->SetInnerResponse(
std::make_unique<const network::ResourceResponseHead>(*inner_response_));
clone->SetCompletionStatus(
std::make_unique<const network::URLLoaderCompletionStatus>(
*completion_status_));
clone->SetBlobDataHandle(
std::make_unique<const storage::BlobDataHandle>(*blob_data_handle_));
return clone;
}
PrefetchedSignedExchangeCache::PrefetchedSignedExchangeCache() {
DCHECK(base::FeatureList::IsEnabled(
features::kSignedExchangeSubresourcePrefetch));
}
PrefetchedSignedExchangeCache::~PrefetchedSignedExchangeCache() {}
void PrefetchedSignedExchangeCache::Store(
std::unique_ptr<const Entry> cached_exchange) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (exchanges_.size() > kMaxEntrySize)
return;
DCHECK(cached_exchange->outer_url().is_valid());
DCHECK(cached_exchange->outer_response());
DCHECK(cached_exchange->header_integrity());
DCHECK(cached_exchange->inner_url().is_valid());
DCHECK(cached_exchange->inner_response());
DCHECK(cached_exchange->completion_status());
DCHECK(cached_exchange->blob_data_handle());
const GURL outer_url = cached_exchange->outer_url();
exchanges_[outer_url] = std::move(cached_exchange);
}
std::unique_ptr<NavigationLoaderInterceptor>
PrefetchedSignedExchangeCache::MaybeCreateInterceptor(const GURL& outer_url) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
const auto it = exchanges_.find(outer_url);
if (it == exchanges_.end())
return nullptr;
return std::make_unique<PrefetchedNavigationLoaderInterceptor>(
it->second->Clone(), GetInfoList());
}
std::vector<PrefetchedSignedExchangeInfo>
PrefetchedSignedExchangeCache::GetInfoList() const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
std::vector<PrefetchedSignedExchangeInfo> info_list;
for (const auto& exchange : exchanges_) {
network::mojom::URLLoaderFactoryPtrInfo loader_factory_info;
new SubresourceSignedExchangeURLLoaderFactory(
mojo::MakeRequest(&loader_factory_info), exchange.second->Clone());
info_list.emplace_back(
exchange.second->outer_url(), *exchange.second->header_integrity(),
exchange.second->inner_url(), *exchange.second->inner_response(),
std::move(loader_factory_info).PassHandle().release());
}
return info_list;
}
} // namespace content