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