blob: 76232713fe917e9a0a53562fd5c683d516fcb9d5 [file] [log] [blame]
// Copyright 2014 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 "extensions/browser/api/storage/storage_api.h"
#include <stddef.h>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/api/storage/storage_frontend.h"
#include "extensions/browser/quota_service.h"
#include "extensions/common/api/storage.h"
namespace extensions {
// Concrete settings functions
namespace {
// Adds all StringValues from a ListValue to a vector of strings.
void AddAllStringValues(const base::ListValue& from,
std::vector<std::string>* to) {
DCHECK(to->empty());
std::string as_string;
for (const auto& entry : from.GetList()) {
if (entry.GetAsString(&as_string)) {
to->push_back(as_string);
}
}
}
// Gets the keys of a DictionaryValue.
std::vector<std::string> GetKeys(const base::DictionaryValue& dict) {
std::vector<std::string> keys;
for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) {
keys.push_back(it.key());
}
return keys;
}
// Creates quota heuristics for settings modification.
void GetModificationQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) {
// See storage.json for the current value of these limits.
QuotaLimitHeuristic::Config short_limit_config = {
api::storage::sync::MAX_WRITE_OPERATIONS_PER_MINUTE,
base::TimeDelta::FromMinutes(1)};
QuotaLimitHeuristic::Config long_limit_config = {
api::storage::sync::MAX_WRITE_OPERATIONS_PER_HOUR,
base::TimeDelta::FromHours(1)};
heuristics->push_back(std::make_unique<QuotaService::TimedLimit>(
short_limit_config,
std::make_unique<QuotaLimitHeuristic::SingletonBucketMapper>(),
"MAX_WRITE_OPERATIONS_PER_MINUTE"));
heuristics->push_back(std::make_unique<QuotaService::TimedLimit>(
long_limit_config,
std::make_unique<QuotaLimitHeuristic::SingletonBucketMapper>(),
"MAX_WRITE_OPERATIONS_PER_HOUR"));
}
} // namespace
// SettingsFunction
SettingsFunction::SettingsFunction() = default;
SettingsFunction::~SettingsFunction() = default;
bool SettingsFunction::ShouldSkipQuotaLimiting() const {
// Only apply quota if this is for sync storage.
std::string storage_area_string;
if (!args_->GetString(0, &storage_area_string)) {
// This should be EXTENSION_FUNCTION_VALIDATE(false) but there is no way
// to signify that from this function. It will be caught in Run().
return false;
}
return StorageAreaFromString(storage_area_string) !=
StorageAreaNamespace::kSync;
}
ExtensionFunction::ResponseAction SettingsFunction::Run() {
std::string storage_area_string;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &storage_area_string));
args_->Remove(0, nullptr);
storage_area_ = StorageAreaFromString(storage_area_string);
EXTENSION_FUNCTION_VALIDATE(storage_area_ != StorageAreaNamespace::kInvalid);
settings_namespace_ = StorageAreaToSettingsNamespace(storage_area_);
// TODO(emiliapaz): Currently we only have sync, local and managed implemented
// in this function. This check will change after we introduce `session`.
EXTENSION_FUNCTION_VALIDATE(
settings_namespace_ == settings_namespace::SYNC ||
settings_namespace_ == settings_namespace::LOCAL ||
settings_namespace_ == settings_namespace::MANAGED);
if (extension()->is_login_screen_extension() &&
storage_area_ != StorageAreaNamespace::kManaged) {
// Login screen extensions are not allowed to use local/sync storage for
// security reasons (see crbug.com/978443).
return RespondNow(Error(base::StringPrintf(
"\"%s\" is not available for login screen extensions",
storage_area_string.c_str())));
}
StorageFrontend* frontend = StorageFrontend::Get(browser_context());
if (!frontend->IsStorageEnabled(settings_namespace_)) {
return RespondNow(Error(
base::StringPrintf("\"%s\" is not available in this instance of Chrome",
storage_area_string.c_str())));
}
observers_ = frontend->GetObservers();
frontend->RunWithStorage(
extension(), settings_namespace_,
base::BindOnce(&SettingsFunction::AsyncRunWithStorage, this));
return RespondLater();
}
void SettingsFunction::AsyncRunWithStorage(ValueStore* storage) {
ResponseValue response = RunWithStorage(storage);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&SettingsFunction::Respond, this, std::move(response)));
}
ExtensionFunction::ResponseValue SettingsFunction::UseReadResult(
ValueStore::ReadResult result) {
TRACE_EVENT2("browser", "SettingsFunction::UseReadResult", "extension_id",
extension_id(), "namespace", storage_area_);
if (!result.status().ok())
return Error(result.status().message);
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->Swap(&result.settings());
return OneArgument(base::Value::FromUniquePtrValue(std::move(dict)));
}
ExtensionFunction::ResponseValue SettingsFunction::UseWriteResult(
ValueStore::WriteResult result) {
TRACE_EVENT2("browser", "SettingsFunction::UseWriteResult", "extension_id",
extension_id(), "namespace", storage_area_);
if (!result.status().ok())
return Error(result.status().message);
if (!result.changes().empty()) {
observers_->Notify(FROM_HERE, &SettingsObserver::OnSettingsChanged,
extension_id(), storage_area_,
ValueStoreChange::ToValue(result.PassChanges()));
}
return NoArguments();
}
ExtensionFunction::ResponseValue StorageStorageAreaGetFunction::RunWithStorage(
ValueStore* storage) {
TRACE_EVENT1("browser", "StorageStorageAreaGetFunction::RunWithStorage",
"extension_id", extension_id());
base::Value* input = NULL;
if (!args_->Get(0, &input))
return BadMessage();
switch (input->type()) {
case base::Value::Type::NONE:
return UseReadResult(storage->Get());
case base::Value::Type::STRING: {
std::string as_string;
input->GetAsString(&as_string);
return UseReadResult(storage->Get(as_string));
}
case base::Value::Type::LIST: {
std::vector<std::string> as_string_list;
AddAllStringValues(*static_cast<base::ListValue*>(input),
&as_string_list);
return UseReadResult(storage->Get(as_string_list));
}
case base::Value::Type::DICTIONARY: {
base::DictionaryValue* as_dict =
static_cast<base::DictionaryValue*>(input);
ValueStore::ReadResult result = storage->Get(GetKeys(*as_dict));
if (!result.status().ok()) {
return UseReadResult(std::move(result));
}
std::unique_ptr<base::DictionaryValue> with_default_values =
as_dict->CreateDeepCopy();
with_default_values->MergeDictionary(&result.settings());
return UseReadResult(ValueStore::ReadResult(
std::move(with_default_values), result.PassStatus()));
}
default:
return BadMessage();
}
}
ExtensionFunction::ResponseValue
StorageStorageAreaGetBytesInUseFunction::RunWithStorage(ValueStore* storage) {
TRACE_EVENT1("browser",
"StorageStorageAreaGetBytesInUseFunction::RunWithStorage",
"extension_id", extension_id());
base::Value* input = NULL;
if (!args_->Get(0, &input))
return BadMessage();
size_t bytes_in_use = 0;
switch (input->type()) {
case base::Value::Type::NONE:
bytes_in_use = storage->GetBytesInUse();
break;
case base::Value::Type::STRING: {
std::string as_string;
input->GetAsString(&as_string);
bytes_in_use = storage->GetBytesInUse(as_string);
break;
}
case base::Value::Type::LIST: {
std::vector<std::string> as_string_list;
AddAllStringValues(*static_cast<base::ListValue*>(input),
&as_string_list);
bytes_in_use = storage->GetBytesInUse(as_string_list);
break;
}
default:
return BadMessage();
}
return OneArgument(base::Value(static_cast<int>(bytes_in_use)));
}
ExtensionFunction::ResponseValue StorageStorageAreaSetFunction::RunWithStorage(
ValueStore* storage) {
TRACE_EVENT1("browser", "StorageStorageAreaSetFunction::RunWithStorage",
"extension_id", extension_id());
base::DictionaryValue* input = NULL;
if (!args_->GetDictionary(0, &input))
return BadMessage();
return UseWriteResult(storage->Set(ValueStore::DEFAULTS, *input));
}
void StorageStorageAreaSetFunction::GetQuotaLimitHeuristics(
QuotaLimitHeuristics* heuristics) const {
GetModificationQuotaLimitHeuristics(heuristics);
}
ExtensionFunction::ResponseValue
StorageStorageAreaRemoveFunction::RunWithStorage(ValueStore* storage) {
TRACE_EVENT1("browser", "StorageStorageAreaRemoveFunction::RunWithStorage",
"extension_id", extension_id());
base::Value* input = NULL;
if (!args_->Get(0, &input))
return BadMessage();
switch (input->type()) {
case base::Value::Type::STRING: {
std::string as_string;
input->GetAsString(&as_string);
return UseWriteResult(storage->Remove(as_string));
}
case base::Value::Type::LIST: {
std::vector<std::string> as_string_list;
AddAllStringValues(*static_cast<base::ListValue*>(input),
&as_string_list);
return UseWriteResult(storage->Remove(as_string_list));
}
default:
return BadMessage();
}
}
void StorageStorageAreaRemoveFunction::GetQuotaLimitHeuristics(
QuotaLimitHeuristics* heuristics) const {
GetModificationQuotaLimitHeuristics(heuristics);
}
ExtensionFunction::ResponseValue
StorageStorageAreaClearFunction::RunWithStorage(ValueStore* storage) {
TRACE_EVENT1("browser", "StorageStorageAreaClearFunction::RunWithStorage",
"extension_id", extension_id());
return UseWriteResult(storage->Clear());
}
void StorageStorageAreaClearFunction::GetQuotaLimitHeuristics(
QuotaLimitHeuristics* heuristics) const {
GetModificationQuotaLimitHeuristics(heuristics);
}
} // namespace extensions