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 "chrome/browser/extensions/crx_installer.h"
9#include "chrome/browser/extensions/extension_install_prompt.h"
10#include "chrome/browser/extensions/extension_install_ui.h"
11#include "chrome/browser/extensions/extension_service.h"
12#include "chrome/browser/extensions/webstore_data_fetcher.h"
13#include "chrome/browser/profiles/profile.h"
14#include "content/public/browser/web_contents.h"
15#include "extensions/common/extension.h"
16#include "url/gurl.h"
17
18using content::WebContents;
19
20namespace extensions {
21
22const char kManifestKey[] = "manifest";
23const char kIconUrlKey[] = "icon_url";
24const char kLocalizedNameKey[] = "localized_name";
25const char kLocalizedDescriptionKey[] = "localized_description";
26const char kUsersKey[] = "users";
27const char kShowUserCountKey[] = "show_user_count";
28const char kAverageRatingKey[] = "average_rating";
29const char kRatingCountKey[] = "rating_count";
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";
37
38
39WebstoreStandaloneInstaller::WebstoreStandaloneInstaller(
40    const std::string& webstore_item_id,
41    Profile* profile,
42    const Callback& callback)
43    : id_(webstore_item_id),
44      callback_(callback),
45      profile_(profile),
46      install_source_(WebstoreInstaller::INSTALL_SOURCE_INLINE),
47      show_user_count_(true),
48      average_rating_(0.0),
49      rating_count_(0) {
50}
51
52WebstoreStandaloneInstaller::~WebstoreStandaloneInstaller() {}
53
54//
55// Private interface implementation.
56//
57
58void WebstoreStandaloneInstaller::BeginInstall() {
59  // Add a ref to keep this alive for WebstoreDataFetcher.
60  // All code paths from here eventually lead to either CompleteInstall or
61  // AbortInstall, which both release this ref.
62  AddRef();
63
64  if (!Extension::IdIsValid(id_)) {
65    CompleteInstall(kInvalidWebstoreItemId);
66    return;
67  }
68
69  // Use the requesting page as the referrer both since that is more correct
70  // (it is the page that caused this request to happen) and so that we can
71  // track top sites that trigger inline install requests.
72  webstore_data_fetcher_.reset(new WebstoreDataFetcher(
73      this,
74      profile_->GetRequestContext(),
75      GetRequestorURL(),
76      id_));
77  webstore_data_fetcher_->Start();
78}
79
80bool WebstoreStandaloneInstaller::CheckInstallValid(
81    const base::DictionaryValue& manifest,
82    std::string* error) {
83  return true;
84}
85
86scoped_ptr<ExtensionInstallPrompt>
87WebstoreStandaloneInstaller::CreateInstallUI() {
88  return make_scoped_ptr(new ExtensionInstallPrompt(GetWebContents()));
89}
90
91scoped_ptr<WebstoreInstaller::Approval>
92WebstoreStandaloneInstaller::CreateApproval() const {
93  scoped_ptr<WebstoreInstaller::Approval> approval(
94      WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
95          profile_,
96          id_,
97          scoped_ptr<base::DictionaryValue>(manifest_.get()->DeepCopy()),
98          true));
99  approval->skip_post_install_ui = !ShouldShowPostInstallUI();
100  approval->use_app_installed_bubble = ShouldShowAppInstalledBubble();
101  approval->installing_icon = gfx::ImageSkia::CreateFrom1xBitmap(icon_);
102  return approval.Pass();
103}
104
105void WebstoreStandaloneInstaller::OnWebstoreRequestFailure() {
106  CompleteInstall(kWebstoreRequestError);
107}
108
109void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess(
110    scoped_ptr<DictionaryValue> webstore_data) {
111  if (!CheckRequestorAlive()) {
112    CompleteInstall(std::string());
113    return;
114  }
115
116  std::string error;
117
118  if (!CheckInlineInstallPermitted(*webstore_data, &error)) {
119    CompleteInstall(error);
120    return;
121  }
122
123  if (!CheckRequestorPermitted(*webstore_data, &error)) {
124    CompleteInstall(error);
125    return;
126  }
127
128  // Manifest, number of users, average rating and rating count are required.
129  std::string manifest;
130  if (!webstore_data->GetString(kManifestKey, &manifest) ||
131      !webstore_data->GetString(kUsersKey, &localized_user_count_) ||
132      !webstore_data->GetDouble(kAverageRatingKey, &average_rating_) ||
133      !webstore_data->GetInteger(kRatingCountKey, &rating_count_)) {
134    CompleteInstall(kInvalidWebstoreResponseError);
135    return;
136  }
137
138  // Optional.
139  show_user_count_ = true;
140  webstore_data->GetBoolean(kShowUserCountKey, &show_user_count_);
141
142  if (average_rating_ < ExtensionInstallPrompt::kMinExtensionRating ||
143      average_rating_ > ExtensionInstallPrompt::kMaxExtensionRating) {
144    CompleteInstall(kInvalidWebstoreResponseError);
145    return;
146  }
147
148  // Localized name and description are optional.
149  if ((webstore_data->HasKey(kLocalizedNameKey) &&
150      !webstore_data->GetString(kLocalizedNameKey, &localized_name_)) ||
151      (webstore_data->HasKey(kLocalizedDescriptionKey) &&
152      !webstore_data->GetString(
153          kLocalizedDescriptionKey, &localized_description_))) {
154    CompleteInstall(kInvalidWebstoreResponseError);
155    return;
156  }
157
158  // Icon URL is optional.
159  GURL icon_url;
160  if (webstore_data->HasKey(kIconUrlKey)) {
161    std::string icon_url_string;
162    if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) {
163      CompleteInstall(kInvalidWebstoreResponseError);
164      return;
165    }
166    icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
167        icon_url_string);
168    if (!icon_url.is_valid()) {
169      CompleteInstall(kInvalidWebstoreResponseError);
170      return;
171    }
172  }
173
174  // Assume ownership of webstore_data.
175  webstore_data_ = webstore_data.Pass();
176
177  scoped_refptr<WebstoreInstallHelper> helper =
178      new WebstoreInstallHelper(this,
179                                id_,
180                                manifest,
181                                std::string(),  // We don't have any icon data.
182                                icon_url,
183                                profile_->GetRequestContext());
184  // The helper will call us back via OnWebstoreParseSucces or
185  // OnWebstoreParseFailure.
186  helper->Start();
187}
188
189void WebstoreStandaloneInstaller::OnWebstoreResponseParseFailure(
190    const std::string& error) {
191  CompleteInstall(error);
192}
193
194void WebstoreStandaloneInstaller::OnWebstoreParseSuccess(
195    const std::string& id,
196    const SkBitmap& icon,
197    base::DictionaryValue* manifest) {
198  CHECK_EQ(id_, id);
199
200  if (!CheckRequestorAlive()) {
201    CompleteInstall(std::string());
202    return;
203  }
204
205  manifest_.reset(manifest);
206  icon_ = icon;
207
208  std::string error;
209  if (!CheckInstallValid(*manifest, &error)) {
210    DCHECK(!error.empty());
211    CompleteInstall(error);
212    return;
213  }
214
215  install_prompt_ = CreateInstallPrompt();
216  if (install_prompt_) {
217    ShowInstallUI();
218    // Control flow finishes up in InstallUIProceed or InstallUIAbort.
219  } else {
220    // Balanced in InstallUIAbort or indirectly in InstallUIProceed via
221    // OnExtensionInstallSuccess or OnExtensionInstallFailure.
222    AddRef();
223    InstallUIProceed();
224  }
225}
226
227void WebstoreStandaloneInstaller::OnWebstoreParseFailure(
228    const std::string& id,
229    InstallHelperResultCode result_code,
230    const std::string& error_message) {
231  CompleteInstall(error_message);
232}
233
234void WebstoreStandaloneInstaller::InstallUIProceed() {
235  if (!CheckRequestorAlive()) {
236    CompleteInstall(std::string());
237    return;
238  }
239
240  scoped_ptr<WebstoreInstaller::Approval> approval = CreateApproval();
241
242  scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller(
243      profile_,
244      this,
245      &(GetWebContents()->GetController()),
246      id_,
247      approval.Pass(),
248      install_source_);
249  installer->Start();
250}
251
252void WebstoreStandaloneInstaller::InstallUIAbort(bool user_initiated) {
253  CompleteInstall(kUserCancelledError);
254  Release();  // Balanced in ShowInstallUI.
255}
256
257void WebstoreStandaloneInstaller::OnExtensionInstallSuccess(
258    const std::string& id) {
259  CHECK_EQ(id_, id);
260  CompleteInstall(std::string());
261  Release();  // Balanced in ShowInstallUI.
262}
263
264void WebstoreStandaloneInstaller::OnExtensionInstallFailure(
265    const std::string& id,
266    const std::string& error,
267    WebstoreInstaller::FailureReason cancelled) {
268  CHECK_EQ(id_, id);
269  CompleteInstall(error);
270  Release();  // Balanced in ShowInstallUI.
271}
272
273void WebstoreStandaloneInstaller::AbortInstall() {
274  callback_.Reset();
275  // Abort any in-progress fetches.
276  if (webstore_data_fetcher_) {
277    webstore_data_fetcher_.reset();
278    Release();  // Matches the AddRef in BeginInstall.
279  }
280}
281
282void WebstoreStandaloneInstaller::CompleteInstall(const std::string& error) {
283  if (!callback_.is_null())
284    callback_.Run(error.empty(), error);
285  Release();  // Matches the AddRef in BeginInstall.
286}
287
288void
289WebstoreStandaloneInstaller::ShowInstallUI() {
290  std::string error;
291  localized_extension_for_display_ =
292      ExtensionInstallPrompt::GetLocalizedExtensionForDisplay(
293          manifest_.get(),
294          Extension::REQUIRE_KEY | Extension::FROM_WEBSTORE,
295          id_,
296          localized_name_,
297          localized_description_,
298          &error);
299  if (!localized_extension_for_display_.get()) {
300    CompleteInstall(kInvalidManifestError);
301    return;
302  }
303
304  // Keep this alive as long as the install prompt lives.
305  // Balanced in InstallUIAbort or indirectly in InstallUIProceed via
306  // OnExtensionInstallSuccess or OnExtensionInstallFailure.
307  AddRef();
308
309  install_ui_ = CreateInstallUI();
310  install_ui_->ConfirmStandaloneInstall(
311      this, localized_extension_for_display_.get(), &icon_, *install_prompt_);
312}
313
314}  // namespace extensions
315