blob: 39d1964e7927f9e0550d2e3fac242ff7ef8ee611 [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]c530c852011-10-24 18:18:3423#include "content/common/net/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
28const char kManifestKey[] = "manifest";
29const char kIconUrlKey[] = "icon_url";
30const char kLocalizedNameKey[] = "localized_name";
[email protected]5fdbd6f2011-09-01 17:33:0431const char kLocalizedDescriptionKey[] = "localized_description";
32const char kUsersKey[] = "users";
33const char kAverageRatingKey[] = "average_rating";
34const char kRatingCountKey[] = "rating_count";
[email protected]a221ef092011-09-07 01:34:1035const char kVerifiedSiteKey[] = "verified_site";
[email protected]dd5161a2011-09-14 17:40:1736const char kInlineInstallNotSupportedKey[] = "inline_install_not_supported";
37const char kRedirectUrlKey[] = "redirect_url";
[email protected]8915f342011-08-29 22:14:3738
[email protected]a221ef092011-09-07 01:34:1039const char kInvalidWebstoreItemId[] = "Invalid Chrome Web Store item ID";
40const char kWebstoreRequestError[] =
41 "Could not fetch data from the Chrome Web Store";
42const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
[email protected]8915f342011-08-29 22:14:3743const char kInvalidManifestError[] = "Invalid manifest";
44const char kUserCancelledError[] = "User cancelled install";
[email protected]5da311ea2011-09-14 20:57:3245const char kNoVerifiedSiteError[] =
46 "Inline installs can only be initiated for Chrome Web Store items that "
47 "have a verified site";
48const char kNotFromVerifiedSiteError[] =
[email protected]a221ef092011-09-07 01:34:1049 "Installs can only be initiated by the Chrome Web Store item's verified "
50 "site";
[email protected]dd5161a2011-09-14 17:40:1751const char kInlineInstallSupportedError[] =
52 "Inline installation is not supported for this item. The user will be "
53 "redirected to the Chrome Web Store.";
[email protected]8915f342011-08-29 22:14:3754
55class SafeWebstoreResponseParser : public UtilityProcessHost::Client {
56 public:
57 SafeWebstoreResponseParser(WebstoreInlineInstaller *client,
58 const std::string& webstore_data)
59 : client_(client),
60 webstore_data_(webstore_data),
61 utility_host_(NULL) {}
62
63 void Start() {
64 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
65 BrowserThread::PostTask(
66 BrowserThread::IO,
67 FROM_HERE,
[email protected]8e6ac4b2011-10-17 19:04:3168 base::Bind(&SafeWebstoreResponseParser::StartWorkOnIOThread, this));
[email protected]8915f342011-08-29 22:14:3769 }
70
71 void StartWorkOnIOThread() {
72 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
73 utility_host_ = new UtilityProcessHost(this, BrowserThread::IO);
74 utility_host_->Send(new ChromeUtilityMsg_ParseJSON(webstore_data_));
75 }
76
77 // Implementing pieces of the UtilityProcessHost::Client interface.
78 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
79 bool handled = true;
80 IPC_BEGIN_MESSAGE_MAP(SafeWebstoreResponseParser, message)
81 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Succeeded,
82 OnJSONParseSucceeded)
83 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Failed,
84 OnJSONParseFailed)
85 IPC_MESSAGE_UNHANDLED(handled = false)
86 IPC_END_MESSAGE_MAP()
87 return handled;
88 }
89
90 void OnJSONParseSucceeded(const ListValue& wrapper) {
91 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
92 Value* value = NULL;
93 CHECK(wrapper.Get(0, &value));
94 if (value->IsType(Value::TYPE_DICTIONARY)) {
95 parsed_webstore_data_.reset(
96 static_cast<DictionaryValue*>(value)->DeepCopy());
97 } else {
98 error_ = kInvalidWebstoreResponseError;
99 }
100
101 ReportResults();
102 }
103
104 virtual void OnJSONParseFailed(const std::string& error_message) {
105 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
106 error_ = error_message;
107 ReportResults();
108 }
109
110 void ReportResults() {
111 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
112
113 // The utility_host_ will take care of deleting itself after this call.
114 utility_host_ = NULL;
115
116 BrowserThread::PostTask(
117 BrowserThread::UI,
118 FROM_HERE,
[email protected]8e6ac4b2011-10-17 19:04:31119 base::Bind(&SafeWebstoreResponseParser::ReportResultOnUIThread, this));
[email protected]8915f342011-08-29 22:14:37120 }
121
122 void ReportResultOnUIThread() {
123 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
124 if (error_.empty() && parsed_webstore_data_.get()) {
125 client_->OnWebstoreResponseParseSuccess(parsed_webstore_data_.release());
126 } else {
127 client_->OnWebstoreResponseParseFailure(error_);
128 }
129 }
130
131 private:
132 virtual ~SafeWebstoreResponseParser() {}
133
134 WebstoreInlineInstaller* client_;
135
136 std::string webstore_data_;
137
138 UtilityProcessHost* utility_host_;
139
140 std::string error_;
141 scoped_ptr<DictionaryValue> parsed_webstore_data_;
142};
143
144WebstoreInlineInstaller::WebstoreInlineInstaller(TabContents* tab_contents,
[email protected]a221ef092011-09-07 01:34:10145 int install_id,
[email protected]8915f342011-08-29 22:14:37146 std::string webstore_item_id,
[email protected]a221ef092011-09-07 01:34:10147 GURL requestor_url,
[email protected]8915f342011-08-29 22:14:37148 Delegate* delegate)
[email protected]2fd920fb2011-09-08 23:33:00149 : TabContentsObserver(tab_contents),
[email protected]a221ef092011-09-07 01:34:10150 install_id_(install_id),
[email protected]8915f342011-08-29 22:14:37151 id_(webstore_item_id),
[email protected]a221ef092011-09-07 01:34:10152 requestor_url_(requestor_url),
[email protected]c7bf7452011-09-12 21:31:50153 delegate_(delegate),
154 average_rating_(0.0),
155 rating_count_(0) {}
[email protected]8915f342011-08-29 22:14:37156
157WebstoreInlineInstaller::~WebstoreInlineInstaller() {
158}
159
160void WebstoreInlineInstaller::BeginInstall() {
[email protected]2fd920fb2011-09-08 23:33:00161 AddRef(); // Balanced in CompleteInstall or TabContentsDestroyed.
[email protected]8915f342011-08-29 22:14:37162
163 if (!Extension::IdIsValid(id_)) {
164 CompleteInstall(kInvalidWebstoreItemId);
165 return;
166 }
167
168 GURL webstore_data_url(extension_urls::GetWebstoreItemJsonDataURL(id_));
169
[email protected]36aea2702011-10-26 01:12:22170 webstore_data_url_fetcher_.reset(content::URLFetcher::Create(
171 webstore_data_url, content::URLFetcher::GET, this));
[email protected]8915f342011-08-29 22:14:37172 Profile* profile = Profile::FromBrowserContext(
[email protected]2fd920fb2011-09-08 23:33:00173 tab_contents()->browser_context());
[email protected]7cc6e5632011-10-25 17:56:12174 webstore_data_url_fetcher_->SetRequestContext(
[email protected]8915f342011-08-29 22:14:37175 profile->GetRequestContext());
[email protected]7cc6e5632011-10-25 17:56:12176 webstore_data_url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
177 net::LOAD_DO_NOT_SAVE_COOKIES |
178 net::LOAD_DISABLE_CACHE);
[email protected]8915f342011-08-29 22:14:37179 webstore_data_url_fetcher_->Start();
180}
181
[email protected]7cc6e5632011-10-25 17:56:12182void WebstoreInlineInstaller::OnURLFetchComplete(
183 const content::URLFetcher* source) {
[email protected]8915f342011-08-29 22:14:37184 CHECK_EQ(webstore_data_url_fetcher_.get(), source);
[email protected]2fd920fb2011-09-08 23:33:00185 // We shouldn't be getting UrlFetcher callbacks if the TabContents has gone
186 // away; we stop any in in-progress fetches in TabContentsDestroyed.
187 CHECK(tab_contents());
[email protected]8915f342011-08-29 22:14:37188
[email protected]7cc6e5632011-10-25 17:56:12189 if (!webstore_data_url_fetcher_->GetStatus().is_success() ||
190 webstore_data_url_fetcher_->GetResponseCode() != 200) {
[email protected]8915f342011-08-29 22:14:37191 CompleteInstall(kWebstoreRequestError);
192 return;
193 }
194
195 std::string webstore_json_data;
196 webstore_data_url_fetcher_->GetResponseAsString(&webstore_json_data);
197 webstore_data_url_fetcher_.reset();
198
199 scoped_refptr<SafeWebstoreResponseParser> parser =
200 new SafeWebstoreResponseParser(this, webstore_json_data);
201 // The parser will call us back via OnWebstoreResponseParseSucces or
202 // OnWebstoreResponseParseFailure.
203 parser->Start();
204}
205
206void WebstoreInlineInstaller::OnWebstoreResponseParseSuccess(
207 DictionaryValue* webstore_data) {
[email protected]4693243e2011-09-09 23:52:37208 // Check if the tab has gone away in the meantime.
209 if (!tab_contents()) {
210 CompleteInstall("");
211 return;
212 }
213
[email protected]8915f342011-08-29 22:14:37214 webstore_data_.reset(webstore_data);
215
[email protected]dd5161a2011-09-14 17:40:17216 // The store may not support inline installs for this item, in which case
217 // we open the store-provided redirect URL in a new tab and abort the
218 // installation process.
219 bool inline_install_not_supported = false;
220 if (webstore_data->HasKey(kInlineInstallNotSupportedKey) &&
221 !webstore_data->GetBoolean(
222 kInlineInstallNotSupportedKey, &inline_install_not_supported)) {
[email protected]8915f342011-08-29 22:14:37223 CompleteInstall(kInvalidWebstoreResponseError);
224 return;
225 }
[email protected]dd5161a2011-09-14 17:40:17226 if (inline_install_not_supported) {
227 std::string redirect_url;
228 if (!webstore_data->GetString(kRedirectUrlKey, &redirect_url)) {
229 CompleteInstall(kInvalidWebstoreResponseError);
230 return;
231 }
[email protected]8915f342011-08-29 22:14:37232
[email protected]dd5161a2011-09-14 17:40:17233 tab_contents()->OpenURL(OpenURLParams(
234 GURL(redirect_url),
235 tab_contents()->GetURL(),
236 NEW_FOREGROUND_TAB,
[email protected]e47ae9472011-10-13 19:48:34237 content::PAGE_TRANSITION_AUTO_BOOKMARK,
238 false));
[email protected]dd5161a2011-09-14 17:40:17239 CompleteInstall(kInlineInstallSupportedError);
240 return;
241 }
242
243 // Manifest, number of users, average rating and rating count are required.
244 std::string manifest;
245 if (!webstore_data->GetString(kManifestKey, &manifest) ||
246 !webstore_data->GetString(kUsersKey, &localized_user_count_) ||
[email protected]5fdbd6f2011-09-01 17:33:04247 !webstore_data->GetDouble(kAverageRatingKey, &average_rating_) ||
248 !webstore_data->GetInteger(kRatingCountKey, &rating_count_)) {
249 CompleteInstall(kInvalidWebstoreResponseError);
250 return;
251 }
252
253 if (average_rating_ < ExtensionInstallUI::kMinExtensionRating ||
254 average_rating_ >ExtensionInstallUI::kMaxExtensionRating) {
255 CompleteInstall(kInvalidWebstoreResponseError);
256 return;
257 }
258
259 // Localized name and description are optional.
260 if ((webstore_data->HasKey(kLocalizedNameKey) &&
261 !webstore_data->GetString(kLocalizedNameKey, &localized_name_)) ||
262 (webstore_data->HasKey(kLocalizedDescriptionKey) &&
263 !webstore_data->GetString(
264 kLocalizedDescriptionKey, &localized_description_))) {
[email protected]8915f342011-08-29 22:14:37265 CompleteInstall(kInvalidWebstoreResponseError);
266 return;
267 }
268
269 // Icon URL is optional.
270 GURL icon_url;
271 if (webstore_data->HasKey(kIconUrlKey)) {
272 std::string icon_url_string;
273 if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) {
274 CompleteInstall(kInvalidWebstoreResponseError);
275 return;
276 }
277 icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
278 icon_url_string);
279 if (!icon_url.is_valid()) {
280 CompleteInstall(kInvalidWebstoreResponseError);
281 return;
282 }
283 }
284
[email protected]5da311ea2011-09-14 20:57:32285 // Verified site is required
[email protected]a221ef092011-09-07 01:34:10286 if (webstore_data->HasKey(kVerifiedSiteKey)) {
287 std::string verified_site_domain;
288 if (!webstore_data->GetString(kVerifiedSiteKey, &verified_site_domain)) {
289 CompleteInstall(kInvalidWebstoreResponseError);
290 return;
291 }
292
293 URLPattern verified_site_pattern(URLPattern::SCHEME_ALL);
294 verified_site_pattern.SetScheme("*");
295 verified_site_pattern.SetHost(verified_site_domain);
296 verified_site_pattern.SetMatchSubdomains(true);
297 verified_site_pattern.SetPath("/*");
298
299 if (!verified_site_pattern.MatchesURL(requestor_url_)) {
[email protected]5da311ea2011-09-14 20:57:32300 CompleteInstall(kNotFromVerifiedSiteError);
[email protected]a221ef092011-09-07 01:34:10301 return;
302 }
[email protected]5da311ea2011-09-14 20:57:32303 } else {
304 CompleteInstall(kNoVerifiedSiteError);
305 return;
[email protected]a221ef092011-09-07 01:34:10306 }
307
[email protected]8915f342011-08-29 22:14:37308 scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper(
309 this,
[email protected]98e4e522011-10-25 13:00:16310 id_,
[email protected]8915f342011-08-29 22:14:37311 manifest,
312 "", // We don't have any icon data.
313 icon_url,
[email protected]2fd920fb2011-09-08 23:33:00314 Profile::FromBrowserContext(tab_contents()->browser_context())->
[email protected]8915f342011-08-29 22:14:37315 GetRequestContext());
316 // The helper will call us back via OnWebstoreParseSucces or
317 // OnWebstoreParseFailure.
318 helper->Start();
319}
320
321void WebstoreInlineInstaller::OnWebstoreResponseParseFailure(
322 const std::string& error) {
323 CompleteInstall(error);
324}
325
326void WebstoreInlineInstaller::OnWebstoreParseSuccess(
[email protected]98e4e522011-10-25 13:00:16327 const std::string& id,
[email protected]8915f342011-08-29 22:14:37328 const SkBitmap& icon,
329 base::DictionaryValue* manifest) {
[email protected]2fd920fb2011-09-08 23:33:00330 // Check if the tab has gone away in the meantime.
331 if (!tab_contents()) {
332 CompleteInstall("");
333 return;
334 }
335
[email protected]98e4e522011-10-25 13:00:16336 CHECK_EQ(id_, id);
[email protected]8915f342011-08-29 22:14:37337 manifest_.reset(manifest);
338 icon_ = icon;
339
340 Profile* profile = Profile::FromBrowserContext(
[email protected]2fd920fb2011-09-08 23:33:00341 tab_contents()->browser_context());
[email protected]5fdbd6f2011-09-01 17:33:04342
343 ExtensionInstallUI::Prompt prompt(ExtensionInstallUI::INLINE_INSTALL_PROMPT);
[email protected]122e8df2011-09-02 18:20:24344 prompt.SetInlineInstallWebstoreData(localized_user_count_,
345 average_rating_,
346 rating_count_);
[email protected]5fdbd6f2011-09-01 17:33:04347
[email protected]d0243b052011-09-10 18:35:11348 if (!ShowExtensionInstallDialogForManifest(profile,
349 this,
350 manifest,
351 id_,
352 localized_name_,
353 localized_description_,
354 &icon_,
355 prompt,
356 &dummy_extension_)) {
[email protected]8915f342011-08-29 22:14:37357 CompleteInstall(kInvalidManifestError);
358 return;
359 }
360
361 // Control flow finishes up in InstallUIProceed or InstallUIAbort.
362}
363
364void WebstoreInlineInstaller::OnWebstoreParseFailure(
[email protected]98e4e522011-10-25 13:00:16365 const std::string& id,
[email protected]8915f342011-08-29 22:14:37366 InstallHelperResultCode result_code,
367 const std::string& error_message) {
368 CompleteInstall(error_message);
369}
370
371void WebstoreInlineInstaller::InstallUIProceed() {
[email protected]2fd920fb2011-09-08 23:33:00372 // Check if the tab has gone away in the meantime.
373 if (!tab_contents()) {
374 CompleteInstall("");
375 return;
376 }
377
[email protected]8915f342011-08-29 22:14:37378 CrxInstaller::WhitelistEntry* entry = new CrxInstaller::WhitelistEntry;
379
380 entry->parsed_manifest.reset(manifest_.get()->DeepCopy());
381 entry->localized_name = localized_name_;
382 entry->use_app_installed_bubble = true;
383 CrxInstaller::SetWhitelistEntry(id_, entry);
384
[email protected]655b2b1a2011-10-13 17:13:06385 Profile* profile = Profile::FromBrowserContext(
386 tab_contents()->browser_context());
[email protected]8915f342011-08-29 22:14:37387
[email protected]98e4e522011-10-25 13:00:16388 scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller(
389 profile, this, &(tab_contents()->controller()), id_,
390 WebstoreInstaller::FLAG_INLINE_INSTALL);
391 installer->Start();
[email protected]8915f342011-08-29 22:14:37392}
393
394void WebstoreInlineInstaller::InstallUIAbort(bool user_initiated) {
395 CompleteInstall(kUserCancelledError);
396}
397
[email protected]2fd920fb2011-09-08 23:33:00398void WebstoreInlineInstaller::TabContentsDestroyed(TabContents* tab_contents) {
399 // Abort any in-progress fetches.
400 if (webstore_data_url_fetcher_.get()) {
401 webstore_data_url_fetcher_.reset();
402 Release(); // Matches the AddRef in BeginInstall.
[email protected]8915f342011-08-29 22:14:37403 }
[email protected]2fd920fb2011-09-08 23:33:00404}
405
[email protected]cb08ba22011-10-19 21:41:40406void WebstoreInlineInstaller::OnExtensionInstallSuccess(const std::string& id) {
407 CHECK_EQ(id_, id);
408 CompleteInstall("");
409}
410
411void WebstoreInlineInstaller::OnExtensionInstallFailure(
412 const std::string& id, const std::string& error) {
413 CHECK_EQ(id_, id);
414 CompleteInstall(error);
415}
416
[email protected]2fd920fb2011-09-08 23:33:00417void WebstoreInlineInstaller::CompleteInstall(const std::string& error) {
418 // Only bother responding if there's still a tab contents to send back the
419 // response to.
420 if (tab_contents()) {
421 if (error.empty()) {
422 delegate_->OnInlineInstallSuccess(install_id_);
423 } else {
424 delegate_->OnInlineInstallFailure(install_id_, error);
425 }
426 }
427
[email protected]8915f342011-08-29 22:14:37428 Release(); // Matches the AddRef in BeginInstall.
429}