blob: 1fc67b0562f0f9d1023bab01926e87f77d22f483 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "storage/browser/blob/blob_url_registry.h"
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/callback.h"
#include "net/base/features.h"
#include "storage/browser/blob/blob_url_store_impl.h"
#include "storage/browser/blob/blob_url_utils.h"
#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
#include "url/gurl.h"
namespace storage {
namespace {
BlobUrlRegistry::URLStoreCreationHook* g_url_store_creation_hook = nullptr;
}
BlobUrlRegistry::BlobUrlRegistry(base::WeakPtr<BlobUrlRegistry> fallback)
: fallback_(std::move(fallback)) {}
BlobUrlRegistry::~BlobUrlRegistry() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void BlobUrlRegistry::AddReceiver(
const blink::StorageKey& storage_key,
const url::Origin& renderer_origin,
int render_process_host_id,
mojo::PendingAssociatedReceiver<blink::mojom::BlobURLStore> receiver,
base::RepeatingCallback<
void(const GURL&, std::optional<blink::mojom::PartitioningBlobURLInfo>)>
partitioning_blob_url_closure,
bool partitioning_disabled_by_policy) {
mojo::ReceiverId receiver_id = frame_receivers_.Add(
std::make_unique<storage::BlobURLStoreImpl>(
storage_key, renderer_origin, render_process_host_id, AsWeakPtr(),
storage::BlobURLValidityCheckBehavior::DEFAULT,
std::move(partitioning_blob_url_closure),
partitioning_disabled_by_policy),
std::move(receiver));
if (g_url_store_creation_hook) {
g_url_store_creation_hook->Run(this, receiver_id);
}
}
void BlobUrlRegistry::AddReceiver(
const blink::StorageKey& storage_key,
const url::Origin& renderer_origin,
int render_process_host_id,
mojo::PendingReceiver<blink::mojom::BlobURLStore> receiver,
bool partitioning_disabled_by_policy,
BlobURLValidityCheckBehavior validity_check_behavior) {
worker_receivers_.Add(
std::make_unique<storage::BlobURLStoreImpl>(
storage_key, renderer_origin, render_process_host_id, AsWeakPtr(),
validity_check_behavior, base::DoNothing(),
partitioning_disabled_by_policy),
std::move(receiver));
}
bool BlobUrlRegistry::AddUrlMapping(
const GURL& blob_url,
mojo::PendingRemote<blink::mojom::Blob> blob,
const blink::StorageKey& storage_key,
const url::Origin& renderer_origin,
int render_process_host_id,
// TODO(crbug.com/40775506): Remove these once experiment is over.
const base::UnguessableToken& unsafe_agent_cluster_id,
const std::optional<net::SchemefulSite>& unsafe_top_level_site) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!BlobUrlUtils::UrlHasFragment(blob_url));
if (IsUrlMapped(blob_url, storage_key) ==
BlobUrlRegistry::MappingStatus::kIsMapped) {
return false;
}
url_to_unsafe_agent_cluster_id_[blob_url] = unsafe_agent_cluster_id;
if (unsafe_top_level_site)
url_to_unsafe_top_level_site_[blob_url] = *unsafe_top_level_site;
url_to_blob_[blob_url] = std::move(blob);
url_to_storage_key_[blob_url] = storage_key;
url_to_origin_[blob_url] = renderer_origin;
url_to_render_process_host_id_[blob_url] = render_process_host_id;
return true;
}
bool BlobUrlRegistry::RemoveUrlMapping(const GURL& blob_url,
const blink::StorageKey& storage_key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!BlobUrlUtils::UrlHasFragment(blob_url));
auto blob_it = url_to_blob_.find(blob_url);
if (blob_it == url_to_blob_.end()) {
return false;
}
if (url_to_storage_key_.at(blob_url) != storage_key) {
return false;
}
url_to_blob_.erase(blob_it);
url_to_unsafe_agent_cluster_id_.erase(blob_url);
url_to_unsafe_top_level_site_.erase(blob_url);
url_to_storage_key_.erase(blob_url);
url_to_origin_.erase(blob_url);
url_to_render_process_host_id_.erase(blob_url);
return true;
}
url::Origin BlobUrlRegistry::GetOriginForNavigation(
const GURL& url,
const url::Origin& precursor_origin,
std::optional<int> target_render_process_host_id) {
// Some Blob URLs have the origin embedded directly within the URL, which we
// can get from calling url::Origin::Create() (which will extract the embedded
// origin if it exists). Cases whether the origin is not embedded within the
// URL (when the content part is "null") would result in an opaque origin.
url::Origin url_origin = url::Origin::Create(url);
if (!url_origin.opaque()) {
return url_origin;
}
// The origin is not embedded within the Blob URL. Strip out the ref from the
// URL (if it exists), and get the origin from our mapping. If
// `target_render_process_host_id` is set, only return the origin if it was
// registered by a process with the same ID. This keeps the legacy behavior
// where the blob URL's origin mapping lives on the renderer process, so only
// the renderer where the blob URL is created knows its origin, so navigations
// to other processes can't use the mapped origin.
GURL url_without_ref = url.GetWithoutRef();
auto it = url_to_origin_.find(url_without_ref);
if (it != url_to_origin_.end() &&
(!target_render_process_host_id.has_value() ||
url_to_render_process_host_id_[url_without_ref] ==
target_render_process_host_id.value())) {
return it->second;
}
return url::Origin::Resolve(url, precursor_origin);
}
BlobUrlRegistry::MappingStatus BlobUrlRegistry::IsUrlMapped(
const GURL& blob_url,
const blink::StorageKey& storage_key) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (base::Contains(url_to_blob_, blob_url) &&
base::Contains(url_to_storage_key_, blob_url)) {
const blink::StorageKey& blob_url_key = url_to_storage_key_.at(blob_url);
if (blob_url_key == storage_key) {
return BlobUrlRegistry::MappingStatus::kIsMapped;
}
if (blob_url_key.origin() == storage_key.origin()) {
return BlobUrlRegistry::MappingStatus::kNotMappedCrossPartitionSameOrigin;
}
// A fallback_ check isn't needed because a given Blob URL will either be
// registered in this BlobUrlRegistry or registered in the fallback
// BlobUrlRegistry but not both.
return BlobUrlRegistry::MappingStatus::kNotMappedOther;
}
if (fallback_) {
return fallback_->IsUrlMapped(blob_url, storage_key);
}
return BlobUrlRegistry::MappingStatus::kNotMappedOther;
}
// TODO(crbug.com/40775506): Remove this once experiment is over.
std::optional<base::UnguessableToken> BlobUrlRegistry::GetUnsafeAgentClusterID(
const GURL& blob_url) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = url_to_unsafe_agent_cluster_id_.find(blob_url);
if (it != url_to_unsafe_agent_cluster_id_.end())
return it->second;
if (fallback_)
return fallback_->GetUnsafeAgentClusterID(blob_url);
return std::nullopt;
}
std::optional<net::SchemefulSite> BlobUrlRegistry::GetUnsafeTopLevelSite(
const GURL& blob_url) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = url_to_unsafe_top_level_site_.find(blob_url);
if (it != url_to_unsafe_top_level_site_.end())
return it->second;
if (fallback_)
return fallback_->GetUnsafeTopLevelSite(blob_url);
return std::nullopt;
}
mojo::PendingRemote<blink::mojom::Blob> BlobUrlRegistry::GetBlobFromUrl(
const GURL& url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = url_to_blob_.find(BlobUrlUtils::ClearUrlFragment(url));
if (it == url_to_blob_.end())
return fallback_ ? fallback_->GetBlobFromUrl(url) : mojo::NullRemote();
mojo::Remote<blink::mojom::Blob> blob(std::move(it->second));
mojo::PendingRemote<blink::mojom::Blob> result;
blob->Clone(result.InitWithNewPipeAndPassReceiver());
it->second = blob.Unbind();
return result;
}
void BlobUrlRegistry::AddTokenMapping(
const base::UnguessableToken& token,
const GURL& url,
mojo::PendingRemote<blink::mojom::Blob> blob) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!base::Contains(token_to_url_and_blob_, token));
token_to_url_and_blob_.emplace(token, std::make_pair(url, std::move(blob)));
}
void BlobUrlRegistry::RemoveTokenMapping(const base::UnguessableToken& token) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(base::Contains(token_to_url_and_blob_, token));
token_to_url_and_blob_.erase(token);
}
bool BlobUrlRegistry::GetTokenMapping(
const base::UnguessableToken& token,
GURL* url,
mojo::PendingRemote<blink::mojom::Blob>* blob) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = token_to_url_and_blob_.find(token);
if (it == token_to_url_and_blob_.end())
return false;
*url = it->second.first;
mojo::Remote<blink::mojom::Blob> source_blob(std::move(it->second.second));
source_blob->Clone(blob->InitWithNewPipeAndPassReceiver());
it->second.second = source_blob.Unbind();
return true;
}
// static
void BlobUrlRegistry::SetURLStoreCreationHookForTesting(
URLStoreCreationHook* hook) {
g_url_store_creation_hook = hook;
}
} // namespace storage