blob: 087f4356e68d21780ced6b310ea0027a170f4620 [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;
29
[email protected]8915f342011-08-29 22:14:3730const char kManifestKey[] = "manifest";
31const char kIconUrlKey[] = "icon_url";
32const char kLocalizedNameKey[] = "localized_name";
[email protected]5fdbd6f2011-09-01 17:33:0433const char kLocalizedDescriptionKey[] = "localized_description";
34const char kUsersKey[] = "users";
35const char kAverageRatingKey[] = "average_rating";
36const char kRatingCountKey[] = "rating_count";
[email protected]a221ef092011-09-07 01:34:1037const char kVerifiedSiteKey[] = "verified_site";
[email protected]dd5161a2011-09-14 17:40:1738const char kInlineInstallNotSupportedKey[] = "inline_install_not_supported";
39const char kRedirectUrlKey[] = "redirect_url";
[email protected]8915f342011-08-29 22:14:3740
[email protected]a221ef092011-09-07 01:34:1041const char kInvalidWebstoreItemId[] = "Invalid Chrome Web Store item ID";
42const char kWebstoreRequestError[] =
43 "Could not fetch data from the Chrome Web Store";
44const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
[email protected]8915f342011-08-29 22:14:3745const char kInvalidManifestError[] = "Invalid manifest";
46const char kUserCancelledError[] = "User cancelled install";
[email protected]5da311ea2011-09-14 20:57:3247const char kNoVerifiedSiteError[] =
48 "Inline installs can only be initiated for Chrome Web Store items that "
49 "have a verified site";
50const char kNotFromVerifiedSiteError[] =
[email protected]a221ef092011-09-07 01:34:1051 "Installs can only be initiated by the Chrome Web Store item's verified "
52 "site";
[email protected]dd5161a2011-09-14 17:40:1753const char kInlineInstallSupportedError[] =
54 "Inline installation is not supported for this item. The user will be "
55 "redirected to the Chrome Web Store.";
[email protected]8915f342011-08-29 22:14:3756
57class SafeWebstoreResponseParser : public UtilityProcessHost::Client {
58 public:
59 SafeWebstoreResponseParser(WebstoreInlineInstaller *client,
60 const std::string& webstore_data)
61 : client_(client),
62 webstore_data_(webstore_data),
63 utility_host_(NULL) {}
64
65 void Start() {
66 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
67 BrowserThread::PostTask(
68 BrowserThread::IO,
69 FROM_HERE,
[email protected]8e6ac4b2011-10-17 19:04:3170 base::Bind(&SafeWebstoreResponseParser::StartWorkOnIOThread, this));
[email protected]8915f342011-08-29 22:14:3771 }
72
73 void StartWorkOnIOThread() {
74 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
75 utility_host_ = new UtilityProcessHost(this, BrowserThread::IO);
[email protected]3dd18d02011-12-13 19:32:3876 utility_host_->set_use_linux_zygote(true);
[email protected]8915f342011-08-29 22:14:3777 utility_host_->Send(new ChromeUtilityMsg_ParseJSON(webstore_data_));
78 }
79
80 // Implementing pieces of the UtilityProcessHost::Client interface.
81 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
82 bool handled = true;
83 IPC_BEGIN_MESSAGE_MAP(SafeWebstoreResponseParser, message)
84 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Succeeded,
85 OnJSONParseSucceeded)
86 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Failed,
87 OnJSONParseFailed)
88 IPC_MESSAGE_UNHANDLED(handled = false)
89 IPC_END_MESSAGE_MAP()
90 return handled;
91 }
92
93 void OnJSONParseSucceeded(const ListValue& wrapper) {
94 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
95 Value* value = NULL;
96 CHECK(wrapper.Get(0, &value));
97 if (value->IsType(Value::TYPE_DICTIONARY)) {
98 parsed_webstore_data_.reset(
99 static_cast<DictionaryValue*>(value)->DeepCopy());
100 } else {
101 error_ = kInvalidWebstoreResponseError;
102 }
103
104 ReportResults();
105 }
106
107 virtual void OnJSONParseFailed(const std::string& error_message) {
108 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
109 error_ = error_message;
110 ReportResults();
111 }
112
113 void ReportResults() {
114 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
115
116 // The utility_host_ will take care of deleting itself after this call.
117 utility_host_ = NULL;
118
119 BrowserThread::PostTask(
120 BrowserThread::UI,
121 FROM_HERE,
[email protected]8e6ac4b2011-10-17 19:04:31122 base::Bind(&SafeWebstoreResponseParser::ReportResultOnUIThread, this));
[email protected]8915f342011-08-29 22:14:37123 }
124
125 void ReportResultOnUIThread() {
126 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
127 if (error_.empty() && parsed_webstore_data_.get()) {
128 client_->OnWebstoreResponseParseSuccess(parsed_webstore_data_.release());
129 } else {
130 client_->OnWebstoreResponseParseFailure(error_);
131 }
132 }
133
134 private:
135 virtual ~SafeWebstoreResponseParser() {}
136
137 WebstoreInlineInstaller* client_;
138
139 std::string webstore_data_;
140
141 UtilityProcessHost* utility_host_;
142
143 std::string error_;
144 scoped_ptr<DictionaryValue> parsed_webstore_data_;
145};
146
147WebstoreInlineInstaller::WebstoreInlineInstaller(TabContents* tab_contents,
[email protected]a221ef092011-09-07 01:34:10148 int install_id,
[email protected]8915f342011-08-29 22:14:37149 std::string webstore_item_id,
[email protected]a221ef092011-09-07 01:34:10150 GURL requestor_url,
[email protected]8915f342011-08-29 22:14:37151 Delegate* delegate)
[email protected]2fd920fb2011-09-08 23:33:00152 : TabContentsObserver(tab_contents),
[email protected]a221ef092011-09-07 01:34:10153 install_id_(install_id),
[email protected]8915f342011-08-29 22:14:37154 id_(webstore_item_id),
[email protected]a221ef092011-09-07 01:34:10155 requestor_url_(requestor_url),
[email protected]c7bf7452011-09-12 21:31:50156 delegate_(delegate),
157 average_rating_(0.0),
158 rating_count_(0) {}
[email protected]8915f342011-08-29 22:14:37159
160WebstoreInlineInstaller::~WebstoreInlineInstaller() {
161}
162
163void WebstoreInlineInstaller::BeginInstall() {
[email protected]2fd920fb2011-09-08 23:33:00164 AddRef(); // Balanced in CompleteInstall or TabContentsDestroyed.
[email protected]8915f342011-08-29 22:14:37165
166 if (!Extension::IdIsValid(id_)) {
167 CompleteInstall(kInvalidWebstoreItemId);
168 return;
169 }
170
171 GURL webstore_data_url(extension_urls::GetWebstoreItemJsonDataURL(id_));
172
[email protected]36aea2702011-10-26 01:12:22173 webstore_data_url_fetcher_.reset(content::URLFetcher::Create(
174 webstore_data_url, content::URLFetcher::GET, this));
[email protected]8915f342011-08-29 22:14:37175 Profile* profile = Profile::FromBrowserContext(
[email protected]2fd920fb2011-09-08 23:33:00176 tab_contents()->browser_context());
[email protected]7cc6e5632011-10-25 17:56:12177 webstore_data_url_fetcher_->SetRequestContext(
[email protected]8915f342011-08-29 22:14:37178 profile->GetRequestContext());
[email protected]b4574c02011-11-17 06:19:13179 // Use the requesting page as the referrer both since that is more correct
180 // (it is the page that caused this request to happen) and so that we can
181 // track top sites that trigger inline install requests.
182 webstore_data_url_fetcher_->SetReferrer(requestor_url_.spec());
[email protected]7cc6e5632011-10-25 17:56:12183 webstore_data_url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
184 net::LOAD_DO_NOT_SAVE_COOKIES |
185 net::LOAD_DISABLE_CACHE);
[email protected]8915f342011-08-29 22:14:37186 webstore_data_url_fetcher_->Start();
187}
188
[email protected]7cc6e5632011-10-25 17:56:12189void WebstoreInlineInstaller::OnURLFetchComplete(
190 const content::URLFetcher* source) {
[email protected]8915f342011-08-29 22:14:37191 CHECK_EQ(webstore_data_url_fetcher_.get(), source);
[email protected]2fd920fb2011-09-08 23:33:00192 // We shouldn't be getting UrlFetcher callbacks if the TabContents has gone
193 // away; we stop any in in-progress fetches in TabContentsDestroyed.
194 CHECK(tab_contents());
[email protected]8915f342011-08-29 22:14:37195
[email protected]7cc6e5632011-10-25 17:56:12196 if (!webstore_data_url_fetcher_->GetStatus().is_success() ||
197 webstore_data_url_fetcher_->GetResponseCode() != 200) {
[email protected]8915f342011-08-29 22:14:37198 CompleteInstall(kWebstoreRequestError);
199 return;
200 }
201
202 std::string webstore_json_data;
203 webstore_data_url_fetcher_->GetResponseAsString(&webstore_json_data);
204 webstore_data_url_fetcher_.reset();
205
206 scoped_refptr<SafeWebstoreResponseParser> parser =
207 new SafeWebstoreResponseParser(this, webstore_json_data);
208 // The parser will call us back via OnWebstoreResponseParseSucces or
209 // OnWebstoreResponseParseFailure.
210 parser->Start();
211}
212
213void WebstoreInlineInstaller::OnWebstoreResponseParseSuccess(
214 DictionaryValue* webstore_data) {
[email protected]4693243e2011-09-09 23:52:37215 // Check if the tab has gone away in the meantime.
216 if (!tab_contents()) {
217 CompleteInstall("");
218 return;
219 }
220
[email protected]8915f342011-08-29 22:14:37221 webstore_data_.reset(webstore_data);
222
[email protected]dd5161a2011-09-14 17:40:17223 // The store may not support inline installs for this item, in which case
224 // we open the store-provided redirect URL in a new tab and abort the
225 // installation process.
226 bool inline_install_not_supported = false;
227 if (webstore_data->HasKey(kInlineInstallNotSupportedKey) &&
228 !webstore_data->GetBoolean(
229 kInlineInstallNotSupportedKey, &inline_install_not_supported)) {
[email protected]8915f342011-08-29 22:14:37230 CompleteInstall(kInvalidWebstoreResponseError);
231 return;
232 }
[email protected]dd5161a2011-09-14 17:40:17233 if (inline_install_not_supported) {
234 std::string redirect_url;
235 if (!webstore_data->GetString(kRedirectUrlKey, &redirect_url)) {
236 CompleteInstall(kInvalidWebstoreResponseError);
237 return;
238 }
[email protected]8915f342011-08-29 22:14:37239
[email protected]dd5161a2011-09-14 17:40:17240 tab_contents()->OpenURL(OpenURLParams(
241 GURL(redirect_url),
[email protected]bce1f1c2011-12-05 15:11:58242 content::Referrer(tab_contents()->GetURL(),
243 WebKit::WebReferrerPolicyDefault),
[email protected]dd5161a2011-09-14 17:40:17244 NEW_FOREGROUND_TAB,
[email protected]e47ae9472011-10-13 19:48:34245 content::PAGE_TRANSITION_AUTO_BOOKMARK,
246 false));
[email protected]dd5161a2011-09-14 17:40:17247 CompleteInstall(kInlineInstallSupportedError);
248 return;
249 }
250
251 // Manifest, number of users, average rating and rating count are required.
252 std::string manifest;
253 if (!webstore_data->GetString(kManifestKey, &manifest) ||
254 !webstore_data->GetString(kUsersKey, &localized_user_count_) ||
[email protected]5fdbd6f2011-09-01 17:33:04255 !webstore_data->GetDouble(kAverageRatingKey, &average_rating_) ||
256 !webstore_data->GetInteger(kRatingCountKey, &rating_count_)) {
257 CompleteInstall(kInvalidWebstoreResponseError);
258 return;
259 }
260
261 if (average_rating_ < ExtensionInstallUI::kMinExtensionRating ||
262 average_rating_ >ExtensionInstallUI::kMaxExtensionRating) {
263 CompleteInstall(kInvalidWebstoreResponseError);
264 return;
265 }
266
267 // Localized name and description are optional.
268 if ((webstore_data->HasKey(kLocalizedNameKey) &&
269 !webstore_data->GetString(kLocalizedNameKey, &localized_name_)) ||
270 (webstore_data->HasKey(kLocalizedDescriptionKey) &&
271 !webstore_data->GetString(
272 kLocalizedDescriptionKey, &localized_description_))) {
[email protected]8915f342011-08-29 22:14:37273 CompleteInstall(kInvalidWebstoreResponseError);
274 return;
275 }
276
277 // Icon URL is optional.
278 GURL icon_url;
279 if (webstore_data->HasKey(kIconUrlKey)) {
280 std::string icon_url_string;
281 if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) {
282 CompleteInstall(kInvalidWebstoreResponseError);
283 return;
284 }
285 icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
286 icon_url_string);
287 if (!icon_url.is_valid()) {
288 CompleteInstall(kInvalidWebstoreResponseError);
289 return;
290 }
291 }
292
[email protected]5da311ea2011-09-14 20:57:32293 // Verified site is required
[email protected]a221ef092011-09-07 01:34:10294 if (webstore_data->HasKey(kVerifiedSiteKey)) {
295 std::string verified_site_domain;
296 if (!webstore_data->GetString(kVerifiedSiteKey, &verified_site_domain)) {
297 CompleteInstall(kInvalidWebstoreResponseError);
298 return;
299 }
300
[email protected]cf57d5f2011-12-09 01:42:07301 URLPattern verified_site_pattern(URLPattern::ERROR_ON_PORTS,
302 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]2fd920fb2011-09-08 23:33:00323 Profile::FromBrowserContext(tab_contents()->browser_context())->
[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]2fd920fb2011-09-08 23:33:00350 tab_contents()->browser_context());
[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(
395 tab_contents()->browser_context());
[email protected]8915f342011-08-29 22:14:37396
[email protected]98e4e522011-10-25 13:00:16397 scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller(
398 profile, this, &(tab_contents()->controller()), id_,
399 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]2fd920fb2011-09-08 23:33:00407void WebstoreInlineInstaller::TabContentsDestroyed(TabContents* tab_contents) {
408 // 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}