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
27using content::WebContents;
28
29namespace extensions {
30
31const char kInvalidWebstoreItemId[] = "Invalid Chrome Web Store item ID";
32const char kWebstoreRequestError[] =
33    "Could not fetch data from the Chrome Web Store";
34const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
35const char kInvalidManifestError[] = "Invalid manifest";
36const char kUserCancelledError[] = "User cancelled install";
37const char kExtensionIsBlacklisted[] = "Extension is blacklisted";
38const char kInstallInProgressError[] = "An install is already in progress";
39const char kLaunchInProgressError[] = "A launch is already in progress";
40
41WebstoreStandaloneInstaller::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
54void 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
87WebstoreStandaloneInstaller::~WebstoreStandaloneInstaller() {
88}
89
90void WebstoreStandaloneInstaller::RunCallback(bool success,
91                                              const std::string& error,
92                                              webstore_install::Result result) {
93  callback_.Run(success, error, result);
94}
95
96void 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
106bool 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
131void 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
140void 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
150scoped_refptr<const Extension>
151WebstoreStandaloneInstaller::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
170void WebstoreStandaloneInstaller::InitInstallData(
171    ActiveInstallData* install_data) const {
172  // Default implementation sets no properties.
173}
174
175void WebstoreStandaloneInstaller::OnManifestParsed() {
176  ProceedWithInstallPrompt();
177}
178
179scoped_ptr<ExtensionInstallPrompt>
180WebstoreStandaloneInstaller::CreateInstallUI() {
181  return make_scoped_ptr(new ExtensionInstallPrompt(GetWebContents()));
182}
183
184scoped_ptr<WebstoreInstaller::Approval>
185WebstoreStandaloneInstaller::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
198void 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
264void WebstoreStandaloneInstaller::InstallUIAbort(bool user_initiated) {
265  CompleteInstall(webstore_install::USER_CANCELLED, kUserCancelledError);
266}
267
268void WebstoreStandaloneInstaller::OnWebstoreRequestFailure() {
269  OnWebStoreDataFetcherDone();
270  CompleteInstall(webstore_install::WEBSTORE_REQUEST_ERROR,
271                  kWebstoreRequestError);
272}
273
274void 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
361void WebstoreStandaloneInstaller::OnWebstoreResponseParseFailure(
362    const std::string& error) {
363  OnWebStoreDataFetcherDone();
364  CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE, error);
365}
366
367void 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
384void 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
403void WebstoreStandaloneInstaller::OnExtensionInstallSuccess(
404    const std::string& id) {
405  CHECK_EQ(id_, id);
406  CompleteInstall(webstore_install::SUCCESS, std::string());
407}
408
409void 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
431void 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
444void 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