// 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 "chrome/browser/extensions/crx_installer.h"
#include <map>
#include <set>
#include "base/bind.h"
#include "base/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/lazy_instance.h"
#include "base/metrics/histogram.h"
#include "base/path_service.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/sequenced_worker_pool.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/version.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/convert_user_script.h"
#include "chrome/browser/extensions/convert_web_app.h"
#include "chrome/browser/extensions/crx_installer_error.h"
#include "chrome/browser/extensions/extension_error_reporter.h"
#include "chrome/browser/extensions/extension_install_ui.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/extensions/permissions_updater.h"
#include "chrome/browser/extensions/webstore_installer.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_file_util.h"
#include "chrome/common/extensions/extension_icon_set.h"
#include "chrome/common/extensions/manifest_url_handler.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/resource_dispatcher_host.h"
#include "content/public/browser/user_metrics.h"
#include "extensions/common/feature_switch.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_handlers/kiosk_mode_info.h"
#include "extensions/common/manifest_handlers/shared_module_info.h"
#include "extensions/common/permissions/permission_message_provider.h"
#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/user_script.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/login/user_manager.h"
#endif
using content::BrowserThread;
using content::UserMetricsAction;
using extensions::SharedModuleInfo;
namespace extensions {
namespace {
// Used in histograms; do not change order.
enum OffStoreInstallDecision {
OnStoreInstall,
OffStoreInstallAllowed,
OffStoreInstallDisallowed,
NumOffStoreInstallDecision
};
} // namespace
// static
scoped_refptr<CrxInstaller> CrxInstaller::CreateSilent(
ExtensionService* frontend) {
return new CrxInstaller(frontend->AsWeakPtr(),
scoped_ptr<ExtensionInstallPrompt>(),
NULL);
}
// static
scoped_refptr<CrxInstaller> CrxInstaller::Create(
ExtensionService* frontend,
scoped_ptr<ExtensionInstallPrompt> client) {
return new CrxInstaller(frontend->AsWeakPtr(), client.Pass(), NULL);
}
// static
scoped_refptr<CrxInstaller> CrxInstaller::Create(
ExtensionService* service,
scoped_ptr<ExtensionInstallPrompt> client,
const WebstoreInstaller::Approval* approval) {
return new CrxInstaller(service->AsWeakPtr(), client.Pass(), approval);
}
CrxInstaller::CrxInstaller(
base::WeakPtr<ExtensionService> service_weak,
scoped_ptr<ExtensionInstallPrompt> client,
const WebstoreInstaller::Approval* approval)
: install_directory_(service_weak->install_directory()),
install_source_(Manifest::INTERNAL),
approved_(false),
expected_manifest_check_level_(
WebstoreInstaller::MANIFEST_CHECK_LEVEL_STRICT),
expected_version_strict_checking_(false),
extensions_enabled_(service_weak->extensions_enabled()),
delete_source_(false),
create_app_shortcut_(false),
service_weak_(service_weak),
// See header file comment on |client_| for why we use a raw pointer here.
client_(client.release()),
apps_require_extension_mime_type_(false),
allow_silent_install_(false),
install_cause_(extension_misc::INSTALL_CAUSE_UNSET),
creation_flags_(Extension::NO_FLAGS),
off_store_install_allow_reason_(OffStoreInstallDisallowed),
did_handle_successfully_(true),
error_on_unsupported_requirements_(false),
has_requirement_errors_(false),
blacklist_state_(extensions::Blacklist::NOT_BLACKLISTED),
install_wait_for_idle_(true),
update_from_settings_page_(false),
installer_(service_weak->profile()) {
installer_task_runner_ = service_weak->GetFileTaskRunner();
if (!approval)
return;
CHECK(profile()->IsSameProfile(approval->profile));
if (client_) {
client_->install_ui()->SetUseAppInstalledBubble(
approval->use_app_installed_bubble);
client_->install_ui()->set_skip_post_install_ui(
approval->skip_post_install_ui);
}
if (approval->skip_install_dialog) {
// Mark the extension as approved, but save the expected manifest and ID
// so we can check that they match the CRX's.
approved_ = true;
expected_manifest_check_level_ = approval->manifest_check_level;
if (expected_manifest_check_level_ !=
WebstoreInstaller::MANIFEST_CHECK_LEVEL_NONE)
expected_manifest_.reset(approval->manifest->DeepCopy());
expected_id_ = approval->extension_id;
}
if (approval->minimum_version.get()) {
expected_version_.reset(new Version(*approval->minimum_version));
expected_version_strict_checking_ = false;
}
show_dialog_callback_ = approval->show_dialog_callback;
if (approval->is_ephemeral)
creation_flags_ |= Extension::IS_EPHEMERAL;
}
CrxInstaller::~CrxInstaller() {
// Make sure the UI is deleted on the ui thread.
if (client_) {
BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, client_);
client_ = NULL;
}
}
void CrxInstaller::InstallCrx(const base::FilePath& source_file) {
ExtensionService* service = service_weak_.get();
if (!service || service->browser_terminating())
return;
source_file_ = source_file;
scoped_refptr<SandboxedUnpacker> unpacker(
new SandboxedUnpacker(source_file,
install_source_,
creation_flags_,
install_directory_,
installer_task_runner_.get(),
this));
if (!installer_task_runner_->PostTask(
FROM_HERE,
base::Bind(&SandboxedUnpacker::Start, unpacker.get())))
NOTREACHED();
}
void CrxInstaller::InstallUserScript(const base::FilePath& source_file,
const GURL& download_url) {
DCHECK(!download_url.is_empty());
source_file_ = source_file;
download_url_ = download_url;
if (!installer_task_runner_->PostTask(
FROM_HERE,
base::Bind(&CrxInstaller::ConvertUserScriptOnFileThread, this)))
NOTREACHED();
}
void CrxInstaller::ConvertUserScriptOnFileThread() {
base::string16 error;
scoped_refptr<Extension> extension = ConvertUserScriptToExtension(
source_file_, download_url_, install_directory_, &error);
if (!extension.get()) {
ReportFailureFromFileThread(CrxInstallerError(error));
return;
}
OnUnpackSuccess(extension->path(), extension->path(), NULL, extension.get(),
SkBitmap());
}
void CrxInstaller::InstallWebApp(const WebApplicationInfo& web_app) {
if (!installer_task_runner_->PostTask(
FROM_HERE,
base::Bind(&CrxInstaller::ConvertWebAppOnFileThread,
this,
web_app,
install_directory_)))
NOTREACHED();
}
void CrxInstaller::ConvertWebAppOnFileThread(
const WebApplicationInfo& web_app,
const base::FilePath& install_directory) {
base::string16 error;
scoped_refptr<Extension> extension(
ConvertWebAppToExtension(web_app, base::Time::Now(), install_directory));
if (!extension.get()) {
// Validation should have stopped any potential errors before getting here.
NOTREACHED() << "Could not convert web app to extension.";
return;
}
// TODO(aa): conversion data gets lost here :(
OnUnpackSuccess(extension->path(), extension->path(), NULL, extension.get(),
SkBitmap());
}
CrxInstallerError CrxInstaller::AllowInstall(const Extension* extension) {
DCHECK(installer_task_runner_->RunsTasksOnCurrentThread());
// Make sure the expected ID matches if one was supplied or if we want to
// bypass the prompt.
if ((approved_ || !expected_id_.empty()) &&
expected_id_ != extension->id()) {
return CrxInstallerError(
l10n_util::GetStringFUTF16(IDS_EXTENSION_INSTALL_UNEXPECTED_ID,
ASCIIToUTF16(expected_id_),
ASCIIToUTF16(extension->id())));
}
if (expected_version_.get()) {
if (expected_version_strict_checking_) {
if (!expected_version_->Equals(*extension->version())) {
return CrxInstallerError(
l10n_util::GetStringFUTF16(
IDS_EXTENSION_INSTALL_UNEXPECTED_VERSION,
ASCIIToUTF16(expected_version_->GetString()),
ASCIIToUTF16(extension->version()->GetString())));
}
} else {
if (extension->version()->CompareTo(*expected_version_) < 0) {
return CrxInstallerError(
l10n_util::GetStringFUTF16(
IDS_EXTENSION_INSTALL_UNEXPECTED_VERSION,
ASCIIToUTF16(expected_version_->GetString() + "+"),
ASCIIToUTF16(extension->version()->GetString())));
}
}
}
// Make sure the manifests match if we want to bypass the prompt.
if (approved_) {
bool valid = false;
if (expected_manifest_check_level_ ==
WebstoreInstaller::MANIFEST_CHECK_LEVEL_NONE) {
// To skip manifest checking, the extension must be a shared module
// and not request any permissions.
if (SharedModuleInfo::IsSharedModule(extension) &&
PermissionsData::GetActivePermissions(extension)->IsEmpty()) {
valid = true;
}
} else {
valid = expected_manifest_->Equals(original_manifest_.get());
if (!valid && expected_manifest_check_level_ ==
WebstoreInstaller::MANIFEST_CHECK_LEVEL_LOOSE) {
std::string error;
scoped_refptr<Extension> dummy_extension =
Extension::Create(base::FilePath(),
install_source_,
*expected_manifest_->value(),
creation_flags_,
&error);
if (error.empty()) {
scoped_refptr<const PermissionSet> expected_permissions =
PermissionsData::GetActivePermissions(dummy_extension.get());
valid = !(PermissionMessageProvider::Get()->IsPrivilegeIncrease(
expected_permissions,
PermissionsData::GetActivePermissions(extension),
extension->GetType()));
}
}
}
if (!valid)
return CrxInstallerError(
l10n_util::GetStringUTF16(IDS_EXTENSION_MANIFEST_INVALID));
}
// The checks below are skipped for themes and external installs.
// TODO(pamg): After ManagementPolicy refactoring is complete, remove this
// and other uses of install_source_ that are no longer needed now that the
// SandboxedUnpacker sets extension->location.
if (extension->is_theme() || Manifest::IsExternalLocation(install_source_))
return CrxInstallerError();
if (!extensions_enabled_) {
return CrxInstallerError(
l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALL_NOT_ENABLED));
}
if (install_cause_ == extension_misc::INSTALL_CAUSE_USER_DOWNLOAD) {
if (FeatureSwitch::easy_off_store_install()->IsEnabled()) {
const char* kHistogramName = "Extensions.OffStoreInstallDecisionEasy";
if (is_gallery_install()) {
UMA_HISTOGRAM_ENUMERATION(kHistogramName, OnStoreInstall,
NumOffStoreInstallDecision);
} else {
UMA_HISTOGRAM_ENUMERATION(kHistogramName, OffStoreInstallAllowed,
NumOffStoreInstallDecision);
}
} else {
const char* kHistogramName = "Extensions.OffStoreInstallDecisionHard";
if (is_gallery_install()) {
UMA_HISTOGRAM_ENUMERATION(kHistogramName, OnStoreInstall,
NumOffStoreInstallDecision);
} else if (off_store_install_allow_reason_ != OffStoreInstallDisallowed) {
UMA_HISTOGRAM_ENUMERATION(kHistogramName, OffStoreInstallAllowed,
NumOffStoreInstallDecision);
UMA_HISTOGRAM_ENUMERATION("Extensions.OffStoreInstallAllowReason",
off_store_install_allow_reason_,
NumOffStoreInstallAllowReasons);
} else {
UMA_HISTOGRAM_ENUMERATION(kHistogramName, OffStoreInstallDisallowed,
NumOffStoreInstallDecision);
// Don't delete source in this case so that the user can install
// manually if they want.
delete_source_ = false;
did_handle_successfully_ = false;
return CrxInstallerError(
CrxInstallerError::ERROR_OFF_STORE,
l10n_util::GetStringUTF16(
IDS_EXTENSION_INSTALL_DISALLOWED_ON_SITE));
}
}
}
if (installer_.extension()->is_app()) {
// If the app was downloaded, apps_require_extension_mime_type_
// will be set. In this case, check that it was served with the
// right mime type. Make an exception for file URLs, which come
// from the users computer and have no headers.
if (!download_url_.SchemeIsFile() &&
apps_require_extension_mime_type_ &&
original_mime_type_ != Extension::kMimeType) {
return CrxInstallerError(
l10n_util::GetStringFUTF16(
IDS_EXTENSION_INSTALL_INCORRECT_APP_CONTENT_TYPE,
ASCIIToUTF16(Extension::kMimeType)));
}
// If the client_ is NULL, then the app is either being installed via
// an internal mechanism like sync, external_extensions, or default apps.
// In that case, we don't want to enforce things like the install origin.
if (!is_gallery_install() && client_) {
// For apps with a gallery update URL, require that they be installed
// from the gallery.
// TODO(erikkay) Apply this rule for paid extensions and themes as well.
if (ManifestURL::UpdatesFromGallery(extension)) {
return CrxInstallerError(
l10n_util::GetStringFUTF16(
IDS_EXTENSION_DISALLOW_NON_DOWNLOADED_GALLERY_INSTALLS,
l10n_util::GetStringUTF16(IDS_EXTENSION_WEB_STORE_TITLE)));
}
// For self-hosted apps, verify that the entire extent is on the same
// host (or a subdomain of the host) the download happened from. There's
// no way for us to verify that the app controls any other hosts.
URLPattern pattern(UserScript::ValidUserScriptSchemes());
pattern.SetHost(download_url_.host());
pattern.SetMatchSubdomains(true);
URLPatternSet patterns = installer_.extension()->web_extent();
for (URLPatternSet::const_iterator i = patterns.begin();
i != patterns.end(); ++i) {
if (!pattern.MatchesHost(i->host())) {
return CrxInstallerError(
l10n_util::GetStringUTF16(
IDS_EXTENSION_INSTALL_INCORRECT_INSTALL_HOST));
}
}
}
}
return CrxInstallerError();
}
void CrxInstaller::OnUnpackFailure(const base::string16& error_message) {
DCHECK(installer_task_runner_->RunsTasksOnCurrentThread());
UMA_HISTOGRAM_ENUMERATION("Extensions.UnpackFailureInstallSource",
install_source(), Manifest::NUM_LOCATIONS);
UMA_HISTOGRAM_ENUMERATION("Extensions.UnpackFailureInstallCause",
install_cause(),
extension_misc::NUM_INSTALL_CAUSES);
ReportFailureFromFileThread(CrxInstallerError(error_message));
}
void CrxInstaller::OnUnpackSuccess(const base::FilePath& temp_dir,
const base::FilePath& extension_dir,
const DictionaryValue* original_manifest,
const Extension* extension,
const SkBitmap& install_icon) {
DCHECK(installer_task_runner_->RunsTasksOnCurrentThread());
UMA_HISTOGRAM_ENUMERATION("Extensions.UnpackSuccessInstallSource",
install_source(), Manifest::NUM_LOCATIONS);
UMA_HISTOGRAM_ENUMERATION("Extensions.UnpackSuccessInstallCause",
install_cause(),
extension_misc::NUM_INSTALL_CAUSES);
installer_.set_extension(extension);
temp_dir_ = temp_dir;
if (!install_icon.empty())
install_icon_.reset(new SkBitmap(install_icon));
if (original_manifest)
original_manifest_.reset(new Manifest(
Manifest::INVALID_LOCATION,
scoped_ptr<DictionaryValue>(original_manifest->DeepCopy())));
// We don't have to delete the unpack dir explicity since it is a child of
// the temp dir.
unpacked_extension_root_ = extension_dir;
CrxInstallerError error = AllowInstall(extension);
if (error.type() != CrxInstallerError::ERROR_NONE) {
ReportFailureFromFileThread(error);
return;
}
if (!BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&CrxInstaller::CheckImportsAndRequirements, this)))
NOTREACHED();
}
void CrxInstaller::CheckImportsAndRequirements() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ExtensionService* service = service_weak_.get();
if (!service || service->browser_terminating())
return;
if (SharedModuleInfo::ImportsModules(extension())) {
const std::vector<SharedModuleInfo::ImportInfo>& imports =
SharedModuleInfo::GetImports(extension());
std::vector<SharedModuleInfo::ImportInfo>::const_iterator i;
for (i = imports.begin(); i != imports.end(); ++i) {
Version version_required(i->minimum_version);
const Extension* imported_module =
service->GetExtensionById(i->extension_id, true);
if (imported_module &&
!SharedModuleInfo::IsSharedModule(imported_module)) {
ReportFailureFromUIThread(
CrxInstallerError(l10n_util::GetStringFUTF16(
IDS_EXTENSION_INSTALL_DEPENDENCY_NOT_SHARED_MODULE,
ASCIIToUTF16(i->extension_id))));
return;
}
}
}
installer_.CheckRequirements(base::Bind(&CrxInstaller::OnRequirementsChecked,
this));
}
void CrxInstaller::OnRequirementsChecked(
std::vector<std::string> requirement_errors) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!service_weak_)
return;
if (!requirement_errors.empty()) {
if (error_on_unsupported_requirements_) {
ReportFailureFromUIThread(CrxInstallerError(
UTF8ToUTF16(JoinString(requirement_errors, ' '))));
return;
}
has_requirement_errors_ = true;
}
ExtensionSystem::Get(profile())->blacklist()->IsBlacklisted(
extension()->id(),
base::Bind(&CrxInstaller::OnBlacklistChecked, this));
}
void CrxInstaller::OnBlacklistChecked(
extensions::Blacklist::BlacklistState blacklist_state) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!service_weak_)
return;
blacklist_state_ = blacklist_state;
if (blacklist_state_ == extensions::Blacklist::BLACKLISTED_MALWARE &&
!allow_silent_install_) {
// User tried to install a blacklisted extension. Show an error and
// refuse to install it.
ReportFailureFromUIThread(extensions::CrxInstallerError(
l10n_util::GetStringFUTF16(IDS_EXTENSION_IS_BLACKLISTED,
UTF8ToUTF16(extension()->name()))));
UMA_HISTOGRAM_ENUMERATION("ExtensionBlacklist.BlockCRX",
extension()->location(),
Manifest::NUM_LOCATIONS);
return;
}
// NOTE: extension may still be blacklisted, but we're forced to silently
// install it. In this case, ExtensionService::OnExtensionInstalled needs to
// deal with it.
ConfirmInstall();
}
void CrxInstaller::ConfirmInstall() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ExtensionService* service = service_weak_.get();
if (!service || service->browser_terminating())
return;
if (KioskModeInfo::IsKioskOnly(installer_.extension())) {
bool in_kiosk_mode = false;
#if defined(OS_CHROMEOS)
chromeos::UserManager* user_manager = chromeos::UserManager::Get();
in_kiosk_mode = user_manager && user_manager->IsLoggedInAsKioskApp();
#endif
if (!in_kiosk_mode) {
ReportFailureFromUIThread(CrxInstallerError(
l10n_util::GetStringUTF16(
IDS_EXTENSION_INSTALL_KIOSK_MODE_ONLY)));
}
}
base::string16 error = installer_.CheckManagementPolicy();
if (!error.empty()) {
// We don't want to show the error infobar for installs from the WebStore,
// because the WebStore already shows an error dialog itself.
// Note: |client_| can be NULL in unit_tests!
if (extension()->from_webstore() && client_)
client_->install_ui()->set_skip_post_install_ui(true);
ReportFailureFromUIThread(CrxInstallerError(error));
return;
}
// Check whether this install is initiated from the settings page to
// update an existing extension or app.
CheckUpdateFromSettingsPage();
GURL overlapping_url;
const Extension* overlapping_extension =
service->extensions()->GetHostedAppByOverlappingWebExtent(
extension()->web_extent());
if (overlapping_extension &&
overlapping_extension->id() != extension()->id()) {
ReportFailureFromUIThread(
CrxInstallerError(
l10n_util::GetStringFUTF16(
IDS_EXTENSION_OVERLAPPING_WEB_EXTENT,
UTF8ToUTF16(overlapping_extension->name()))));
return;
}
current_version_ =
service->extension_prefs()->GetVersionString(extension()->id());
if (client_ &&
(!allow_silent_install_ || !approved_) &&
!update_from_settings_page_) {
AddRef(); // Balanced in InstallUIProceed() and InstallUIAbort().
client_->ConfirmInstall(this, extension(), show_dialog_callback_);
} else {
if (!installer_task_runner_->PostTask(
FROM_HERE,
base::Bind(&CrxInstaller::CompleteInstall, this)))
NOTREACHED();
}
return;
}
void CrxInstaller::InstallUIProceed() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ExtensionService* service = service_weak_.get();
if (!service || service->browser_terminating())
return;
// If update_from_settings_page_ boolean is true, this functions is
// getting called in response to ExtensionInstallPrompt::ConfirmReEnable()
// and if it is false, this function is called in response to
// ExtensionInstallPrompt::ConfirmInstall().
if (update_from_settings_page_) {
service->GrantPermissionsAndEnableExtension(extension());
} else {
if (!installer_task_runner_->PostTask(
FROM_HERE,
base::Bind(&CrxInstaller::CompleteInstall, this)))
NOTREACHED();
}
Release(); // balanced in ConfirmInstall() or ConfirmReEnable().
}
void CrxInstaller::InstallUIAbort(bool user_initiated) {
// If update_from_settings_page_ boolean is true, this functions is
// getting called in response to ExtensionInstallPrompt::ConfirmReEnable()
// and if it is false, this function is called in response to
// ExtensionInstallPrompt::ConfirmInstall().
if (!update_from_settings_page_) {
std::string histogram_name = user_initiated ?
"Extensions.Permissions_InstallCancel" :
"Extensions.Permissions_InstallAbort";
ExtensionService::RecordPermissionMessagesHistogram(
extension(), histogram_name.c_str());
NotifyCrxInstallComplete(false);
}
Release(); // balanced in ConfirmInstall() or ConfirmReEnable().
// 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(installer_task_runner_->RunsTasksOnCurrentThread());
if (!current_version_.empty()) {
Version current_version(current_version_);
if (current_version.CompareTo(*(extension()->version())) > 0) {
ReportFailureFromFileThread(
CrxInstallerError(
l10n_util::GetStringUTF16(extension()->is_app() ?
IDS_APP_CANT_DOWNGRADE_VERSION :
IDS_EXTENSION_CANT_DOWNGRADE_VERSION)));
return;
}
}
// See how long extension install paths are. This is important on
// windows, because file operations may fail if the path to a file
// exceeds a small constant. See crbug.com/69693 .
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Extensions.CrxInstallDirPathLength",
install_directory_.value().length(), 0, 500, 100);
base::FilePath version_dir = extension_file_util::InstallExtension(
unpacked_extension_root_,
extension()->id(),
extension()->VersionString(),
install_directory_);
if (version_dir.empty()) {
ReportFailureFromFileThread(
CrxInstallerError(
l10n_util::GetStringUTF16(
IDS_EXTENSION_MOVE_DIRECTORY_TO_PROFILE_FAILED)));
return;
}
// 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.
// TODO(rdevlin.cronin): Continue removing std::string errors and replacing
// with base::string16
std::string extension_id = extension()->id();
std::string error;
installer_.set_extension(extension_file_util::LoadExtension(
version_dir,
install_source_,
extension()->creation_flags() | Extension::REQUIRE_KEY,
&error).get());
if (extension()) {
ReportSuccessFromFileThread();
} else {
LOG(ERROR) << error << " " << extension_id << " " << download_url_;
ReportFailureFromFileThread(CrxInstallerError(UTF8ToUTF16(error)));
}
}
void CrxInstaller::ReportFailureFromFileThread(const CrxInstallerError& error) {
DCHECK(installer_task_runner_->RunsTasksOnCurrentThread());
if (!BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&CrxInstaller::ReportFailureFromUIThread, this, error))) {
NOTREACHED();
}
}
void CrxInstaller::ReportFailureFromUIThread(const CrxInstallerError& error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
content::NotificationService* service =
content::NotificationService::current();
service->Notify(chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
content::Source<CrxInstaller>(this),
content::Details<const base::string16>(&error.message()));
// 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.message(), false); // quiet
if (client_)
client_->OnInstallFailure(error);
NotifyCrxInstallComplete(false);
// Delete temporary files.
CleanupTempFiles();
}
void CrxInstaller::ReportSuccessFromFileThread() {
DCHECK(installer_task_runner_->RunsTasksOnCurrentThread());
// Tracking number of extensions installed by users
if (install_cause() == extension_misc::INSTALL_CAUSE_USER_DOWNLOAD)
UMA_HISTOGRAM_ENUMERATION("Extensions.ExtensionInstalled", 1, 2);
if (!BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&CrxInstaller::ReportSuccessFromUIThread, this)))
NOTREACHED();
// Delete temporary files.
CleanupTempFiles();
}
void CrxInstaller::ReportSuccessFromUIThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!service_weak_.get() || service_weak_->browser_terminating())
return;
if (!update_from_settings_page_) {
// If there is a client, tell the client about installation.
if (client_)
client_->OnInstallSuccess(extension(), install_icon_.get());
// We update the extension's granted permissions if the user already
// approved the install (client_ is non NULL), or we are allowed to install
// this silently.
if (client_ || allow_silent_install_) {
PermissionsUpdater perms_updater(profile());
perms_updater.GrantActivePermissions(extension());
}
}
service_weak_->OnExtensionInstalled(extension(),
page_ordinal_,
has_requirement_errors_,
blacklist_state_,
install_wait_for_idle_);
NotifyCrxInstallComplete(true);
}
void CrxInstaller::NotifyCrxInstallComplete(bool success) {
// Some users (such as the download shelf) need to know when a
// CRXInstaller is done. Listening for the EXTENSION_* events
// is problematic because they don't know anything about the
// extension before it is unpacked, so they cannot filter based
// on the extension.
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_CRX_INSTALLER_DONE,
content::Source<CrxInstaller>(this),
content::Details<const Extension>(
success ? extension() : NULL));
if (success)
ConfirmReEnable();
}
void CrxInstaller::CleanupTempFiles() {
if (!installer_task_runner_->RunsTasksOnCurrentThread()) {
if (!installer_task_runner_->PostTask(
FROM_HERE,
base::Bind(&CrxInstaller::CleanupTempFiles, this))) {
NOTREACHED();
}
return;
}
// Delete the temp directory and crx file as necessary.
if (!temp_dir_.value().empty()) {
extension_file_util::DeleteFile(temp_dir_, true);
temp_dir_ = base::FilePath();
}
if (delete_source_ && !source_file_.value().empty()) {
extension_file_util::DeleteFile(source_file_, false);
source_file_ = base::FilePath();
}
}
void CrxInstaller::CheckUpdateFromSettingsPage() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ExtensionService* service = service_weak_.get();
if (!service || service->browser_terminating())
return;
if (off_store_install_allow_reason_ != OffStoreInstallAllowedFromSettingsPage)
return;
const Extension* installed_extension =
service->GetInstalledExtension(extension()->id());
if (installed_extension) {
// Previous version of the extension exists.
update_from_settings_page_ = true;
expected_id_ = installed_extension->id();
install_source_ = installed_extension->location();
install_cause_ = extension_misc::INSTALL_CAUSE_UPDATE;
}
}
void CrxInstaller::ConfirmReEnable() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ExtensionService* service = service_weak_.get();
if (!service || service->browser_terminating())
return;
if (!update_from_settings_page_)
return;
ExtensionPrefs* prefs = service->extension_prefs();
if (!prefs->DidExtensionEscalatePermissions(extension()->id()))
return;
if (client_) {
AddRef(); // Balanced in InstallUIProceed() and InstallUIAbort().
client_->ConfirmReEnable(this, extension());
}
}
} // namespace extensions