blob: ffa8158f4e9f203a40b0fc3a0fdb09832904ad95 [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/webstore_inline_installer.h"
#include <vector>
#include "base/string_util.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_install_dialog.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_utility_messages.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/browser/utility_process_host.h"
#include "googleurl/src/gurl.h"
#include "net/base/escape.h"
#include "net/url_request/url_request_status.h"
const char kManifestKey[] = "manifest";
const char kIconUrlKey[] = "icon_url";
const char kLocalizedNameKey[] = "localized_name";
const char kLocalizedDescriptionKey[] = "localized_description";
const char kUsersKey[] = "users";
const char kAverageRatingKey[] = "average_rating";
const char kRatingCountKey[] = "rating_count";
const char kInvalidWebstoreItemId[] = "Invalid webstore item ID";
const char kWebstoreRequestError[] = "Could not fetch data from webstore";
const char kInvalidWebstoreResponseError[] = "Invalid webstore reponse";
const char kInvalidManifestError[] = "Invalid manifest";
const char kUserCancelledError[] = "User cancelled install";
class SafeWebstoreResponseParser : public UtilityProcessHost::Client {
public:
SafeWebstoreResponseParser(WebstoreInlineInstaller *client,
const std::string& webstore_data)
: client_(client),
webstore_data_(webstore_data),
utility_host_(NULL) {}
void Start() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
NewRunnableMethod(this,
&SafeWebstoreResponseParser::StartWorkOnIOThread));
}
void StartWorkOnIOThread() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
utility_host_ = new UtilityProcessHost(this, BrowserThread::IO);
utility_host_->Send(new ChromeUtilityMsg_ParseJSON(webstore_data_));
}
// Implementing pieces of the UtilityProcessHost::Client interface.
virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(SafeWebstoreResponseParser, message)
IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Succeeded,
OnJSONParseSucceeded)
IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Failed,
OnJSONParseFailed)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void OnJSONParseSucceeded(const ListValue& wrapper) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
Value* value = NULL;
CHECK(wrapper.Get(0, &value));
if (value->IsType(Value::TYPE_DICTIONARY)) {
parsed_webstore_data_.reset(
static_cast<DictionaryValue*>(value)->DeepCopy());
} else {
error_ = kInvalidWebstoreResponseError;
}
ReportResults();
}
virtual void OnJSONParseFailed(const std::string& error_message) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
error_ = error_message;
ReportResults();
}
void ReportResults() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// The utility_host_ will take care of deleting itself after this call.
utility_host_ = NULL;
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
NewRunnableMethod(this,
&SafeWebstoreResponseParser::ReportResultOnUIThread));
}
void ReportResultOnUIThread() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (error_.empty() && parsed_webstore_data_.get()) {
client_->OnWebstoreResponseParseSuccess(parsed_webstore_data_.release());
} else {
client_->OnWebstoreResponseParseFailure(error_);
}
}
private:
virtual ~SafeWebstoreResponseParser() {}
WebstoreInlineInstaller* client_;
std::string webstore_data_;
UtilityProcessHost* utility_host_;
std::string error_;
scoped_ptr<DictionaryValue> parsed_webstore_data_;
};
WebstoreInlineInstaller::WebstoreInlineInstaller(TabContents* tab_contents,
std::string webstore_item_id,
Delegate* delegate)
: tab_contents_(tab_contents),
id_(webstore_item_id),
delegate_(delegate) {}
WebstoreInlineInstaller::~WebstoreInlineInstaller() {
}
void WebstoreInlineInstaller::BeginInstall() {
AddRef(); // Balanced in CompleteInstall.
if (!Extension::IdIsValid(id_)) {
CompleteInstall(kInvalidWebstoreItemId);
return;
}
GURL webstore_data_url(extension_urls::GetWebstoreItemJsonDataURL(id_));
webstore_data_url_fetcher_.reset(
new URLFetcher(webstore_data_url, URLFetcher::GET, this));
Profile* profile = Profile::FromBrowserContext(
tab_contents_->browser_context());
webstore_data_url_fetcher_->set_request_context(
profile->GetRequestContext());
webstore_data_url_fetcher_->Start();
}
void WebstoreInlineInstaller::OnURLFetchComplete(const URLFetcher* source) {
CHECK_EQ(webstore_data_url_fetcher_.get(), source);
if (!webstore_data_url_fetcher_->status().is_success() ||
webstore_data_url_fetcher_->response_code() != 200) {
CompleteInstall(kWebstoreRequestError);
return;
}
std::string webstore_json_data;
webstore_data_url_fetcher_->GetResponseAsString(&webstore_json_data);
webstore_data_url_fetcher_.reset();
scoped_refptr<SafeWebstoreResponseParser> parser =
new SafeWebstoreResponseParser(this, webstore_json_data);
// The parser will call us back via OnWebstoreResponseParseSucces or
// OnWebstoreResponseParseFailure.
parser->Start();
}
void WebstoreInlineInstaller::OnWebstoreResponseParseSuccess(
DictionaryValue* webstore_data) {
webstore_data_.reset(webstore_data);
std::string manifest;
if (!webstore_data->GetString(kManifestKey, &manifest)) {
CompleteInstall(kInvalidWebstoreResponseError);
return;
}
// Number of users, average rating and rating count are required.
if (!webstore_data->GetString(kUsersKey, &localized_user_count_) ||
!webstore_data->GetDouble(kAverageRatingKey, &average_rating_) ||
!webstore_data->GetInteger(kRatingCountKey, &rating_count_)) {
CompleteInstall(kInvalidWebstoreResponseError);
return;
}
if (average_rating_ < ExtensionInstallUI::kMinExtensionRating ||
average_rating_ >ExtensionInstallUI::kMaxExtensionRating) {
CompleteInstall(kInvalidWebstoreResponseError);
return;
}
// Localized name and description are optional.
if ((webstore_data->HasKey(kLocalizedNameKey) &&
!webstore_data->GetString(kLocalizedNameKey, &localized_name_)) ||
(webstore_data->HasKey(kLocalizedDescriptionKey) &&
!webstore_data->GetString(
kLocalizedDescriptionKey, &localized_description_))) {
CompleteInstall(kInvalidWebstoreResponseError);
return;
}
// Icon URL is optional.
GURL icon_url;
if (webstore_data->HasKey(kIconUrlKey)) {
std::string icon_url_string;
if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) {
CompleteInstall(kInvalidWebstoreResponseError);
return;
}
icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
icon_url_string);
if (!icon_url.is_valid()) {
CompleteInstall(kInvalidWebstoreResponseError);
return;
}
}
scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper(
this,
manifest,
"", // We don't have any icon data.
icon_url,
Profile::FromBrowserContext(tab_contents_->browser_context())->
GetRequestContext());
// The helper will call us back via OnWebstoreParseSucces or
// OnWebstoreParseFailure.
helper->Start();
}
void WebstoreInlineInstaller::OnWebstoreResponseParseFailure(
const std::string& error) {
CompleteInstall(error);
}
void WebstoreInlineInstaller::OnWebstoreParseSuccess(
const SkBitmap& icon,
base::DictionaryValue* manifest) {
manifest_.reset(manifest);
icon_ = icon;
Profile* profile = Profile::FromBrowserContext(
tab_contents_->browser_context());
ExtensionInstallUI::Prompt prompt(ExtensionInstallUI::INLINE_INSTALL_PROMPT);
prompt.localized_user_count = localized_user_count_;
prompt.average_rating = average_rating_;
prompt.rating_count = rating_count_;
ShowExtensionInstallDialogForManifest(profile,
this,
manifest,
id_,
localized_name_,
localized_description_,
&icon_,
prompt,
&dummy_extension_);
if (!dummy_extension_.get()) {
CompleteInstall(kInvalidManifestError);
return;
}
// Control flow finishes up in InstallUIProceed or InstallUIAbort.
}
void WebstoreInlineInstaller::OnWebstoreParseFailure(
InstallHelperResultCode result_code,
const std::string& error_message) {
CompleteInstall(error_message);
}
void WebstoreInlineInstaller::InstallUIProceed() {
CrxInstaller::WhitelistEntry* entry = new CrxInstaller::WhitelistEntry;
entry->parsed_manifest.reset(manifest_.get()->DeepCopy());
entry->localized_name = localized_name_;
entry->use_app_installed_bubble = true;
CrxInstaller::SetWhitelistEntry(id_, entry);
GURL install_url(extension_urls::GetWebstoreInstallUrl(
id_, g_browser_process->GetApplicationLocale()));
NavigationController& controller = tab_contents_->controller();
// TODO(mihaip): we pretend like the referrer is the gallery in order to pass
// the checks in ExtensionService::IsDownloadFromGallery. We should instead
// pass the real referrer, track that this is an inline install in the
// whitelist entry and look that up when checking that this is a valid
// download.
GURL referrer(extension_urls::GetWebstoreItemDetailURLPrefix() + id_);
controller.LoadURL(install_url, referrer, PageTransition::LINK);
// TODO(mihaip): the success message should happen later, when the extension
// is actually downloaded and installed (when NOTIFICATION_EXTENSION_INSTALLED
// or NOTIFICATION_EXTENSION_INSTALL_ERROR fire).
CompleteInstall("");
}
void WebstoreInlineInstaller::InstallUIAbort(bool user_initiated) {
CompleteInstall(kUserCancelledError);
}
void WebstoreInlineInstaller::CompleteInstall(const std::string& error) {
if (error.empty()) {
delegate_->OnInlineInstallSuccess();
} else {
delegate_->OnInlineInstallFailure(error);
}
Release(); // Matches the AddRef in BeginInstall.
}