blob: 51e630e562f116a5a705a15fa41793701c075a29 [file]
// 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)
: 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.
// See https://crbug.com/791049.
if (base::FeatureList::IsEnabled(network::features::kNetworkService))
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 NavigationLoaderInterceptor which handles a request which matches the
// prefetched signed exchange that has been stored to a
// PrefetchedSignedExchangeCache.
class PrefetchedNavigationLoaderInterceptor
: public NavigationLoaderInterceptor {
public:
explicit PrefetchedNavigationLoaderInterceptor(
std::unique_ptr<const PrefetchedSignedExchangeCache::Entry> exchange)
: exchange_(std::move(exchange)), 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 (!outer_request_handled_ &&
tentative_resource_request.url == exchange_->outer_url()) {
outer_request_handled_ = true;
std::move(callback).Run(base::BindOnce(
&PrefetchedNavigationLoaderInterceptor::StartRedirectResponse,
weak_factory_.GetWeakPtr()));
return;
}
if (tentative_resource_request.url == exchange_->inner_url()) {
std::move(callback).Run(base::BindOnce(
&PrefetchedNavigationLoaderInterceptor::StartInnerResponse,
weak_factory_.GetWeakPtr()));
return;
}
NOTREACHED();
}
base::Optional<SubresourceLoaderParams> MaybeCreateSubresourceLoaderParams()
override {
// TODO(crbug.com/935267): Implement this to pass the prefetched signed
// exchanges of subresources to the renderer process.
return base::nullopt;
}
private:
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)),
std::move(request));
}
std::unique_ptr<const PrefetchedSignedExchangeCache::Entry> exchange_;
bool outer_request_handled_ = false;
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());
}
} // namespace content