blob: 7db39fbf8483915a44183819402ca1962984e64a [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)
139 : tab_contents_(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() {
149 AddRef(); // Balanced in CompleteInstall.
150
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(
161 tab_contents_->browser_context());
162 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);
169
170 if (!webstore_data_url_fetcher_->status().is_success() ||
171 webstore_data_url_fetcher_->response_code() != 200) {
172 CompleteInstall(kWebstoreRequestError);
173 return;
174 }
175
176 std::string webstore_json_data;
177 webstore_data_url_fetcher_->GetResponseAsString(&webstore_json_data);
178 webstore_data_url_fetcher_.reset();
179
180 scoped_refptr<SafeWebstoreResponseParser> parser =
181 new SafeWebstoreResponseParser(this, webstore_json_data);
182 // The parser will call us back via OnWebstoreResponseParseSucces or
183 // OnWebstoreResponseParseFailure.
184 parser->Start();
185}
186
187void WebstoreInlineInstaller::OnWebstoreResponseParseSuccess(
188 DictionaryValue* webstore_data) {
189 webstore_data_.reset(webstore_data);
190
191 std::string manifest;
192 if (!webstore_data->GetString(kManifestKey, &manifest)) {
193 CompleteInstall(kInvalidWebstoreResponseError);
194 return;
195 }
196
[email protected]5fdbd6f2011-09-01 17:33:04197 // Number of users, average rating and rating count are required.
198 if (!webstore_data->GetString(kUsersKey, &localized_user_count_) ||
199 !webstore_data->GetDouble(kAverageRatingKey, &average_rating_) ||
200 !webstore_data->GetInteger(kRatingCountKey, &rating_count_)) {
201 CompleteInstall(kInvalidWebstoreResponseError);
202 return;
203 }
204
205 if (average_rating_ < ExtensionInstallUI::kMinExtensionRating ||
206 average_rating_ >ExtensionInstallUI::kMaxExtensionRating) {
207 CompleteInstall(kInvalidWebstoreResponseError);
208 return;
209 }
210
211 // Localized name and description are optional.
212 if ((webstore_data->HasKey(kLocalizedNameKey) &&
213 !webstore_data->GetString(kLocalizedNameKey, &localized_name_)) ||
214 (webstore_data->HasKey(kLocalizedDescriptionKey) &&
215 !webstore_data->GetString(
216 kLocalizedDescriptionKey, &localized_description_))) {
[email protected]8915f342011-08-29 22:14:37217 CompleteInstall(kInvalidWebstoreResponseError);
218 return;
219 }
220
221 // Icon URL is optional.
222 GURL icon_url;
223 if (webstore_data->HasKey(kIconUrlKey)) {
224 std::string icon_url_string;
225 if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) {
226 CompleteInstall(kInvalidWebstoreResponseError);
227 return;
228 }
229 icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
230 icon_url_string);
231 if (!icon_url.is_valid()) {
232 CompleteInstall(kInvalidWebstoreResponseError);
233 return;
234 }
235 }
236
[email protected]a221ef092011-09-07 01:34:10237 // Verified site is optional
238 if (webstore_data->HasKey(kVerifiedSiteKey)) {
239 std::string verified_site_domain;
240 if (!webstore_data->GetString(kVerifiedSiteKey, &verified_site_domain)) {
241 CompleteInstall(kInvalidWebstoreResponseError);
242 return;
243 }
244
245 URLPattern verified_site_pattern(URLPattern::SCHEME_ALL);
246 verified_site_pattern.SetScheme("*");
247 verified_site_pattern.SetHost(verified_site_domain);
248 verified_site_pattern.SetMatchSubdomains(true);
249 verified_site_pattern.SetPath("/*");
250
251 if (!verified_site_pattern.MatchesURL(requestor_url_)) {
252 CompleteInstall(kNotFromVerifiedSite);
253 return;
254 }
255 }
256
[email protected]8915f342011-08-29 22:14:37257 scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper(
258 this,
259 manifest,
260 "", // We don't have any icon data.
261 icon_url,
262 Profile::FromBrowserContext(tab_contents_->browser_context())->
263 GetRequestContext());
264 // The helper will call us back via OnWebstoreParseSucces or
265 // OnWebstoreParseFailure.
266 helper->Start();
267}
268
269void WebstoreInlineInstaller::OnWebstoreResponseParseFailure(
270 const std::string& error) {
271 CompleteInstall(error);
272}
273
274void WebstoreInlineInstaller::OnWebstoreParseSuccess(
275 const SkBitmap& icon,
276 base::DictionaryValue* manifest) {
277 manifest_.reset(manifest);
278 icon_ = icon;
279
280 Profile* profile = Profile::FromBrowserContext(
281 tab_contents_->browser_context());
[email protected]5fdbd6f2011-09-01 17:33:04282
283 ExtensionInstallUI::Prompt prompt(ExtensionInstallUI::INLINE_INSTALL_PROMPT);
[email protected]122e8df2011-09-02 18:20:24284 prompt.SetInlineInstallWebstoreData(localized_user_count_,
285 average_rating_,
286 rating_count_);
[email protected]5fdbd6f2011-09-01 17:33:04287
[email protected]8915f342011-08-29 22:14:37288 ShowExtensionInstallDialogForManifest(profile,
289 this,
290 manifest,
291 id_,
292 localized_name_,
[email protected]5fdbd6f2011-09-01 17:33:04293 localized_description_,
[email protected]8915f342011-08-29 22:14:37294 &icon_,
[email protected]5fdbd6f2011-09-01 17:33:04295 prompt,
296 &dummy_extension_);
[email protected]8915f342011-08-29 22:14:37297
[email protected]5fdbd6f2011-09-01 17:33:04298 if (!dummy_extension_.get()) {
[email protected]8915f342011-08-29 22:14:37299 CompleteInstall(kInvalidManifestError);
300 return;
301 }
302
303 // Control flow finishes up in InstallUIProceed or InstallUIAbort.
304}
305
306void WebstoreInlineInstaller::OnWebstoreParseFailure(
307 InstallHelperResultCode result_code,
308 const std::string& error_message) {
309 CompleteInstall(error_message);
310}
311
312void WebstoreInlineInstaller::InstallUIProceed() {
313 CrxInstaller::WhitelistEntry* entry = new CrxInstaller::WhitelistEntry;
314
315 entry->parsed_manifest.reset(manifest_.get()->DeepCopy());
316 entry->localized_name = localized_name_;
317 entry->use_app_installed_bubble = true;
318 CrxInstaller::SetWhitelistEntry(id_, entry);
319
320 GURL install_url(extension_urls::GetWebstoreInstallUrl(
321 id_, g_browser_process->GetApplicationLocale()));
322
323 NavigationController& controller = tab_contents_->controller();
324 // TODO(mihaip): we pretend like the referrer is the gallery in order to pass
325 // the checks in ExtensionService::IsDownloadFromGallery. We should instead
326 // pass the real referrer, track that this is an inline install in the
327 // whitelist entry and look that up when checking that this is a valid
328 // download.
329 GURL referrer(extension_urls::GetWebstoreItemDetailURLPrefix() + id_);
330 controller.LoadURL(install_url, referrer, PageTransition::LINK);
331
332 // TODO(mihaip): the success message should happen later, when the extension
333 // is actually downloaded and installed (when NOTIFICATION_EXTENSION_INSTALLED
334 // or NOTIFICATION_EXTENSION_INSTALL_ERROR fire).
335 CompleteInstall("");
336}
337
338void WebstoreInlineInstaller::InstallUIAbort(bool user_initiated) {
339 CompleteInstall(kUserCancelledError);
340}
341
342void WebstoreInlineInstaller::CompleteInstall(const std::string& error) {
343 if (error.empty()) {
[email protected]a221ef092011-09-07 01:34:10344 delegate_->OnInlineInstallSuccess(install_id_);
[email protected]8915f342011-08-29 22:14:37345 } else {
[email protected]a221ef092011-09-07 01:34:10346 delegate_->OnInlineInstallFailure(install_id_, error);
[email protected]8915f342011-08-29 22:14:37347 }
348 Release(); // Matches the AddRef in BeginInstall.
349}