blob: fc0ed81f49cb6e67c243f6436df581d3acd66bfd [file] [log] [blame]
// Copyright 2018 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/content_verifier/test_utils.h"
#include "base/bind.h"
#include "base/strings/stringprintf.h"
#include "base/task/post_task.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "content/public/browser/browser_task_traits.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/file_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/zlib/google/zip.h"
namespace extensions {
// TestContentVerifySingleJobObserver ------------------------------------------
TestContentVerifySingleJobObserver::TestContentVerifySingleJobObserver(
const ExtensionId& extension_id,
const base::FilePath& relative_path)
: extension_id_(extension_id), relative_path_(relative_path) {
ContentVerifyJob::SetObserverForTests(this);
}
TestContentVerifySingleJobObserver::~TestContentVerifySingleJobObserver() {
ContentVerifyJob::SetObserverForTests(nullptr);
}
void TestContentVerifySingleJobObserver::JobFinished(
const ExtensionId& extension_id,
const base::FilePath& relative_path,
ContentVerifyJob::FailureReason reason) {
if (extension_id != extension_id_ || relative_path != relative_path_)
return;
EXPECT_FALSE(failure_reason_.has_value());
failure_reason_ = reason;
job_finished_run_loop_.Quit();
}
void TestContentVerifySingleJobObserver::OnHashesReady(
const ExtensionId& extension_id,
const base::FilePath& relative_path,
bool success) {
if (extension_id != extension_id_ || relative_path != relative_path_)
return;
EXPECT_FALSE(seen_on_hashes_ready_);
seen_on_hashes_ready_ = true;
on_hashes_ready_run_loop_.Quit();
}
ContentVerifyJob::FailureReason
TestContentVerifySingleJobObserver::WaitForJobFinished() {
// Run() returns immediately if Quit() has already been called.
job_finished_run_loop_.Run();
EXPECT_TRUE(failure_reason_.has_value());
return failure_reason_.value_or(ContentVerifyJob::FAILURE_REASON_MAX);
}
void TestContentVerifySingleJobObserver::WaitForOnHashesReady() {
// Run() returns immediately if Quit() has already been called.
on_hashes_ready_run_loop_.Run();
}
// TestContentVerifyJobObserver ------------------------------------------------
void TestContentVerifyJobObserver::ExpectJobResult(
const ExtensionId& extension_id,
const base::FilePath& relative_path,
Result expected_result) {
expectations_.push_back(
ExpectedResult(extension_id, relative_path, expected_result));
}
TestContentVerifyJobObserver::TestContentVerifyJobObserver() {
EXPECT_TRUE(
content::BrowserThread::GetCurrentThreadIdentifier(&creation_thread_));
ContentVerifyJob::SetObserverForTests(this);
}
TestContentVerifyJobObserver::~TestContentVerifyJobObserver() {
ContentVerifyJob::SetObserverForTests(nullptr);
}
bool TestContentVerifyJobObserver::WaitForExpectedJobs() {
EXPECT_TRUE(content::BrowserThread::CurrentlyOn(creation_thread_));
if (!expectations_.empty()) {
base::RunLoop run_loop;
job_quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
return expectations_.empty();
}
void TestContentVerifyJobObserver::JobStarted(
const ExtensionId& extension_id,
const base::FilePath& relative_path) {}
void TestContentVerifyJobObserver::JobFinished(
const ExtensionId& extension_id,
const base::FilePath& relative_path,
ContentVerifyJob::FailureReason failure_reason) {
if (!content::BrowserThread::CurrentlyOn(creation_thread_)) {
base::PostTask(FROM_HERE, {creation_thread_},
base::BindOnce(&TestContentVerifyJobObserver::JobFinished,
base::Unretained(this), extension_id,
relative_path, failure_reason));
return;
}
Result result = failure_reason == ContentVerifyJob::NONE ? Result::SUCCESS
: Result::FAILURE;
bool found = false;
for (auto i = expectations_.begin(); i != expectations_.end(); ++i) {
if (i->extension_id == extension_id && i->path == relative_path &&
i->result == result) {
found = true;
expectations_.erase(i);
break;
}
}
if (found) {
if (expectations_.empty() && job_quit_closure_)
std::move(job_quit_closure_).Run();
} else {
LOG(WARNING) << "Ignoring unexpected JobFinished " << extension_id << "/"
<< relative_path.value()
<< " failure_reason:" << failure_reason;
}
}
// MockContentVerifierDelegate ------------------------------------------------
MockContentVerifierDelegate::MockContentVerifierDelegate() = default;
MockContentVerifierDelegate::~MockContentVerifierDelegate() = default;
ContentVerifierDelegate::VerifierSourceType
MockContentVerifierDelegate::GetVerifierSourceType(const Extension& extension) {
return verifier_source_type_;
}
ContentVerifierKey MockContentVerifierDelegate::GetPublicKey() {
DCHECK_EQ(VerifierSourceType::SIGNED_HASHES, verifier_source_type_);
return ContentVerifierKey(kWebstoreSignaturesPublicKey,
kWebstoreSignaturesPublicKeySize);
}
GURL MockContentVerifierDelegate::GetSignatureFetchUrl(
const ExtensionId& extension_id,
const base::Version& version) {
DCHECK_EQ(VerifierSourceType::SIGNED_HASHES, verifier_source_type_);
std::string url =
base::StringPrintf("http://localhost/getsignature?id=%s&version=%s",
extension_id.c_str(), version.GetString().c_str());
return GURL(url);
}
std::set<base::FilePath> MockContentVerifierDelegate::GetBrowserImagePaths(
const extensions::Extension* extension) {
return std::set<base::FilePath>();
}
void MockContentVerifierDelegate::VerifyFailed(
const ExtensionId& extension_id,
ContentVerifyJob::FailureReason reason) {
ADD_FAILURE() << "Unexpected call for this test";
}
void MockContentVerifierDelegate::Shutdown() {}
void MockContentVerifierDelegate::SetVerifierSourceType(
VerifierSourceType type) {
verifier_source_type_ = type;
}
// VerifierObserver -----------------------------------------------------------
VerifierObserver::VerifierObserver() {
EXPECT_TRUE(
content::BrowserThread::GetCurrentThreadIdentifier(&creation_thread_));
ContentVerifier::SetObserverForTests(this);
}
VerifierObserver::~VerifierObserver() {
ContentVerifier::SetObserverForTests(nullptr);
}
void VerifierObserver::WaitForFetchComplete(const ExtensionId& extension_id) {
EXPECT_TRUE(content::BrowserThread::CurrentlyOn(creation_thread_));
EXPECT_TRUE(id_to_wait_for_.empty());
EXPECT_EQ(loop_runner_.get(), nullptr);
id_to_wait_for_ = extension_id;
loop_runner_ = new content::MessageLoopRunner();
loop_runner_->Run();
id_to_wait_for_.clear();
loop_runner_ = nullptr;
}
void VerifierObserver::OnFetchComplete(const ExtensionId& extension_id,
bool success) {
if (!content::BrowserThread::CurrentlyOn(creation_thread_)) {
base::PostTask(
FROM_HERE, {creation_thread_},
base::BindOnce(&VerifierObserver::OnFetchComplete,
base::Unretained(this), extension_id, success));
return;
}
completed_fetches_.insert(extension_id);
if (extension_id == id_to_wait_for_)
loop_runner_->Quit();
}
// ContentHashResult ----------------------------------------------------------
ContentHashResult::ContentHashResult(
const ExtensionId& extension_id,
bool success,
bool was_cancelled,
const std::set<base::FilePath> mismatch_paths)
: extension_id(extension_id),
success(success),
was_cancelled(was_cancelled),
mismatch_paths(mismatch_paths) {}
ContentHashResult::~ContentHashResult() = default;
// ContentHashWaiter ----------------------------------------------------------
ContentHashWaiter::ContentHashWaiter()
: reply_task_runner_(base::SequencedTaskRunnerHandle::Get()) {}
ContentHashWaiter::~ContentHashWaiter() = default;
std::unique_ptr<ContentHashResult> ContentHashWaiter::CreateAndWaitForCallback(
ContentHash::FetchKey key,
ContentVerifierDelegate::VerifierSourceType source_type) {
GetExtensionFileTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&ContentHashWaiter::CreateContentHash,
base::Unretained(this), std::move(key), source_type));
run_loop_.Run();
DCHECK(result_);
return std::move(result_);
}
void ContentHashWaiter::CreatedCallback(scoped_refptr<ContentHash> content_hash,
bool was_cancelled) {
if (!reply_task_runner_->RunsTasksInCurrentSequence()) {
reply_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ContentHashWaiter::CreatedCallback,
base::Unretained(this), content_hash, was_cancelled));
return;
}
DCHECK(content_hash);
result_ = std::make_unique<ContentHashResult>(
content_hash->extension_id(), content_hash->succeeded(), was_cancelled,
content_hash->hash_mismatch_unix_paths());
run_loop_.QuitWhenIdle();
}
void ContentHashWaiter::CreateContentHash(
ContentHash::FetchKey key,
ContentVerifierDelegate::VerifierSourceType source_type) {
ContentHash::Create(std::move(key), source_type,
ContentHash::IsCancelledCallback(),
base::BindOnce(&ContentHashWaiter::CreatedCallback,
base::Unretained(this)));
}
namespace content_verifier_test_utils {
scoped_refptr<Extension> UnzipToDirAndLoadExtension(
const base::FilePath& extension_zip,
const base::FilePath& unzip_dir) {
if (!zip::Unzip(extension_zip, unzip_dir)) {
ADD_FAILURE() << "Failed to unzip path.";
return nullptr;
}
std::string error;
scoped_refptr<Extension> extension = file_util::LoadExtension(
unzip_dir, Manifest::INTERNAL, 0 /* flags */, &error);
EXPECT_NE(nullptr, extension.get()) << " error:'" << error << "'";
return extension;
}
} // namespace content_verifier_test_utils
} // namespace extensions