blob: af1247a876df162a29d374e5a8c49e23fe161d7d [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 "chrome/browser/extensions/updater/local_extension_cache.h"
#include <stddef.h>
#include <stdint.h>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task_scheduler/post_task.h"
#include "base/values.h"
#include "chrome/common/extensions/extension_constants.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "crypto/secure_hash.h"
#include "crypto/sha2.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const char kTestExtensionId1[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const char kTestExtensionId2[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
const char kTestExtensionId3[] = "cccccccccccccccccccccccccccccccc";
} // namespace
namespace extensions {
class LocalExtensionCacheTest : public testing::Test {
public:
LocalExtensionCacheTest() {}
~LocalExtensionCacheTest() override {}
base::FilePath CreateCacheDir() {
EXPECT_TRUE(cache_dir_.CreateUniqueTempDir());
return cache_dir_.GetPath();
}
base::FilePath CreateTempDir() {
EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
return temp_dir_.GetPath();
}
void CreateExtensionFile(const base::FilePath& dir,
const std::string& id,
const std::string& version,
size_t size,
const base::Time& timestamp,
base::FilePath* filename) {
const base::FilePath file = GetExtensionFileName(dir, id, version, "");
if (filename)
*filename = file;
CreateFile(file, size, timestamp);
}
void CreateFile(const base::FilePath& file,
size_t size,
const base::Time& timestamp) {
std::string data(size, 0);
EXPECT_EQ(base::WriteFile(file, data.data(), data.size()), int(size));
EXPECT_TRUE(base::TouchFile(file, timestamp, timestamp));
}
std::string CreateSignedExtensionFile(const base::FilePath& dir,
const std::string& id,
const std::string& version,
size_t size,
const base::Time& timestamp,
base::FilePath* filename) {
std::string data(size, 0);
std::unique_ptr<crypto::SecureHash> hash =
crypto::SecureHash::Create(crypto::SecureHash::SHA256);
hash->Update(data.c_str(), size);
uint8_t output[crypto::kSHA256Length];
hash->Finish(output, sizeof(output));
const std::string hex_hash =
base::ToLowerASCII(base::HexEncode(output, sizeof(output)));
const base::FilePath file =
GetExtensionFileName(dir, id, version, hex_hash);
if (filename)
*filename = file;
EXPECT_EQ(base::WriteFile(file, data.data(), data.size()), int(size));
EXPECT_TRUE(base::TouchFile(file, timestamp, timestamp));
return hex_hash;
}
base::FilePath GetExtensionFileName(const base::FilePath& dir,
const std::string& id,
const std::string& version,
const std::string& hash) {
return dir.Append(
extensions::LocalExtensionCache::ExtensionFileName(id, version, hash));
}
private:
content::TestBrowserThreadBundle thread_bundle_;
base::ScopedTempDir cache_dir_;
base::ScopedTempDir temp_dir_;
DISALLOW_COPY_AND_ASSIGN(LocalExtensionCacheTest);
};
static void SimpleCallback(bool* ptr) {
*ptr = true;
}
TEST_F(LocalExtensionCacheTest, Basic) {
base::FilePath cache_dir(CreateCacheDir());
LocalExtensionCache cache(
cache_dir, 1000, base::TimeDelta::FromDays(30),
base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()}));
cache.SetCacheStatusPollingDelayForTests(base::TimeDelta());
bool initialized = false;
cache.Init(true, base::Bind(&SimpleCallback, &initialized));
base::FilePath file10, file01, file20, file30;
CreateExtensionFile(cache_dir, kTestExtensionId1, "1.0", 100,
base::Time::Now() - base::TimeDelta::FromDays(1),
&file10);
CreateExtensionFile(cache_dir, kTestExtensionId1, "0.1", 100,
base::Time::Now() - base::TimeDelta::FromDays(10),
&file01);
CreateExtensionFile(cache_dir, kTestExtensionId2, "2.0", 100,
base::Time::Now() - base::TimeDelta::FromDays(40),
&file20);
CreateExtensionFile(cache_dir, kTestExtensionId3, "3.0", 900,
base::Time::Now() - base::TimeDelta::FromDays(41),
&file30);
content::RunAllTasksUntilIdle();
ASSERT_TRUE(initialized);
// Older version should be removed on cache initialization.
EXPECT_FALSE(base::PathExists(file01));
// All extensions should be there because cleanup happens on shutdown to
// support use case when device was not used to more than 30 days and cache
// shouldn't be cleaned before someone will have a chance to use it.
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, "", NULL, NULL));
EXPECT_TRUE(cache.GetExtension(kTestExtensionId2, "", NULL, NULL));
EXPECT_TRUE(cache.GetExtension(kTestExtensionId3, "", NULL, NULL));
bool did_shutdown = false;
cache.Shutdown(base::Bind(&SimpleCallback, &did_shutdown));
content::RunAllTasksUntilIdle();
ASSERT_TRUE(did_shutdown);
EXPECT_TRUE(base::PathExists(file10));
EXPECT_FALSE(base::PathExists(file20));
EXPECT_FALSE(base::PathExists(file30));
}
TEST_F(LocalExtensionCacheTest, KeepHashed) {
base::FilePath cache_dir(CreateCacheDir());
LocalExtensionCache cache(
cache_dir, 1000, base::TimeDelta::FromDays(30),
base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()}));
cache.SetCacheStatusPollingDelayForTests(base::TimeDelta());
bool initialized = false;
cache.Init(true, base::Bind(&SimpleCallback, &initialized));
// Add three identical extensions with different hash sums
const base::Time time = base::Time::Now() - base::TimeDelta::FromDays(1);
base::FilePath file, file1, file2;
CreateExtensionFile(cache_dir, kTestExtensionId1, "1.0", 100, time, &file);
const std::string hash1 = CreateSignedExtensionFile(
cache_dir, kTestExtensionId1, "1.0", 100, time, &file1);
const std::string hash2 = CreateSignedExtensionFile(
cache_dir, kTestExtensionId1, "1.0", 123, time, &file2);
content::RunAllTasksUntilIdle();
ASSERT_TRUE(initialized);
// Unhashed version should be removed on cache initialization.
EXPECT_FALSE(base::PathExists(file));
// Both hashed versions should stay
EXPECT_TRUE(base::PathExists(file1));
EXPECT_TRUE(base::PathExists(file2));
// We should be able to lookup all three extension queries
std::string version;
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, "", NULL, &version));
EXPECT_EQ(version, "1.0");
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, hash1, NULL, NULL));
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, hash2, NULL, NULL));
}
TEST_F(LocalExtensionCacheTest, KeepLatest) {
base::FilePath cache_dir(CreateCacheDir());
LocalExtensionCache cache(
cache_dir, 1000, base::TimeDelta::FromDays(30),
base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()}));
cache.SetCacheStatusPollingDelayForTests(base::TimeDelta());
bool initialized = false;
cache.Init(true, base::Bind(&SimpleCallback, &initialized));
// All extension files are hashed, but have different versions
const base::Time time = base::Time::Now() - base::TimeDelta::FromDays(1);
base::FilePath file1, file21, file22;
const std::string hash1 = CreateSignedExtensionFile(
cache_dir, kTestExtensionId1, "1.0", 100, time, &file1);
const std::string hash21 = CreateSignedExtensionFile(
cache_dir, kTestExtensionId1, "2.0", 101, time, &file21);
const std::string hash22 = CreateSignedExtensionFile(
cache_dir, kTestExtensionId1, "2.0", 123, time, &file22);
content::RunAllTasksUntilIdle();
ASSERT_TRUE(initialized);
// Older version should be removed
EXPECT_FALSE(base::PathExists(file1));
// Both newer hashed versions should stay
EXPECT_TRUE(base::PathExists(file21));
EXPECT_TRUE(base::PathExists(file22));
// We should be able to lookup only the latest version queries
EXPECT_FALSE(cache.GetExtension(kTestExtensionId1, hash1, NULL, NULL));
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, hash21, NULL, NULL));
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, hash22, NULL, NULL));
}
TEST_F(LocalExtensionCacheTest, Complex) {
base::FilePath cache_dir(CreateCacheDir());
LocalExtensionCache cache(
cache_dir, 1000, base::TimeDelta::FromDays(30),
base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()}));
cache.SetCacheStatusPollingDelayForTests(base::TimeDelta());
bool initialized = false;
cache.Init(true, base::Bind(&SimpleCallback, &initialized));
// Like in KeepHashed test, but with two different versions
const base::Time time = base::Time::Now() - base::TimeDelta::FromDays(1);
base::FilePath file1, file11, file12, file2, file21, file22;
CreateExtensionFile(cache_dir, kTestExtensionId1, "1.0", 100, time, &file1);
const std::string hash11 = CreateSignedExtensionFile(
cache_dir, kTestExtensionId1, "1.0", 101, time, &file11);
const std::string hash12 = CreateSignedExtensionFile(
cache_dir, kTestExtensionId1, "1.0", 102, time, &file12);
CreateExtensionFile(cache_dir, kTestExtensionId1, "2.0", 103, time, &file2);
const std::string hash21 = CreateSignedExtensionFile(
cache_dir, kTestExtensionId1, "2.0", 104, time, &file21);
const std::string hash22 = CreateSignedExtensionFile(
cache_dir, kTestExtensionId1, "2.0", 105, time, &file22);
content::RunAllTasksUntilIdle();
ASSERT_TRUE(initialized);
// Older and unhashed versions should be removed
EXPECT_FALSE(base::PathExists(file1));
EXPECT_FALSE(base::PathExists(file11));
EXPECT_FALSE(base::PathExists(file12));
EXPECT_FALSE(base::PathExists(file2));
// Newest hashed versions should stay
EXPECT_TRUE(base::PathExists(file21));
EXPECT_TRUE(base::PathExists(file22));
// We should be able to lookup only the latest version queries, both with and
// without hash
std::string version;
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, "", NULL, &version));
EXPECT_EQ(version, "2.0");
EXPECT_FALSE(cache.GetExtension(kTestExtensionId1, hash11, NULL, NULL));
EXPECT_FALSE(cache.GetExtension(kTestExtensionId1, hash12, NULL, NULL));
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, hash21, NULL, NULL));
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, hash22, NULL, NULL));
}
static void OnPutExtension(std::unique_ptr<base::RunLoop>* run_loop,
const base::FilePath& file_path,
bool file_ownership_passed) {
ASSERT_TRUE(*run_loop);
(*run_loop)->Quit();
}
static void PutExtensionAndWait(LocalExtensionCache& cache,
const std::string& id,
const std::string& expected_hash,
const base::FilePath& path,
const std::string& version) {
std::unique_ptr<base::RunLoop> run_loop;
run_loop.reset(new base::RunLoop);
cache.PutExtension(id, expected_hash, path, version,
base::Bind(&OnPutExtension, &run_loop));
run_loop->Run();
}
TEST_F(LocalExtensionCacheTest, PutExtensionCases) {
base::FilePath cache_dir(CreateCacheDir());
LocalExtensionCache cache(
cache_dir, 1000, base::TimeDelta::FromDays(30),
base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()}));
cache.SetCacheStatusPollingDelayForTests(base::TimeDelta());
bool initialized = false;
cache.Init(true, base::Bind(&SimpleCallback, &initialized));
// Initialize cache with several different files
const base::Time time = base::Time::Now() - base::TimeDelta::FromDays(1);
base::FilePath file11, file12, file2, file3;
const std::string hash11 = CreateSignedExtensionFile(
cache_dir, kTestExtensionId1, "1.0", 101, time, &file11);
const std::string hash12 = CreateSignedExtensionFile(
cache_dir, kTestExtensionId1, "1.0", 102, time, &file12);
CreateSignedExtensionFile(cache_dir, kTestExtensionId2, "0.2", 200, time,
&file2);
CreateExtensionFile(cache_dir, kTestExtensionId3, "0.3", 300, time, &file3);
content::RunAllTasksUntilIdle();
ASSERT_TRUE(initialized);
// Create and initialize installation source directory.
base::ScopedTempDir temp_dir;
EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
const base::FilePath temp_path = temp_dir.GetPath();
std::string version;
// Right now we have two files for the first extension
EXPECT_TRUE(base::PathExists(file11));
EXPECT_TRUE(base::PathExists(file12));
EXPECT_TRUE(base::PathExists(file2));
EXPECT_TRUE(base::PathExists(file3));
// 1. Cache contains an older version
base::FilePath temp1;
CreateExtensionFile(temp_path, kTestExtensionId1, "3.0", 110, time, &temp1);
PutExtensionAndWait(cache, kTestExtensionId1, "", temp1, "3.0");
// New file added
const base::FilePath unhashed =
GetExtensionFileName(cache_dir, kTestExtensionId1, "3.0", "");
EXPECT_TRUE(base::PathExists(unhashed));
// Old files removed from cache (kept in the directory though)
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, hash11, NULL, &version));
EXPECT_EQ(version, "3.0");
EXPECT_TRUE(base::DeleteFile(temp1, false));
// 2. Cache contains a newer version
base::FilePath temp2;
CreateExtensionFile(temp_path, kTestExtensionId1, "2.0", 120, time, &temp2);
PutExtensionAndWait(cache, kTestExtensionId1, "", temp2, "2.0");
// New file skipped
EXPECT_FALSE(base::PathExists(
GetExtensionFileName(cache_dir, kTestExtensionId1, "2.0", "")));
// Old file kept
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, "", NULL, &version));
EXPECT_EQ(version, "3.0");
EXPECT_TRUE(base::DeleteFile(temp2, false));
// 3. Cache contains the same version without hash, our file is unhashed
base::FilePath temp3;
CreateExtensionFile(temp_path, kTestExtensionId1, "3.0", 130, time, &temp3);
PutExtensionAndWait(cache, kTestExtensionId1, "", temp3, "3.0");
// New file skipped, old file kept
EXPECT_EQ(base::File(unhashed, base::File::FLAG_READ | base::File::FLAG_OPEN)
.GetLength(),
110);
EXPECT_TRUE(base::DeleteFile(temp3, false));
// 4. Cache contains the same version without hash, our file is hashed
base::FilePath temp4;
const std::string hash3 = CreateSignedExtensionFile(
temp_path, kTestExtensionId1, "3.0", 140, time, &temp4);
PutExtensionAndWait(cache, kTestExtensionId1, hash3, temp4, "3.0");
// New file added
const base::FilePath hashed =
GetExtensionFileName(cache_dir, kTestExtensionId1, "3.0", hash3);
EXPECT_TRUE(base::PathExists(hashed));
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, hash3, NULL, NULL));
// Old file removed (queries return hashed version)
base::FilePath unhashed_path;
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, "", &unhashed_path, NULL));
EXPECT_EQ(unhashed_path, hashed);
EXPECT_TRUE(base::DeleteFile(temp4, false));
EXPECT_TRUE(base::DeleteFile(unhashed, false));
// 5. Cache contains the same version with hash, our file is unhashed
base::FilePath temp5;
CreateExtensionFile(temp_path, kTestExtensionId1, "3.0", 150, time, &temp5);
PutExtensionAndWait(cache, kTestExtensionId1, "", temp5, "3.0");
// New file skipped
EXPECT_FALSE(base::PathExists(unhashed));
// Old file kept
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, hash3, NULL, NULL));
EXPECT_TRUE(base::DeleteFile(temp5, false));
// 6. Cache contains the same version with hash, our file has the "same" hash
base::FilePath temp6;
CreateExtensionFile(temp_path, kTestExtensionId1, "3.0", 160, time, &temp6);
PutExtensionAndWait(cache, kTestExtensionId1, hash3, temp6, "3.0");
// New file skipped, old file kept
EXPECT_EQ(base::File(hashed, base::File::FLAG_READ | base::File::FLAG_OPEN)
.GetLength(),
140);
EXPECT_TRUE(base::DeleteFile(temp6, false));
// 7. Cache contains the same version with hash, our file is different
base::FilePath temp7;
const std::string hash4 = CreateSignedExtensionFile(
temp_path, kTestExtensionId1, "3.0", 170, time, &temp7);
PutExtensionAndWait(cache, kTestExtensionId1, hash4, temp7, "3.0");
// New file addded
const base::FilePath hashed2 =
GetExtensionFileName(cache_dir, kTestExtensionId1, "3.0", hash4);
EXPECT_TRUE(base::PathExists(hashed2));
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, hash4, NULL, NULL));
// Old file kept
EXPECT_TRUE(cache.GetExtension(kTestExtensionId1, hash3, NULL, NULL));
EXPECT_TRUE(base::DeleteFile(temp7, false));
}
} // namespace extensions