Commit b6f2cc9c authored by Jay Civelli's avatar Jay Civelli Committed by Commit Bot

Changing ZipFileInstaller to use the Unzip service.

As part of the effort to deprecate UtilityProcessHost, changing
extensions::UnzipInstaller to use the new Unzip service instead of
UtilityProcessMojoClient.

This remove the last use of a utility process for extensions, and as a
result the extensions/utility directory can be removed.

Tbr: tsepez@chromium.org,finnur@chromium.org
Bug: 799220
Change-Id: Ibe4c7f0c16909c99ed572822718ac56a6bcb57fa
Reviewed-on: https://chromium-review.googlesource.com/937902Reviewed-by: default avatarJay Civelli <jcivelli@chromium.org>
Reviewed-by: default avatarJohn Budorick <jbudorick@chromium.org>
Reviewed-by: default avatarKen Rockot <rockot@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Reviewed-by: default avatarJoshua Pawlicki <waffles@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Commit-Queue: Jay Civelli <jcivelli@chromium.org>
Cr-Commit-Position: refs/heads/master@{#542303}
parent 242098c2
......@@ -113,7 +113,6 @@ check_targets = [
"//extensions/renderer:unit_tests",
"//extensions/shell:browser_tests",
"//extensions/shell:unit_tests",
"//extensions/utility:unit_tests",
"//gin/*",
"//google_apis/*",
"//google_update/*",
......
......@@ -853,6 +853,7 @@ static_library("extensions") {
"//components/sync_sessions",
"//components/translate/core/browser",
"//components/undo",
"//components/unzip_service/public/cpp",
"//components/update_client",
"//components/url_matcher",
"//components/user_prefs",
......
......@@ -37,6 +37,9 @@ specific_include_rules = {
# TODO(mash): Remove. http://crbug.com/678705
"+ash/shell.h",
],
"zipfile_installer_unittest.cc": [
"+services/data_decoder",
],
"test_extension_system.cc": [
"+services/data_decoder",
],
......
......@@ -2,8 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
......@@ -19,12 +22,16 @@
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/testing_profile.h"
#include "components/unzip_service/unzip_service.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_observer.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "services/data_decoder/data_decoder_service.h"
#include "services/service_manager/public/cpp/connector.h"
#include "services/service_manager/public/cpp/test/test_connector_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_CHROMEOS)
......@@ -81,12 +88,27 @@ struct MockExtensionRegistryObserver : public ExtensionRegistryObserver {
base::Closure quit_closure;
};
struct UnzipFileFilterTestCase {
const base::FilePath::CharType* input;
const bool should_unzip;
};
} // namespace
class ZipFileInstallerTest : public testing::Test {
public:
ZipFileInstallerTest()
: browser_threads_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
: browser_threads_(content::TestBrowserThreadBundle::IO_MAINLOOP) {
service_manager::TestConnectorFactory::NameToServiceMap services;
services.insert(std::make_pair("data_decoder",
data_decoder::DataDecoderService::Create()));
services.insert(
std::make_pair("unzip_service", unzip::UnzipService::CreateService()));
test_connector_factory_ =
service_manager::TestConnectorFactory::CreateForServices(
std::move(services));
connector_ = test_connector_factory_->CreateConnector();
}
void SetUp() override {
extensions::LoadErrorReporter::Init(/*enable_noisy_errors=*/false);
......@@ -121,8 +143,8 @@ class ZipFileInstallerTest : public testing::Test {
.AppendASCII("zipfile_installer")
.AppendASCII(zip_name);
ASSERT_TRUE(base::PathExists(original_path)) << original_path.value();
zipfile_installer_ = ZipFileInstaller::Create(
connector_.get(),
MakeRegisterInExtensionServiceCallback(extension_service_));
base::ThreadTaskRunnerHandle::Get()->PostTask(
......@@ -131,6 +153,17 @@ class ZipFileInstallerTest : public testing::Test {
observer_.WaitForInstall(expect_error);
}
void RunZipFileFilterTest(
const std::vector<UnzipFileFilterTestCase>& cases,
base::RepeatingCallback<bool(const base::FilePath&)>& filter) {
for (size_t i = 0; i < cases.size(); ++i) {
base::FilePath input(cases[i].input);
bool observed = filter.Run(input);
EXPECT_EQ(cases[i].should_unzip, observed)
<< "i: " << i << ", input: " << input.value();
}
}
protected:
scoped_refptr<ZipFileInstaller> zipfile_installer_;
......@@ -148,6 +181,11 @@ class ZipFileInstallerTest : public testing::Test {
// ChromeOS needs a user manager to instantiate an extension service.
chromeos::ScopedTestUserManager test_user_manager_;
#endif
private:
std::unique_ptr<service_manager::TestConnectorFactory>
test_connector_factory_;
std::unique_ptr<service_manager::Connector> connector_;
};
TEST_F(ZipFileInstallerTest, GoodZip) {
......@@ -165,4 +203,49 @@ TEST_F(ZipFileInstallerTest, ZipWithPublicKey) {
EXPECT_EQ(observer_.last_extension_installed, kIdForPublicKey);
}
TEST_F(ZipFileInstallerTest, NonTheme_FileExtractionFilter) {
const std::vector<UnzipFileFilterTestCase> cases = {
{FILE_PATH_LITERAL("foo"), true},
{FILE_PATH_LITERAL("foo.nexe"), true},
{FILE_PATH_LITERAL("foo.dll"), true},
{FILE_PATH_LITERAL("foo.jpg.exe"), false},
{FILE_PATH_LITERAL("foo.exe"), false},
{FILE_PATH_LITERAL("foo.EXE"), false},
{FILE_PATH_LITERAL("file_without_extension"), true},
};
base::RepeatingCallback<bool(const base::FilePath&)> filter =
base::BindRepeating(&ZipFileInstaller::ShouldExtractFile, false);
RunZipFileFilterTest(cases, filter);
}
TEST_F(ZipFileInstallerTest, Theme_FileExtractionFilter) {
const std::vector<UnzipFileFilterTestCase> cases = {
{FILE_PATH_LITERAL("image.jpg"), true},
{FILE_PATH_LITERAL("IMAGE.JPEG"), true},
{FILE_PATH_LITERAL("test/image.bmp"), true},
{FILE_PATH_LITERAL("test/IMAGE.gif"), true},
{FILE_PATH_LITERAL("test/image.WEBP"), true},
{FILE_PATH_LITERAL("test/dir/file.image.png"), true},
{FILE_PATH_LITERAL("manifest.json"), true},
{FILE_PATH_LITERAL("other.html"), false},
{FILE_PATH_LITERAL("file_without_extension"), true},
};
base::RepeatingCallback<bool(const base::FilePath&)> filter =
base::BindRepeating(&ZipFileInstaller::ShouldExtractFile, true);
RunZipFileFilterTest(cases, filter);
}
TEST_F(ZipFileInstallerTest, ManifestExtractionFilter) {
const std::vector<UnzipFileFilterTestCase> cases = {
{FILE_PATH_LITERAL("manifest.json"), true},
{FILE_PATH_LITERAL("MANIFEST.JSON"), true},
{FILE_PATH_LITERAL("test/manifest.json"), false},
{FILE_PATH_LITERAL("manifest.json/test"), false},
{FILE_PATH_LITERAL("other.file"), false},
};
base::RepeatingCallback<bool(const base::FilePath&)> filter =
base::BindRepeating(&ZipFileInstaller::IsManifestFile);
RunZipFileFilterTest(cases, filter);
}
} // namespace extensions
......@@ -20,6 +20,7 @@
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/common/drop_data.h"
#include "content/public/common/service_manager_connection.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/feature_switch.h"
#include "net/base/filename_util.h"
......@@ -100,6 +101,7 @@ void InstallExtensionHandler::HandleInstallMessage(
if (file_display_name_.MatchesExtension(FILE_PATH_LITERAL(".zip"))) {
ZipFileInstaller::Create(
content::ServiceManagerConnection::GetForProcess()->GetConnector(),
MakeRegisterInExtensionServiceCallback(
ExtensionSystem::Get(profile)->extension_service()))
->LoadFromZipFile(file_to_install_);
......
......@@ -116,7 +116,6 @@ static_library("utility") {
"//chrome/common/extensions/api",
"//chrome/services/media_gallery_util:lib",
"//chrome/services/removable_storage_writer:lib",
"//extensions/utility",
]
public_deps += [ "//chrome/common/extensions/api" ]
......
......@@ -29,7 +29,6 @@ include_rules = [
"+content/public/utility",
"+extensions/common",
"+extensions/buildflags",
"+extensions/utility",
"+media",
"+services/network/public",
"+services/network/url_request_context_builder_mojo.h",
......
......@@ -51,7 +51,6 @@
#include "chrome/services/removable_storage_writer/public/mojom/constants.mojom.h"
#include "chrome/services/removable_storage_writer/removable_storage_writer_service.h"
#include "chrome/utility/extensions/extensions_handler.h"
#include "extensions/utility/utility_handler.h"
#if defined(OS_WIN)
#include "chrome/services/wifi_util_win/public/mojom/constants.mojom.h"
#include "chrome/services/wifi_util_win/wifi_util_win_service.h"
......@@ -118,10 +117,6 @@ ChromeContentUtilityClient::ChromeContentUtilityClient()
ChromeContentUtilityClient::~ChromeContentUtilityClient() = default;
void ChromeContentUtilityClient::UtilityThreadStarted() {
#if BUILDFLAG(ENABLE_EXTENSIONS)
extensions::utility_handler::UtilityThreadStarted();
#endif
#if defined(OS_WIN)
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
utility_process_running_elevated_ = command_line->HasSwitch(
......@@ -137,10 +132,6 @@ void ChromeContentUtilityClient::UtilityThreadStarted() {
return;
auto registry = std::make_unique<service_manager::BinderRegistry>();
#if BUILDFLAG(ENABLE_EXTENSIONS)
extensions::utility_handler::ExposeInterfacesToBrowser(
registry.get(), utility_process_running_elevated_);
#endif
// If our process runs with elevated privileges, only add elevated Mojo
// interfaces to the interface registry.
if (!utility_process_running_elevated_) {
......
include_rules = [
"+extensions/buildflags",
"+extensions/common",
"+extensions/utility",
"+extensions/features",
]
......@@ -223,7 +223,6 @@ test("extensions_unittests") {
"//extensions/common:unit_tests",
"//extensions/renderer:unit_tests",
"//extensions/shell:unit_tests",
"//extensions/utility:unit_tests",
"//services/data_decoder:lib",
"//services/service_manager/public/cpp/test:test_support",
"//ui/gl:test_support",
......
......@@ -615,6 +615,8 @@ source_set("unit_tests") {
"//components/pref_registry:pref_registry",
"//components/prefs:test_support",
"//components/sync_preferences:test_support",
"//components/unzip_service:lib",
"//components/unzip_service/public/cpp:test_support",
"//components/update_client",
"//components/url_matcher",
"//components/user_prefs",
......@@ -629,8 +631,10 @@ source_set("unit_tests") {
"//extensions/strings",
"//ipc:test_support",
"//net:test_support",
"//services/data_decoder:lib",
"//services/data_decoder/public/cpp:test_support",
"//services/device/public/mojom",
"//services/service_manager/public/cpp/test:test_support",
"//storage/browser:test_support",
"//third_party/leveldatabase",
"//third_party/zlib/google:zip",
......
......@@ -8,6 +8,7 @@ include_rules = [
"+components/sync",
"+components/sync_preferences",
"+components/update_client",
"+components/unzip_service/public",
"+components/variations",
"+components/version_info",
"+components/web_cache",
......@@ -56,4 +57,8 @@ specific_include_rules = {
"+chrome/test/base/testing_profile.h",
"+chrome/test/base/ui_test_utils.h",
],
"sandboxed_unpacker_unittest.cc": [
"+components/unzip_service",
"+services/data_decoder",
],
}
......@@ -25,15 +25,16 @@
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "components/crx_file/crx_verifier.h"
#include "components/unzip_service/public/cpp/unzip.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/api/declarative_net_request/utils.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "extensions/browser/zipfile_installer.h"
#include "extensions/common/api/declarative_net_request/dnr_manifest_data.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_l10n_util.h"
#include "extensions/common/extension_resource_path_normalizer.h"
#include "extensions/common/extension_unpacker.mojom.h"
#include "extensions/common/extension_utility_types.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/features/feature_channel.h"
......@@ -233,6 +234,9 @@ SandboxedUnpacker::SandboxedUnpacker(
CHECK_GT(location, Manifest::INVALID_LOCATION);
CHECK_LT(location, Manifest::NUM_LOCATIONS);
// The connector should not be bound to any thread yet.
DCHECK(!connector_->IsBound());
// Use a random instance ID to guarantee the connection is to a new data
// decoder service (running in its own process).
data_decoder_identity_ = service_manager::Identity(
......@@ -323,9 +327,20 @@ void SandboxedUnpacker::StartWithCrx(const CRXFileInfo& crx_info) {
PATH_LENGTH_HISTOGRAM("Extensions.SandboxUnpackLinkFreeCrxPathLength",
link_free_crx_path);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&SandboxedUnpacker::Unzip, this, link_free_crx_path));
// Make sure to create the directory where the extension will be unzipped, as
// the unzipper service requires it.
base::FilePath unzipped_dir =
link_free_crx_path.DirName().AppendASCII(kTempExtensionName);
base::File::Error error;
if (!base::CreateDirectoryAndGetError(unzipped_dir, &error)) {
LOG(ERROR) << "Failed to created directory " << unzipped_dir.value()
<< " with error " << error;
ReportFailure(UNZIP_FAILED,
l10n_util::GetStringUTF16(IDS_EXTENSION_PACKAGE_UNZIP_ERROR));
return;
}
Unzip(link_free_crx_path, unzipped_dir);
}
void SandboxedUnpacker::StartWithDirectory(const std::string& extension_id,
......@@ -348,8 +363,8 @@ void SandboxedUnpacker::StartWithDirectory(const std::string& extension_id,
return;
}
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
unpacker_io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SandboxedUnpacker::Unpack, this, extension_root_));
}
......@@ -373,63 +388,23 @@ SandboxedUnpacker::~SandboxedUnpacker() {
unpacker_io_task_runner_->DeleteSoon(FROM_HERE, std::move(connector_));
}
void SandboxedUnpacker::StartUtilityProcessIfNeeded() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (utility_process_mojo_client_)
return;
utility_process_mojo_client_ = std::make_unique<
content::UtilityProcessMojoClient<mojom::ExtensionUnpacker>>(
l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_EXTENSION_UNPACKER_NAME));
utility_process_mojo_client_->set_error_callback(
base::Bind(&SandboxedUnpacker::UtilityProcessCrashed, this));
utility_process_mojo_client_->set_exposed_directory(temp_dir_.GetPath());
utility_process_mojo_client_->Start();
}
void SandboxedUnpacker::UtilityProcessCrashed() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
utility_process_mojo_client_.reset();
unpacker_io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&SandboxedUnpacker::ReportFailure, this,
UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL,
l10n_util::GetStringFUTF16(
IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
ASCIIToUTF16("UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL")) +
ASCIIToUTF16(". ") +
l10n_util::GetStringUTF16(
IDS_EXTENSION_INSTALL_PROCESS_CRASHED)));
}
void SandboxedUnpacker::Unzip(const base::FilePath& crx_path) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
StartUtilityProcessIfNeeded();
void SandboxedUnpacker::Unzip(const base::FilePath& crx_path,
const base::FilePath& unzipped_dir) {
DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
DCHECK(crx_path.DirName() == temp_dir_.GetPath());
base::FilePath unzipped_dir =
crx_path.DirName().AppendASCII(kTempExtensionName);
utility_process_mojo_client_->service()->Unzip(
crx_path, unzipped_dir,
base::BindOnce(&SandboxedUnpacker::UnzipDone, this, unzipped_dir));
ZipFileInstaller::Create(connector_.get(),
base::BindOnce(&SandboxedUnpacker::UnzipDone, this))
->LoadFromZipFileInDir(crx_path, unzipped_dir);
}
void SandboxedUnpacker::UnzipDone(const base::FilePath& directory,
bool success) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
utility_process_mojo_client_.reset();
void SandboxedUnpacker::UnzipDone(const base::FilePath& zip_file,
const base::FilePath& unzip_dir,
const std::string& error) {
DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
if (!success) {
utility_process_mojo_client_.reset();
if (!error.empty()) {
unpacker_io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
......@@ -438,22 +413,18 @@ void SandboxedUnpacker::UnzipDone(const base::FilePath& directory,
return;
}
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&SandboxedUnpacker::Unpack, this, directory));
Unpack(unzip_dir);
}
void SandboxedUnpacker::Unpack(const base::FilePath& directory) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
DCHECK(directory.DirName() == temp_dir_.GetPath());
base::FilePath manifest_path = extension_root_.Append(kManifestFilename);
unpacker_io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&SandboxedUnpacker::ParseJsonFile, this, manifest_path,
base::BindOnce(&SandboxedUnpacker::ReadManifestDone, this)));
ParseJsonFile(manifest_path,
base::BindOnce(&SandboxedUnpacker::ReadManifestDone, this));
}
void SandboxedUnpacker::ReadManifestDone(
......@@ -607,6 +578,12 @@ void SandboxedUnpacker::ImageSanitizationDone(
l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
ASCIIToUTF16("ERROR_SAVING_THEME_IMAGE"));
break;
case ImageSanitizer::Status::kServiceError:
failure_reason = UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL;
error = l10n_util::GetStringFUTF16(
IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
ASCIIToUTF16("ERROR_UTILITY_PROCESS_CRASH"));
break;
default:
NOTREACHED();
break;
......@@ -786,7 +763,6 @@ void SandboxedUnpacker::ReportUnpackingError(base::StringPiece error) {
void SandboxedUnpacker::UnpackExtensionFailed(const base::string16& error) {
DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
ReportFailure(
UNPACKER_CLIENT_FAILED,
l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_ERROR_MESSAGE, error));
......
......@@ -16,7 +16,6 @@
#include "base/optional.h"
#include "base/strings/string_piece.h"
#include "base/time/time.h"
#include "content/public/browser/utility_process_mojo_client.h"
#include "extensions/browser/crx_file_info.h"
#include "extensions/browser/image_sanitizer.h"
#include "extensions/browser/install/crx_install_error.h"
......@@ -40,10 +39,6 @@ class Connector;
namespace extensions {
class Extension;
namespace mojom {
class ExtensionUnpacker;
}
class SandboxedUnpackerClient
: public base::RefCountedDeleteOnSequence<SandboxedUnpackerClient> {
public:
......@@ -85,9 +80,8 @@ class SandboxedUnpackerClient
// SandboxedUnpacker does work to optionally unpack and then validate/sanitize
// an extension, either starting from a crx file, or else an already unzipped
// directory (eg., from a differential update). This is done in a sandboxed
// subprocess to protect the browser process from parsing complex data formats
// like JPEG or JSON from untrusted sources.
// directory (eg., from a differential update). The parsing of complex data
// formats like JPEG or JSON is performed in specific, sandboxed services.
//
// Unpacking an extension using this class makes changes to its source, such as
// transcoding all images to PNG, parsing all message catalogs, and rewriting
......@@ -96,8 +90,7 @@ class SandboxedUnpackerClient
//
// Lifetime management:
//
// This class is ref-counted by each call it makes to itself on another thread,
// and by UtilityProcessMojoClient.
// This class is ref-counted by each call it makes to itself on another thread.
//
// Additionally, we hold a reference to our own client so that the client lives
// long enough to receive the result of unpacking.
......@@ -229,15 +222,12 @@ class SandboxedUnpacker : public base::RefCountedThreadSafe<SandboxedUnpacker> {
bool ValidateSignature(const base::FilePath& crx_path,
const std::string& expected_hash);
// Ensures the utility process is created.
void StartUtilityProcessIfNeeded();
// Utility process crashed or failed while trying to install.
void UtilityProcessCrashed();
// Unzips the extension into directory.
void Unzip(const base::FilePath& crx_path);
void UnzipDone(const base::FilePath& directory, bool success);
void Unzip(const base::FilePath& crx_path,
const base::FilePath& unzipped_dir);
void UnzipDone(const base::FilePath& zip_file,
const base::FilePath& unzip_dir,
const std::string& error);
// Unpacks the extension in directory and returns the manifest.
void Unpack(const base::FilePath& directory);
......@@ -342,10 +332,6 @@ class SandboxedUnpacker : public base::RefCountedThreadSafe<SandboxedUnpacker> {
// Sequenced task runner where file I/O operations will be performed.
scoped_refptr<base::SequencedTaskRunner> unpacker_io_task_runner_;
// Utility client used for sending tasks to the utility process.
std::unique_ptr<content::UtilityProcessMojoClient<mojom::ExtensionUnpacker>>
utility_process_mojo_client_;
// The normalized path of the install icon path, retrieved from the manifest.
base::FilePath install_icon_path_;
......
......@@ -17,6 +17,8 @@
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "components/crx_file/id_util.h"
#include "components/unzip_service/public/cpp/test_unzip_service.h"
#include "components/unzip_service/unzip_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
......@@ -28,8 +30,10 @@
#include "extensions/common/switches.h"
#include "extensions/strings/grit/extensions_strings.h"
#include "extensions/test/test_extensions_client.h"
#include "services/data_decoder/data_decoder_service.h"
#include "services/data_decoder/public/cpp/test_data_decoder_service.h"
#include "services/service_manager/public/cpp/connector.h"
#include "services/service_manager/public/cpp/test/test_connector_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/zlib/google/zip.h"
......@@ -128,10 +132,30 @@ class SandboxedUnpackerTest : public ExtensionsTest {
// It will delete itself.
client_ = new MockSandboxedUnpackerClient;
sandboxed_unpacker_ = new SandboxedUnpacker(
test_data_decoder_service_.connector()->Clone(), Manifest::INTERNAL,
Extension::NO_FLAGS, extensions_dir_.GetPath(),
base::ThreadTaskRunnerHandle::Get(), client_);
InitSanboxedUnpacker(/*data_decode_service=*/nullptr,
/*unzip_service=*/nullptr);
}
void InitSanboxedUnpacker(
std::unique_ptr<service_manager::Service> data_decode_service,
std::unique_ptr<service_manager::Service> unzip_service) {
service_manager::TestConnectorFactory::NameToServiceMap services;
if (!data_decode_service)
data_decode_service = data_decoder::DataDecoderService::Create();
if (!unzip_service)
unzip_service = unzip::UnzipService::CreateService();
services.insert(
std::make_pair("data_decoder", std::move(data_decode_service)));
services.insert(std::make_pair("unzip_service", std::move(unzip_service)));
test_connector_factory_ =
service_manager::TestConnectorFactory::CreateForServices(
std::move(services));
connector_ = test_connector_factory_->CreateConnector();
sandboxed_unpacker_ =
new SandboxedUnpacker(connector_->Clone(), Manifest::INTERNAL,
Extension::NO_FLAGS, extensions_dir_.GetPath(),
base::ThreadTaskRunnerHandle::Get(), client_);
}
void TearDown() override {
......@@ -178,20 +202,6 @@ class SandboxedUnpackerTest : public ExtensionsTest {
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_));
}
bool InstallSucceeded() const { return !client_->temp_dir().empty(); }
base::FilePath GetInstallPath() const {
......@@ -222,12 +232,14 @@ class SandboxedUnpackerTest : public ExtensionsTest {
}
protected:
data_decoder::TestDataDecoderService test_data_decoder_service_;
base::ScopedTempDir extensions_dir_;
MockSandboxedUnpackerClient* client_;
scoped_refptr<SandboxedUnpacker> sandboxed_unpacker_;
std::unique_ptr<content::InProcessUtilityThreadHelper>
in_process_utility_thread_helper_;
std::unique_ptr<service_manager::TestConnectorFactory>
test_connector_factory_;
std::unique_ptr<service_manager::Connector> connector_;
};
TEST_F(SandboxedUnpackerTest, EmptyDefaultLocale) {
......@@ -353,6 +365,34 @@ TEST_F(SandboxedUnpackerTest, SkipHashCheck) {
EXPECT_EQ(base::string16(), GetInstallError());
}
// The following tests simulate the utility services failling.
TEST_F(SandboxedUnpackerTest, UnzipperServiceFails) {
InitSanboxedUnpacker(
/*data_decoder_service=*/nullptr,
std::make_unique<unzip::CrashyUnzipService>());
SetupUnpacker("good_package.crx", "");
EXPECT_FALSE(InstallSucceeded());
EXPECT_FALSE(GetInstallError().empty());
}
TEST_F(SandboxedUnpackerTest, JsonParserFails) {
InitSanboxedUnpacker(std::make_unique<data_decoder::CrashyDataDecoderService>(
/*crash_json=*/true, /*crash_image=*/false),
/*unzip_service=*/nullptr);
SetupUnpacker("good_package.crx", "");
EXPECT_FALSE(InstallSucceeded());
EXPECT_FALSE(GetInstallError().empty());
}
TEST_F(SandboxedUnpackerTest, ImageDecoderFails) {
InitSanboxedUnpacker(std::make_unique<data_decoder::CrashyDataDecoderService>(
/*crash_json=*/false, /*crash_image=*/true),
/*unzip_service=*/nullptr);
SetupUnpacker("good_package.crx", "");
EXPECT_FALSE(InstallSucceeded());
EXPECT_FALSE(GetInstallError().empty());
}
// SandboxedUnpacker is ref counted and is reference by callbacks and
// InterfacePtrs. This tests that it gets deleted as expected (so that no extra
// refs are left).
......@@ -364,25 +404,4 @@ TEST_F(SandboxedUnpackerTest, DeletedOnFailure) {
TestSandboxedUnpackerDeleted("bad_image.crx", /*expect_success=*/false);
}
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
......@@ -6,19 +6,33 @@
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/task_runner_util.h"
#include "base/task_scheduler/post_task.h"
#include "components/unzip_service/public/cpp/unzip.h"
#include "components/unzip_service/public/interfaces/unzipper.mojom.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "extensions/common/extension_unpacker.mojom.h"
#include "extensions/common/constants.h"
#include "extensions/common/manifest.h"
#include "extensions/strings/grit/extensions_strings.h"
#include "services/data_decoder/public/cpp/safe_json_parser.h"
#include "services/service_manager/public/cpp/connector.h"
#include "ui/base/l10n/l10n_util.h"
namespace extensions {
namespace {
const char kExtensionHandlerTempDirError[] =
constexpr char kExtensionHandlerTempDirError[] =
"Could not create temporary directory for zipped extension.";
const char kExtensionHandlerFileUnzipError[] =
constexpr char kExtensionHandlerFileUnzipError[] =
"Could not unzip extension for install.";
constexpr const base::FilePath::CharType* kAllowedThemeFiletypes[] = {
FILE_PATH_LITERAL(".bmp"), FILE_PATH_LITERAL(".gif"),
FILE_PATH_LITERAL(".jpeg"), FILE_PATH_LITERAL(".jpg"),
FILE_PATH_LITERAL(".json"), FILE_PATH_LITERAL(".png"),
FILE_PATH_LITERAL(".webp")};
base::Optional<base::FilePath> PrepareAndGetUnzipDir(
const base::FilePath& zip_file) {
base::AssertBlockingAllowed();
......@@ -36,31 +50,59 @@ base::Optional<base::FilePath> PrepareAndGetUnzipDir(
return unzip_dir;
}
} // namespace
base::Optional<std::string> ReadFileContent(const base::FilePath& path) {
base::AssertBlockingAllowed();
namespace extensions {
std::string content;
return base::ReadFileToString(path, &content) ? content
: base::Optional<std::string>();
}
} // namespace
// static
scoped_refptr<ZipFileInstaller> ZipFileInstaller::Create(
service_manager::Connector* connector,
DoneCallback done_callback) {
DCHECK(connector);
DCHECK(done_callback);
return base::WrapRefCounted(new ZipFileInstaller(std::move(done_callback)));
return base::WrapRefCounted(
new ZipFileInstaller(connector, std::move(done_callback)));
}
void ZipFileInstaller::LoadFromZipFile(const base::FilePath& zip_file) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
LoadFromZipFileImpl(zip_file, base::FilePath());
}
void ZipFileInstaller::LoadFromZipFileInDir(const base::FilePath& zip_file,
const base::FilePath& unzip_dir) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!unzip_dir.empty());
LoadFromZipFileImpl(zip_file, unzip_dir);
}
void ZipFileInstaller::LoadFromZipFileImpl(const base::FilePath& zip_file,
const base::FilePath& unzip_dir) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!zip_file.empty());
zip_file_ = zip_file;
if (!unzip_dir.empty()) {
Unzip(unzip_dir);
return;
}
base::PostTaskAndReplyWithResult(
GetExtensionFileTaskRunner().get(), FROM_HERE,
base::BindOnce(&PrepareAndGetUnzipDir, zip_file),
base::BindOnce(&ZipFileInstaller::Unzip, this));
}
ZipFileInstaller::ZipFileInstaller(DoneCallback done_callback)
: done_callback_(std::move(done_callback)) {}
ZipFileInstaller::ZipFileInstaller(service_manager::Connector* connector,
DoneCallback done_callback)
: done_callback_(std::move(done_callback)), connector_(connector) {}
ZipFileInstaller::~ZipFileInstaller() = default;
......@@ -71,29 +113,76 @@ void ZipFileInstaller::Unzip(base::Optional<base::FilePath> unzip_dir) {
ReportFailure(std::string(kExtensionHandlerTempDirError));
return;
}
DCHECK(!utility_process_mojo_client_);
utility_process_mojo_client_ = std::make_unique<
content::UtilityProcessMojoClient<mojom::ExtensionUnpacker>>(
l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_ZIP_FILE_INSTALLER_NAME));
utility_process_mojo_client_->set_error_callback(
base::Bind(&ZipFileInstaller::UnzipDone, this, *unzip_dir, false));
unzip::UnzipWithFilter(
connector_->Clone(), zip_file_, *unzip_dir,
base::BindRepeating(&ZipFileInstaller::IsManifestFile),
base::BindOnce(&ZipFileInstaller::ManifestUnzipped, this, *unzip_dir));
}
void ZipFileInstaller::ManifestUnzipped(const base::FilePath& unzip_dir,
bool success) {
if (!success) {
ReportFailure(kExtensionHandlerFileUnzipError);
return;
}
base::PostTaskAndReplyWithResult(
GetExtensionFileTaskRunner().get(), FROM_HERE,
base::BindOnce(&ReadFileContent, unzip_dir.Append(kManifestFilename)),
base::BindOnce(&ZipFileInstaller::ManifestRead, this, unzip_dir));
}
void ZipFileInstaller::ManifestRead(
const base::FilePath& unzip_dir,
base::Optional<std::string> manifest_content) {
if (!manifest_content) {
ReportFailure(std::string(kExtensionHandlerFileUnzipError));
return;
}
data_decoder::SafeJsonParser::Parse(
connector_, *manifest_content,
base::Bind(&ZipFileInstaller::ManifestParsed, this, unzip_dir),
base::Bind(&ZipFileInstaller::ManifestParsingFailed, this));
}
utility_process_mojo_client_->set_exposed_directory(*unzip_dir);
void ZipFileInstaller::ManifestParsingFailed(const std::string& error) {
ReportFailure(std::string(kExtensionHandlerFileUnzipError));
}
utility_process_mojo_client_->Start();
void ZipFileInstaller::ManifestParsed(
const base::FilePath& unzip_dir,
std::unique_ptr<base::Value> manifest_value) {
std::unique_ptr<base::DictionaryValue> manifest_dictionary =
base::DictionaryValue::From(std::move(manifest_value));
if (!manifest_dictionary) {
ReportFailure(std::string(kExtensionHandlerFileUnzipError));
return;
}
utility_process_mojo_client_->service()->Unzip(
zip_file_, *unzip_dir,
base::BindOnce(&ZipFileInstaller::UnzipDone, this, *unzip_dir));
Manifest manifest(Manifest::INTERNAL, std::move(manifest_dictionary));
unzip::UnzipFilterCallback filter = base::BindRepeating(
[](bool is_theme, const base::FilePath& file_path) -> bool {
// Note that we ignore the manifest as it has already been extracted and
// would cause the unzipping to fail.
return ZipFileInstaller::ShouldExtractFile(is_theme, file_path) &&
!ZipFileInstaller::IsManifestFile(file_path);
},
manifest.is_theme());
// TODO(crbug.com/645263): This silently ignores blocked file types.
// Add install warnings.
unzip::UnzipWithFilter(
connector_->Clone(), zip_file_, unzip_dir, filter,
base::BindOnce(&ZipFileInstaller::UnzipDone, this, unzip_dir));
}
void ZipFileInstaller::UnzipDone(const base::FilePath& unzip_dir,
bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
utility_process_mojo_client_.reset();
if (!success) {
ReportFailure(kExtensionHandlerFileUnzipError);
return;
......@@ -108,4 +197,26 @@ void ZipFileInstaller::ReportFailure(const std::string& error) {
std::move(done_callback_).Run(zip_file_, base::FilePath(), error);
}
// static
bool ZipFileInstaller::ShouldExtractFile(bool is_theme,
const base::FilePath& file_path) {
if (is_theme) {
const base::FilePath::StringType extension =
base::ToLowerASCII(file_path.FinalExtension());
// Allow filenames with no extension.
if (extension.empty())
return true;
return base::ContainsValue(kAllowedThemeFiletypes, extension);
}
return !base::FilePath::CompareEqualIgnoreCase(file_path.FinalExtension(),
FILE_PATH_LITERAL(".exe"));
}
// static
bool ZipFileInstaller::IsManifestFile(const base::FilePath& file_path) {
CHECK(!file_path.IsAbsolute());
return base::FilePath::CompareEqualIgnoreCase(file_path.value(),
kManifestFilename);
}
} // namespace extensions
......@@ -8,20 +8,24 @@
#include <memory>
#include <string>
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/sequence_checker.h"
#include "content/public/browser/utility_process_mojo_client.h"
#include "base/values.h"
namespace extensions {
namespace mojom {
class ExtensionUnpacker;
namespace service_manager {
class Connector;
}
// ZipFileInstaller unzips an extension in a utility process.
namespace extensions {
// ZipFileInstaller unzips an extension safely using the Unzipper and
// SafeJSONParser services.
// This class is not thread-safe: it is bound to the sequence it is created on.
class ZipFileInstaller : public base::RefCountedThreadSafe<ZipFileInstaller> {
public:
......@@ -34,18 +38,38 @@ class ZipFileInstaller : public base::RefCountedThreadSafe<ZipFileInstaller> {
const std::string& error)>;
// Creates a ZipFileInstaller that invokes |done_callback| when done.
static scoped_refptr<ZipFileInstaller> Create(DoneCallback done_callback);
static scoped_refptr<ZipFileInstaller> Create(
service_manager::Connector* connector,
DoneCallback done_callback);
// Creates a temporary directory and unzips the extension in it.
void LoadFromZipFile(const base::FilePath& zip_file);
// Unzips the extension in |unzip_dir|.
void LoadFromZipFileInDir(const base::FilePath& zip_file,
const base::FilePath& unzip_dir);
private:
friend class base::RefCountedThreadSafe<ZipFileInstaller>;
FRIEND_TEST_ALL_PREFIXES(ZipFileInstallerTest, NonTheme_FileExtractionFilter);
FRIEND_TEST_ALL_PREFIXES(ZipFileInstallerTest, Theme_FileExtractionFilter);
FRIEND_TEST_ALL_PREFIXES(ZipFileInstallerTest, ManifestExtractionFilter);
explicit ZipFileInstaller(DoneCallback done_callback);
ZipFileInstaller(service_manager::Connector* connector,
DoneCallback done_callback);
~ZipFileInstaller();
void LoadFromZipFileImpl(const base::FilePath& zip_file,
const base::FilePath& unzip_dir);
// Unzip an extension into |unzip_dir| and load it with an UnpackedInstaller.
void Unzip(base::Optional<base::FilePath> unzip_dir);
void ManifestUnzipped(const base::FilePath& unzip_dir, bool success);
void ManifestRead(const base::FilePath& unzip_dir,
base::Optional<std::string> manifest_content);
void ManifestParsingFailed(const std::string& error);
void ManifestParsed(const base::FilePath& unzip_dir,
std::unique_ptr<base::Value> manifest);
void UnzipDone(const base::FilePath& unzip_dir, bool success);
// On failure, report the |error| reason.
......@@ -54,12 +78,18 @@ class ZipFileInstaller : public base::RefCountedThreadSafe<ZipFileInstaller> {
// Callback invoked when unzipping has finished.
DoneCallback done_callback_;
// Whether a file should be extracted as part of installing an
// extension/theme. Protects against unused or potentially hamrful files.
static bool ShouldExtractFile(bool is_theme, const base::FilePath& file_path);
// Returns true if |file_path| points to an extension manifest.
static bool IsManifestFile(const base::FilePath& file_path);
// File containing the extension to unzip.
base::FilePath zip_file_;
// Utility process used to perform the unzip.
std::unique_ptr<content::UtilityProcessMojoClient<mojom::ExtensionUnpacker>>
utility_process_mojo_client_;
// Connector to the ServiceManager. Bound to the UI thread.
service_manager::Connector* connector_;
SEQUENCE_CHECKER(sequence_checker_);
......
......@@ -23,7 +23,6 @@ source_set("common_constants") {
if (enable_extensions) {
mojom("mojo") {
sources = [
"extension_unpacker.mojom",
"mojo/app_window.mojom",
"mojo/guest_view.mojom",
"mojo/keep_alive.mojom",
......@@ -35,7 +34,6 @@ if (enable_extensions) {
public_deps = [
"//content/public/common:interfaces",
"//mojo/common:common_custom_types",
"//ui/gfx/geometry/mojo",
"//url/mojom:url_mojom_gurl",
]
......
// Copyright 2017 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.
// Safe chrome extension unpacker service provided by the utility process
// and exposed by mojo policy to the chrome browser process.
module extensions.mojom;
import "mojo/common/file_path.mojom";
interface ExtensionUnpacker {
// Unzip |file| into the directory |path|.
Unzip(mojo.common.mojom.FilePath file,
mojo.common.mojom.FilePath path) => (bool success);
};
......@@ -65,7 +65,6 @@ source_set("app_shell_lib") {
"//extensions/shell/common/api",
"//extensions/shell/common/api:api_registration",
"//extensions/shell/common/api:extensions_features",
"//extensions/utility",
"//skia",
"//third_party/WebKit/public:blink",
"//third_party/cld_3/src/src:cld_3",
......@@ -183,8 +182,6 @@ source_set("app_shell_lib") {
"renderer/shell_content_renderer_client.h",
"renderer/shell_extensions_renderer_client.cc",
"renderer/shell_extensions_renderer_client.h",
"utility/shell_content_utility_client.cc",
"utility/shell_content_utility_client.h",
]
if (use_aura) {
......
......@@ -4,5 +4,6 @@ include_rules = [
"+components/nacl",
"+content/public/app",
"+content/public/browser",
"+content/public/utility",
"+sandbox",
]
......@@ -12,13 +12,13 @@
#include "components/nacl/common/buildflags.h"
#include "content/public/browser/browser_main_runner.h"
#include "content/public/common/content_switches.h"
#include "content/public/utility/content_utility_client.h"
#include "content/shell/common/shell_switches.h"
#include "extensions/common/extension_paths.h"
#include "extensions/shell/browser/default_shell_browser_main_delegate.h"
#include "extensions/shell/browser/shell_content_browser_client.h"
#include "extensions/shell/common/shell_content_client.h"
#include "extensions/shell/renderer/shell_content_renderer_client.h"
#include "extensions/shell/utility/shell_content_utility_client.h"
#include "ui/base/resource/resource_bundle.h"
#if defined(OS_CHROMEOS)
......@@ -168,11 +168,6 @@ ShellMainDelegate::CreateContentRendererClient() {
return renderer_client_.get();
}
content::ContentUtilityClient* ShellMainDelegate::CreateContentUtilityClient() {
utility_client_.reset(CreateShellContentUtilityClient());
return utility_client_.get();
}
void ShellMainDelegate::ProcessExiting(const std::string& process_type) {
logging::CloseLogFile();
}
......@@ -202,7 +197,7 @@ ShellMainDelegate::CreateShellContentRendererClient() {
content::ContentUtilityClient*
ShellMainDelegate::CreateShellContentUtilityClient() {
return new ShellContentUtilityClient();
return new content::ContentUtilityClient();
}
void ShellMainDelegate::InitializeResourceBundle() {
......
......@@ -31,7 +31,6 @@ class ShellMainDelegate : public content::ContentMainDelegate {
void PreSandboxStartup() override;
content::ContentBrowserClient* CreateContentBrowserClient() override;
content::ContentRendererClient* CreateContentRendererClient() override;
content::ContentUtilityClient* CreateContentUtilityClient() override;
void ProcessExiting(const std::string& process_type) override;
#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
void ZygoteStarting(std::vector<std::unique_ptr<content::ZygoteForkDelegate>>*
......
include_rules = [
"+content/public/utility",
]
// 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/shell/utility/shell_content_utility_client.h"
namespace extensions {
ShellContentUtilityClient::ShellContentUtilityClient() = default;
ShellContentUtilityClient::~ShellContentUtilityClient() = default;
void ShellContentUtilityClient::UtilityThreadStarted() {
utility_handler::UtilityThreadStarted();
}
} // namespace extensions
// 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.
#ifndef EXTENSIONS_SHELL_UTILITY_SHELL_CONTENT_UTILITY_CLIENT_H_
#define EXTENSIONS_SHELL_UTILITY_SHELL_CONTENT_UTILITY_CLIENT_H_
#include "content/public/utility/content_utility_client.h"
#include "extensions/utility/utility_handler.h"
namespace extensions {
class ShellContentUtilityClient : public content::ContentUtilityClient {
public:
ShellContentUtilityClient();
~ShellContentUtilityClient() override;
// content::ContentUtilityClient:
void UtilityThreadStarted() override;
};
} // namespace extensions
#endif // EXTENSIONS_SHELL_UTILITY_SHELL_CONTENT_UTILITY_CLIENT_H_
......@@ -385,17 +385,6 @@
Shaped windows are not supported.
</message>
</if>
<!-- Utility process names. Please keep alphabetized. -->
<message name="IDS_UTILITY_PROCESS_EXTENSION_UNPACKER_NAME" desc="The name of the utility process used for unpacking extensions.">
Extension Unpacker
</message>
<message name="IDS_UTILITY_PROCESS_MANIFEST_PARSER_NAME" desc="The name of the utility process used for parsing extension manifests.">
Extension Manifest Parser
</message>
<message name="IDS_UTILITY_PROCESS_ZIP_FILE_INSTALLER_NAME" desc="The name of the utility process used for unpacking zip files.">
Zip File Installer
</message>
</messages>
</release>
</grit>
......@@ -17,10 +17,7 @@ TestContentUtilityClient::TestContentUtilityClient() = default;
TestContentUtilityClient::~TestContentUtilityClient() = default;
void TestContentUtilityClient::UtilityThreadStarted() {
utility_handler::UtilityThreadStarted();
auto registry = std::make_unique<service_manager::BinderRegistry>();
utility_handler::ExposeInterfacesToBrowser(registry.get(), false);
content::ChildThread::Get()
->GetServiceManagerConnection()
->AddConnectionFilter(std::make_unique<content::SimpleConnectionFilter>(
......
......@@ -6,7 +6,6 @@
#define EXTENSIONS_TEST_TEST_CONTENT_UTILITY_CLIENT_H_
#include "content/public/utility/content_utility_client.h"
#include "extensions/utility/utility_handler.h"
namespace extensions {
......
# 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.
import("//build/config/features.gni")
import("//extensions/buildflags/buildflags.gni")
assert(enable_extensions)
source_set("utility") {
sources = [
"utility_handler.cc",
"utility_handler.h",
]
# TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
deps = [
"//content/public/common",
"//content/public/utility",
"//extensions/common",
"//skia",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"utility_handler_unittest.cc",
]
deps = [
":utility",
"//base",
"//extensions:test_support",
"//extensions/common",
"//extensions/strings",
"//testing/gtest",
"//third_party/zlib/google:zip",
"//ui/base",
]
}
include_rules = [
"+content/public/utility",
"+content/public/child",
"+net",
"+third_party/zlib/google",
]
// 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/utility/utility_handler.h"
#include <memory>
#include <string>
#include <utility>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_file_value_serializer.h"
#include "base/location.h"
#include "base/task_scheduler/post_task.h"
#include "content/public/utility/utility_thread.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension_l10n_util.h"
#include "extensions/common/extension_unpacker.mojom.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/features/feature_channel.h"
#include "extensions/common/features/feature_session_type.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/strings/grit/extensions_strings.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "third_party/zlib/google/zip.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_switches.h"
namespace extensions {
namespace {
constexpr const base::FilePath::CharType* kAllowedThemeFiletypes[] = {
FILE_PATH_LITERAL(".bmp"), FILE_PATH_LITERAL(".gif"),
FILE_PATH_LITERAL(".jpeg"), FILE_PATH_LITERAL(".jpg"),
FILE_PATH_LITERAL(".json"), FILE_PATH_LITERAL(".png"),
FILE_PATH_LITERAL(".webp")};
std::unique_ptr<base::DictionaryValue> ReadManifest(
const base::FilePath& extension_dir,
std::string* error) {
DCHECK(error);
base::FilePath manifest_path = extension_dir.Append(kManifestFilename);
if (!base::PathExists(manifest_path)) {
*error = manifest_errors::kInvalidManifest;
return nullptr;
}
JSONFileValueDeserializer deserializer(manifest_path);
std::unique_ptr<base::Value> root = deserializer.Deserialize(NULL, error);
if (!root) {
return nullptr;
}
if (!root->is_dict()) {
*error = manifest_errors::kInvalidManifest;
return nullptr;
}
return base::DictionaryValue::From(std::move(root));
}
class ExtensionUnpackerImpl : public extensions::mojom::ExtensionUnpacker {
public:
ExtensionUnpackerImpl() = default;
~ExtensionUnpackerImpl() override = default;
static void Create(extensions::mojom::ExtensionUnpackerRequest request) {
mojo::MakeStrongBinding(std::make_unique<ExtensionUnpackerImpl>(),
std::move(request));
}
private:
// extensions::mojom::ExtensionUnpacker:
void Unzip(const base::FilePath& file,
const base::FilePath& path,
UnzipCallback callback) override {
// Move unzip operation to background thread to avoid blocking the main
// utility thread for extended amont of time. For example, this prevents
// extension unzipping block receipt of the connection complete
// notification for the utility process channel to the browser process,
// which could cause the utility process to terminate itself due to browser
// process being considered unreachable.
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
{base::TaskPriority::USER_BLOCKING, base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&ExtensionUnpackerImpl::UnzipOnBackgroundTaskRunner,
file, path),
std::move(callback));
}
static bool UnzipFileManifestIntoPath(
const base::FilePath& file,
const base::FilePath& path,
std::unique_ptr<base::DictionaryValue>* manifest) {
if (zip::UnzipWithFilterCallback(
file, path, base::BindRepeating(&utility_handler::IsManifestFile),
false)) {
std::string error;
*manifest = ReadManifest(path, &error);
return error.empty() && manifest->get();
}
return false;
}
static bool UnzipFileIntoPath(
const base::FilePath& file,
const base::FilePath& path,
std::unique_ptr<base::DictionaryValue> manifest) {
Manifest internal(Manifest::INTERNAL, std::move(manifest));
// TODO(crbug.com/645263): This silently ignores blocked file types.
// Add install warnings.
return zip::UnzipWithFilterCallback(
file, path,
base::BindRepeating(&utility_handler::ShouldExtractFile,
internal.is_theme()),
true /* log_skipped_files */);
}
// Unzips the extension from |file| to |path|.
// Returns whether the unzip operation succeeded.
static bool UnzipOnBackgroundTaskRunner(const base::FilePath& file,
const base::FilePath& path) {
std::unique_ptr<base::DictionaryValue> manifest;
if (!UnzipFileManifestIntoPath(file, path, &manifest))
return false;
return UnzipFileIntoPath(file, path, std::move(manifest));
}
DISALLOW_COPY_AND_ASSIGN(ExtensionUnpackerImpl);
};
} // namespace
namespace utility_handler {
void UtilityThreadStarted() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
std::string lang = command_line->GetSwitchValueASCII(switches::kLang);
if (!lang.empty())
extension_l10n_util::SetProcessLocale(lang);
}
void ExposeInterfacesToBrowser(service_manager::BinderRegistry* registry,
bool running_elevated) {
// If our process runs with elevated privileges, only add elevated Mojo
// interfaces to the interface registry.
if (running_elevated)
return;
registry->AddInterface(base::Bind(&ExtensionUnpackerImpl::Create),
base::ThreadTaskRunnerHandle::Get());
}
bool ShouldExtractFile(bool is_theme, const base::FilePath& file_path) {
if (is_theme) {
const base::FilePath::StringType extension =
base::ToLowerASCII(file_path.FinalExtension());
// Allow filenames with no extension.
if (extension.empty())
return true;
return base::ContainsValue(kAllowedThemeFiletypes, extension);
}
return !base::FilePath::CompareEqualIgnoreCase(file_path.FinalExtension(),
FILE_PATH_LITERAL(".exe"));
}
bool IsManifestFile(const base::FilePath& file_path) {
CHECK(!file_path.IsAbsolute());
return base::FilePath::CompareEqualIgnoreCase(file_path.value(),
kManifestFilename);
}
} // namespace utility_handler
} // namespace extensions
// 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.
#ifndef EXTENSIONS_UTILITY_UTILITY_HANDLER_H_
#define EXTENSIONS_UTILITY_UTILITY_HANDLER_H_
#include "services/service_manager/public/cpp/binder_registry.h"
namespace base {
class FilePath;
}
namespace extensions {
namespace utility_handler {
void UtilityThreadStarted();
void ExposeInterfacesToBrowser(service_manager::BinderRegistry* registry,
bool running_elevated);
bool ShouldExtractFile(bool is_theme, const base::FilePath& file_path);
bool IsManifestFile(const base::FilePath& file_path);
} // namespace utility_handler
} // namespace extensions
#endif // EXTENSIONS_UTILITY_UTILITY_HANDLER_H_
// 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 <vector>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "extensions/utility/utility_handler.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
using UtilityHandlerTest = testing::Test;
struct UnzipFileFilterTestCase {
const base::FilePath::CharType* input;
const bool should_unzip;
};
void RunZipFileFilterTest(
const std::vector<UnzipFileFilterTestCase>& cases,
base::RepeatingCallback<bool(const base::FilePath&)>& filter) {
for (size_t i = 0; i < cases.size(); ++i) {
base::FilePath input(cases[i].input);
bool observed = filter.Run(input);
EXPECT_EQ(cases[i].should_unzip, observed)
<< "i: " << i << ", input: " << input.value();
}
}
TEST_F(UtilityHandlerTest, NonTheme_FileExtractionFilter) {
const std::vector<UnzipFileFilterTestCase> cases = {
{FILE_PATH_LITERAL("foo"), true},
{FILE_PATH_LITERAL("foo.nexe"), true},
{FILE_PATH_LITERAL("foo.dll"), true},
{FILE_PATH_LITERAL("foo.jpg.exe"), false},
{FILE_PATH_LITERAL("foo.exe"), false},
{FILE_PATH_LITERAL("foo.EXE"), false},
{FILE_PATH_LITERAL("file_without_extension"), true},
};
base::RepeatingCallback<bool(const base::FilePath&)> filter =
base::BindRepeating(&utility_handler::ShouldExtractFile, false);
RunZipFileFilterTest(cases, filter);
}
TEST_F(UtilityHandlerTest, Theme_FileExtractionFilter) {
const std::vector<UnzipFileFilterTestCase> cases = {
{FILE_PATH_LITERAL("image.jpg"), true},
{FILE_PATH_LITERAL("IMAGE.JPEG"), true},
{FILE_PATH_LITERAL("test/image.bmp"), true},
{FILE_PATH_LITERAL("test/IMAGE.gif"), true},
{FILE_PATH_LITERAL("test/image.WEBP"), true},
{FILE_PATH_LITERAL("test/dir/file.image.png"), true},
{FILE_PATH_LITERAL("manifest.json"), true},
{FILE_PATH_LITERAL("other.html"), false},
{FILE_PATH_LITERAL("file_without_extension"), true},
};
base::RepeatingCallback<bool(const base::FilePath&)> filter =
base::BindRepeating(&utility_handler::ShouldExtractFile, true);
RunZipFileFilterTest(cases, filter);
}
TEST_F(UtilityHandlerTest, ManifestExtractionFilter) {
const std::vector<UnzipFileFilterTestCase> cases = {
{FILE_PATH_LITERAL("manifest.json"), true},
{FILE_PATH_LITERAL("MANIFEST.JSON"), true},
{FILE_PATH_LITERAL("test/manifest.json"), false},
{FILE_PATH_LITERAL("manifest.json/test"), false},
{FILE_PATH_LITERAL("other.file"), false},
};
base::RepeatingCallback<bool(const base::FilePath&)> filter =
base::BindRepeating(&utility_handler::IsManifestFile);
RunZipFileFilterTest(cases, filter);
}
} // namespace extensions
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment