blob: 8219ab3074ae9840402a4223368d3c5559cf7561 [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
9#include "base/string_util.h"
10#include "base/values.h"
11#include "chrome/browser/browser_process.h"
12#include "chrome/browser/extensions/crx_installer.h"
13#include "chrome/browser/extensions/extension_install_dialog.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/common/chrome_utility_messages.h"
16#include "chrome/common/extensions/extension.h"
17#include "chrome/common/extensions/extension_constants.h"
[email protected]a221ef092011-09-07 01:34:1018#include "chrome/common/extensions/url_pattern.h"
[email protected]8915f342011-08-29 22:14:3719#include "content/browser/tab_contents/tab_contents.h"
20#include "content/browser/utility_process_host.h"
[email protected]8915f342011-08-29 22:14:3721#include "net/base/escape.h"
22#include "net/url_request/url_request_status.h"
23
24const char kManifestKey[] = "manifest";
25const char kIconUrlKey[] = "icon_url";
26const char kLocalizedNameKey[] = "localized_name";
[email protected]5fdbd6f2011-09-01 17:33:0427const char kLocalizedDescriptionKey[] = "localized_description";
28const char kUsersKey[] = "users";
29const char kAverageRatingKey[] = "average_rating";
30const char kRatingCountKey[] = "rating_count";
[email protected]a221ef092011-09-07 01:34:1031const char kVerifiedSiteKey[] = "verified_site";
[email protected]8915f342011-08-29 22:14:3732
[email protected]a221ef092011-09-07 01:34:1033const char kInvalidWebstoreItemId[] = "Invalid Chrome Web Store item ID";
34const char kWebstoreRequestError[] =
35 "Could not fetch data from the Chrome Web Store";
36const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
[email protected]8915f342011-08-29 22:14:3737const char kInvalidManifestError[] = "Invalid manifest";
38const char kUserCancelledError[] = "User cancelled install";
[email protected]a221ef092011-09-07 01:34:1039const char kNotFromVerifiedSite[] =
40 "Installs can only be initiated by the Chrome Web Store item's verified "
41 "site";
[email protected]8915f342011-08-29 22:14:3742
43class SafeWebstoreResponseParser : public UtilityProcessHost::Client {
44 public:
45 SafeWebstoreResponseParser(WebstoreInlineInstaller *client,
46 const std::string& webstore_data)
47 : client_(client),
48 webstore_data_(webstore_data),
49 utility_host_(NULL) {}
50
51 void Start() {
52 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
53 BrowserThread::PostTask(
54 BrowserThread::IO,
55 FROM_HERE,
56 NewRunnableMethod(this,
57 &SafeWebstoreResponseParser::StartWorkOnIOThread));
58 }
59
60 void StartWorkOnIOThread() {
61 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
62 utility_host_ = new UtilityProcessHost(this, BrowserThread::IO);
63 utility_host_->Send(new ChromeUtilityMsg_ParseJSON(webstore_data_));
64 }
65
66 // Implementing pieces of the UtilityProcessHost::Client interface.
67 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
68 bool handled = true;
69 IPC_BEGIN_MESSAGE_MAP(SafeWebstoreResponseParser, message)
70 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Succeeded,
71 OnJSONParseSucceeded)
72 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Failed,
73 OnJSONParseFailed)
74 IPC_MESSAGE_UNHANDLED(handled = false)
75 IPC_END_MESSAGE_MAP()
76 return handled;
77 }
78
79 void OnJSONParseSucceeded(const ListValue& wrapper) {
80 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
81 Value* value = NULL;
82 CHECK(wrapper.Get(0, &value));
83 if (value->IsType(Value::TYPE_DICTIONARY)) {
84 parsed_webstore_data_.reset(
85 static_cast<DictionaryValue*>(value)->DeepCopy());
86 } else {
87 error_ = kInvalidWebstoreResponseError;
88 }
89
90 ReportResults();
91 }
92
93 virtual void OnJSONParseFailed(const std::string& error_message) {
94 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
95 error_ = error_message;
96 ReportResults();
97 }
98
99 void ReportResults() {
100 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
101
102 // The utility_host_ will take care of deleting itself after this call.
103 utility_host_ = NULL;
104
105 BrowserThread::PostTask(
106 BrowserThread::UI,
107 FROM_HERE,
108 NewRunnableMethod(this,
109 &SafeWebstoreResponseParser::ReportResultOnUIThread));
110 }
111
112 void ReportResultOnUIThread() {
113 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
114 if (error_.empty() && parsed_webstore_data_.get()) {
115 client_->OnWebstoreResponseParseSuccess(parsed_webstore_data_.release());
116 } else {
117 client_->OnWebstoreResponseParseFailure(error_);
118 }
119 }
120
121 private:
122 virtual ~SafeWebstoreResponseParser() {}
123
124 WebstoreInlineInstaller* client_;
125
126 std::string webstore_data_;
127
128 UtilityProcessHost* utility_host_;
129
130 std::string error_;
131 scoped_ptr<DictionaryValue> parsed_webstore_data_;
132};
133
134WebstoreInlineInstaller::WebstoreInlineInstaller(TabContents* tab_contents,
[email protected]a221ef092011-09-07 01:34:10135 int install_id,
[email protected]8915f342011-08-29 22:14:37136 std::string webstore_item_id,
[email protected]a221ef092011-09-07 01:34:10137 GURL requestor_url,
[email protected]8915f342011-08-29 22:14:37138 Delegate* delegate)
[email protected]2fd920fb2011-09-08 23:33:00139 : TabContentsObserver(tab_contents),
[email protected]a221ef092011-09-07 01:34:10140 install_id_(install_id),
[email protected]8915f342011-08-29 22:14:37141 id_(webstore_item_id),
[email protected]a221ef092011-09-07 01:34:10142 requestor_url_(requestor_url),
[email protected]8915f342011-08-29 22:14:37143 delegate_(delegate) {}
144
145WebstoreInlineInstaller::~WebstoreInlineInstaller() {
146}
147
148void WebstoreInlineInstaller::BeginInstall() {
[email protected]2fd920fb2011-09-08 23:33:00149 AddRef(); // Balanced in CompleteInstall or TabContentsDestroyed.
[email protected]8915f342011-08-29 22:14:37150
151 if (!Extension::IdIsValid(id_)) {
152 CompleteInstall(kInvalidWebstoreItemId);
153 return;
154 }
155
156 GURL webstore_data_url(extension_urls::GetWebstoreItemJsonDataURL(id_));
157
158 webstore_data_url_fetcher_.reset(
159 new URLFetcher(webstore_data_url, URLFetcher::GET, this));
160 Profile* profile = Profile::FromBrowserContext(
[email protected]2fd920fb2011-09-08 23:33:00161 tab_contents()->browser_context());
[email protected]8915f342011-08-29 22:14:37162 webstore_data_url_fetcher_->set_request_context(
163 profile->GetRequestContext());
164 webstore_data_url_fetcher_->Start();
165}
166
167void WebstoreInlineInstaller::OnURLFetchComplete(const URLFetcher* source) {
168 CHECK_EQ(webstore_data_url_fetcher_.get(), source);
[email protected]2fd920fb2011-09-08 23:33:00169 // We shouldn't be getting UrlFetcher callbacks if the TabContents has gone
170 // away; we stop any in in-progress fetches in TabContentsDestroyed.
171 CHECK(tab_contents());
[email protected]8915f342011-08-29 22:14:37172
173 if (!webstore_data_url_fetcher_->status().is_success() ||
174 webstore_data_url_fetcher_->response_code() != 200) {
175 CompleteInstall(kWebstoreRequestError);
176 return;
177 }
178
179 std::string webstore_json_data;
180 webstore_data_url_fetcher_->GetResponseAsString(&webstore_json_data);
181 webstore_data_url_fetcher_.reset();
182
183 scoped_refptr<SafeWebstoreResponseParser> parser =
184 new SafeWebstoreResponseParser(this, webstore_json_data);
185 // The parser will call us back via OnWebstoreResponseParseSucces or
186 // OnWebstoreResponseParseFailure.
187 parser->Start();
188}
189
190void WebstoreInlineInstaller::OnWebstoreResponseParseSuccess(
191 DictionaryValue* webstore_data) {
192 webstore_data_.reset(webstore_data);
193
194 std::string manifest;
195 if (!webstore_data->GetString(kManifestKey, &manifest)) {
196 CompleteInstall(kInvalidWebstoreResponseError);
197 return;
198 }
199
[email protected]5fdbd6f2011-09-01 17:33:04200 // Number of users, average rating and rating count are required.
201 if (!webstore_data->GetString(kUsersKey, &localized_user_count_) ||
202 !webstore_data->GetDouble(kAverageRatingKey, &average_rating_) ||
203 !webstore_data->GetInteger(kRatingCountKey, &rating_count_)) {
204 CompleteInstall(kInvalidWebstoreResponseError);
205 return;
206 }
207
208 if (average_rating_ < ExtensionInstallUI::kMinExtensionRating ||
209 average_rating_ >ExtensionInstallUI::kMaxExtensionRating) {
210 CompleteInstall(kInvalidWebstoreResponseError);
211 return;
212 }
213
214 // Localized name and description are optional.
215 if ((webstore_data->HasKey(kLocalizedNameKey) &&
216 !webstore_data->GetString(kLocalizedNameKey, &localized_name_)) ||
217 (webstore_data->HasKey(kLocalizedDescriptionKey) &&
218 !webstore_data->GetString(
219 kLocalizedDescriptionKey, &localized_description_))) {
[email protected]8915f342011-08-29 22:14:37220 CompleteInstall(kInvalidWebstoreResponseError);
221 return;
222 }
223
224 // Icon URL is optional.
225 GURL icon_url;
226 if (webstore_data->HasKey(kIconUrlKey)) {
227 std::string icon_url_string;
228 if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) {
229 CompleteInstall(kInvalidWebstoreResponseError);
230 return;
231 }
232 icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
233 icon_url_string);
234 if (!icon_url.is_valid()) {
235 CompleteInstall(kInvalidWebstoreResponseError);
236 return;
237 }
238 }
239
[email protected]a221ef092011-09-07 01:34:10240 // Verified site is optional
241 if (webstore_data->HasKey(kVerifiedSiteKey)) {
242 std::string verified_site_domain;
243 if (!webstore_data->GetString(kVerifiedSiteKey, &verified_site_domain)) {
244 CompleteInstall(kInvalidWebstoreResponseError);
245 return;
246 }
247
248 URLPattern verified_site_pattern(URLPattern::SCHEME_ALL);
249 verified_site_pattern.SetScheme("*");
250 verified_site_pattern.SetHost(verified_site_domain);
251 verified_site_pattern.SetMatchSubdomains(true);
252 verified_site_pattern.SetPath("/*");
253
254 if (!verified_site_pattern.MatchesURL(requestor_url_)) {
255 CompleteInstall(kNotFromVerifiedSite);
256 return;
257 }
258 }
259
[email protected]8915f342011-08-29 22:14:37260 scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper(
261 this,
262 manifest,
263 "", // We don't have any icon data.
264 icon_url,
[email protected]2fd920fb2011-09-08 23:33:00265 Profile::FromBrowserContext(tab_contents()->browser_context())->
[email protected]8915f342011-08-29 22:14:37266 GetRequestContext());
267 // The helper will call us back via OnWebstoreParseSucces or
268 // OnWebstoreParseFailure.
269 helper->Start();
270}
271
272void WebstoreInlineInstaller::OnWebstoreResponseParseFailure(
273 const std::string& error) {
274 CompleteInstall(error);
275}
276
277void WebstoreInlineInstaller::OnWebstoreParseSuccess(
278 const SkBitmap& icon,
279 base::DictionaryValue* manifest) {
[email protected]2fd920fb2011-09-08 23:33:00280 // Check if the tab has gone away in the meantime.
281 if (!tab_contents()) {
282 CompleteInstall("");
283 return;
284 }
285
[email protected]8915f342011-08-29 22:14:37286 manifest_.reset(manifest);
287 icon_ = icon;
288
289 Profile* profile = Profile::FromBrowserContext(
[email protected]2fd920fb2011-09-08 23:33:00290 tab_contents()->browser_context());
[email protected]5fdbd6f2011-09-01 17:33:04291
292 ExtensionInstallUI::Prompt prompt(ExtensionInstallUI::INLINE_INSTALL_PROMPT);
[email protected]122e8df2011-09-02 18:20:24293 prompt.SetInlineInstallWebstoreData(localized_user_count_,
294 average_rating_,
295 rating_count_);
[email protected]5fdbd6f2011-09-01 17:33:04296
[email protected]8915f342011-08-29 22:14:37297 ShowExtensionInstallDialogForManifest(profile,
298 this,
299 manifest,
300 id_,
301 localized_name_,
[email protected]5fdbd6f2011-09-01 17:33:04302 localized_description_,
[email protected]8915f342011-08-29 22:14:37303 &icon_,
[email protected]5fdbd6f2011-09-01 17:33:04304 prompt,
305 &dummy_extension_);
[email protected]8915f342011-08-29 22:14:37306
[email protected]5fdbd6f2011-09-01 17:33:04307 if (!dummy_extension_.get()) {
[email protected]8915f342011-08-29 22:14:37308 CompleteInstall(kInvalidManifestError);
309 return;
310 }
311
312 // Control flow finishes up in InstallUIProceed or InstallUIAbort.
313}
314
315void WebstoreInlineInstaller::OnWebstoreParseFailure(
316 InstallHelperResultCode result_code,
317 const std::string& error_message) {
318 CompleteInstall(error_message);
319}
320
321void WebstoreInlineInstaller::InstallUIProceed() {
[email protected]2fd920fb2011-09-08 23:33:00322 // Check if the tab has gone away in the meantime.
323 if (!tab_contents()) {
324 CompleteInstall("");
325 return;
326 }
327
[email protected]8915f342011-08-29 22:14:37328 CrxInstaller::WhitelistEntry* entry = new CrxInstaller::WhitelistEntry;
329
330 entry->parsed_manifest.reset(manifest_.get()->DeepCopy());
331 entry->localized_name = localized_name_;
332 entry->use_app_installed_bubble = true;
333 CrxInstaller::SetWhitelistEntry(id_, entry);
334
335 GURL install_url(extension_urls::GetWebstoreInstallUrl(
336 id_, g_browser_process->GetApplicationLocale()));
337
[email protected]2fd920fb2011-09-08 23:33:00338 NavigationController& controller = tab_contents()->controller();
[email protected]8915f342011-08-29 22:14:37339 // TODO(mihaip): we pretend like the referrer is the gallery in order to pass
340 // the checks in ExtensionService::IsDownloadFromGallery. We should instead
341 // pass the real referrer, track that this is an inline install in the
342 // whitelist entry and look that up when checking that this is a valid
343 // download.
344 GURL referrer(extension_urls::GetWebstoreItemDetailURLPrefix() + id_);
345 controller.LoadURL(install_url, referrer, PageTransition::LINK);
346
347 // TODO(mihaip): the success message should happen later, when the extension
348 // is actually downloaded and installed (when NOTIFICATION_EXTENSION_INSTALLED
349 // or NOTIFICATION_EXTENSION_INSTALL_ERROR fire).
350 CompleteInstall("");
351}
352
353void WebstoreInlineInstaller::InstallUIAbort(bool user_initiated) {
354 CompleteInstall(kUserCancelledError);
355}
356
[email protected]2fd920fb2011-09-08 23:33:00357void WebstoreInlineInstaller::TabContentsDestroyed(TabContents* tab_contents) {
358 // Abort any in-progress fetches.
359 if (webstore_data_url_fetcher_.get()) {
360 webstore_data_url_fetcher_.reset();
361 Release(); // Matches the AddRef in BeginInstall.
[email protected]8915f342011-08-29 22:14:37362 }
[email protected]2fd920fb2011-09-08 23:33:00363}
364
365void WebstoreInlineInstaller::CompleteInstall(const std::string& error) {
366 // Only bother responding if there's still a tab contents to send back the
367 // response to.
368 if (tab_contents()) {
369 if (error.empty()) {
370 delegate_->OnInlineInstallSuccess(install_id_);
371 } else {
372 delegate_->OnInlineInstallFailure(install_id_, error);
373 }
374 }
375
[email protected]8915f342011-08-29 22:14:37376 Release(); // Matches the AddRef in BeginInstall.
377}