blob: d6927b2a3c1fccdb8a6e16de6e156330ed11dc33 [file] [log] [blame]
[email protected]bdb74a22012-01-25 20:33:331// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]8915f342011-08-29 22:14:372// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]734bcec2012-10-08 20:29:055#include "chrome/browser/extensions/webstore_standalone_installer.h"
[email protected]8915f342011-08-29 22:14:376
7#include <vector>
8
[email protected]7b3941052013-02-13 01:21:599#include "base/stringprintf.h"
[email protected]8915f342011-08-29 22:14:3710#include "base/values.h"
[email protected]8915f342011-08-29 22:14:3711#include "chrome/browser/extensions/crx_installer.h"
[email protected]00b38242012-07-18 18:43:2212#include "chrome/browser/extensions/extension_install_ui.h"
[email protected]655b2b1a2011-10-13 17:13:0613#include "chrome/browser/extensions/extension_service.h"
[email protected]7b3941052013-02-13 01:21:5914#include "chrome/browser/extensions/webstore_data_fetcher.h"
[email protected]8915f342011-08-29 22:14:3715#include "chrome/browser/profiles/profile.h"
[email protected]8915f342011-08-29 22:14:3716#include "chrome/common/extensions/extension.h"
[email protected]10c2d692012-05-11 05:32:2317#include "content/public/browser/web_contents.h"
[email protected]3076a2232012-10-15 21:18:0418#include "content/public/browser/web_contents_view.h"
[email protected]8915f342011-08-29 22:14:3719
[email protected]631bb742011-11-02 11:29:3920using content::BrowserThread;
[email protected]e5d549d2011-12-28 01:29:2021using content::OpenURLParams;
[email protected]26b5e322011-12-23 01:36:4722using content::WebContents;
[email protected]631bb742011-11-02 11:29:3923
[email protected]3d61a7f2012-07-12 19:11:2524namespace extensions {
25
[email protected]8915f342011-08-29 22:14:3726const char kManifestKey[] = "manifest";
27const char kIconUrlKey[] = "icon_url";
28const char kLocalizedNameKey[] = "localized_name";
[email protected]5fdbd6f2011-09-01 17:33:0429const char kLocalizedDescriptionKey[] = "localized_description";
30const char kUsersKey[] = "users";
31const char kAverageRatingKey[] = "average_rating";
32const char kRatingCountKey[] = "rating_count";
[email protected]a221ef092011-09-07 01:34:1033const char kVerifiedSiteKey[] = "verified_site";
[email protected]dd5161a2011-09-14 17:40:1734const char kInlineInstallNotSupportedKey[] = "inline_install_not_supported";
35const char kRedirectUrlKey[] = "redirect_url";
[email protected]8915f342011-08-29 22:14:3736
[email protected]a221ef092011-09-07 01:34:1037const char kInvalidWebstoreItemId[] = "Invalid Chrome Web Store item ID";
38const char kWebstoreRequestError[] =
39 "Could not fetch data from the Chrome Web Store";
40const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
[email protected]8915f342011-08-29 22:14:3741const char kInvalidManifestError[] = "Invalid manifest";
42const char kUserCancelledError[] = "User cancelled install";
[email protected]5da311ea2011-09-14 20:57:3243const char kNoVerifiedSiteError[] =
44 "Inline installs can only be initiated for Chrome Web Store items that "
45 "have a verified site";
46const char kNotFromVerifiedSiteError[] =
[email protected]a221ef092011-09-07 01:34:1047 "Installs can only be initiated by the Chrome Web Store item's verified "
48 "site";
[email protected]dd5161a2011-09-14 17:40:1749const char kInlineInstallSupportedError[] =
50 "Inline installation is not supported for this item. The user will be "
51 "redirected to the Chrome Web Store.";
[email protected]8915f342011-08-29 22:14:3752
[email protected]734bcec2012-10-08 20:29:0553WebstoreStandaloneInstaller::WebstoreStandaloneInstaller(
[email protected]d2a639e2012-09-17 07:41:2154 std::string webstore_item_id,
55 VerifiedSiteRequired require_verified_site,
[email protected]734bcec2012-10-08 20:29:0556 PromptType prompt_type,
[email protected]d2a639e2012-09-17 07:41:2157 GURL requestor_url,
[email protected]c1b2d042013-02-23 00:31:0458 Profile* profile,
59 WebContents* web_contents,
60 const Callback& callback)
61 : content::WebContentsObserver(
62 prompt_type == INLINE_PROMPT ?
63 web_contents :
64 content::WebContents::Create(
65 content::WebContents::CreateParams(profile))),
[email protected]8915f342011-08-29 22:14:3766 id_(webstore_item_id),
[email protected]d2a639e2012-09-17 07:41:2167 require_verified_site_(require_verified_site == REQUIRE_VERIFIED_SITE),
[email protected]77e9261d2013-02-04 22:01:4768 prompt_type_(prompt_type),
[email protected]a221ef092011-09-07 01:34:1069 requestor_url_(requestor_url),
[email protected]c1b2d042013-02-23 00:31:0470 profile_(profile),
[email protected]d2a639e2012-09-17 07:41:2171 callback_(callback),
[email protected]c7bf7452011-09-12 21:31:5072 average_rating_(0.0),
[email protected]5f2a4752012-04-27 22:18:5873 rating_count_(0) {
[email protected]c1b2d042013-02-23 00:31:0474 DCHECK((prompt_type == SKIP_PROMPT &&
75 profile != NULL &&
76 web_contents == NULL) ||
77 (prompt_type == STANDARD_PROMPT &&
78 profile != NULL &&
79 web_contents == NULL) ||
80 (prompt_type == INLINE_PROMPT &&
81 profile != NULL &&
82 web_contents != NULL &&
83 profile == Profile::FromBrowserContext(
84 web_contents->GetBrowserContext()))
85 ) << " prompt_type=" << prompt_type
86 << " profile=" << profile
87 << " web_contents=" << web_contents;
88 DCHECK(this->web_contents() != NULL);
89 CHECK(!callback_.is_null());
90}
91
92WebstoreStandaloneInstaller::~WebstoreStandaloneInstaller() {
93 if (prompt_type_ != INLINE_PROMPT) {
94 // We create and own the web_contents in this case.
95 delete web_contents();
96 }
[email protected]8915f342011-08-29 22:14:3797}
98
[email protected]734bcec2012-10-08 20:29:0599void WebstoreStandaloneInstaller::BeginInstall() {
[email protected]5db2e882012-12-20 10:17:26100 AddRef(); // Balanced in CompleteInstall or WebContentsDestroyed.
[email protected]8915f342011-08-29 22:14:37101
[email protected]3d61a7f2012-07-12 19:11:25102 if (!Extension::IdIsValid(id_)) {
[email protected]8915f342011-08-29 22:14:37103 CompleteInstall(kInvalidWebstoreItemId);
104 return;
105 }
106
[email protected]b4574c02011-11-17 06:19:13107 // Use the requesting page as the referrer both since that is more correct
108 // (it is the page that caused this request to happen) and so that we can
109 // track top sites that trigger inline install requests.
[email protected]7b3941052013-02-13 01:21:59110 webstore_data_fetcher_.reset(new WebstoreDataFetcher(
111 this,
[email protected]c1b2d042013-02-23 00:31:04112 profile_->GetRequestContext(),
[email protected]7b3941052013-02-13 01:21:59113 requestor_url_,
114 id_));
115 webstore_data_fetcher_->Start();
[email protected]8915f342011-08-29 22:14:37116}
117
[email protected]7b3941052013-02-13 01:21:59118void WebstoreStandaloneInstaller::OnWebstoreRequestFailure() {
119 CompleteInstall(kWebstoreRequestError);
[email protected]8915f342011-08-29 22:14:37120}
121
[email protected]734bcec2012-10-08 20:29:05122void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess(
[email protected]8915f342011-08-29 22:14:37123 DictionaryValue* webstore_data) {
[email protected]4693243e2011-09-09 23:52:37124 // Check if the tab has gone away in the meantime.
[email protected]c1b2d042013-02-23 00:31:04125 if (prompt_type_ == INLINE_PROMPT && !web_contents()) {
[email protected]4693243e2011-09-09 23:52:37126 CompleteInstall("");
127 return;
128 }
129
[email protected]8915f342011-08-29 22:14:37130 webstore_data_.reset(webstore_data);
131
[email protected]dd5161a2011-09-14 17:40:17132 // The store may not support inline installs for this item, in which case
133 // we open the store-provided redirect URL in a new tab and abort the
134 // installation process.
135 bool inline_install_not_supported = false;
136 if (webstore_data->HasKey(kInlineInstallNotSupportedKey) &&
137 !webstore_data->GetBoolean(
138 kInlineInstallNotSupportedKey, &inline_install_not_supported)) {
[email protected]8915f342011-08-29 22:14:37139 CompleteInstall(kInvalidWebstoreResponseError);
140 return;
141 }
[email protected]dd5161a2011-09-14 17:40:17142 if (inline_install_not_supported) {
143 std::string redirect_url;
144 if (!webstore_data->GetString(kRedirectUrlKey, &redirect_url)) {
145 CompleteInstall(kInvalidWebstoreResponseError);
146 return;
147 }
[email protected]8915f342011-08-29 22:14:37148
[email protected]c1b2d042013-02-23 00:31:04149 if (prompt_type_ == INLINE_PROMPT) {
150 web_contents()->OpenURL(OpenURLParams(
151 GURL(redirect_url),
152 content::Referrer(web_contents()->GetURL(),
153 WebKit::WebReferrerPolicyDefault),
154 NEW_FOREGROUND_TAB,
155 content::PAGE_TRANSITION_AUTO_BOOKMARK,
156 false));
157 }
[email protected]dd5161a2011-09-14 17:40:17158 CompleteInstall(kInlineInstallSupportedError);
159 return;
160 }
161
162 // Manifest, number of users, average rating and rating count are required.
163 std::string manifest;
164 if (!webstore_data->GetString(kManifestKey, &manifest) ||
165 !webstore_data->GetString(kUsersKey, &localized_user_count_) ||
[email protected]5fdbd6f2011-09-01 17:33:04166 !webstore_data->GetDouble(kAverageRatingKey, &average_rating_) ||
167 !webstore_data->GetInteger(kRatingCountKey, &rating_count_)) {
168 CompleteInstall(kInvalidWebstoreResponseError);
169 return;
170 }
171
[email protected]c82da8c42012-06-08 19:49:11172 if (average_rating_ < ExtensionInstallPrompt::kMinExtensionRating ||
173 average_rating_ > ExtensionInstallPrompt::kMaxExtensionRating) {
[email protected]5fdbd6f2011-09-01 17:33:04174 CompleteInstall(kInvalidWebstoreResponseError);
175 return;
176 }
177
178 // Localized name and description are optional.
179 if ((webstore_data->HasKey(kLocalizedNameKey) &&
180 !webstore_data->GetString(kLocalizedNameKey, &localized_name_)) ||
181 (webstore_data->HasKey(kLocalizedDescriptionKey) &&
182 !webstore_data->GetString(
183 kLocalizedDescriptionKey, &localized_description_))) {
[email protected]8915f342011-08-29 22:14:37184 CompleteInstall(kInvalidWebstoreResponseError);
185 return;
186 }
187
188 // Icon URL is optional.
189 GURL icon_url;
190 if (webstore_data->HasKey(kIconUrlKey)) {
191 std::string icon_url_string;
192 if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) {
193 CompleteInstall(kInvalidWebstoreResponseError);
194 return;
195 }
196 icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
197 icon_url_string);
198 if (!icon_url.is_valid()) {
199 CompleteInstall(kInvalidWebstoreResponseError);
200 return;
201 }
202 }
203
[email protected]d2a639e2012-09-17 07:41:21204 // Check for a verified site if required.
205 if (require_verified_site_) {
206 if (!webstore_data->HasKey(kVerifiedSiteKey)) {
207 CompleteInstall(kNoVerifiedSiteError);
208 return;
209 }
[email protected]bdb74a22012-01-25 20:33:33210 std::string verified_site;
211 if (!webstore_data->GetString(kVerifiedSiteKey, &verified_site)) {
[email protected]a221ef092011-09-07 01:34:10212 CompleteInstall(kInvalidWebstoreResponseError);
213 return;
214 }
[email protected]bdb74a22012-01-25 20:33:33215 if (!IsRequestorURLInVerifiedSite(requestor_url_, verified_site)) {
[email protected]5da311ea2011-09-14 20:57:32216 CompleteInstall(kNotFromVerifiedSiteError);
[email protected]a221ef092011-09-07 01:34:10217 return;
218 }
219 }
220
[email protected]8915f342011-08-29 22:14:37221 scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper(
222 this,
[email protected]98e4e522011-10-25 13:00:16223 id_,
[email protected]8915f342011-08-29 22:14:37224 manifest,
[email protected]5db2e882012-12-20 10:17:26225 "", // We don't have any icon data.
[email protected]8915f342011-08-29 22:14:37226 icon_url,
[email protected]c1b2d042013-02-23 00:31:04227 profile_->GetRequestContext());
[email protected]8915f342011-08-29 22:14:37228 // The helper will call us back via OnWebstoreParseSucces or
229 // OnWebstoreParseFailure.
230 helper->Start();
231}
232
[email protected]734bcec2012-10-08 20:29:05233void WebstoreStandaloneInstaller::OnWebstoreResponseParseFailure(
[email protected]8915f342011-08-29 22:14:37234 const std::string& error) {
235 CompleteInstall(error);
236}
237
[email protected]734bcec2012-10-08 20:29:05238void WebstoreStandaloneInstaller::OnWebstoreParseSuccess(
[email protected]98e4e522011-10-25 13:00:16239 const std::string& id,
[email protected]8915f342011-08-29 22:14:37240 const SkBitmap& icon,
241 base::DictionaryValue* manifest) {
[email protected]2fd920fb2011-09-08 23:33:00242 // Check if the tab has gone away in the meantime.
[email protected]c1b2d042013-02-23 00:31:04243 if (prompt_type_ == INLINE_PROMPT && !web_contents()) {
[email protected]2fd920fb2011-09-08 23:33:00244 CompleteInstall("");
245 return;
246 }
247
[email protected]98e4e522011-10-25 13:00:16248 CHECK_EQ(id_, id);
[email protected]8915f342011-08-29 22:14:37249 manifest_.reset(manifest);
250 icon_ = icon;
251
[email protected]77e9261d2013-02-04 22:01:47252 if (prompt_type_ == SKIP_PROMPT) {
253 InstallUIProceed();
254 return;
255 }
256
257 ExtensionInstallPrompt::PromptType prompt_type =
258 prompt_type_ == INLINE_PROMPT ?
259 ExtensionInstallPrompt::INLINE_INSTALL_PROMPT :
260 ExtensionInstallPrompt::INSTALL_PROMPT;
[email protected]5db2e882012-12-20 10:17:26261 ExtensionInstallPrompt::Prompt prompt(prompt_type);
[email protected]77e9261d2013-02-04 22:01:47262 if (prompt_type_ == INLINE_PROMPT) {
[email protected]734bcec2012-10-08 20:29:05263 prompt.SetInlineInstallWebstoreData(localized_user_count_,
264 average_rating_,
265 rating_count_);
266 }
[email protected]514c5472012-04-20 22:56:36267 std::string error;
[email protected]c82da8c42012-06-08 19:49:11268 dummy_extension_ = ExtensionInstallPrompt::GetLocalizedExtensionForDisplay(
[email protected]c422a862012-07-31 15:46:13269 manifest,
270 Extension::REQUIRE_KEY | Extension::FROM_WEBSTORE,
271 id_,
272 localized_name_,
273 localized_description_,
274 &error);
[email protected]514c5472012-04-20 22:56:36275 if (!dummy_extension_) {
276 OnWebstoreParseFailure(id_, WebstoreInstallHelper::Delegate::MANIFEST_ERROR,
277 kInvalidManifestError);
[email protected]8915f342011-08-29 22:14:37278 return;
279 }
280
[email protected]91e51d612012-10-21 23:03:05281 install_ui_.reset(new ExtensionInstallPrompt(web_contents()));
[email protected]77e9261d2013-02-04 22:01:47282 install_ui_->ConfirmStandaloneInstall(this,
283 dummy_extension_,
284 &icon_,
285 prompt);
[email protected]8915f342011-08-29 22:14:37286 // Control flow finishes up in InstallUIProceed or InstallUIAbort.
287}
288
[email protected]734bcec2012-10-08 20:29:05289void WebstoreStandaloneInstaller::OnWebstoreParseFailure(
[email protected]98e4e522011-10-25 13:00:16290 const std::string& id,
[email protected]8915f342011-08-29 22:14:37291 InstallHelperResultCode result_code,
292 const std::string& error_message) {
293 CompleteInstall(error_message);
294}
295
[email protected]734bcec2012-10-08 20:29:05296void WebstoreStandaloneInstaller::InstallUIProceed() {
[email protected]2fd920fb2011-09-08 23:33:00297 // Check if the tab has gone away in the meantime.
[email protected]c1b2d042013-02-23 00:31:04298 if (prompt_type_ == INLINE_PROMPT && !web_contents()) {
[email protected]2fd920fb2011-09-08 23:33:00299 CompleteInstall("");
300 return;
301 }
302
[email protected]21a5ad62012-04-03 04:48:45303 scoped_ptr<WebstoreInstaller::Approval> approval(
[email protected]89019d62012-05-17 18:47:09304 WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
[email protected]c1b2d042013-02-23 00:31:04305 profile_,
[email protected]89019d62012-05-17 18:47:09306 id_,
307 scoped_ptr<base::DictionaryValue>(manifest_.get()->DeepCopy())));
[email protected]c1b2d042013-02-23 00:31:04308 approval->skip_post_install_ui = (prompt_type_ != INLINE_PROMPT);
309 approval->use_app_installed_bubble = (prompt_type_ == INLINE_PROMPT);
[email protected]21a5ad62012-04-03 04:48:45310
[email protected]98e4e522011-10-25 13:00:16311 scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller(
[email protected]c1b2d042013-02-23 00:31:04312 profile_, this, &(web_contents()->GetController()), id_, approval.Pass(),
[email protected]98e4e522011-10-25 13:00:16313 WebstoreInstaller::FLAG_INLINE_INSTALL);
314 installer->Start();
[email protected]8915f342011-08-29 22:14:37315}
316
[email protected]734bcec2012-10-08 20:29:05317void WebstoreStandaloneInstaller::InstallUIAbort(bool user_initiated) {
[email protected]8915f342011-08-29 22:14:37318 CompleteInstall(kUserCancelledError);
319}
320
[email protected]734bcec2012-10-08 20:29:05321void WebstoreStandaloneInstaller::WebContentsDestroyed(
322 WebContents* web_contents) {
[email protected]d2a639e2012-09-17 07:41:21323 callback_.Reset();
[email protected]2fd920fb2011-09-08 23:33:00324 // Abort any in-progress fetches.
[email protected]7b3941052013-02-13 01:21:59325 if (webstore_data_fetcher_.get()) {
326 webstore_data_fetcher_.reset();
[email protected]5db2e882012-12-20 10:17:26327 Release(); // Matches the AddRef in BeginInstall.
[email protected]8915f342011-08-29 22:14:37328 }
[email protected]2fd920fb2011-09-08 23:33:00329}
330
[email protected]734bcec2012-10-08 20:29:05331void WebstoreStandaloneInstaller::OnExtensionInstallSuccess(
332 const std::string& id) {
[email protected]cb08ba22011-10-19 21:41:40333 CHECK_EQ(id_, id);
334 CompleteInstall("");
335}
336
[email protected]734bcec2012-10-08 20:29:05337void WebstoreStandaloneInstaller::OnExtensionInstallFailure(
[email protected]bcd1eaf72012-10-03 05:42:29338 const std::string& id,
339 const std::string& error,
340 WebstoreInstaller::FailureReason cancelled) {
[email protected]cb08ba22011-10-19 21:41:40341 CHECK_EQ(id_, id);
342 CompleteInstall(error);
343}
344
[email protected]734bcec2012-10-08 20:29:05345void WebstoreStandaloneInstaller::CompleteInstall(const std::string& error) {
[email protected]0688d8dc2013-02-16 04:10:38346 // Clear webstore_data_fetcher_ so that WebContentsDestroyed will no longer
347 // call Release in case the WebContents is destroyed before this object.
348 scoped_ptr<WebstoreDataFetcher> webstore_data_fetcher(
349 webstore_data_fetcher_.Pass());
[email protected]d2a639e2012-09-17 07:41:21350 if (!callback_.is_null())
351 callback_.Run(error.empty(), error);
[email protected]2fd920fb2011-09-08 23:33:00352
[email protected]5db2e882012-12-20 10:17:26353 Release(); // Matches the AddRef in BeginInstall.
[email protected]8915f342011-08-29 22:14:37354}
[email protected]5f2a4752012-04-27 22:18:58355
356// static
[email protected]734bcec2012-10-08 20:29:05357bool WebstoreStandaloneInstaller::IsRequestorURLInVerifiedSite(
[email protected]5f2a4752012-04-27 22:18:58358 const GURL& requestor_url,
359 const std::string& verified_site) {
360 // Turn the verified site (which may be a bare domain, or have a port and/or a
361 // path) into a URL that can be parsed by URLPattern.
362 std::string verified_site_url =
363 StringPrintf("http://*.%s%s",
364 verified_site.c_str(),
365 verified_site.find('/') == std::string::npos ? "/*" : "*");
366
367 URLPattern verified_site_pattern(
368 URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS);
369 URLPattern::ParseResult parse_result =
370 verified_site_pattern.Parse(verified_site_url);
371 if (parse_result != URLPattern::PARSE_SUCCESS) {
372 DLOG(WARNING) << "Could not parse " << verified_site_url <<
373 " as URL pattern " << parse_result;
374 return false;
375 }
376 verified_site_pattern.SetScheme("*");
377
378 return verified_site_pattern.MatchesURL(requestor_url);
379}
[email protected]3d61a7f2012-07-12 19:11:25380
381} // namespace extensions