blob: 8608a7c510345f1ca0e697510ae2ef62eb185afd [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/buckets/bucket_manager_host.h"
#include "base/containers/contains.h"
#include "base/strings/string_util.h"
#include "base/types/pass_key.h"
#include "components/services/storage/public/cpp/buckets/bucket_info.h"
#include "content/browser/buckets/bucket_manager.h"
#include "content/browser/storage_partition_impl.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
namespace content {
namespace {
bool IsValidBucketName(const std::string& name) {
// Details on bucket name validation and reasoning explained in
// https://github.com/WICG/storage-buckets/blob/gh-pages/explainer.md
if (name.empty() || name.length() >= 64)
return false;
// The name must only contain characters in a restricted set.
for (char ch : name) {
if (base::IsAsciiLower(ch))
continue;
if (base::IsAsciiDigit(ch))
continue;
if (ch == '_' || ch == '-')
continue;
return false;
}
// The first character in the name is more restricted.
if (name[0] == '_' || name[0] == '-')
return false;
return true;
}
} // namespace
BucketManagerHost::BucketManagerHost(BucketManager* manager,
const blink::StorageKey& storage_key)
: manager_(manager), storage_key_(storage_key) {
DCHECK(manager != nullptr);
// base::Unretained is safe here because this BucketManagerHost owns
// `receivers_`. So, the unretained BucketManagerHost is guaranteed to
// outlive |receivers_| and the closure that it uses.
receivers_.set_disconnect_handler(base::BindRepeating(
&BucketManagerHost::OnReceiverDisconnect, base::Unretained(this)));
}
BucketManagerHost::~BucketManagerHost() = default;
void BucketManagerHost::BindReceiver(
mojo::PendingReceiver<blink::mojom::BucketManagerHost> receiver,
base::WeakPtr<BucketContext> context) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
receivers_.Add(this, std::move(receiver), std::move(context));
}
void BucketManagerHost::OpenBucket(const std::string& name,
blink::mojom::BucketPoliciesPtr policies,
OpenBucketCallback callback) {
if (!IsValidBucketName(name)) {
receivers_.ReportBadMessage("Invalid bucket name");
return;
}
storage::BucketInitParams params(storage_key_, name);
if (policies) {
if (policies->expires)
params.expiration = *policies->expires;
if (policies->has_quota)
params.quota = policies->quota;
if (policies->has_durability)
params.durability = policies->durability;
if (policies->has_persisted) {
// Only grant persistence if permitted.
if (receivers_.current_context() &&
receivers_.current_context()->GetPermissionStatus(
blink::PermissionType::DURABLE_STORAGE) ==
blink::mojom::PermissionStatus::GRANTED) {
params.persistent = policies->persisted;
}
}
}
GetQuotaManagerProxy()->UpdateOrCreateBucket(
params, base::SequencedTaskRunnerHandle::Get(),
base::BindOnce(&BucketManagerHost::DidGetBucket,
weak_factory_.GetWeakPtr(), receivers_.current_context(),
std::move(callback)));
}
void BucketManagerHost::Keys(KeysCallback callback) {
GetQuotaManagerProxy()->GetBucketsForStorageKey(
storage_key_, blink::mojom::StorageType::kTemporary,
/*delete_expired=*/true, base::SequencedTaskRunnerHandle::Get(),
base::BindOnce(&BucketManagerHost::DidGetBuckets,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void BucketManagerHost::DeleteBucket(const std::string& name,
DeleteBucketCallback callback) {
if (!IsValidBucketName(name)) {
receivers_.ReportBadMessage("Invalid bucket name");
return;
}
GetQuotaManagerProxy()->DeleteBucket(
storage_key_, name, base::SequencedTaskRunnerHandle::Get(),
base::BindOnce(&BucketManagerHost::DidDeleteBucket,
weak_factory_.GetWeakPtr(), name, std::move(callback)));
}
void BucketManagerHost::RemoveBucketHost(const std::string& bucket_name) {
DCHECK(base::Contains(bucket_map_, bucket_name));
bucket_map_.erase(bucket_name);
}
StoragePartitionImpl* BucketManagerHost::GetStoragePartition() {
return manager_->storage_partition();
}
storage::QuotaManagerProxy* BucketManagerHost::GetQuotaManagerProxy() {
return manager_->storage_partition()->GetQuotaManagerProxy();
}
void BucketManagerHost::OnReceiverDisconnect() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
manager_->OnHostReceiverDisconnect(this, base::PassKey<BucketManagerHost>());
}
void BucketManagerHost::DidGetBucket(
base::WeakPtr<BucketContext> bucket_context,
OpenBucketCallback callback,
storage::QuotaErrorOr<storage::BucketInfo> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!result.ok() || !bucket_context) {
// Getting a bucket can fail if there is a database error.
std::move(callback).Run(mojo::NullRemote());
return;
}
const auto& bucket = result.value();
auto it = bucket_map_.find(bucket.name);
if (it == bucket_map_.end()) {
it = bucket_map_
.emplace(bucket.name, std::make_unique<BucketHost>(this, bucket))
.first;
}
auto pending_remote = it->second->CreateStorageBucketBinding(bucket_context);
std::move(callback).Run(std::move(pending_remote));
}
void BucketManagerHost::DidGetBuckets(
KeysCallback callback,
storage::QuotaErrorOr<std::set<storage::BucketInfo>> buckets) {
if (!buckets.ok()) {
std::move(callback).Run({}, false);
return;
}
std::vector<std::string> keys;
for (auto& bucket : buckets.value()) {
if (!bucket.is_default())
keys.push_back(bucket.name);
}
std::sort(keys.begin(), keys.end());
std::move(callback).Run(keys, true);
}
void BucketManagerHost::DidDeleteBucket(const std::string& bucket_name,
DeleteBucketCallback callback,
blink::mojom::QuotaStatusCode status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (status != blink::mojom::QuotaStatusCode::kOk) {
std::move(callback).Run(false);
return;
}
bucket_map_.erase(bucket_name);
std::move(callback).Run(true);
}
} // namespace content