// Copyright (c) 2009 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/crx_installer.h" #include "app/l10n_util.h" #include "base/file_util.h" #include "base/scoped_temp_dir.h" #include "base/string_util.h" #include "base/task.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/extension_file_util.h" #include "chrome/common/extensions/extension_error_reporter.h" #include "grit/chromium_strings.h" namespace { // Helper function to delete files. This is used to avoid ugly casts which // would be necessary with PostMessage since file_util::Delete is overloaded. static void DeleteFileHelper(const FilePath& path, bool recursive) { file_util::Delete(path, recursive); } } void CrxInstaller::Start(const FilePath& crx_path, const FilePath& install_directory, Extension::Location install_source, const std::string& expected_id, bool delete_crx, MessageLoop* file_loop, ExtensionsService* frontend, CrxInstallerClient* client) { // Note: We don't keep a reference because this object manages its own // lifetime. new CrxInstaller(crx_path, install_directory, install_source, expected_id, delete_crx, file_loop, frontend, client); } CrxInstaller::CrxInstaller(const FilePath& crx_path, const FilePath& install_directory, Extension::Location install_source, const std::string& expected_id, bool delete_crx, MessageLoop* file_loop, ExtensionsService* frontend, CrxInstallerClient* client) : crx_path_(crx_path), install_directory_(install_directory), install_source_(install_source), expected_id_(expected_id), delete_crx_(delete_crx), file_loop_(file_loop), ui_loop_(MessageLoop::current()), frontend_(frontend), client_(client) { extensions_enabled_ = frontend_->extensions_enabled(); unpacker_ = new SandboxedExtensionUnpacker( crx_path, g_browser_process->resource_dispatcher_host(), this); file_loop->PostTask(FROM_HERE, NewRunnableMethod(unpacker_, &SandboxedExtensionUnpacker::Start)); } CrxInstaller::~CrxInstaller() { // Delete the temp directory and crx file as necessary. Note that the // destructor might be called on any thread, so we post a task to the file // thread to make sure the delete happens there. if (!temp_dir_.value().empty()) { file_loop_->PostTask(FROM_HERE, NewRunnableFunction(&DeleteFileHelper, temp_dir_, true)); // recursive delete } if (delete_crx_) { file_loop_->PostTask(FROM_HERE, NewRunnableFunction(&DeleteFileHelper, crx_path_, false)); // non-recursive delete } } void CrxInstaller::OnUnpackFailure(const std::string& error_message) { DCHECK(MessageLoop::current() == file_loop_); ReportFailureFromFileThread(error_message); } void CrxInstaller::OnUnpackSuccess(const FilePath& temp_dir, const FilePath& extension_dir, Extension* extension) { DCHECK(MessageLoop::current() == file_loop_); // Note: We take ownership of |extension| and |temp_dir|. extension_.reset(extension); temp_dir_ = temp_dir; // The unpack dir we don't have to delete explicity since it is a child of // the temp dir. unpacked_extension_root_ = extension_dir; DCHECK(file_util::ContainsPath(temp_dir_, unpacked_extension_root_)); // Determine whether to allow installation. We always allow themes and // external installs. if (!extensions_enabled_ && !extension->IsTheme() && !Extension::IsExternalLocation(install_source_)) { ReportFailureFromFileThread("Extensions are not enabled."); return; } // Make sure the expected id matches. // TODO(aa): Also support expected version? if (!expected_id_.empty() && expected_id_ != extension->id()) { ReportFailureFromFileThread(StringPrintf( "ID in new extension manifest (%s) does not match expected id (%s)", extension->id().c_str(), expected_id_.c_str())); return; } if (client_.get()) { ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &CrxInstaller::ConfirmInstall)); } else { CompleteInstall(); } } void CrxInstaller::ConfirmInstall() { if (!client_->ConfirmInstall(extension_.get())) { // We're done. Since we don't post any more tasks to ourselves, our ref // count should go to zero and we die. The destructor will clean up the temp // dir. return; } file_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &CrxInstaller::CompleteInstall)); } void CrxInstaller::CompleteInstall() { DCHECK(MessageLoop::current() == file_loop_); FilePath version_dir; Extension::InstallType install_type = Extension::INSTALL_ERROR; std::string error_msg; if (!extension_file_util::InstallExtension(unpacked_extension_root_, install_directory_, extension_->id(), extension_->VersionString(), &version_dir, &install_type, &error_msg)) { ReportFailureFromFileThread(error_msg); return; } if (install_type == Extension::DOWNGRADE) { ReportFailureFromFileThread("Attempted to downgrade extension."); return; } extension_->set_path(version_dir); extension_->set_location(install_source_); if (install_type == Extension::REINSTALL) { // We use this as a signal to switch themes. ReportOverinstallFromFileThread(); return; } ReportSuccessFromFileThread(); } void CrxInstaller::ReportFailureFromFileThread(const std::string& error) { DCHECK(MessageLoop::current() == file_loop_); ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &CrxInstaller::ReportFailureFromUIThread, error)); } void CrxInstaller::ReportFailureFromUIThread(const std::string& error) { DCHECK(MessageLoop::current() == ui_loop_); // This isn't really necessary, it is only used because unit tests expect to // see errors get reported via this interface. // // TODO(aa): Need to go through unit tests and clean them up too, probably get // rid of this line. ExtensionErrorReporter::GetInstance()->ReportError(error, false); // quiet if (client_) client_->OnInstallFailure(error); } void CrxInstaller::ReportOverinstallFromFileThread() { DCHECK(MessageLoop::current() == file_loop_); ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &CrxInstaller::ReportOverinstallFromUIThread)); } void CrxInstaller::ReportOverinstallFromUIThread() { DCHECK(MessageLoop::current() == ui_loop_); if (client_.get()) client_->OnOverinstallAttempted(extension_.get()); frontend_->OnExtensionOverinstallAttempted(extension_->id()); } void CrxInstaller::ReportSuccessFromFileThread() { DCHECK(MessageLoop::current() == file_loop_); ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &CrxInstaller::ReportSuccessFromUIThread)); } void CrxInstaller::ReportSuccessFromUIThread() { DCHECK(MessageLoop::current() == ui_loop_); // If there is a client, tell the client about installation. if (client_.get()) client_->OnInstallSuccess(extension_.get()); // Tell the frontend about the installation and hand off ownership of // extension_ to it. frontend_->OnExtensionInstalled(extension_.release()); // We're done. We don't post any more tasks to ourselves so we are deleted // soon. }