| // 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 |