| // Copyright (c) 2012 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/sandboxed_unpacker.h" |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/files/file_util.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_util.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/values.h" |
| #include "components/crx_file/id_util.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "content/public/test/test_utils.h" |
| #include "extensions/browser/extensions_test.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_paths.h" |
| #include "extensions/common/switches.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "third_party/zlib/google/zip.h" |
| |
| namespace extensions { |
| |
| class MockSandboxedUnpackerClient : public SandboxedUnpackerClient { |
| public: |
| void WaitForUnpack() { |
| scoped_refptr<content::MessageLoopRunner> runner = |
| new content::MessageLoopRunner; |
| quit_closure_ = runner->QuitClosure(); |
| runner->Run(); |
| } |
| |
| base::FilePath temp_dir() const { return temp_dir_; } |
| base::string16 unpack_err() const { return error_; } |
| |
| private: |
| ~MockSandboxedUnpackerClient() override {} |
| |
| void OnUnpackSuccess(const base::FilePath& temp_dir, |
| const base::FilePath& extension_root, |
| std::unique_ptr<base::DictionaryValue> original_manifest, |
| const Extension* extension, |
| const SkBitmap& install_icon) override { |
| temp_dir_ = temp_dir; |
| quit_closure_.Run(); |
| } |
| |
| void OnUnpackFailure(const CrxInstallError& error) override { |
| error_ = error.message(); |
| quit_closure_.Run(); |
| } |
| |
| base::string16 error_; |
| base::Closure quit_closure_; |
| base::FilePath temp_dir_; |
| }; |
| |
| class SandboxedUnpackerTest : public ExtensionsTest { |
| public: |
| SandboxedUnpackerTest() |
| : SandboxedUnpackerTest(content::TestBrowserThreadBundle::IO_MAINLOOP) {} |
| |
| SandboxedUnpackerTest(content::TestBrowserThreadBundle::Options options) |
| : ExtensionsTest( |
| std::make_unique<content::TestBrowserThreadBundle>(options)) {} |
| |
| void SetUp() override { |
| ExtensionsTest::SetUp(); |
| ASSERT_TRUE(extensions_dir_.CreateUniqueTempDir()); |
| in_process_utility_thread_helper_.reset( |
| new content::InProcessUtilityThreadHelper); |
| // It will delete itself. |
| client_ = new MockSandboxedUnpackerClient; |
| |
| sandboxed_unpacker_ = new SandboxedUnpacker( |
| Manifest::INTERNAL, Extension::NO_FLAGS, extensions_dir_.GetPath(), |
| base::ThreadTaskRunnerHandle::Get(), client_); |
| } |
| |
| void TearDown() override { |
| // Need to destruct SandboxedUnpacker before the message loop since |
| // it posts a task to it. |
| sandboxed_unpacker_ = nullptr; |
| base::RunLoop().RunUntilIdle(); |
| ExtensionsTest::TearDown(); |
| in_process_utility_thread_helper_.reset(); |
| } |
| |
| base::FilePath GetCrxFullPath(const std::string& crx_name) { |
| base::FilePath full_path; |
| EXPECT_TRUE(PathService::Get(extensions::DIR_TEST_DATA, &full_path)); |
| full_path = full_path.AppendASCII("unpacker").AppendASCII(crx_name); |
| EXPECT_TRUE(base::PathExists(full_path)) << full_path.value(); |
| return full_path; |
| } |
| |
| void SetupUnpacker(const std::string& crx_name, |
| const std::string& package_hash) { |
| base::FilePath crx_path = GetCrxFullPath(crx_name); |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind( |
| &SandboxedUnpacker::StartWithCrx, sandboxed_unpacker_, |
| extensions::CRXFileInfo(std::string(), crx_path, package_hash))); |
| client_->WaitForUnpack(); |
| } |
| |
| void SetupUnpackerWithDirectory(const std::string& crx_name) { |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| base::FilePath crx_path = GetCrxFullPath(crx_name); |
| ASSERT_TRUE(zip::Unzip(crx_path, temp_dir.GetPath())); |
| |
| std::string fake_id = crx_file::id_util::GenerateId(crx_name); |
| std::string fake_public_key; |
| base::Base64Encode(std::string(2048, 'k'), &fake_public_key); |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(&SandboxedUnpacker::StartWithDirectory, sandboxed_unpacker_, |
| fake_id, fake_public_key, temp_dir.Take())); |
| client_->WaitForUnpack(); |
| } |
| |
| void SimulateUtilityProcessCrash() { |
| sandboxed_unpacker_->CreateTempDirectory(); |
| |
| content::BrowserThread::PostTask( |
| content::BrowserThread::IO, FROM_HERE, |
| base::Bind(&SandboxedUnpacker::StartUtilityProcessIfNeeded, |
| sandboxed_unpacker_)); |
| |
| content::BrowserThread::PostTask( |
| content::BrowserThread::IO, FROM_HERE, |
| base::Bind(&SandboxedUnpacker::UtilityProcessCrashed, |
| sandboxed_unpacker_)); |
| } |
| |
| base::FilePath GetInstallPath() { |
| return client_->temp_dir().AppendASCII(kTempExtensionName); |
| } |
| |
| base::string16 GetInstallError() { return client_->unpack_err(); } |
| |
| protected: |
| base::ScopedTempDir extensions_dir_; |
| MockSandboxedUnpackerClient* client_; |
| scoped_refptr<SandboxedUnpacker> sandboxed_unpacker_; |
| std::unique_ptr<content::InProcessUtilityThreadHelper> |
| in_process_utility_thread_helper_; |
| }; |
| |
| TEST_F(SandboxedUnpackerTest, NoCatalogsSuccess) { |
| SetupUnpacker("no_l10n.crx", ""); |
| // Check that there is no _locales folder. |
| base::FilePath install_path = GetInstallPath().Append(kLocaleFolder); |
| EXPECT_FALSE(base::PathExists(install_path)); |
| } |
| |
| TEST_F(SandboxedUnpackerTest, FromDirNoCatalogsSuccess) { |
| SetupUnpackerWithDirectory("no_l10n.crx"); |
| // Check that there is no _locales folder. |
| base::FilePath install_path = GetInstallPath().Append(kLocaleFolder); |
| EXPECT_FALSE(base::PathExists(install_path)); |
| } |
| |
| TEST_F(SandboxedUnpackerTest, WithCatalogsSuccess) { |
| SetupUnpacker("good_l10n.crx", ""); |
| // Check that there is _locales folder. |
| base::FilePath install_path = GetInstallPath().Append(kLocaleFolder); |
| EXPECT_TRUE(base::PathExists(install_path)); |
| } |
| |
| TEST_F(SandboxedUnpackerTest, FromDirWithCatalogsSuccess) { |
| SetupUnpackerWithDirectory("good_l10n.crx"); |
| // Check that there is _locales folder. |
| base::FilePath install_path = GetInstallPath().Append(kLocaleFolder); |
| EXPECT_TRUE(base::PathExists(install_path)); |
| } |
| |
| TEST_F(SandboxedUnpackerTest, FailHashCheck) { |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| extensions::switches::kEnableCrxHashCheck); |
| SetupUnpacker("good_l10n.crx", std::string(64, '0')); |
| // Check that there is an error message. |
| EXPECT_NE(base::string16(), GetInstallError()); |
| } |
| |
| TEST_F(SandboxedUnpackerTest, PassHashCheck) { |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| extensions::switches::kEnableCrxHashCheck); |
| SetupUnpacker( |
| "good_l10n.crx", |
| "6fa171c726373785aa4fcd2df448c3db0420a95d5044fbee831f089b979c4068"); |
| // Check that there is no error message. |
| EXPECT_EQ(base::string16(), GetInstallError()); |
| } |
| |
| TEST_F(SandboxedUnpackerTest, SkipHashCheck) { |
| SetupUnpacker("good_l10n.crx", "badhash"); |
| // Check that there is no error message. |
| EXPECT_EQ(base::string16(), GetInstallError()); |
| } |
| |
| class SandboxedUnpackerTestWithRealIOThread : public SandboxedUnpackerTest { |
| public: |
| SandboxedUnpackerTestWithRealIOThread() |
| : SandboxedUnpackerTest( |
| content::TestBrowserThreadBundle::REAL_IO_THREAD) {} |
| |
| void TearDown() override { |
| // The utility process task could still be running. Ensure it is fully |
| // finished before ending the test. |
| content::RunAllPendingInMessageLoop(content::BrowserThread::IO); |
| SandboxedUnpackerTest::TearDown(); |
| } |
| }; |
| |
| TEST_F(SandboxedUnpackerTestWithRealIOThread, UtilityProcessCrash) { |
| SimulateUtilityProcessCrash(); |
| client_->WaitForUnpack(); |
| // Check that there is an error message. |
| EXPECT_NE(base::string16(), GetInstallError()); |
| } |
| |
| } // namespace extensions |