blob: 03342292b8c30bd4bc58afdc4b06a412c6cf4d3f [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// 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 "stdint.h"
#include <limits>
#include <memory>
#include <set>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/strings/stringprintf.h"
#include "components/crx_file/id_util.h"
#include "components/value_store/leveldb_value_store.h"
#include "components/value_store/value_store.h"
#include "components/value_store/value_store_factory_impl.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "extensions/browser/api/extensions_api_client.h"
#include "extensions/browser/api/storage/settings_storage_quota_enforcer.h"
#include "extensions/browser/api/storage/settings_test_util.h"
#include "extensions/browser/api/storage/storage_frontend.h"
#include "extensions/browser/api_unittest.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/event_router_factory.h"
#include "extensions/browser/test_event_router_observer.h"
#include "extensions/browser/test_extensions_browser_client.h"
#include "extensions/common/api/storage.h"
#include "extensions/common/manifest.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"
#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
namespace extensions {
namespace {
// Caller owns the returned object.
std::unique_ptr<KeyedService> CreateStorageFrontendForTesting(
content::BrowserContext* context) {
scoped_refptr<value_store::ValueStoreFactory> factory =
new value_store::ValueStoreFactoryImpl(context->GetPath());
return StorageFrontend::CreateForTesting(factory, context);
}
std::unique_ptr<KeyedService> BuildEventRouter(
content::BrowserContext* context) {
return std::make_unique<extensions::EventRouter>(context, nullptr);
}
} // namespace
class StorageApiUnittest : public ApiUnitTest {
public:
StorageApiUnittest() {}
~StorageApiUnittest() override {}
protected:
void SetUp() override {
ApiUnitTest::SetUp();
EventRouterFactory::GetInstance()->SetTestingFactory(
browser_context(), base::BindRepeating(&BuildEventRouter));
// Ensure a StorageFrontend can be created on demand. The StorageFrontend
// will be owned by the KeyedService system.
StorageFrontend::GetFactoryInstance()->SetTestingFactory(
browser_context(),
base::BindRepeating(&CreateStorageFrontendForTesting));
render_process_host_ =
std::make_unique<content::MockRenderProcessHost>(browser_context());
}
void TearDown() override {
render_process_host_.reset();
ApiUnitTest::TearDown();
}
content::RenderProcessHost* render_process_host() const {
return render_process_host_.get();
}
// Runs the storage.set() API function with local storage.
void RunSetFunction(const std::string& key, const std::string& value) {
RunFunction(
new StorageStorageAreaSetFunction(),
base::StringPrintf(
"[\"local\", {\"%s\": \"%s\"}]", key.c_str(), value.c_str()));
}
// Runs the storage.get() API function with the local storage, and populates
// |out_value| with the string result.
testing::AssertionResult RunGetFunction(const std::string& key,
std::string* out_value) {
std::optional<base::Value> result = RunFunctionAndReturnValue(
new StorageStorageAreaGetFunction(),
base::StringPrintf("[\"local\", \"%s\"]", key.c_str()));
if (!result)
return testing::AssertionFailure() << "No result";
const base::Value::Dict* dict = result->GetIfDict();
if (!dict) {
return testing::AssertionFailure() << *result << " was not a dictionary.";
}
const std::string* dict_value = dict->FindString(key);
if (!dict_value) {
return testing::AssertionFailure() << " could not retrieve a string from"
<< dict << " at " << key;
}
*out_value = *dict_value;
return testing::AssertionSuccess();
}
ExtensionsAPIClient extensions_api_client_;
std::unique_ptr<content::RenderProcessHost> render_process_host_;
};
TEST_F(StorageApiUnittest, RestoreCorruptedStorage) {
const char kKey[] = "key";
const char kValue[] = "value";
std::string result;
// Do a simple set/get combo to make sure the API works.
RunSetFunction(kKey, kValue);
EXPECT_TRUE(RunGetFunction(kKey, &result));
EXPECT_EQ(kValue, result);
// Corrupt the store. This is not as pretty as ideal, because we use knowledge
// of the underlying structure, but there's no real good way to corrupt a
// store other than directly modifying the files.
value_store::ValueStore* store =
settings_test_util::GetStorage(extension_ref(), settings_namespace::LOCAL,
StorageFrontend::Get(browser_context()));
ASSERT_TRUE(store);
// TODO(cmumford): Modify test as this requires that the factory always
// creates instances of LeveldbValueStore.
SettingsStorageQuotaEnforcer* quota_store =
static_cast<SettingsStorageQuotaEnforcer*>(store);
value_store::LeveldbValueStore* leveldb_store =
static_cast<value_store::LeveldbValueStore*>(
quota_store->get_delegate_for_test());
leveldb::WriteBatch batch;
batch.Put(kKey, "[{(.*+\"\'\\");
EXPECT_TRUE(leveldb_store->WriteToDbForTest(&batch));
EXPECT_TRUE(leveldb_store->Get(kKey).status().IsCorrupted());
// Running another set should end up working (even though it will restore the
// store behind the scenes).
RunSetFunction(kKey, kValue);
EXPECT_TRUE(RunGetFunction(kKey, &result));
EXPECT_EQ(kValue, result);
}
TEST_F(StorageApiUnittest, StorageAreaOnChanged) {
TestEventRouterObserver event_observer(EventRouter::Get(browser_context()));
EventRouter* event_router = EventRouter::Get(browser_context());
event_router->AddEventListener(api::storage::OnChanged::kEventName,
render_process_host(), extension()->id());
event_router->AddEventListener("storage.local.onChanged",
render_process_host(), extension()->id());
RunSetFunction("key", "value");
EXPECT_EQ(2u, event_observer.events().size());
EXPECT_TRUE(base::Contains(event_observer.events(),
api::storage::OnChanged::kEventName));
EXPECT_TRUE(
base::Contains(event_observer.events(), "storage.local.onChanged"));
}
// Test that no event is dispatched if no listener is added.
TEST_F(StorageApiUnittest, StorageAreaOnChangedNoListener) {
TestEventRouterObserver event_observer(EventRouter::Get(browser_context()));
RunSetFunction("key", "value");
EXPECT_EQ(0u, event_observer.events().size());
}
// Test that no event is dispatched if a listener for a different extension is
// added.
TEST_F(StorageApiUnittest, StorageAreaOnChangedOtherListener) {
TestEventRouterObserver event_observer(EventRouter::Get(browser_context()));
EventRouter* event_router = EventRouter::Get(browser_context());
std::string other_listener_id =
crx_file::id_util::GenerateId("other-listener");
event_router->AddEventListener(api::storage::OnChanged::kEventName,
render_process_host(), other_listener_id);
event_router->AddEventListener("storage.local.onChanged",
render_process_host(), other_listener_id);
RunSetFunction("key", "value");
EXPECT_EQ(0u, event_observer.events().size());
}
TEST_F(StorageApiUnittest, StorageAreaOnChangedOnlyOneListener) {
TestEventRouterObserver event_observer(EventRouter::Get(browser_context()));
EventRouter* event_router = EventRouter::Get(browser_context());
event_router->AddEventListener(api::storage::OnChanged::kEventName,
render_process_host(), extension()->id());
RunSetFunction("key", "value");
EXPECT_EQ(1u, event_observer.events().size());
EXPECT_TRUE(base::Contains(event_observer.events(),
api::storage::OnChanged::kEventName));
}
// This is a regression test for crbug.com/1483828.
TEST_F(StorageApiUnittest, GetBytesInUseIntOverflow) {
// A fake value store that only implements the overloads of GetBytesInUse().
class FakeValueStore : public value_store::ValueStore {
public:
explicit FakeValueStore(size_t bytes_in_use)
: bytes_in_use_(bytes_in_use) {}
size_t GetBytesInUse(const std::string& key) override {
return bytes_in_use_;
}
size_t GetBytesInUse(const std::vector<std::string>& keys) override {
return bytes_in_use_;
}
size_t GetBytesInUse() override { return bytes_in_use_; }
ReadResult Get(const std::string& key) override {
NOTREACHED();
return ReadResult(Status());
}
ReadResult Get(const std::vector<std::string>& keys) override {
NOTREACHED();
return ReadResult(Status());
}
ReadResult Get() override {
NOTREACHED();
return ReadResult(Status());
}
WriteResult Set(WriteOptions options,
const std::string& key,
const base::Value& value) override {
NOTREACHED();
return WriteResult(Status());
}
WriteResult Set(WriteOptions options,
const base::Value::Dict& values) override {
NOTREACHED();
return WriteResult(Status());
}
WriteResult Remove(const std::string& key) override {
NOTREACHED();
return WriteResult(Status());
}
WriteResult Remove(const std::vector<std::string>& keys) override {
NOTREACHED();
return WriteResult(Status());
}
WriteResult Clear() override {
NOTREACHED();
return WriteResult(Status());
}
private:
size_t bytes_in_use_ = 0;
};
static constexpr struct TestCase {
size_t bytes_in_use;
double result;
} test_cases[] = {
{1, 1.0},
{std::numeric_limits<int>::max(), std::numeric_limits<int>::max()},
// Test the overflow case from the bug. It's enough to have a value
// that exceeds the max value that an int can represent.
{static_cast<size_t>(std::numeric_limits<int>::max()) + 1,
static_cast<size_t>(std::numeric_limits<int>::max()) + 1}};
for (const auto& test_case : test_cases) {
FakeValueStore value_store(test_case.bytes_in_use);
auto function =
base::MakeRefCounted<StorageStorageAreaGetBytesInUseFunction>();
// Call into the protected member function to avoid dealing with how the
// base class gets the StorageArea to use. Set `args` to a base::Value
// "none" so we get the total bytes in use.
base::Value::List args;
args.Append(base::Value());
function->SetArgs(std::move(args));
function->RunWithStorage(&value_store);
const base::Value::List* results = function->GetResultListForTest();
ASSERT_TRUE(results);
ASSERT_EQ(1u, results->size());
EXPECT_EQ(test_case.result, (*results)[0]);
}
}
} // namespace extensions