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