#include "content/browser/renderer_host/code_cache_host_impl.h"
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
#include "components/services/storage/public/mojom/cache_storage_control.mojom.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/code_cache/generated_code_cache.h"
#include "content/browser/code_cache/generated_code_cache_context.h"
#include "content/browser/process_lock.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h"
#include "content/public/common/url_constants.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/base/io_buffer.h"
#include "third_party/blink/public/common/cache_storage/cache_storage_utils.h"
#include "url/gurl.h"
#include "url/origin.h"
#if BUILDFLAG(IS_OHOS)
#include "base/logging.h"
#include "url/url_util.h"
#endif
using blink::mojom::CacheStorageError;
namespace content {
namespace {
enum class Operation {
kRead,
kWrite,
};
bool CheckSecurityForAccessingCodeCacheData(const GURL& resource_url,
int render_process_id,
Operation operation) {
ProcessLock process_lock =
ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
render_process_id);
if (resource_url.SchemeIs(content::kChromeUIScheme) ||
resource_url.SchemeIs(content::kChromeUIUntrustedScheme)) {
if (!process_lock.is_locked_to_site()) {
return false;
}
if (process_lock.matches_scheme(url::kHttpScheme) ||
process_lock.matches_scheme(url::kHttpsScheme)) {
if (operation == Operation::kWrite) {
mojo::ReportBadMessage("HTTP(S) pages cannot cache WebUI code");
}
return false;
}
return process_lock.matches_scheme(content::kChromeUIScheme) ||
process_lock.matches_scheme(content::kChromeUIUntrustedScheme);
}
if (resource_url.SchemeIsHTTPOrHTTPS()) {
if (process_lock.matches_scheme(content::kChromeUIScheme) ||
process_lock.matches_scheme(content::kChromeUIUntrustedScheme)) {
return false;
}
return true;
}
#if BUILDFLAG(IS_OHOS)
if (resource_url.SchemeIsCodeCacheEnabled()) {
LOG(DEBUG) << "CheckSecurity scheme:" << resource_url.scheme().c_str()
<< " is code cache enabled.";
return true;
}
#endif
if (operation == Operation::kWrite) {
mojo::ReportBadMessage("Invalid URL scheme for code cache.");
}
return false;
}
absl::optional<GURL> GetSecondaryKeyForCodeCache(const GURL& resource_url,
int render_process_id,
Operation operation) {
if (!CheckSecurityForAccessingCodeCacheData(resource_url, render_process_id,
operation)) {
return absl::nullopt;
}
if (!resource_url.is_valid())
return absl::nullopt;
ProcessLock process_lock =
ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
render_process_id);
if (!process_lock.is_locked_to_site())
return GURL::EmptyGURL();
if (process_lock.HasOpaqueOrigin())
return absl::nullopt;
if (process_lock.matches_scheme(url::kHttpScheme) ||
process_lock.matches_scheme(url::kHttpsScheme) ||
process_lock.matches_scheme(content::kChromeUIScheme) ||
process_lock.matches_scheme(content::kChromeUIUntrustedScheme)) {
return process_lock.lock_url();
}
return absl::nullopt;
}
void DidGenerateCacheableMetadataInCacheStorageOnUI(
const GURL& url,
base::Time expected_response_time,
mojo_base::BigBuffer data,
const std::string& cache_storage_cache_name,
int render_process_id,
const blink::StorageKey& code_cache_storage_key,
storage::mojom::CacheStorageControl* cache_storage_control_for_testing,
mojo::ReportBadMessageCallback bad_message_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* render_process_host = RenderProcessHost::FromID(render_process_id);
if (!render_process_host)
return;
int64_t trace_id = blink::cache_storage::CreateTraceId();
TRACE_EVENT_WITH_FLOW1(
"CacheStorage",
"CodeCacheHostImpl::DidGenerateCacheableMetadataInCacheStorage",
TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT, "url", url.spec());
mojo::Remote<blink::mojom::CacheStorage> remote;
network::CrossOriginEmbedderPolicy cross_origin_embedder_policy;
storage::mojom::CacheStorageControl* cache_storage_control =
cache_storage_control_for_testing
? cache_storage_control_for_testing
: render_process_host->GetStoragePartition()
->GetCacheStorageControl();
cache_storage_control->AddReceiver(
cross_origin_embedder_policy, mojo::NullRemote(),
storage::BucketLocator::ForDefaultBucket(code_cache_storage_key),
storage::mojom::CacheStorageOwner::kCacheAPI,
remote.BindNewPipeAndPassReceiver());
auto* raw_remote = remote.get();
raw_remote->Open(
base::UTF8ToUTF16(cache_storage_cache_name), trace_id,
base::BindOnce(
[](const GURL& url, base::Time expected_response_time,
mojo_base::BigBuffer data, int64_t trace_id,
mojo::Remote<blink::mojom::CacheStorage> preserve_remote_lifetime,
blink::mojom::OpenResultPtr result) {
if (result->is_status()) {
return;
}
mojo::AssociatedRemote<blink::mojom::CacheStorageCache> remote;
remote.Bind(std::move(result->get_cache()));
remote->WriteSideData(
url, expected_response_time, std::move(data), trace_id,
base::BindOnce(
[](mojo::Remote<blink::mojom::CacheStorage>
preserve_remote_lifetime,
CacheStorageError error) {
},
std::move(preserve_remote_lifetime)));
},
url, expected_response_time, std::move(data), trace_id,
std::move(remote)));
}
void AddCodeCacheReceiver(
mojo::UniqueReceiverSet<blink::mojom::CodeCacheHost>* receiver_set,
scoped_refptr<GeneratedCodeCacheContext> context,
int render_process_id,
const net::NetworkIsolationKey& nik,
const blink::StorageKey& storage_key,
mojo::PendingReceiver<blink::mojom::CodeCacheHost> receiver,
CodeCacheHostImpl::ReceiverSet::CodeCacheHostReceiverHandler handler) {
auto host = std::make_unique<CodeCacheHostImpl>(render_process_id, context,
nik, storage_key);
auto* raw_host = host.get();
auto id = receiver_set->Add(std::move(host), std::move(receiver));
if (handler)
std::move(handler).Run(raw_host, id, *receiver_set);
}
}
CodeCacheHostImpl::ReceiverSet::ReceiverSet(
scoped_refptr<GeneratedCodeCacheContext> generated_code_cache_context)
: generated_code_cache_context_(generated_code_cache_context),
receiver_set_(
new mojo::UniqueReceiverSet<blink::mojom::CodeCacheHost>(),
base::OnTaskRunnerDeleter(GeneratedCodeCacheContext::GetTaskRunner(
generated_code_cache_context))) {}
CodeCacheHostImpl::ReceiverSet::~ReceiverSet() = default;
void CodeCacheHostImpl::ReceiverSet::Add(
int render_process_id,
const net::NetworkIsolationKey& nik,
const blink::StorageKey& storage_key,
mojo::PendingReceiver<blink::mojom::CodeCacheHost> receiver,
CodeCacheHostReceiverHandler handler) {
if (!receiver_set_) {
receiver_set_ = {
new mojo::UniqueReceiverSet<blink::mojom::CodeCacheHost>(),
base::OnTaskRunnerDeleter(GeneratedCodeCacheContext::GetTaskRunner(
generated_code_cache_context_))};
}
GeneratedCodeCacheContext::RunOrPostTask(
generated_code_cache_context_, FROM_HERE,
base::BindOnce(&AddCodeCacheReceiver, receiver_set_.get(),
generated_code_cache_context_, render_process_id, nik,
storage_key, std::move(receiver), std::move(handler)));
}
void CodeCacheHostImpl::ReceiverSet::Add(
int render_process_id,
const net::NetworkIsolationKey& nik,
const blink::StorageKey& storage_key,
mojo::PendingReceiver<blink::mojom::CodeCacheHost> receiver) {
Add(render_process_id, nik, storage_key, std::move(receiver),
CodeCacheHostReceiverHandler());
}
void CodeCacheHostImpl::ReceiverSet::Clear() {
receiver_set_.reset();
}
CodeCacheHostImpl::CodeCacheHostImpl(
int render_process_id,
scoped_refptr<GeneratedCodeCacheContext> generated_code_cache_context,
const net::NetworkIsolationKey& nik,
const blink::StorageKey& storage_key)
: render_process_id_(render_process_id),
generated_code_cache_context_(std::move(generated_code_cache_context)),
network_isolation_key_(nik),
storage_key_(storage_key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
CodeCacheHostImpl::~CodeCacheHostImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void CodeCacheHostImpl::SetCacheStorageControlForTesting(
storage::mojom::CacheStorageControl* cache_storage_control) {
cache_storage_control_for_testing_ = cache_storage_control;
}
void CodeCacheHostImpl::DidGenerateCacheableMetadata(
blink::mojom::CodeCacheType cache_type,
const GURL& url,
base::Time expected_response_time,
mojo_base::BigBuffer data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
GeneratedCodeCache* code_cache = GetCodeCache(cache_type);
if (!code_cache)
return;
absl::optional<GURL> origin_lock =
GetSecondaryKeyForCodeCache(url, render_process_id_, Operation::kWrite);
if (!origin_lock)
return;
code_cache->WriteEntry(url, *origin_lock, network_isolation_key_,
expected_response_time, std::move(data));
}
void CodeCacheHostImpl::FetchCachedCode(blink::mojom::CodeCacheType cache_type,
const GURL& url,
FetchCachedCodeCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
GeneratedCodeCache* code_cache = GetCodeCache(cache_type);
if (!code_cache) {
std::move(callback).Run(base::Time(), std::vector<uint8_t>());
return;
}
absl::optional<GURL> origin_lock =
GetSecondaryKeyForCodeCache(url, render_process_id_, Operation::kRead);
if (!origin_lock) {
std::move(callback).Run(base::Time(), std::vector<uint8_t>());
return;
}
auto read_callback = base::BindOnce(
&CodeCacheHostImpl::OnReceiveCachedCode, weak_ptr_factory_.GetWeakPtr(),
cache_type, base::TimeTicks::Now(), std::move(callback));
code_cache->FetchEntry(url, *origin_lock, network_isolation_key_,
std::move(read_callback));
}
void CodeCacheHostImpl::ClearCodeCacheEntry(
blink::mojom::CodeCacheType cache_type,
const GURL& url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
GeneratedCodeCache* code_cache = GetCodeCache(cache_type);
if (!code_cache)
return;
absl::optional<GURL> origin_lock =
GetSecondaryKeyForCodeCache(url, render_process_id_, Operation::kWrite);
if (!origin_lock)
return;
code_cache->DeleteEntry(url, *origin_lock, network_isolation_key_);
}
void CodeCacheHostImpl::DidGenerateCacheableMetadataInCacheStorage(
const GURL& url,
base::Time expected_response_time,
mojo_base::BigBuffer data,
const std::string& cache_storage_cache_name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&DidGenerateCacheableMetadataInCacheStorageOnUI, url,
expected_response_time, std::move(data),
cache_storage_cache_name, render_process_id_, storage_key_,
cache_storage_control_for_testing_,
mojo::GetBadMessageCallback()));
}
GeneratedCodeCache* CodeCacheHostImpl::GetCodeCache(
blink::mojom::CodeCacheType cache_type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!generated_code_cache_context_)
return nullptr;
ProcessLock process_lock =
ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(
render_process_id_);
if (process_lock.matches_scheme(content::kChromeUIScheme) ||
process_lock.matches_scheme(content::kChromeUIUntrustedScheme)) {
if (cache_type == blink::mojom::CodeCacheType::kJavascript) {
return generated_code_cache_context_->generated_webui_js_code_cache();
}
return nullptr;
}
if (cache_type == blink::mojom::CodeCacheType::kJavascript)
return generated_code_cache_context_->generated_js_code_cache();
DCHECK_EQ(blink::mojom::CodeCacheType::kWebAssembly, cache_type);
return generated_code_cache_context_->generated_wasm_code_cache();
}
void CodeCacheHostImpl::OnReceiveCachedCode(
blink::mojom::CodeCacheType cache_type,
base::TimeTicks start_time,
FetchCachedCodeCallback callback,
const base::Time& response_time,
mojo_base::BigBuffer data) {
if (cache_type == blink::mojom::CodeCacheType::kJavascript &&
data.size() > 0) {
base::UmaHistogramTimes("SiteIsolatedCodeCache.JS.FetchCodeCache",
base::TimeTicks::Now() - start_time);
}
std::move(callback).Run(response_time, std::move(data));
}
}