blob: b58a65213085c51c10898a8700c4312394d3f2f3 [file] [log] [blame]
[email protected]8915f342011-08-29 22:14:371// Copyright (c) 2011 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_inline_installer.h"
6
7#include <vector>
8
[email protected]8e6ac4b2011-10-17 19:04:319#include "base/bind.h"
[email protected]8915f342011-08-29 22:14:3710#include "base/string_util.h"
11#include "base/values.h"
12#include "chrome/browser/browser_process.h"
13#include "chrome/browser/extensions/crx_installer.h"
14#include "chrome/browser/extensions/extension_install_dialog.h"
[email protected]655b2b1a2011-10-13 17:13:0615#include "chrome/browser/extensions/extension_service.h"
[email protected]8915f342011-08-29 22:14:3716#include "chrome/browser/profiles/profile.h"
17#include "chrome/common/chrome_utility_messages.h"
18#include "chrome/common/extensions/extension.h"
19#include "chrome/common/extensions/extension_constants.h"
[email protected]a221ef092011-09-07 01:34:1020#include "chrome/common/extensions/url_pattern.h"
[email protected]8915f342011-08-29 22:14:3721#include "content/browser/tab_contents/tab_contents.h"
22#include "content/browser/utility_process_host.h"
[email protected]7b419b82011-10-27 04:23:4623#include "content/public/common/url_fetcher.h"
[email protected]8915f342011-08-29 22:14:3724#include "net/base/escape.h"
[email protected]41e9a0de2011-09-14 17:24:5125#include "net/base/load_flags.h"
[email protected]8915f342011-08-29 22:14:3726#include "net/url_request/url_request_status.h"
27
[email protected]631bb742011-11-02 11:29:3928using content::BrowserThread;
[email protected]26b5e322011-12-23 01:36:4729using content::WebContents;
[email protected]631bb742011-11-02 11:29:3930
[email protected]8915f342011-08-29 22:14:3731const char kManifestKey[] = "manifest";
32const char kIconUrlKey[] = "icon_url";
33const char kLocalizedNameKey[] = "localized_name";
[email protected]5fdbd6f2011-09-01 17:33:0434const char kLocalizedDescriptionKey[] = "localized_description";
35const char kUsersKey[] = "users";
36const char kAverageRatingKey[] = "average_rating";
37const char kRatingCountKey[] = "rating_count";
[email protected]a221ef092011-09-07 01:34:1038const char kVerifiedSiteKey[] = "verified_site";
[email protected]dd5161a2011-09-14 17:40:1739const char kInlineInstallNotSupportedKey[] = "inline_install_not_supported";
40const char kRedirectUrlKey[] = "redirect_url";
[email protected]8915f342011-08-29 22:14:3741
[email protected]a221ef092011-09-07 01:34:1042const char kInvalidWebstoreItemId[] = "Invalid Chrome Web Store item ID";
43const char kWebstoreRequestError[] =
44 "Could not fetch data from the Chrome Web Store";
45const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
[email protected]8915f342011-08-29 22:14:3746const char kInvalidManifestError[] = "Invalid manifest";
47const char kUserCancelledError[] = "User cancelled install";
[email protected]5da311ea2011-09-14 20:57:3248const char kNoVerifiedSiteError[] =
49 "Inline installs can only be initiated for Chrome Web Store items that "
50 "have a verified site";
51const char kNotFromVerifiedSiteError[] =
[email protected]a221ef092011-09-07 01:34:1052 "Installs can only be initiated by the Chrome Web Store item's verified "
53 "site";
[email protected]dd5161a2011-09-14 17:40:1754const char kInlineInstallSupportedError[] =
55 "Inline installation is not supported for this item. The user will be "
56 "redirected to the Chrome Web Store.";
[email protected]8915f342011-08-29 22:14:3757
58class SafeWebstoreResponseParser : public UtilityProcessHost::Client {
59 public:
60 SafeWebstoreResponseParser(WebstoreInlineInstaller *client,
61 const std::string& webstore_data)
62 : client_(client),
63 webstore_data_(webstore_data),
64 utility_host_(NULL) {}
65
66 void Start() {
67 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
68 BrowserThread::PostTask(
69 BrowserThread::IO,
70 FROM_HERE,
[email protected]8e6ac4b2011-10-17 19:04:3171 base::Bind(&SafeWebstoreResponseParser::StartWorkOnIOThread, this));
[email protected]8915f342011-08-29 22:14:3772 }
73
74 void StartWorkOnIOThread() {
75 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
76 utility_host_ = new UtilityProcessHost(this, BrowserThread::IO);
[email protected]3dd18d02011-12-13 19:32:3877 utility_host_->set_use_linux_zygote(true);
[email protected]8915f342011-08-29 22:14:3778 utility_host_->Send(new ChromeUtilityMsg_ParseJSON(webstore_data_));
79 }
80
81 // Implementing pieces of the UtilityProcessHost::Client interface.
82 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
83 bool handled = true;
84 IPC_BEGIN_MESSAGE_MAP(SafeWebstoreResponseParser, message)
85 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Succeeded,
86 OnJSONParseSucceeded)
87 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Failed,
88 OnJSONParseFailed)
89 IPC_MESSAGE_UNHANDLED(handled = false)
90 IPC_END_MESSAGE_MAP()
91 return handled;
92 }
93
94 void OnJSONParseSucceeded(const ListValue& wrapper) {
95 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
96 Value* value = NULL;
97 CHECK(wrapper.Get(0, &value));
98 if (value->IsType(Value::TYPE_DICTIONARY)) {
99 parsed_webstore_data_.reset(
100 static_cast<DictionaryValue*>(value)->DeepCopy());
101 } else {
102 error_ = kInvalidWebstoreResponseError;
103 }
104
105 ReportResults();
106 }
107
108 virtual void OnJSONParseFailed(const std::string& error_message) {
109 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
110 error_ = error_message;
111 ReportResults();
112 }
113
114 void ReportResults() {
115 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
116
117 // The utility_host_ will take care of deleting itself after this call.
118 utility_host_ = NULL;
119
120 BrowserThread::PostTask(
121 BrowserThread::UI,
122 FROM_HERE,
[email protected]8e6ac4b2011-10-17 19:04:31123 base::Bind(&SafeWebstoreResponseParser::ReportResultOnUIThread, this));
[email protected]8915f342011-08-29 22:14:37124 }
125
126 void ReportResultOnUIThread() {
127 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
128 if (error_.empty() && parsed_webstore_data_.get()) {
129 client_->OnWebstoreResponseParseSuccess(parsed_webstore_data_.release());
130 } else {
131 client_->OnWebstoreResponseParseFailure(error_);
132 }
133 }
134
135 private:
136 virtual ~SafeWebstoreResponseParser() {}
137
138 WebstoreInlineInstaller* client_;
139
140 std::string webstore_data_;
141
142 UtilityProcessHost* utility_host_;
143
144 std::string error_;
145 scoped_ptr<DictionaryValue> parsed_webstore_data_;
146};
147
[email protected]26b5e322011-12-23 01:36:47148WebstoreInlineInstaller::WebstoreInlineInstaller(WebContents* web_contents,
[email protected]a221ef092011-09-07 01:34:10149 int install_id,
[email protected]8915f342011-08-29 22:14:37150 std::string webstore_item_id,
[email protected]a221ef092011-09-07 01:34:10151 GURL requestor_url,
[email protected]8915f342011-08-29 22:14:37152 Delegate* delegate)
[email protected]26b5e322011-12-23 01:36:47153 : content::WebContentsObserver(web_contents),
[email protected]a221ef092011-09-07 01:34:10154 install_id_(install_id),
[email protected]8915f342011-08-29 22:14:37155 id_(webstore_item_id),
[email protected]a221ef092011-09-07 01:34:10156 requestor_url_(requestor_url),
[email protected]c7bf7452011-09-12 21:31:50157 delegate_(delegate),
158 average_rating_(0.0),
159 rating_count_(0) {}
[email protected]8915f342011-08-29 22:14:37160
161WebstoreInlineInstaller::~WebstoreInlineInstaller() {
162}
163
164void WebstoreInlineInstaller::BeginInstall() {
[email protected]26b5e322011-12-23 01:36:47165 AddRef(); // Balanced in CompleteInstall or WebContentsDestroyed.
[email protected]8915f342011-08-29 22:14:37166
167 if (!Extension::IdIsValid(id_)) {
168 CompleteInstall(kInvalidWebstoreItemId);
169 return;
170 }
171
172 GURL webstore_data_url(extension_urls::GetWebstoreItemJsonDataURL(id_));
173
[email protected]36aea2702011-10-26 01:12:22174 webstore_data_url_fetcher_.reset(content::URLFetcher::Create(
175 webstore_data_url, content::URLFetcher::GET, this));
[email protected]8915f342011-08-29 22:14:37176 Profile* profile = Profile::FromBrowserContext(
[email protected]627e0512011-12-21 22:55:30177 tab_contents()->GetBrowserContext());
[email protected]7cc6e5632011-10-25 17:56:12178 webstore_data_url_fetcher_->SetRequestContext(
[email protected]8915f342011-08-29 22:14:37179 profile->GetRequestContext());
[email protected]b4574c02011-11-17 06:19:13180 // Use the requesting page as the referrer both since that is more correct
181 // (it is the page that caused this request to happen) and so that we can
182 // track top sites that trigger inline install requests.
183 webstore_data_url_fetcher_->SetReferrer(requestor_url_.spec());
[email protected]7cc6e5632011-10-25 17:56:12184 webstore_data_url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
185 net::LOAD_DO_NOT_SAVE_COOKIES |
186 net::LOAD_DISABLE_CACHE);
[email protected]8915f342011-08-29 22:14:37187 webstore_data_url_fetcher_->Start();
188}
189
[email protected]7cc6e5632011-10-25 17:56:12190void WebstoreInlineInstaller::OnURLFetchComplete(
191 const content::URLFetcher* source) {
[email protected]8915f342011-08-29 22:14:37192 CHECK_EQ(webstore_data_url_fetcher_.get(), source);
[email protected]26b5e322011-12-23 01:36:47193 // We shouldn't be getting UrlFetcher callbacks if the WebContents has gone
194 // away; we stop any in in-progress fetches in WebContentsDestroyed.
[email protected]2fd920fb2011-09-08 23:33:00195 CHECK(tab_contents());
[email protected]8915f342011-08-29 22:14:37196
[email protected]7cc6e5632011-10-25 17:56:12197 if (!webstore_data_url_fetcher_->GetStatus().is_success() ||
198 webstore_data_url_fetcher_->GetResponseCode() != 200) {
[email protected]8915f342011-08-29 22:14:37199 CompleteInstall(kWebstoreRequestError);
200 return;
201 }
202
203 std::string webstore_json_data;
204 webstore_data_url_fetcher_->GetResponseAsString(&webstore_json_data);
205 webstore_data_url_fetcher_.reset();
206
207 scoped_refptr<SafeWebstoreResponseParser> parser =
208 new SafeWebstoreResponseParser(this, webstore_json_data);
209 // The parser will call us back via OnWebstoreResponseParseSucces or
210 // OnWebstoreResponseParseFailure.
211 parser->Start();
212}
213
214void WebstoreInlineInstaller::OnWebstoreResponseParseSuccess(
215 DictionaryValue* webstore_data) {
[email protected]4693243e2011-09-09 23:52:37216 // Check if the tab has gone away in the meantime.
217 if (!tab_contents()) {
218 CompleteInstall("");
219 return;
220 }
221
[email protected]8915f342011-08-29 22:14:37222 webstore_data_.reset(webstore_data);
223
[email protected]dd5161a2011-09-14 17:40:17224 // The store may not support inline installs for this item, in which case
225 // we open the store-provided redirect URL in a new tab and abort the
226 // installation process.
227 bool inline_install_not_supported = false;
228 if (webstore_data->HasKey(kInlineInstallNotSupportedKey) &&
229 !webstore_data->GetBoolean(
230 kInlineInstallNotSupportedKey, &inline_install_not_supported)) {
[email protected]8915f342011-08-29 22:14:37231 CompleteInstall(kInvalidWebstoreResponseError);
232 return;
233 }
[email protected]dd5161a2011-09-14 17:40:17234 if (inline_install_not_supported) {
235 std::string redirect_url;
236 if (!webstore_data->GetString(kRedirectUrlKey, &redirect_url)) {
237 CompleteInstall(kInvalidWebstoreResponseError);
238 return;
239 }
[email protected]8915f342011-08-29 22:14:37240
[email protected]dd5161a2011-09-14 17:40:17241 tab_contents()->OpenURL(OpenURLParams(
242 GURL(redirect_url),
[email protected]bce1f1c2011-12-05 15:11:58243 content::Referrer(tab_contents()->GetURL(),
244 WebKit::WebReferrerPolicyDefault),
[email protected]dd5161a2011-09-14 17:40:17245 NEW_FOREGROUND_TAB,
[email protected]e47ae9472011-10-13 19:48:34246 content::PAGE_TRANSITION_AUTO_BOOKMARK,
247 false));
[email protected]dd5161a2011-09-14 17:40:17248 CompleteInstall(kInlineInstallSupportedError);
249 return;
250 }
251
252 // Manifest, number of users, average rating and rating count are required.
253 std::string manifest;
254 if (!webstore_data->GetString(kManifestKey, &manifest) ||
255 !webstore_data->GetString(kUsersKey, &localized_user_count_) ||
[email protected]5fdbd6f2011-09-01 17:33:04256 !webstore_data->GetDouble(kAverageRatingKey, &average_rating_) ||
257 !webstore_data->GetInteger(kRatingCountKey, &rating_count_)) {
258 CompleteInstall(kInvalidWebstoreResponseError);
259 return;
260 }
261
262 if (average_rating_ < ExtensionInstallUI::kMinExtensionRating ||
263 average_rating_ >ExtensionInstallUI::kMaxExtensionRating) {
264 CompleteInstall(kInvalidWebstoreResponseError);
265 return;
266 }
267
268 // Localized name and description are optional.
269 if ((webstore_data->HasKey(kLocalizedNameKey) &&
270 !webstore_data->GetString(kLocalizedNameKey, &localized_name_)) ||
271 (webstore_data->HasKey(kLocalizedDescriptionKey) &&
272 !webstore_data->GetString(
273 kLocalizedDescriptionKey, &localized_description_))) {
[email protected]8915f342011-08-29 22:14:37274 CompleteInstall(kInvalidWebstoreResponseError);
275 return;
276 }
277
278 // Icon URL is optional.
279 GURL icon_url;
280 if (webstore_data->HasKey(kIconUrlKey)) {
281 std::string icon_url_string;
282 if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) {
283 CompleteInstall(kInvalidWebstoreResponseError);
284 return;
285 }
286 icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
287 icon_url_string);
288 if (!icon_url.is_valid()) {
289 CompleteInstall(kInvalidWebstoreResponseError);
290 return;
291 }
292 }
293
[email protected]5da311ea2011-09-14 20:57:32294 // Verified site is required
[email protected]a221ef092011-09-07 01:34:10295 if (webstore_data->HasKey(kVerifiedSiteKey)) {
296 std::string verified_site_domain;
297 if (!webstore_data->GetString(kVerifiedSiteKey, &verified_site_domain)) {
298 CompleteInstall(kInvalidWebstoreResponseError);
299 return;
300 }
301
[email protected]838b9432011-12-19 05:15:15302 URLPattern verified_site_pattern(URLPattern::SCHEME_ALL);
[email protected]a221ef092011-09-07 01:34:10303 verified_site_pattern.SetScheme("*");
304 verified_site_pattern.SetHost(verified_site_domain);
305 verified_site_pattern.SetMatchSubdomains(true);
306 verified_site_pattern.SetPath("/*");
307
308 if (!verified_site_pattern.MatchesURL(requestor_url_)) {
[email protected]5da311ea2011-09-14 20:57:32309 CompleteInstall(kNotFromVerifiedSiteError);
[email protected]a221ef092011-09-07 01:34:10310 return;
311 }
[email protected]5da311ea2011-09-14 20:57:32312 } else {
313 CompleteInstall(kNoVerifiedSiteError);
314 return;
[email protected]a221ef092011-09-07 01:34:10315 }
316
[email protected]8915f342011-08-29 22:14:37317 scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper(
318 this,
[email protected]98e4e522011-10-25 13:00:16319 id_,
[email protected]8915f342011-08-29 22:14:37320 manifest,
321 "", // We don't have any icon data.
322 icon_url,
[email protected]627e0512011-12-21 22:55:30323 Profile::FromBrowserContext(tab_contents()->GetBrowserContext())->
[email protected]8915f342011-08-29 22:14:37324 GetRequestContext());
325 // The helper will call us back via OnWebstoreParseSucces or
326 // OnWebstoreParseFailure.
327 helper->Start();
328}
329
330void WebstoreInlineInstaller::OnWebstoreResponseParseFailure(
331 const std::string& error) {
332 CompleteInstall(error);
333}
334
335void WebstoreInlineInstaller::OnWebstoreParseSuccess(
[email protected]98e4e522011-10-25 13:00:16336 const std::string& id,
[email protected]8915f342011-08-29 22:14:37337 const SkBitmap& icon,
338 base::DictionaryValue* manifest) {
[email protected]2fd920fb2011-09-08 23:33:00339 // Check if the tab has gone away in the meantime.
340 if (!tab_contents()) {
341 CompleteInstall("");
342 return;
343 }
344
[email protected]98e4e522011-10-25 13:00:16345 CHECK_EQ(id_, id);
[email protected]8915f342011-08-29 22:14:37346 manifest_.reset(manifest);
347 icon_ = icon;
348
349 Profile* profile = Profile::FromBrowserContext(
[email protected]627e0512011-12-21 22:55:30350 tab_contents()->GetBrowserContext());
[email protected]5fdbd6f2011-09-01 17:33:04351
352 ExtensionInstallUI::Prompt prompt(ExtensionInstallUI::INLINE_INSTALL_PROMPT);
[email protected]122e8df2011-09-02 18:20:24353 prompt.SetInlineInstallWebstoreData(localized_user_count_,
354 average_rating_,
355 rating_count_);
[email protected]5fdbd6f2011-09-01 17:33:04356
[email protected]d0243b052011-09-10 18:35:11357 if (!ShowExtensionInstallDialogForManifest(profile,
358 this,
359 manifest,
360 id_,
361 localized_name_,
362 localized_description_,
363 &icon_,
364 prompt,
365 &dummy_extension_)) {
[email protected]8915f342011-08-29 22:14:37366 CompleteInstall(kInvalidManifestError);
367 return;
368 }
369
370 // Control flow finishes up in InstallUIProceed or InstallUIAbort.
371}
372
373void WebstoreInlineInstaller::OnWebstoreParseFailure(
[email protected]98e4e522011-10-25 13:00:16374 const std::string& id,
[email protected]8915f342011-08-29 22:14:37375 InstallHelperResultCode result_code,
376 const std::string& error_message) {
377 CompleteInstall(error_message);
378}
379
380void WebstoreInlineInstaller::InstallUIProceed() {
[email protected]2fd920fb2011-09-08 23:33:00381 // Check if the tab has gone away in the meantime.
382 if (!tab_contents()) {
383 CompleteInstall("");
384 return;
385 }
386
[email protected]8915f342011-08-29 22:14:37387 CrxInstaller::WhitelistEntry* entry = new CrxInstaller::WhitelistEntry;
388
389 entry->parsed_manifest.reset(manifest_.get()->DeepCopy());
390 entry->localized_name = localized_name_;
391 entry->use_app_installed_bubble = true;
392 CrxInstaller::SetWhitelistEntry(id_, entry);
393
[email protected]655b2b1a2011-10-13 17:13:06394 Profile* profile = Profile::FromBrowserContext(
[email protected]627e0512011-12-21 22:55:30395 tab_contents()->GetBrowserContext());
[email protected]8915f342011-08-29 22:14:37396
[email protected]98e4e522011-10-25 13:00:16397 scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller(
[email protected]f5fa20e2011-12-21 22:35:56398 profile, this, &(tab_contents()->GetController()), id_,
[email protected]98e4e522011-10-25 13:00:16399 WebstoreInstaller::FLAG_INLINE_INSTALL);
400 installer->Start();
[email protected]8915f342011-08-29 22:14:37401}
402
403void WebstoreInlineInstaller::InstallUIAbort(bool user_initiated) {
404 CompleteInstall(kUserCancelledError);
405}
406
[email protected]26b5e322011-12-23 01:36:47407void WebstoreInlineInstaller::WebContentsDestroyed(WebContents* web_contents) {
[email protected]2fd920fb2011-09-08 23:33:00408 // Abort any in-progress fetches.
409 if (webstore_data_url_fetcher_.get()) {
410 webstore_data_url_fetcher_.reset();
411 Release(); // Matches the AddRef in BeginInstall.
[email protected]8915f342011-08-29 22:14:37412 }
[email protected]2fd920fb2011-09-08 23:33:00413}
414
[email protected]cb08ba22011-10-19 21:41:40415void WebstoreInlineInstaller::OnExtensionInstallSuccess(const std::string& id) {
416 CHECK_EQ(id_, id);
417 CompleteInstall("");
418}
419
420void WebstoreInlineInstaller::OnExtensionInstallFailure(
421 const std::string& id, const std::string& error) {
422 CHECK_EQ(id_, id);
423 CompleteInstall(error);
424}
425
[email protected]2fd920fb2011-09-08 23:33:00426void WebstoreInlineInstaller::CompleteInstall(const std::string& error) {
427 // Only bother responding if there's still a tab contents to send back the
428 // response to.
429 if (tab_contents()) {
430 if (error.empty()) {
431 delegate_->OnInlineInstallSuccess(install_id_);
432 } else {
433 delegate_->OnInlineInstallFailure(install_id_, error);
434 }
435 }
436
[email protected]8915f342011-08-29 22:14:37437 Release(); // Matches the AddRef in BeginInstall.
438}