crx_installer.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/extensions/crx_installer.h" 6 7#include <map> 8#include <set> 9 10#include "base/bind.h" 11#include "base/file_util.h" 12#include "base/files/scoped_temp_dir.h" 13#include "base/lazy_instance.h" 14#include "base/metrics/histogram.h" 15#include "base/path_service.h" 16#include "base/sequenced_task_runner.h" 17#include "base/string_util.h" 18#include "base/stringprintf.h" 19#include "base/threading/sequenced_worker_pool.h" 20#include "base/threading/thread_restrictions.h" 21#include "base/time.h" 22#include "base/utf_string_conversions.h" 23#include "base/version.h" 24#include "chrome/browser/extensions/convert_user_script.h" 25#include "chrome/browser/extensions/convert_web_app.h" 26#include "chrome/browser/extensions/crx_installer_error.h" 27#include "chrome/browser/extensions/extension_error_reporter.h" 28#include "chrome/browser/extensions/extension_install_ui.h" 29#include "chrome/browser/extensions/extension_service.h" 30#include "chrome/browser/extensions/extension_system.h" 31#include "chrome/browser/extensions/permissions_updater.h" 32#include "chrome/browser/extensions/webstore_installer.h" 33#include "chrome/browser/profiles/profile.h" 34#include "chrome/browser/web_applications/web_app.h" 35#include "chrome/common/chrome_notification_types.h" 36#include "chrome/common/chrome_paths.h" 37#include "chrome/common/extensions/extension_constants.h" 38#include "chrome/common/extensions/extension_file_util.h" 39#include "chrome/common/extensions/extension_icon_set.h" 40#include "chrome/common/extensions/feature_switch.h" 41#include "chrome/common/extensions/manifest.h" 42#include "chrome/common/extensions/manifest_handlers/icons_handler.h" 43#include "chrome/common/extensions/manifest_handlers/shared_module_info.h" 44#include "content/public/browser/browser_thread.h" 45#include "content/public/browser/notification_service.h" 46#include "content/public/browser/resource_dispatcher_host.h" 47#include "content/public/browser/user_metrics.h" 48#include "grit/chromium_strings.h" 49#include "grit/generated_resources.h" 50#include "grit/theme_resources.h" 51#include "third_party/skia/include/core/SkBitmap.h" 52#include "ui/base/l10n/l10n_util.h" 53#include "ui/base/resource/resource_bundle.h" 54 55using content::BrowserThread; 56using content::UserMetricsAction; 57using extensions::SharedModuleInfo; 58 59namespace extensions { 60 61namespace { 62 63// Used in histograms; do not change order. 64enum OffStoreInstallDecision { 65 OnStoreInstall, 66 OffStoreInstallAllowed, 67 OffStoreInstallDisallowed, 68 NumOffStoreInstallDecision 69}; 70 71} // namespace 72 73// static 74scoped_refptr<CrxInstaller> CrxInstaller::Create( 75 ExtensionService* frontend, 76 ExtensionInstallPrompt* client) { 77 return new CrxInstaller(frontend->AsWeakPtr(), client, NULL); 78} 79 80// static 81scoped_refptr<CrxInstaller> CrxInstaller::Create( 82 ExtensionService* service, 83 ExtensionInstallPrompt* client, 84 const WebstoreInstaller::Approval* approval) { 85 return new CrxInstaller(service->AsWeakPtr(), client, approval); 86} 87 88CrxInstaller::CrxInstaller( 89 base::WeakPtr<ExtensionService> service_weak, 90 ExtensionInstallPrompt* client, 91 const WebstoreInstaller::Approval* approval) 92 : install_directory_(service_weak->install_directory()), 93 install_source_(Manifest::INTERNAL), 94 approved_(false), 95 extensions_enabled_(service_weak->extensions_enabled()), 96 delete_source_(false), 97 create_app_shortcut_(false), 98 service_weak_(service_weak), 99 client_(client), 100 apps_require_extension_mime_type_(false), 101 allow_silent_install_(false), 102 bypass_blacklist_for_test_(false), 103 install_cause_(extension_misc::INSTALL_CAUSE_UNSET), 104 creation_flags_(Extension::NO_FLAGS), 105 off_store_install_allow_reason_(OffStoreInstallDisallowed), 106 did_handle_successfully_(true), 107 error_on_unsupported_requirements_(false), 108 has_requirement_errors_(false), 109 install_wait_for_idle_(true), 110 update_from_settings_page_(false), 111 installer_(service_weak->profile()) { 112 installer_task_runner_ = service_weak->GetFileTaskRunner(); 113 if (!approval) 114 return; 115 116 CHECK(profile()->IsSameProfile(approval->profile)); 117 if (client_) { 118 client_->install_ui()->SetUseAppInstalledBubble( 119 approval->use_app_installed_bubble); 120 client_->install_ui()->SetSkipPostInstallUI(approval->skip_post_install_ui); 121 } 122 123 if (approval->skip_install_dialog) { 124 // Mark the extension as approved, but save the expected manifest and ID 125 // so we can check that they match the CRX's. 126 approved_ = true; 127 expected_manifest_.reset(approval->manifest->DeepCopy()); 128 expected_id_ = approval->extension_id; 129 } 130 131 show_dialog_callback_ = approval->show_dialog_callback; 132} 133 134CrxInstaller::~CrxInstaller() { 135 // Make sure the UI is deleted on the ui thread. 136 if (client_) { 137 BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, client_); 138 client_ = NULL; 139 } 140} 141 142void CrxInstaller::InstallCrx(const base::FilePath& source_file) { 143 source_file_ = source_file; 144 145 scoped_refptr<SandboxedUnpacker> unpacker( 146 new SandboxedUnpacker( 147 source_file, 148 content::ResourceDispatcherHost::Get() != NULL, 149 install_source_, 150 creation_flags_, 151 install_directory_, 152 installer_task_runner_, 153 this)); 154 155 if (!installer_task_runner_->PostTask( 156 FROM_HERE, 157 base::Bind(&SandboxedUnpacker::Start, unpacker.get()))) 158 NOTREACHED(); 159} 160 161void CrxInstaller::InstallUserScript(const base::FilePath& source_file, 162 const GURL& download_url) { 163 DCHECK(!download_url.is_empty()); 164 165 source_file_ = source_file; 166 download_url_ = download_url; 167 168 if (!installer_task_runner_->PostTask( 169 FROM_HERE, 170 base::Bind(&CrxInstaller::ConvertUserScriptOnFileThread, this))) 171 NOTREACHED(); 172} 173 174void CrxInstaller::ConvertUserScriptOnFileThread() { 175 string16 error; 176 scoped_refptr<Extension> extension = ConvertUserScriptToExtension( 177 source_file_, download_url_, install_directory_, &error); 178 if (!extension) { 179 ReportFailureFromFileThread(CrxInstallerError(error)); 180 return; 181 } 182 183 OnUnpackSuccess(extension->path(), extension->path(), NULL, extension); 184} 185 186void CrxInstaller::InstallWebApp(const WebApplicationInfo& web_app) { 187 if (!installer_task_runner_->PostTask( 188 FROM_HERE, 189 base::Bind(&CrxInstaller::ConvertWebAppOnFileThread, 190 this, 191 web_app, 192 install_directory_))) 193 NOTREACHED(); 194} 195 196void CrxInstaller::ConvertWebAppOnFileThread( 197 const WebApplicationInfo& web_app, 198 const base::FilePath& install_directory) { 199 string16 error; 200 scoped_refptr<Extension> extension( 201 ConvertWebAppToExtension(web_app, base::Time::Now(), install_directory)); 202 if (!extension) { 203 // Validation should have stopped any potential errors before getting here. 204 NOTREACHED() << "Could not convert web app to extension."; 205 return; 206 } 207 208 // TODO(aa): conversion data gets lost here :( 209 210 OnUnpackSuccess(extension->path(), extension->path(), NULL, extension); 211} 212 213CrxInstallerError CrxInstaller::AllowInstall(const Extension* extension) { 214 DCHECK(installer_task_runner_->RunsTasksOnCurrentThread()); 215 216 // Make sure the expected ID matches if one was supplied or if we want to 217 // bypass the prompt. 218 if ((approved_ || !expected_id_.empty()) && 219 expected_id_ != extension->id()) { 220 return CrxInstallerError( 221 l10n_util::GetStringFUTF16(IDS_EXTENSION_INSTALL_UNEXPECTED_ID, 222 ASCIIToUTF16(expected_id_), 223 ASCIIToUTF16(extension->id()))); 224 } 225 226 if (expected_version_.get() && 227 !expected_version_->Equals(*extension->version())) { 228 return CrxInstallerError( 229 l10n_util::GetStringFUTF16( 230 IDS_EXTENSION_INSTALL_UNEXPECTED_VERSION, 231 ASCIIToUTF16(expected_version_->GetString()), 232 ASCIIToUTF16(extension->version()->GetString()))); 233 } 234 235 // Make sure the manifests match if we want to bypass the prompt. 236 if (approved_ && 237 (!expected_manifest_.get() || 238 !expected_manifest_->Equals(original_manifest_.get()))) { 239 return CrxInstallerError( 240 l10n_util::GetStringUTF16(IDS_EXTENSION_MANIFEST_INVALID)); 241 } 242 243 // The checks below are skipped for themes and external installs. 244 // TODO(pamg): After ManagementPolicy refactoring is complete, remove this 245 // and other uses of install_source_ that are no longer needed now that the 246 // SandboxedUnpacker sets extension->location. 247 if (extension->is_theme() || Manifest::IsExternalLocation(install_source_)) 248 return CrxInstallerError(); 249 250 if (!extensions_enabled_) { 251 return CrxInstallerError( 252 l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALL_NOT_ENABLED)); 253 } 254 255 if (install_cause_ == extension_misc::INSTALL_CAUSE_USER_DOWNLOAD) { 256 if (FeatureSwitch::easy_off_store_install()->IsEnabled()) { 257 const char* kHistogramName = "Extensions.OffStoreInstallDecisionEasy"; 258 if (is_gallery_install()) { 259 UMA_HISTOGRAM_ENUMERATION(kHistogramName, OnStoreInstall, 260 NumOffStoreInstallDecision); 261 } else { 262 UMA_HISTOGRAM_ENUMERATION(kHistogramName, OffStoreInstallAllowed, 263 NumOffStoreInstallDecision); 264 } 265 } else { 266 const char* kHistogramName = "Extensions.OffStoreInstallDecisionHard"; 267 if (is_gallery_install()) { 268 UMA_HISTOGRAM_ENUMERATION(kHistogramName, OnStoreInstall, 269 NumOffStoreInstallDecision); 270 } else if (off_store_install_allow_reason_ != OffStoreInstallDisallowed) { 271 UMA_HISTOGRAM_ENUMERATION(kHistogramName, OffStoreInstallAllowed, 272 NumOffStoreInstallDecision); 273 UMA_HISTOGRAM_ENUMERATION("Extensions.OffStoreInstallAllowReason", 274 off_store_install_allow_reason_, 275 NumOffStoreInstallAllowReasons); 276 } else { 277 UMA_HISTOGRAM_ENUMERATION(kHistogramName, OffStoreInstallDisallowed, 278 NumOffStoreInstallDecision); 279 // Don't delete source in this case so that the user can install 280 // manually if they want. 281 delete_source_ = false; 282 did_handle_successfully_ = false; 283 284 return CrxInstallerError( 285 CrxInstallerError::ERROR_OFF_STORE, 286 l10n_util::GetStringUTF16( 287 IDS_EXTENSION_INSTALL_DISALLOWED_ON_SITE)); 288 } 289 } 290 } 291 292 if (installer_.extension()->is_app()) { 293 // If the app was downloaded, apps_require_extension_mime_type_ 294 // will be set. In this case, check that it was served with the 295 // right mime type. Make an exception for file URLs, which come 296 // from the users computer and have no headers. 297 if (!download_url_.SchemeIsFile() && 298 apps_require_extension_mime_type_ && 299 original_mime_type_ != Extension::kMimeType) { 300 return CrxInstallerError( 301 l10n_util::GetStringFUTF16( 302 IDS_EXTENSION_INSTALL_INCORRECT_APP_CONTENT_TYPE, 303 ASCIIToUTF16(Extension::kMimeType))); 304 } 305 306 // If the client_ is NULL, then the app is either being installed via 307 // an internal mechanism like sync, external_extensions, or default apps. 308 // In that case, we don't want to enforce things like the install origin. 309 if (!is_gallery_install() && client_) { 310 // For apps with a gallery update URL, require that they be installed 311 // from the gallery. 312 // TODO(erikkay) Apply this rule for paid extensions and themes as well. 313 if (extension->UpdatesFromGallery()) { 314 return CrxInstallerError( 315 l10n_util::GetStringFUTF16( 316 IDS_EXTENSION_DISALLOW_NON_DOWNLOADED_GALLERY_INSTALLS, 317 l10n_util::GetStringUTF16(IDS_EXTENSION_WEB_STORE_TITLE))); 318 } 319 320 // For self-hosted apps, verify that the entire extent is on the same 321 // host (or a subdomain of the host) the download happened from. There's 322 // no way for us to verify that the app controls any other hosts. 323 URLPattern pattern(UserScript::ValidUserScriptSchemes()); 324 pattern.SetHost(download_url_.host()); 325 pattern.SetMatchSubdomains(true); 326 327 URLPatternSet patterns = installer_.extension()->web_extent(); 328 for (URLPatternSet::const_iterator i = patterns.begin(); 329 i != patterns.end(); ++i) { 330 if (!pattern.MatchesHost(i->host())) { 331 return CrxInstallerError( 332 l10n_util::GetStringUTF16( 333 IDS_EXTENSION_INSTALL_INCORRECT_INSTALL_HOST)); 334 } 335 } 336 } 337 } 338 339 return CrxInstallerError(); 340} 341 342void CrxInstaller::OnUnpackFailure(const string16& error_message) { 343 DCHECK(installer_task_runner_->RunsTasksOnCurrentThread()); 344 345 UMA_HISTOGRAM_ENUMERATION("Extensions.UnpackFailureInstallSource", 346 install_source(), Manifest::NUM_LOCATIONS); 347 348 UMA_HISTOGRAM_ENUMERATION("Extensions.UnpackFailureInstallCause", 349 install_cause(), 350 extension_misc::NUM_INSTALL_CAUSES); 351 352 ReportFailureFromFileThread(CrxInstallerError(error_message)); 353} 354 355void CrxInstaller::OnUnpackSuccess(const base::FilePath& temp_dir, 356 const base::FilePath& extension_dir, 357 const DictionaryValue* original_manifest, 358 const Extension* extension) { 359 DCHECK(installer_task_runner_->RunsTasksOnCurrentThread()); 360 361 UMA_HISTOGRAM_ENUMERATION("Extensions.UnpackSuccessInstallSource", 362 install_source(), Manifest::NUM_LOCATIONS); 363 364 365 UMA_HISTOGRAM_ENUMERATION("Extensions.UnpackSuccessInstallCause", 366 install_cause(), 367 extension_misc::NUM_INSTALL_CAUSES); 368 369 installer_.set_extension(extension); 370 temp_dir_ = temp_dir; 371 372 if (original_manifest) 373 original_manifest_.reset(new Manifest( 374 Manifest::INVALID_LOCATION, 375 scoped_ptr<DictionaryValue>(original_manifest->DeepCopy()))); 376 377 // We don't have to delete the unpack dir explicity since it is a child of 378 // the temp dir. 379 unpacked_extension_root_ = extension_dir; 380 381 CrxInstallerError error = AllowInstall(extension); 382 if (error.type() != CrxInstallerError::ERROR_NONE) { 383 ReportFailureFromFileThread(error); 384 return; 385 } 386 387 if (client_) { 388 IconsInfo::DecodeIcon(installer_.extension(), 389 extension_misc::EXTENSION_ICON_LARGE, 390 ExtensionIconSet::MATCH_BIGGER, 391 &install_icon_); 392 } 393 394 if (!BrowserThread::PostTask( 395 BrowserThread::UI, FROM_HERE, 396 base::Bind(&CrxInstaller::CheckImportsAndRequirements, this))) 397 NOTREACHED(); 398} 399 400void CrxInstaller::CheckImportsAndRequirements() { 401 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 402 ExtensionService* service = service_weak_; 403 if (!service || service->browser_terminating()) 404 return; 405 406 if (SharedModuleInfo::ImportsModules(extension())) { 407 const std::vector<SharedModuleInfo::ImportInfo>& imports = 408 SharedModuleInfo::GetImports(extension()); 409 std::vector<SharedModuleInfo::ImportInfo>::const_iterator i; 410 for (i = imports.begin(); i != imports.end(); ++i) { 411 Version version_required(i->minimum_version); 412 const Extension* imported_module = 413 service->GetExtensionById(i->extension_id, true); 414 if (!imported_module || 415 (version_required.IsValid() && 416 imported_module->version()->CompareTo(version_required) < 0)) { 417 ReportFailureFromUIThread( 418 CrxInstallerError(l10n_util::GetStringFUTF16( 419 IDS_EXTENSION_INSTALL_DEPENDENCY_NOT_FOUND, 420 ASCIIToUTF16(i->extension_id), 421 ASCIIToUTF16(i->minimum_version)))); 422 return; 423 } 424 if (!SharedModuleInfo::IsSharedModule(imported_module)) { 425 ReportFailureFromUIThread( 426 CrxInstallerError(l10n_util::GetStringFUTF16( 427 IDS_EXTENSION_INSTALL_DEPENDENCY_NOT_SHARED_MODULE, 428 ASCIIToUTF16(i->extension_id)))); 429 return; 430 } 431 } 432 } 433 installer_.CheckRequirements(base::Bind(&CrxInstaller::OnRequirementsChecked, 434 this)); 435} 436 437void CrxInstaller::OnRequirementsChecked( 438 std::vector<std::string> requirement_errors) { 439 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 440 if (!requirement_errors.empty()) { 441 if (error_on_unsupported_requirements_) { 442 ReportFailureFromUIThread(CrxInstallerError( 443 UTF8ToUTF16(JoinString(requirement_errors, ' ')))); 444 return; 445 } 446 has_requirement_errors_ = true; 447 } 448 449#if defined(ENABLE_MANAGED_USERS) && !defined(OS_CHROMEOS) 450 // Extensions which should be installed by policy are installed without 451 // using the ExtensionInstallPrompt. In that case, |client_| is NULL. 452 if (!client_) { 453 // Automatically set authorization 454 installer_.OnAuthorizationResult( 455 base::Bind(&CrxInstaller::ConfirmInstall, this), 456 true); 457 return; 458 } 459 // |parent_web_contents| could be NULL when the client is instantiated from 460 // ExtensionEnableFlow, but that code path does not lead to here. Also they 461 // are NULL in some tests. 462 installer_.ShowPassphraseDialog( 463 client_->parent_web_contents(), 464 base::Bind(&CrxInstaller::ConfirmInstall, this)); 465#else 466 ConfirmInstall(); 467#endif 468} 469 470void CrxInstaller::ConfirmInstall() { 471 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 472 ExtensionService* service = service_weak_; 473 if (!service || service->browser_terminating()) 474 return; 475 476 string16 error = installer_.CheckManagementPolicy(); 477 if (!error.empty()) { 478 ReportFailureFromUIThread(CrxInstallerError(error)); 479 return; 480 } 481 482 // Check whether this install is initiated from the settings page to 483 // update an existing extension or app. 484 CheckUpdateFromSettingsPage(); 485 486 GURL overlapping_url; 487 const Extension* overlapping_extension = 488 service->extensions()->GetHostedAppByOverlappingWebExtent( 489 extension()->web_extent()); 490 if (overlapping_extension && 491 overlapping_extension->id() != extension()->id()) { 492 ReportFailureFromUIThread( 493 CrxInstallerError( 494 l10n_util::GetStringFUTF16( 495 IDS_EXTENSION_OVERLAPPING_WEB_EXTENT, 496 UTF8ToUTF16(overlapping_extension->name())))); 497 return; 498 } 499 500 current_version_ = 501 service->extension_prefs()->GetVersionString(extension()->id()); 502 503 if (client_ && 504 (!allow_silent_install_ || !approved_) && 505 !update_from_settings_page_) { 506 AddRef(); // Balanced in InstallUIProceed() and InstallUIAbort(). 507 client_->ConfirmInstall(this, extension(), show_dialog_callback_); 508 } else { 509 if (!installer_task_runner_->PostTask( 510 FROM_HERE, 511 base::Bind(&CrxInstaller::CompleteInstall, this))) 512 NOTREACHED(); 513 } 514 return; 515} 516 517void CrxInstaller::InstallUIProceed() { 518 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 519 520 ExtensionService* service = service_weak_; 521 if (!service || service->browser_terminating()) 522 return; 523 524 // If update_from_settings_page_ boolean is true, this functions is 525 // getting called in response to ExtensionInstallPrompt::ConfirmReEnable() 526 // and if it is false, this function is called in response to 527 // ExtensionInstallPrompt::ConfirmInstall(). 528 if (update_from_settings_page_) { 529 service->GrantPermissionsAndEnableExtension(extension()); 530 } else { 531 if (!installer_task_runner_->PostTask( 532 FROM_HERE, 533 base::Bind(&CrxInstaller::CompleteInstall, this))) 534 NOTREACHED(); 535 } 536 537 Release(); // balanced in ConfirmInstall() or ConfirmReEnable(). 538} 539 540void CrxInstaller::InstallUIAbort(bool user_initiated) { 541 // If update_from_settings_page_ boolean is true, this functions is 542 // getting called in response to ExtensionInstallPrompt::ConfirmReEnable() 543 // and if it is false, this function is called in response to 544 // ExtensionInstallPrompt::ConfirmInstall(). 545 if (!update_from_settings_page_) { 546 std::string histogram_name = user_initiated ? 547 "Extensions.Permissions_InstallCancel" : 548 "Extensions.Permissions_InstallAbort"; 549 ExtensionService::RecordPermissionMessagesHistogram( 550 extension(), histogram_name.c_str()); 551 552 // Kill the theme loading bubble. 553 content::NotificationService* service = 554 content::NotificationService::current(); 555 service->Notify(chrome::NOTIFICATION_NO_THEME_DETECTED, 556 content::Source<CrxInstaller>(this), 557 content::NotificationService::NoDetails()); 558 559 NotifyCrxInstallComplete(false); 560 } 561 562 Release(); // balanced in ConfirmInstall() or ConfirmReEnable(). 563 564 // We're done. Since we don't post any more tasks to ourself, our ref count 565 // should go to zero and we die. The destructor will clean up the temp dir. 566} 567 568void CrxInstaller::CompleteInstall() { 569 DCHECK(installer_task_runner_->RunsTasksOnCurrentThread()); 570 571 if (!current_version_.empty()) { 572 Version current_version(current_version_); 573 if (current_version.CompareTo(*(extension()->version())) > 0) { 574 ReportFailureFromFileThread( 575 CrxInstallerError( 576 l10n_util::GetStringUTF16(extension()->is_app() ? 577 IDS_APP_CANT_DOWNGRADE_VERSION : 578 IDS_EXTENSION_CANT_DOWNGRADE_VERSION))); 579 return; 580 } 581 } 582 583 // See how long extension install paths are. This is important on 584 // windows, because file operations may fail if the path to a file 585 // exceeds a small constant. See crbug.com/69693 . 586 UMA_HISTOGRAM_CUSTOM_COUNTS( 587 "Extensions.CrxInstallDirPathLength", 588 install_directory_.value().length(), 0, 500, 100); 589 590 base::FilePath version_dir = extension_file_util::InstallExtension( 591 unpacked_extension_root_, 592 extension()->id(), 593 extension()->VersionString(), 594 install_directory_); 595 if (version_dir.empty()) { 596 ReportFailureFromFileThread( 597 CrxInstallerError( 598 l10n_util::GetStringUTF16( 599 IDS_EXTENSION_MOVE_DIRECTORY_TO_PROFILE_FAILED))); 600 return; 601 } 602 603 // This is lame, but we must reload the extension because absolute paths 604 // inside the content scripts are established inside InitFromValue() and we 605 // just moved the extension. 606 // TODO(aa): All paths to resources inside extensions should be created 607 // lazily and based on the Extension's root path at that moment. 608 // TODO(rdevlin.cronin): Continue removing std::string errors and replacing 609 // with string16 610 std::string extension_id = extension()->id(); 611 std::string error; 612 installer_.set_extension(extension_file_util::LoadExtension( 613 version_dir, 614 install_source_, 615 extension()->creation_flags() | Extension::REQUIRE_KEY, 616 &error)); 617 618 if (extension()) { 619 ReportSuccessFromFileThread(); 620 } else { 621 LOG(ERROR) << error << " " << extension_id << " " << download_url_; 622 ReportFailureFromFileThread(CrxInstallerError(UTF8ToUTF16(error))); 623 } 624 625} 626 627void CrxInstaller::ReportFailureFromFileThread(const CrxInstallerError& error) { 628 DCHECK(installer_task_runner_->RunsTasksOnCurrentThread()); 629 if (!BrowserThread::PostTask( 630 BrowserThread::UI, FROM_HERE, 631 base::Bind(&CrxInstaller::ReportFailureFromUIThread, this, error))) { 632 NOTREACHED(); 633 } 634} 635 636void CrxInstaller::ReportFailureFromUIThread(const CrxInstallerError& error) { 637 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 638 639 content::NotificationService* service = 640 content::NotificationService::current(); 641 service->Notify(chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR, 642 content::Source<CrxInstaller>(this), 643 content::Details<const string16>(&error.message())); 644 645 // This isn't really necessary, it is only used because unit tests expect to 646 // see errors get reported via this interface. 647 // 648 // TODO(aa): Need to go through unit tests and clean them up too, probably get 649 // rid of this line. 650 ExtensionErrorReporter::GetInstance()->ReportError( 651 error.message(), false); // quiet 652 653 if (client_) 654 client_->OnInstallFailure(error); 655 656 NotifyCrxInstallComplete(false); 657 658 // Delete temporary files. 659 CleanupTempFiles(); 660} 661 662void CrxInstaller::ReportSuccessFromFileThread() { 663 DCHECK(installer_task_runner_->RunsTasksOnCurrentThread()); 664 665 // Tracking number of extensions installed by users 666 if (install_cause() == extension_misc::INSTALL_CAUSE_USER_DOWNLOAD) 667 UMA_HISTOGRAM_ENUMERATION("Extensions.ExtensionInstalled", 1, 2); 668 669 if (!BrowserThread::PostTask( 670 BrowserThread::UI, FROM_HERE, 671 base::Bind(&CrxInstaller::ReportSuccessFromUIThread, this))) 672 NOTREACHED(); 673 674 // Delete temporary files. 675 CleanupTempFiles(); 676} 677 678void CrxInstaller::ReportSuccessFromUIThread() { 679 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 680 681 if (!service_weak_ || service_weak_->browser_terminating()) 682 return; 683 684 if (!update_from_settings_page_) { 685 // If there is a client, tell the client about installation. 686 if (client_) 687 client_->OnInstallSuccess(extension(), install_icon_.get()); 688 689 // We update the extension's granted permissions if the user already 690 // approved the install (client_ is non NULL), or we are allowed to install 691 // this silently. 692 if (client_ || allow_silent_install_) { 693 PermissionsUpdater perms_updater(profile()); 694 perms_updater.GrantActivePermissions(extension()); 695 } 696 } 697 698 // Install the extension if it's not blacklisted, but notify either way. 699 base::Closure on_success = 700 base::Bind(&ExtensionService::OnExtensionInstalled, 701 service_weak_, 702 extension(), 703 page_ordinal_, 704 has_requirement_errors_, 705 install_wait_for_idle_); 706 if (bypass_blacklist_for_test_) { 707 HandleIsBlacklistedResponse(on_success, false); 708 } else { 709 ExtensionSystem::Get(profile())->blacklist()->IsBlacklisted( 710 extension()->id(), 711 base::Bind(&CrxInstaller::HandleIsBlacklistedResponse, 712 this, 713 on_success)); 714 } 715} 716 717void CrxInstaller::HandleIsBlacklistedResponse( 718 const base::Closure& on_success, 719 bool is_blacklisted) { 720 if (is_blacklisted) { 721 string16 error = l10n_util::GetStringFUTF16( 722 IDS_EXTENSION_IS_BLACKLISTED, 723 UTF8ToUTF16(extension()->name())); 724 make_scoped_ptr(ExtensionInstallUI::Create(profile()))->OnInstallFailure( 725 extensions::CrxInstallerError(error)); 726 // Show error via reporter to make tests happy. 727 ExtensionErrorReporter::GetInstance()->ReportError(error, false); // quiet 728 UMA_HISTOGRAM_ENUMERATION("ExtensionBlacklist.BlockCRX", 729 extension()->location(), 730 Manifest::NUM_LOCATIONS); 731 } else { 732 on_success.Run(); 733 } 734 NotifyCrxInstallComplete(!is_blacklisted); 735} 736 737void CrxInstaller::NotifyCrxInstallComplete(bool success) { 738 // Some users (such as the download shelf) need to know when a 739 // CRXInstaller is done. Listening for the EXTENSION_* events 740 // is problematic because they don't know anything about the 741 // extension before it is unpacked, so they cannot filter based 742 // on the extension. 743 content::NotificationService::current()->Notify( 744 chrome::NOTIFICATION_CRX_INSTALLER_DONE, 745 content::Source<CrxInstaller>(this), 746 content::Details<const Extension>( 747 success ? extension() : NULL)); 748 749 if (success) 750 ConfirmReEnable(); 751 752} 753 754void CrxInstaller::CleanupTempFiles() { 755 if (!installer_task_runner_->RunsTasksOnCurrentThread()) { 756 if (!installer_task_runner_->PostTask( 757 FROM_HERE, 758 base::Bind(&CrxInstaller::CleanupTempFiles, this))) { 759 NOTREACHED(); 760 } 761 return; 762 } 763 764 // Delete the temp directory and crx file as necessary. 765 if (!temp_dir_.value().empty()) { 766 extension_file_util::DeleteFile(temp_dir_, true); 767 temp_dir_ = base::FilePath(); 768 } 769 770 if (delete_source_ && !source_file_.value().empty()) { 771 extension_file_util::DeleteFile(source_file_, false); 772 source_file_ = base::FilePath(); 773 } 774} 775 776void CrxInstaller::CheckUpdateFromSettingsPage() { 777 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 778 779 ExtensionService* service = service_weak_; 780 if (!service || service->browser_terminating()) 781 return; 782 783 if (off_store_install_allow_reason_ != OffStoreInstallAllowedFromSettingsPage) 784 return; 785 786 const Extension* installed_extension = 787 service->GetInstalledExtension(extension()->id()); 788 if (installed_extension) { 789 // Previous version of the extension exists. 790 update_from_settings_page_ = true; 791 expected_id_ = installed_extension->id(); 792 install_source_ = installed_extension->location(); 793 install_cause_ = extension_misc::INSTALL_CAUSE_UPDATE; 794 } 795} 796 797void CrxInstaller::ConfirmReEnable() { 798 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 799 800 ExtensionService* service = service_weak_; 801 if (!service || service->browser_terminating()) 802 return; 803 804 if (!update_from_settings_page_) 805 return; 806 807 ExtensionPrefs* prefs = service->extension_prefs(); 808 if (!prefs->DidExtensionEscalatePermissions(extension()->id())) 809 return; 810 811 if (client_) { 812 AddRef(); // Balanced in InstallUIProceed() and InstallUIAbort(). 813 client_->ConfirmReEnable(this, extension()); 814 } 815} 816 817} // namespace extensions 818