// 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 #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 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(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 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 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; } // Localized name is optional. if (webstore_data->HasKey(kLocalizedNameKey) && !webstore_data->GetString(kLocalizedNameKey, &localized_name_)) { 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 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()); scoped_refptr dummy_extension; ShowExtensionInstallDialogForManifest(profile, this, manifest, id_, localized_name_, &icon_, ExtensionInstallUI::INSTALL_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. }