blob: e4c5a93f5521e1f13ae26d2d87bd968a11f8e112 [file] [log] [blame]
// 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 "app/resource_bundle.h"
#include "base/file_util.h"
#include "base/scoped_temp_dir.h"
#include "base/task.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/extensions/convert_user_script.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/common/extensions/extension_error_reporter.h"
#include "chrome/common/extensions/extension_file_util.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/notification_type.h"
#include "grit/browser_resources.h"
#include "grit/chromium_strings.h"
#include "third_party/skia/include/core/SkBitmap.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_source,
bool allow_privilege_increase,
ExtensionsService* frontend,
ExtensionInstallUI* client) {
// Note: This object manages its own lifetime.
scoped_refptr<CrxInstaller> installer(
new CrxInstaller(crx_path, install_directory, delete_source, frontend,
client));
installer->install_source_ = install_source;
installer->expected_id_ = expected_id;
installer->allow_privilege_increase_ = allow_privilege_increase;
scoped_refptr<SandboxedExtensionUnpacker> unpacker(
new SandboxedExtensionUnpacker(
installer->source_file_,
g_browser_process->resource_dispatcher_host(),
installer.get()));
ChromeThread::PostTask(
ChromeThread::FILE, FROM_HERE,
NewRunnableMethod(
unpacker.get(), &SandboxedExtensionUnpacker::Start));
}
void CrxInstaller::InstallUserScript(const FilePath& source_file,
const GURL& original_url,
const FilePath& install_directory,
bool delete_source,
ExtensionsService* frontend,
ExtensionInstallUI* client) {
scoped_refptr<CrxInstaller> installer(
new CrxInstaller(source_file, install_directory, delete_source, frontend,
client));
installer->original_url_ = original_url;
ChromeThread::PostTask(
ChromeThread::FILE, FROM_HERE,
NewRunnableMethod(installer.get(),
&CrxInstaller::ConvertUserScriptOnFileThread));
}
CrxInstaller::CrxInstaller(const FilePath& source_file,
const FilePath& install_directory,
bool delete_source,
ExtensionsService* frontend,
ExtensionInstallUI* client)
: source_file_(source_file),
install_directory_(install_directory),
install_source_(Extension::INTERNAL),
delete_source_(delete_source),
allow_privilege_increase_(false),
create_app_shortcut_(false),
frontend_(frontend),
client_(client) {
extensions_enabled_ = frontend_->extensions_enabled();
}
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()) {
ChromeThread::PostTask(
ChromeThread::FILE, FROM_HERE,
NewRunnableFunction(&DeleteFileHelper, temp_dir_, true));
}
if (delete_source_) {
ChromeThread::PostTask(
ChromeThread::FILE, FROM_HERE,
NewRunnableFunction(&DeleteFileHelper, source_file_, false));
}
}
void CrxInstaller::ConvertUserScriptOnFileThread() {
std::string error;
Extension* extension = ConvertUserScriptToExtension(source_file_,
original_url_, &error);
if (!extension) {
ReportFailureFromFileThread(error);
return;
}
OnUnpackSuccess(extension->path(), extension->path(), extension);
}
void CrxInstaller::OnUnpackFailure(const std::string& error_message) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
ReportFailureFromFileThread(error_message);
}
void CrxInstaller::OnUnpackSuccess(const FilePath& temp_dir,
const FilePath& extension_dir,
Extension* extension) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
// 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;
// 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() || extension_->IsApp()) {
Extension::DecodeIcon(extension_.get(), Extension::EXTENSION_ICON_LARGE,
&install_icon_);
}
ChromeThread::PostTask(
ChromeThread::UI, FROM_HERE,
NewRunnableMethod(this, &CrxInstaller::ConfirmInstall));
}
void CrxInstaller::ConfirmInstall() {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
if (frontend_->extension_prefs()->IsExtensionBlacklisted(extension_->id())) {
LOG(INFO) << "This extension: " << extension_->id()
<< " is blacklisted. Install failed.";
ReportFailureFromUIThread("This extension is blacklisted.");
return;
}
current_version_ =
frontend_->extension_prefs()->GetVersionString(extension_->id());
if (client_.get()) {
AddRef(); // Balanced in Proceed() and Abort().
client_->ConfirmInstall(this, extension_.get(), install_icon_.get());
} else {
ChromeThread::PostTask(
ChromeThread::FILE, FROM_HERE,
NewRunnableMethod(this, &CrxInstaller::CompleteInstall));
}
return;
}
void CrxInstaller::InstallUIProceed(bool create_app_shortcut) {
if (create_app_shortcut) {
DCHECK(extension_->IsApp());
create_app_shortcut_ = true;
}
ChromeThread::PostTask(
ChromeThread::FILE, FROM_HERE,
NewRunnableMethod(this, &CrxInstaller::CompleteInstall));
Release(); // balanced in ConfirmInstall().
}
void CrxInstaller::InstallUIAbort() {
// Kill the theme loading bubble.
NotificationService* service = NotificationService::current();
service->Notify(NotificationType::NO_THEME_DETECTED,
Source<CrxInstaller>(this),
NotificationService::NoDetails());
Release(); // balanced in ConfirmInstall().
// We're done. Since we don't post any more tasks to ourself, our ref count
// should go to zero and we die. The destructor will clean up the temp dir.
}
void CrxInstaller::CompleteInstall() {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
FilePath version_dir;
Extension::InstallType install_type =
extension_file_util::CompareToInstalledVersion(
install_directory_, extension_->id(), current_version_,
extension_->VersionString(), &version_dir);
if (install_type == Extension::DOWNGRADE) {
ReportFailureFromFileThread("Attempted to downgrade extension.");
return;
}
if (install_type == Extension::REINSTALL) {
// We use this as a signal to switch themes.
ReportOverinstallFromFileThread();
return;
}
std::string error_msg;
if (!extension_file_util::InstallExtension(unpacked_extension_root_,
version_dir, &error_msg)) {
ReportFailureFromFileThread(error_msg);
return;
}
if (create_app_shortcut_) {
SkBitmap icon = install_icon_.get() ? *install_icon_ :
*ResourceBundle::GetSharedInstance().GetBitmapNamed(
IDR_EXTENSION_DEFAULT_ICON);
ShellIntegration::ShortcutInfo shortcut_info;
shortcut_info.url = extension_->app_launch_url();
shortcut_info.extension_id = UTF8ToUTF16(extension_->id());
shortcut_info.title = UTF8ToUTF16(extension_->name());
shortcut_info.description = UTF8ToUTF16(extension_->description());
shortcut_info.favicon = icon;
shortcut_info.create_on_desktop = true;
// TODO(aa): Seems nasty to be reusing the old webapps code this way. What
// baggage am I inheriting?
web_app::CreateShortcut(frontend_->profile()->GetPath(), shortcut_info,
NULL);
}
// This is lame, but we must reload the extension because absolute paths
// inside the content scripts are established inside InitFromValue() and we
// just moved the extension.
// TODO(aa): All paths to resources inside extensions should be created
// lazily and based on the Extension's root path at that moment.
std::string error;
extension_.reset(extension_file_util::LoadExtension(version_dir, true,
&error));
DCHECK(error.empty());
extension_->set_location(install_source_);
ReportSuccessFromFileThread();
}
void CrxInstaller::ReportFailureFromFileThread(const std::string& error) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
ChromeThread::PostTask(
ChromeThread::UI, FROM_HERE,
NewRunnableMethod(this, &CrxInstaller::ReportFailureFromUIThread, error));
}
void CrxInstaller::ReportFailureFromUIThread(const std::string& error) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
NotificationService* service = NotificationService::current();
service->Notify(NotificationType::EXTENSION_INSTALL_ERROR,
Source<CrxInstaller>(this),
Details<const std::string>(&error));
// 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_.get())
client_->OnInstallFailure(error);
}
void CrxInstaller::ReportOverinstallFromFileThread() {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
ChromeThread::PostTask(
ChromeThread::UI, FROM_HERE,
NewRunnableMethod(this, &CrxInstaller::ReportOverinstallFromUIThread));
}
void CrxInstaller::ReportOverinstallFromUIThread() {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
NotificationService* service = NotificationService::current();
service->Notify(NotificationType::EXTENSION_OVERINSTALL_ERROR,
Source<CrxInstaller>(this),
Details<const FilePath>(&extension_->path()));
if (client_.get())
client_->OnOverinstallAttempted(extension_.get());
frontend_->OnExtensionOverinstallAttempted(extension_->id());
}
void CrxInstaller::ReportSuccessFromFileThread() {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
ChromeThread::PostTask(
ChromeThread::UI, FROM_HERE,
NewRunnableMethod(this, &CrxInstaller::ReportSuccessFromUIThread));
}
void CrxInstaller::ReportSuccessFromUIThread() {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
// 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(),
allow_privilege_increase_);
// We're done. We don't post any more tasks to ourselves so we are deleted
// soon.
}