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/webstore_standalone_installer.h" 6 7 #include "base/values.h" 8 #include "base/version.h" 9 #include "chrome/browser/extensions/crx_installer.h" 10 #include "chrome/browser/extensions/extension_install_prompt.h" 11 #include "chrome/browser/extensions/extension_install_ui.h" 12 #include "chrome/browser/extensions/extension_install_ui_util.h" 13 #include "chrome/browser/extensions/extension_service.h" 14 #include "chrome/browser/extensions/install_tracker.h" 15 #include "chrome/browser/extensions/webstore_data_fetcher.h" 16 #include "chrome/browser/profiles/profile.h" 17 #include "components/crx_file/id_util.h" 18 #include "content/public/browser/web_contents.h" 19 #include "extensions/browser/extension_prefs.h" 20 #include "extensions/browser/extension_registry.h" 21 #include "extensions/browser/extension_system.h" 22 #include "extensions/browser/extension_util.h" 23 #include "extensions/common/extension.h" 24 #include "extensions/common/extension_urls.h" 25 #include "url/gurl.h" 26 27 using content::WebContents; 28 29 namespace extensions { 30 31 const char kInvalidWebstoreItemId[] = "Invalid Chrome Web Store item ID"; 32 const char kWebstoreRequestError[] = 33 "Could not fetch data from the Chrome Web Store"; 34 const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse"; 35 const char kInvalidManifestError[] = "Invalid manifest"; 36 const char kUserCancelledError[] = "User cancelled install"; 37 const char kExtensionIsBlacklisted[] = "Extension is blacklisted"; 38 const char kInstallInProgressError[] = "An install is already in progress"; 39 const char kLaunchInProgressError[] = "A launch is already in progress"; 40 41 WebstoreStandaloneInstaller::WebstoreStandaloneInstaller( 42 const std::string& webstore_item_id, 43 Profile* profile, 44 const Callback& callback) 45 : id_(webstore_item_id), 46 callback_(callback), 47 profile_(profile), 48 install_source_(WebstoreInstaller::INSTALL_SOURCE_INLINE), 49 show_user_count_(true), 50 average_rating_(0.0), 51 rating_count_(0) { 52 } 53 54 void WebstoreStandaloneInstaller::BeginInstall() { 55 // Add a ref to keep this alive for WebstoreDataFetcher. 56 // All code paths from here eventually lead to either CompleteInstall or 57 // AbortInstall, which both release this ref. 58 AddRef(); 59 60 if (!crx_file::id_util::IdIsValid(id_)) { 61 CompleteInstall(webstore_install::INVALID_ID, kInvalidWebstoreItemId); 62 return; 63 } 64 65 webstore_install::Result result = webstore_install::OTHER_ERROR; 66 std::string error; 67 if (!EnsureUniqueInstall(&result, &error)) { 68 CompleteInstall(result, error); 69 return; 70 } 71 72 // Use the requesting page as the referrer both since that is more correct 73 // (it is the page that caused this request to happen) and so that we can 74 // track top sites that trigger inline install requests. 75 webstore_data_fetcher_.reset(new WebstoreDataFetcher( 76 this, 77 profile_->GetRequestContext(), 78 GetRequestorURL(), 79 id_)); 80 webstore_data_fetcher_->Start(); 81 } 82 83 // 84 // Private interface implementation. 85 // 86 87 WebstoreStandaloneInstaller::~WebstoreStandaloneInstaller() { 88 } 89 90 void WebstoreStandaloneInstaller::RunCallback(bool success, 91 const std::string& error, 92 webstore_install::Result result) { 93 callback_.Run(success, error, result); 94 } 95 96 void WebstoreStandaloneInstaller::AbortInstall() { 97 callback_.Reset(); 98 // Abort any in-progress fetches. 99 if (webstore_data_fetcher_) { 100 webstore_data_fetcher_.reset(); 101 scoped_active_install_.reset(); 102 Release(); // Matches the AddRef in BeginInstall. 103 } 104 } 105 106 bool WebstoreStandaloneInstaller::EnsureUniqueInstall( 107 webstore_install::Result* reason, 108 std::string* error) { 109 InstallTracker* tracker = InstallTracker::Get(profile_); 110 DCHECK(tracker); 111 112 const ActiveInstallData* existing_install_data = 113 tracker->GetActiveInstall(id_); 114 if (existing_install_data) { 115 if (existing_install_data->is_ephemeral) { 116 *reason = webstore_install::LAUNCH_IN_PROGRESS; 117 *error = kLaunchInProgressError; 118 } else { 119 *reason = webstore_install::INSTALL_IN_PROGRESS; 120 *error = kInstallInProgressError; 121 } 122 return false; 123 } 124 125 ActiveInstallData install_data(id_); 126 InitInstallData(&install_data); 127 scoped_active_install_.reset(new ScopedActiveInstall(tracker, install_data)); 128 return true; 129 } 130 131 void WebstoreStandaloneInstaller::CompleteInstall( 132 webstore_install::Result result, 133 const std::string& error) { 134 scoped_active_install_.reset(); 135 if (!callback_.is_null()) 136 callback_.Run(result == webstore_install::SUCCESS, error, result); 137 Release(); // Matches the AddRef in BeginInstall. 138 } 139 140 void WebstoreStandaloneInstaller::ProceedWithInstallPrompt() { 141 install_prompt_ = CreateInstallPrompt(); 142 if (install_prompt_.get()) { 143 ShowInstallUI(); 144 // Control flow finishes up in InstallUIProceed or InstallUIAbort. 145 } else { 146 InstallUIProceed(); 147 } 148 } 149 150 scoped_refptr<const Extension> 151 WebstoreStandaloneInstaller::GetLocalizedExtensionForDisplay() { 152 if (!localized_extension_for_display_.get()) { 153 DCHECK(manifest_.get()); 154 if (!manifest_.get()) 155 return NULL; 156 157 std::string error; 158 localized_extension_for_display_ = 159 ExtensionInstallPrompt::GetLocalizedExtensionForDisplay( 160 manifest_.get(), 161 Extension::REQUIRE_KEY | Extension::FROM_WEBSTORE, 162 id_, 163 localized_name_, 164 localized_description_, 165 &error); 166 } 167 return localized_extension_for_display_.get(); 168 } 169 170 void WebstoreStandaloneInstaller::InitInstallData( 171 ActiveInstallData* install_data) const { 172 // Default implementation sets no properties. 173 } 174 175 void WebstoreStandaloneInstaller::OnManifestParsed() { 176 ProceedWithInstallPrompt(); 177 } 178 179 scoped_ptr<ExtensionInstallPrompt> 180 WebstoreStandaloneInstaller::CreateInstallUI() { 181 return make_scoped_ptr(new ExtensionInstallPrompt(GetWebContents())); 182 } 183 184 scoped_ptr<WebstoreInstaller::Approval> 185 WebstoreStandaloneInstaller::CreateApproval() const { 186 scoped_ptr<WebstoreInstaller::Approval> approval( 187 WebstoreInstaller::Approval::CreateWithNoInstallPrompt( 188 profile_, 189 id_, 190 scoped_ptr<base::DictionaryValue>(manifest_.get()->DeepCopy()), 191 true)); 192 approval->skip_post_install_ui = !ShouldShowPostInstallUI(); 193 approval->use_app_installed_bubble = ShouldShowAppInstalledBubble(); 194 approval->installing_icon = gfx::ImageSkia::CreateFrom1xBitmap(icon_); 195 return approval.Pass(); 196 } 197 198 void WebstoreStandaloneInstaller::InstallUIProceed() { 199 if (!CheckRequestorAlive()) { 200 CompleteInstall(webstore_install::ABORTED, std::string()); 201 return; 202 } 203 204 scoped_ptr<WebstoreInstaller::Approval> approval = CreateApproval(); 205 206 ExtensionService* extension_service = 207 ExtensionSystem::Get(profile_)->extension_service(); 208 const Extension* installed_extension = 209 extension_service->GetExtensionById(id_, true /* include disabled */); 210 if (installed_extension) { 211 std::string install_message; 212 webstore_install::Result install_result = webstore_install::SUCCESS; 213 bool done = true; 214 215 if (ExtensionPrefs::Get(profile_)->IsExtensionBlacklisted(id_)) { 216 // Don't install a blacklisted extension. 217 install_result = webstore_install::BLACKLISTED; 218 install_message = kExtensionIsBlacklisted; 219 } else if (util::IsEphemeralApp(installed_extension->id(), profile_) && 220 !approval->is_ephemeral) { 221 // If the target extension has already been installed ephemerally and is 222 // up to date, it can be promoted to a regular installed extension and 223 // downloading from the Web Store is not necessary. 224 scoped_refptr<const Extension> extension_to_install = 225 GetLocalizedExtensionForDisplay(); 226 if (!extension_to_install.get()) { 227 CompleteInstall(webstore_install::INVALID_MANIFEST, 228 kInvalidManifestError); 229 return; 230 } 231 232 if (installed_extension->version()->CompareTo( 233 *extension_to_install->version()) < 0) { 234 // If the existing extension is out of date, proceed with the install 235 // to update the extension. 236 done = false; 237 } else { 238 install_ui::ShowPostInstallUIForApproval( 239 profile_, *approval, installed_extension); 240 extension_service->PromoteEphemeralApp(installed_extension, false); 241 } 242 } else if (!extension_service->IsExtensionEnabled(id_)) { 243 // If the extension is installed but disabled, and not blacklisted, 244 // enable it. 245 extension_service->EnableExtension(id_); 246 } // else extension is installed and enabled; no work to be done. 247 248 if (done) { 249 CompleteInstall(install_result, install_message); 250 return; 251 } 252 } 253 254 scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller( 255 profile_, 256 this, 257 GetWebContents(), 258 id_, 259 approval.Pass(), 260 install_source_); 261 installer->Start(); 262 } 263 264 void WebstoreStandaloneInstaller::InstallUIAbort(bool user_initiated) { 265 CompleteInstall(webstore_install::USER_CANCELLED, kUserCancelledError); 266 } 267 268 void WebstoreStandaloneInstaller::OnWebstoreRequestFailure() { 269 OnWebStoreDataFetcherDone(); 270 CompleteInstall(webstore_install::WEBSTORE_REQUEST_ERROR, 271 kWebstoreRequestError); 272 } 273 274 void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess( 275 scoped_ptr<base::DictionaryValue> webstore_data) { 276 OnWebStoreDataFetcherDone(); 277 278 if (!CheckRequestorAlive()) { 279 CompleteInstall(webstore_install::ABORTED, std::string()); 280 return; 281 } 282 283 std::string error; 284 285 if (!CheckInlineInstallPermitted(*webstore_data, &error)) { 286 CompleteInstall(webstore_install::NOT_PERMITTED, error); 287 return; 288 } 289 290 if (!CheckRequestorPermitted(*webstore_data, &error)) { 291 CompleteInstall(webstore_install::NOT_PERMITTED, error); 292 return; 293 } 294 295 // Manifest, number of users, average rating and rating count are required. 296 std::string manifest; 297 if (!webstore_data->GetString(kManifestKey, &manifest) || 298 !webstore_data->GetString(kUsersKey, &localized_user_count_) || 299 !webstore_data->GetDouble(kAverageRatingKey, &average_rating_) || 300 !webstore_data->GetInteger(kRatingCountKey, &rating_count_)) { 301 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE, 302 kInvalidWebstoreResponseError); 303 return; 304 } 305 306 // Optional. 307 show_user_count_ = true; 308 webstore_data->GetBoolean(kShowUserCountKey, &show_user_count_); 309 310 if (average_rating_ < ExtensionInstallPrompt::kMinExtensionRating || 311 average_rating_ > ExtensionInstallPrompt::kMaxExtensionRating) { 312 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE, 313 kInvalidWebstoreResponseError); 314 return; 315 } 316 317 // Localized name and description are optional. 318 if ((webstore_data->HasKey(kLocalizedNameKey) && 319 !webstore_data->GetString(kLocalizedNameKey, &localized_name_)) || 320 (webstore_data->HasKey(kLocalizedDescriptionKey) && 321 !webstore_data->GetString( 322 kLocalizedDescriptionKey, &localized_description_))) { 323 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE, 324 kInvalidWebstoreResponseError); 325 return; 326 } 327 328 // Icon URL is optional. 329 GURL icon_url; 330 if (webstore_data->HasKey(kIconUrlKey)) { 331 std::string icon_url_string; 332 if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) { 333 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE, 334 kInvalidWebstoreResponseError); 335 return; 336 } 337 icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve( 338 icon_url_string); 339 if (!icon_url.is_valid()) { 340 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE, 341 kInvalidWebstoreResponseError); 342 return; 343 } 344 } 345 346 // Assume ownership of webstore_data. 347 webstore_data_ = webstore_data.Pass(); 348 349 scoped_refptr<WebstoreInstallHelper> helper = 350 new WebstoreInstallHelper(this, 351 id_, 352 manifest, 353 std::string(), // We don't have any icon data. 354 icon_url, 355 profile_->GetRequestContext()); 356 // The helper will call us back via OnWebstoreParseSucces or 357 // OnWebstoreParseFailure. 358 helper->Start(); 359 } 360 361 void WebstoreStandaloneInstaller::OnWebstoreResponseParseFailure( 362 const std::string& error) { 363 OnWebStoreDataFetcherDone(); 364 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE, error); 365 } 366 367 void WebstoreStandaloneInstaller::OnWebstoreParseSuccess( 368 const std::string& id, 369 const SkBitmap& icon, 370 base::DictionaryValue* manifest) { 371 CHECK_EQ(id_, id); 372 373 if (!CheckRequestorAlive()) { 374 CompleteInstall(webstore_install::ABORTED, std::string()); 375 return; 376 } 377 378 manifest_.reset(manifest); 379 icon_ = icon; 380 381 OnManifestParsed(); 382 } 383 384 void WebstoreStandaloneInstaller::OnWebstoreParseFailure( 385 const std::string& id, 386 InstallHelperResultCode result_code, 387 const std::string& error_message) { 388 webstore_install::Result install_result = webstore_install::OTHER_ERROR; 389 switch (result_code) { 390 case WebstoreInstallHelper::Delegate::MANIFEST_ERROR: 391 install_result = webstore_install::INVALID_MANIFEST; 392 break; 393 case WebstoreInstallHelper::Delegate::ICON_ERROR: 394 install_result = webstore_install::ICON_ERROR; 395 break; 396 default: 397 break; 398 } 399 400 CompleteInstall(install_result, error_message); 401 } 402 403 void WebstoreStandaloneInstaller::OnExtensionInstallSuccess( 404 const std::string& id) { 405 CHECK_EQ(id_, id); 406 CompleteInstall(webstore_install::SUCCESS, std::string()); 407 } 408 409 void WebstoreStandaloneInstaller::OnExtensionInstallFailure( 410 const std::string& id, 411 const std::string& error, 412 WebstoreInstaller::FailureReason reason) { 413 CHECK_EQ(id_, id); 414 415 webstore_install::Result install_result = webstore_install::OTHER_ERROR; 416 switch (reason) { 417 case WebstoreInstaller::FAILURE_REASON_CANCELLED: 418 install_result = webstore_install::USER_CANCELLED; 419 break; 420 case WebstoreInstaller::FAILURE_REASON_DEPENDENCY_NOT_FOUND: 421 case WebstoreInstaller::FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE: 422 install_result = webstore_install::MISSING_DEPENDENCIES; 423 break; 424 default: 425 break; 426 } 427 428 CompleteInstall(install_result, error); 429 } 430 431 void WebstoreStandaloneInstaller::ShowInstallUI() { 432 scoped_refptr<const Extension> localized_extension = 433 GetLocalizedExtensionForDisplay(); 434 if (!localized_extension.get()) { 435 CompleteInstall(webstore_install::INVALID_MANIFEST, kInvalidManifestError); 436 return; 437 } 438 439 install_ui_ = CreateInstallUI(); 440 install_ui_->ConfirmStandaloneInstall( 441 this, localized_extension.get(), &icon_, install_prompt_); 442 } 443 444 void WebstoreStandaloneInstaller::OnWebStoreDataFetcherDone() { 445 // An instance of this class is passed in as a delegate for the 446 // WebstoreInstallHelper, ExtensionInstallPrompt and WebstoreInstaller, and 447 // therefore needs to remain alive until they are done. Clear the webstore 448 // data fetcher to avoid calling Release in AbortInstall while any of these 449 // operations are in progress. 450 webstore_data_fetcher_.reset(); 451 } 452 453 } // namespace extensions 454