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
WebstoreStandaloneInstaller(const std::string & webstore_item_id,Profile * profile,const Callback & callback)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
BeginInstall()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
~WebstoreStandaloneInstaller()87 WebstoreStandaloneInstaller::~WebstoreStandaloneInstaller() {
88 }
89
RunCallback(bool success,const std::string & error,webstore_install::Result result)90 void WebstoreStandaloneInstaller::RunCallback(bool success,
91 const std::string& error,
92 webstore_install::Result result) {
93 callback_.Run(success, error, result);
94 }
95
AbortInstall()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
EnsureUniqueInstall(webstore_install::Result * reason,std::string * error)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
CompleteInstall(webstore_install::Result result,const std::string & error)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
ProceedWithInstallPrompt()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>
GetLocalizedExtensionForDisplay()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
InitInstallData(ActiveInstallData * install_data) const170 void WebstoreStandaloneInstaller::InitInstallData(
171 ActiveInstallData* install_data) const {
172 // Default implementation sets no properties.
173 }
174
OnManifestParsed()175 void WebstoreStandaloneInstaller::OnManifestParsed() {
176 ProceedWithInstallPrompt();
177 }
178
179 scoped_ptr<ExtensionInstallPrompt>
CreateInstallUI()180 WebstoreStandaloneInstaller::CreateInstallUI() {
181 return make_scoped_ptr(new ExtensionInstallPrompt(GetWebContents()));
182 }
183
184 scoped_ptr<WebstoreInstaller::Approval>
CreateApproval() const185 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
InstallUIProceed()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
InstallUIAbort(bool user_initiated)264 void WebstoreStandaloneInstaller::InstallUIAbort(bool user_initiated) {
265 CompleteInstall(webstore_install::USER_CANCELLED, kUserCancelledError);
266 }
267
OnWebstoreRequestFailure()268 void WebstoreStandaloneInstaller::OnWebstoreRequestFailure() {
269 OnWebStoreDataFetcherDone();
270 CompleteInstall(webstore_install::WEBSTORE_REQUEST_ERROR,
271 kWebstoreRequestError);
272 }
273
OnWebstoreResponseParseSuccess(scoped_ptr<base::DictionaryValue> webstore_data)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
OnWebstoreResponseParseFailure(const std::string & error)361 void WebstoreStandaloneInstaller::OnWebstoreResponseParseFailure(
362 const std::string& error) {
363 OnWebStoreDataFetcherDone();
364 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE, error);
365 }
366
OnWebstoreParseSuccess(const std::string & id,const SkBitmap & icon,base::DictionaryValue * manifest)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
OnWebstoreParseFailure(const std::string & id,InstallHelperResultCode result_code,const std::string & error_message)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
OnExtensionInstallSuccess(const std::string & id)403 void WebstoreStandaloneInstaller::OnExtensionInstallSuccess(
404 const std::string& id) {
405 CHECK_EQ(id_, id);
406 CompleteInstall(webstore_install::SUCCESS, std::string());
407 }
408
OnExtensionInstallFailure(const std::string & id,const std::string & error,WebstoreInstaller::FailureReason reason)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
ShowInstallUI()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
OnWebStoreDataFetcherDone()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