1 // Copyright (c) 2011 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/file_util.h" 11 #include "base/lazy_instance.h" 12 #include "base/memory/scoped_temp_dir.h" 13 #include "base/metrics/histogram.h" 14 #include "base/path_service.h" 15 #include "base/stl_util-inl.h" 16 #include "base/stringprintf.h" 17 #include "base/task.h" 18 #include "base/threading/thread_restrictions.h" 19 #include "base/time.h" 20 #include "base/utf_string_conversions.h" 21 #include "base/version.h" 22 #include "chrome/browser/browser_process.h" 23 #include "chrome/browser/extensions/convert_user_script.h" 24 #include "chrome/browser/extensions/convert_web_app.h" 25 #include "chrome/browser/extensions/extension_error_reporter.h" 26 #include "chrome/browser/extensions/extension_service.h" 27 #include "chrome/browser/shell_integration.h" 28 #include "chrome/browser/web_applications/web_app.h" 29 #include "chrome/common/chrome_paths.h" 30 #include "chrome/common/extensions/extension_constants.h" 31 #include "chrome/common/extensions/extension_file_util.h" 32 #include "content/browser/browser_thread.h" 33 #include "content/common/notification_service.h" 34 #include "content/common/notification_type.h" 35 #include "grit/chromium_strings.h" 36 #include "grit/generated_resources.h" 37 #include "grit/theme_resources.h" 38 #include "third_party/skia/include/core/SkBitmap.h" 39 #include "ui/base/l10n/l10n_util.h" 40 #include "ui/base/resource/resource_bundle.h" 41 42 namespace { 43 44 struct WhitelistedInstallData { 45 WhitelistedInstallData() {} 46 std::set<std::string> ids; 47 std::map<std::string, linked_ptr<DictionaryValue> > manifests; 48 }; 49 50 static base::LazyInstance<WhitelistedInstallData> 51 g_whitelisted_install_data(base::LINKER_INITIALIZED); 52 53 } // namespace 54 55 // static 56 void CrxInstaller::SetWhitelistedInstallId(const std::string& id) { 57 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 58 g_whitelisted_install_data.Get().ids.insert(id); 59 } 60 61 // static 62 void CrxInstaller::SetWhitelistedManifest(const std::string& id, 63 DictionaryValue* parsed_manifest) { 64 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 65 WhitelistedInstallData& data = g_whitelisted_install_data.Get(); 66 data.manifests[id] = linked_ptr<DictionaryValue>(parsed_manifest); 67 } 68 69 // static 70 const DictionaryValue* CrxInstaller::GetWhitelistedManifest( 71 const std::string& id) { 72 WhitelistedInstallData& data = g_whitelisted_install_data.Get(); 73 if (ContainsKey(data.manifests, id)) 74 return data.manifests[id].get(); 75 else 76 return NULL; 77 } 78 79 // static 80 DictionaryValue* CrxInstaller::RemoveWhitelistedManifest( 81 const std::string& id) { 82 WhitelistedInstallData& data = g_whitelisted_install_data.Get(); 83 if (ContainsKey(data.manifests, id)) { 84 DictionaryValue* manifest = data.manifests[id].release(); 85 data.manifests.erase(id); 86 return manifest; 87 } 88 return NULL; 89 } 90 91 // static 92 bool CrxInstaller::IsIdWhitelisted(const std::string& id) { 93 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 94 std::set<std::string>& ids = g_whitelisted_install_data.Get().ids; 95 return ContainsKey(ids, id); 96 } 97 98 // static 99 bool CrxInstaller::ClearWhitelistedInstallId(const std::string& id) { 100 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 101 std::set<std::string>& ids = g_whitelisted_install_data.Get().ids; 102 if (ContainsKey(ids, id)) { 103 ids.erase(id); 104 return true; 105 } 106 return false; 107 } 108 109 CrxInstaller::CrxInstaller(ExtensionService* frontend, 110 ExtensionInstallUI* client) 111 : install_directory_(frontend->install_directory()), 112 install_source_(Extension::INTERNAL), 113 extensions_enabled_(frontend->extensions_enabled()), 114 delete_source_(false), 115 is_gallery_install_(false), 116 create_app_shortcut_(false), 117 frontend_(frontend), 118 client_(client), 119 apps_require_extension_mime_type_(false), 120 allow_silent_install_(false) { 121 } 122 123 CrxInstaller::~CrxInstaller() { 124 // Delete the temp directory and crx file as necessary. Note that the 125 // destructor might be called on any thread, so we post a task to the file 126 // thread to make sure the delete happens there. 127 if (!temp_dir_.value().empty()) { 128 BrowserThread::PostTask( 129 BrowserThread::FILE, FROM_HERE, 130 NewRunnableFunction( 131 &extension_file_util::DeleteFile, temp_dir_, true)); 132 } 133 134 if (delete_source_) { 135 BrowserThread::PostTask( 136 BrowserThread::FILE, FROM_HERE, 137 NewRunnableFunction( 138 &extension_file_util::DeleteFile, source_file_, false)); 139 } 140 141 // Make sure the UI is deleted on the ui thread. 142 BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, client_); 143 client_ = NULL; 144 } 145 146 void CrxInstaller::InstallCrx(const FilePath& source_file) { 147 source_file_ = source_file; 148 149 scoped_refptr<SandboxedExtensionUnpacker> unpacker( 150 new SandboxedExtensionUnpacker( 151 source_file, 152 g_browser_process->resource_dispatcher_host(), 153 this)); 154 155 BrowserThread::PostTask( 156 BrowserThread::FILE, FROM_HERE, 157 NewRunnableMethod( 158 unpacker.get(), &SandboxedExtensionUnpacker::Start)); 159 } 160 161 void CrxInstaller::InstallUserScript(const FilePath& source_file, 162 const GURL& original_url) { 163 DCHECK(!original_url.is_empty()); 164 165 source_file_ = source_file; 166 original_url_ = original_url; 167 168 BrowserThread::PostTask( 169 BrowserThread::FILE, FROM_HERE, 170 NewRunnableMethod(this, &CrxInstaller::ConvertUserScriptOnFileThread)); 171 } 172 173 void CrxInstaller::ConvertUserScriptOnFileThread() { 174 std::string error; 175 scoped_refptr<Extension> extension = 176 ConvertUserScriptToExtension(source_file_, original_url_, &error); 177 if (!extension) { 178 ReportFailureFromFileThread(error); 179 return; 180 } 181 182 OnUnpackSuccess(extension->path(), extension->path(), extension); 183 } 184 185 void CrxInstaller::InstallWebApp(const WebApplicationInfo& web_app) { 186 BrowserThread::PostTask( 187 BrowserThread::FILE, FROM_HERE, 188 NewRunnableMethod(this, &CrxInstaller::ConvertWebAppOnFileThread, 189 web_app)); 190 } 191 192 void CrxInstaller::ConvertWebAppOnFileThread( 193 const WebApplicationInfo& web_app) { 194 std::string error; 195 scoped_refptr<Extension> extension( 196 ConvertWebAppToExtension(web_app, base::Time::Now())); 197 if (!extension) { 198 // Validation should have stopped any potential errors before getting here. 199 NOTREACHED() << "Could not convert web app to extension."; 200 return; 201 } 202 203 // TODO(aa): conversion data gets lost here :( 204 205 OnUnpackSuccess(extension->path(), extension->path(), extension); 206 } 207 208 bool CrxInstaller::AllowInstall(const Extension* extension, 209 std::string* error) { 210 DCHECK(error); 211 212 // Make sure the expected id matches. 213 if (!expected_id_.empty() && expected_id_ != extension->id()) { 214 *error = base::StringPrintf( 215 "ID in new CRX manifest (%s) does not match expected id (%s)", 216 extension->id().c_str(), 217 expected_id_.c_str()); 218 return false; 219 } 220 221 if (expected_version_.get() && 222 !expected_version_->Equals(*extension->version())) { 223 *error = base::StringPrintf( 224 "Version in new CRX %s manifest (%s) does not match expected " 225 "version (%s)", 226 extension->id().c_str(), 227 expected_version_->GetString().c_str(), 228 extension->version()->GetString().c_str()); 229 return false; 230 } 231 232 // The checks below are skipped for themes and external installs. 233 if (extension->is_theme() || Extension::IsExternalLocation(install_source_)) 234 return true; 235 236 if (!extensions_enabled_) { 237 *error = "Extensions are not enabled."; 238 return false; 239 } 240 241 if (extension_->is_app()) { 242 // If the app was downloaded, apps_require_extension_mime_type_ 243 // will be set. In this case, check that it was served with the 244 // right mime type. Make an exception for file URLs, which come 245 // from the users computer and have no headers. 246 if (!original_url_.SchemeIsFile() && 247 apps_require_extension_mime_type_ && 248 original_mime_type_ != Extension::kMimeType) { 249 *error = base::StringPrintf( 250 "Apps must be served with content type %s.", 251 Extension::kMimeType); 252 return false; 253 } 254 255 // If the client_ is NULL, then the app is either being installed via 256 // an internal mechanism like sync, external_extensions, or default apps. 257 // In that case, we don't want to enforce things like the install origin. 258 if (!is_gallery_install_ && client_) { 259 // For apps with a gallery update URL, require that they be installed 260 // from the gallery. 261 // TODO(erikkay) Apply this rule for paid extensions and themes as well. 262 if (extension->UpdatesFromGallery()) { 263 *error = l10n_util::GetStringFUTF8( 264 IDS_EXTENSION_DISALLOW_NON_DOWNLOADED_GALLERY_INSTALLS, 265 l10n_util::GetStringUTF16(IDS_EXTENSION_WEB_STORE_TITLE)); 266 return false; 267 } 268 269 // For self-hosted apps, verify that the entire extent is on the same 270 // host (or a subdomain of the host) the download happened from. There's 271 // no way for us to verify that the app controls any other hosts. 272 URLPattern pattern(UserScript::kValidUserScriptSchemes); 273 pattern.set_host(original_url_.host()); 274 pattern.set_match_subdomains(true); 275 276 ExtensionExtent::PatternList patterns = 277 extension_->web_extent().patterns(); 278 for (size_t i = 0; i < patterns.size(); ++i) { 279 if (!pattern.MatchesHost(patterns[i].host())) { 280 *error = base::StringPrintf( 281 "Apps must be served from the host that they affect."); 282 return false; 283 } 284 } 285 } 286 } 287 288 return true; 289 } 290 291 void CrxInstaller::OnUnpackFailure(const std::string& error_message) { 292 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 293 ReportFailureFromFileThread(error_message); 294 } 295 296 void CrxInstaller::OnUnpackSuccess(const FilePath& temp_dir, 297 const FilePath& extension_dir, 298 const Extension* extension) { 299 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 300 301 // Note: We take ownership of |extension| and |temp_dir|. 302 extension_ = extension; 303 temp_dir_ = temp_dir; 304 305 // We don't have to delete the unpack dir explicity since it is a child of 306 // the temp dir. 307 unpacked_extension_root_ = extension_dir; 308 309 std::string error; 310 if (!AllowInstall(extension, &error)) { 311 ReportFailureFromFileThread(error); 312 return; 313 } 314 315 if (client_) { 316 Extension::DecodeIcon(extension_.get(), Extension::EXTENSION_ICON_LARGE, 317 &install_icon_); 318 } 319 320 BrowserThread::PostTask( 321 BrowserThread::UI, FROM_HERE, 322 NewRunnableMethod(this, &CrxInstaller::ConfirmInstall)); 323 } 324 325 // Helper method to let us compare a whitelisted manifest with the actual 326 // downloaded extension's manifest, but ignoring the kPublicKey since the 327 // whitelisted manifest doesn't have that value. 328 static bool EqualsIgnoringPublicKey( 329 const DictionaryValue& extension_manifest, 330 const DictionaryValue& whitelisted_manifest) { 331 scoped_ptr<DictionaryValue> manifest_copy(extension_manifest.DeepCopy()); 332 manifest_copy->Remove(extension_manifest_keys::kPublicKey, NULL); 333 return manifest_copy->Equals(&whitelisted_manifest); 334 } 335 336 void CrxInstaller::ConfirmInstall() { 337 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 338 if (frontend_->extension_prefs()->IsExtensionBlacklisted(extension_->id())) { 339 VLOG(1) << "This extension: " << extension_->id() 340 << " is blacklisted. Install failed."; 341 ReportFailureFromUIThread("This extension is blacklisted."); 342 return; 343 } 344 345 if (!frontend_->extension_prefs()->IsExtensionAllowedByPolicy( 346 extension_->id())) { 347 ReportFailureFromUIThread("This extension is blacklisted by admin policy."); 348 return; 349 } 350 351 GURL overlapping_url; 352 const Extension* overlapping_extension = 353 frontend_->GetExtensionByOverlappingWebExtent(extension_->web_extent()); 354 if (overlapping_extension && 355 overlapping_extension->id() != extension_->id()) { 356 ReportFailureFromUIThread(l10n_util::GetStringFUTF8( 357 IDS_EXTENSION_OVERLAPPING_WEB_EXTENT, 358 UTF8ToUTF16(overlapping_extension->name()))); 359 return; 360 } 361 362 current_version_ = 363 frontend_->extension_prefs()->GetVersionString(extension_->id()); 364 365 // First see if it's whitelisted by id (the old mechanism). 366 bool whitelisted = ClearWhitelistedInstallId(extension_->id()) && 367 extension_->plugins().empty() && is_gallery_install_; 368 369 // Now check if it's whitelisted by manifest. 370 scoped_ptr<DictionaryValue> whitelisted_manifest( 371 RemoveWhitelistedManifest(extension_->id())); 372 if (is_gallery_install_ && whitelisted_manifest.get()) { 373 if (!EqualsIgnoringPublicKey(*extension_->manifest_value(), 374 *whitelisted_manifest)) { 375 ReportFailureFromUIThread( 376 l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_INVALID)); 377 return; 378 } 379 whitelisted = true; 380 } 381 382 if (client_ && 383 (!allow_silent_install_ || !whitelisted)) { 384 AddRef(); // Balanced in Proceed() and Abort(). 385 client_->ConfirmInstall(this, extension_.get()); 386 } else { 387 BrowserThread::PostTask( 388 BrowserThread::FILE, FROM_HERE, 389 NewRunnableMethod(this, &CrxInstaller::CompleteInstall)); 390 } 391 return; 392 } 393 394 void CrxInstaller::InstallUIProceed() { 395 BrowserThread::PostTask( 396 BrowserThread::FILE, FROM_HERE, 397 NewRunnableMethod(this, &CrxInstaller::CompleteInstall)); 398 399 Release(); // balanced in ConfirmInstall(). 400 } 401 402 void CrxInstaller::InstallUIAbort() { 403 // Technically, this can be called for other reasons than the user hitting 404 // cancel, but they're rare. 405 ExtensionService::RecordPermissionMessagesHistogram( 406 extension_, "Extensions.Permissions_InstallCancel"); 407 408 // Kill the theme loading bubble. 409 NotificationService* service = NotificationService::current(); 410 service->Notify(NotificationType::NO_THEME_DETECTED, 411 Source<CrxInstaller>(this), 412 NotificationService::NoDetails()); 413 Release(); // balanced in ConfirmInstall(). 414 415 // We're done. Since we don't post any more tasks to ourself, our ref count 416 // should go to zero and we die. The destructor will clean up the temp dir. 417 } 418 419 void CrxInstaller::CompleteInstall() { 420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 421 422 if (!current_version_.empty()) { 423 scoped_ptr<Version> current_version( 424 Version::GetVersionFromString(current_version_)); 425 if (current_version->CompareTo(*(extension_->version())) > 0) { 426 ReportFailureFromFileThread("Attempted to downgrade extension."); 427 return; 428 } 429 } 430 431 // See how long extension install paths are. This is important on 432 // windows, because file operations may fail if the path to a file 433 // exceeds a small constant. See crbug.com/69693 . 434 UMA_HISTOGRAM_CUSTOM_COUNTS( 435 "Extensions.CrxInstallDirPathLength", 436 install_directory_.value().length(), 0, 500, 100); 437 438 FilePath version_dir = extension_file_util::InstallExtension( 439 unpacked_extension_root_, 440 extension_->id(), 441 extension_->VersionString(), 442 install_directory_); 443 if (version_dir.empty()) { 444 ReportFailureFromFileThread( 445 l10n_util::GetStringUTF8( 446 IDS_EXTENSION_MOVE_DIRECTORY_TO_PROFILE_FAILED)); 447 return; 448 } 449 450 // This is lame, but we must reload the extension because absolute paths 451 // inside the content scripts are established inside InitFromValue() and we 452 // just moved the extension. 453 // TODO(aa): All paths to resources inside extensions should be created 454 // lazily and based on the Extension's root path at that moment. 455 std::string error; 456 extension_ = extension_file_util::LoadExtension( 457 version_dir, 458 install_source_, 459 Extension::REQUIRE_KEY, 460 &error); 461 CHECK(error.empty()) << error; 462 463 ReportSuccessFromFileThread(); 464 } 465 466 void CrxInstaller::ReportFailureFromFileThread(const std::string& error) { 467 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 468 BrowserThread::PostTask( 469 BrowserThread::UI, FROM_HERE, 470 NewRunnableMethod(this, &CrxInstaller::ReportFailureFromUIThread, error)); 471 } 472 473 void CrxInstaller::ReportFailureFromUIThread(const std::string& error) { 474 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 475 476 NotificationService* service = NotificationService::current(); 477 service->Notify(NotificationType::EXTENSION_INSTALL_ERROR, 478 Source<CrxInstaller>(this), 479 Details<const std::string>(&error)); 480 481 // This isn't really necessary, it is only used because unit tests expect to 482 // see errors get reported via this interface. 483 // 484 // TODO(aa): Need to go through unit tests and clean them up too, probably get 485 // rid of this line. 486 ExtensionErrorReporter::GetInstance()->ReportError(error, false); // quiet 487 488 if (client_) 489 client_->OnInstallFailure(error); 490 } 491 492 void CrxInstaller::ReportSuccessFromFileThread() { 493 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 494 BrowserThread::PostTask( 495 BrowserThread::UI, FROM_HERE, 496 NewRunnableMethod(this, &CrxInstaller::ReportSuccessFromUIThread)); 497 } 498 499 void CrxInstaller::ReportSuccessFromUIThread() { 500 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 501 502 // If there is a client, tell the client about installation. 503 if (client_) 504 client_->OnInstallSuccess(extension_.get(), install_icon_.get()); 505 506 // Tell the frontend about the installation and hand off ownership of 507 // extension_ to it. 508 frontend_->OnExtensionInstalled(extension_); 509 extension_ = NULL; 510 511 // We're done. We don't post any more tasks to ourselves so we are deleted 512 // soon. 513 } 514