| // 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/bind.h" |
| #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/extensions/extension_service.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 "chrome/common/extensions/url_pattern.h" |
| #include "content/browser/tab_contents/tab_contents.h" |
| #include "content/browser/utility_process_host.h" |
| #include "content/public/common/url_fetcher.h" |
| #include "net/base/escape.h" |
| #include "net/base/load_flags.h" |
| #include "net/url_request/url_request_status.h" |
| |
| using content::BrowserThread; |
| |
| 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 kVerifiedSiteKey[] = "verified_site"; |
| const char kInlineInstallNotSupportedKey[] = "inline_install_not_supported"; |
| const char kRedirectUrlKey[] = "redirect_url"; |
| |
| const char kInvalidWebstoreItemId[] = "Invalid Chrome Web Store item ID"; |
| const char kWebstoreRequestError[] = |
| "Could not fetch data from the Chrome Web Store"; |
| const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse"; |
| const char kInvalidManifestError[] = "Invalid manifest"; |
| const char kUserCancelledError[] = "User cancelled install"; |
| const char kNoVerifiedSiteError[] = |
| "Inline installs can only be initiated for Chrome Web Store items that " |
| "have a verified site"; |
| const char kNotFromVerifiedSiteError[] = |
| "Installs can only be initiated by the Chrome Web Store item's verified " |
| "site"; |
| const char kInlineInstallSupportedError[] = |
| "Inline installation is not supported for this item. The user will be " |
| "redirected to the Chrome Web Store."; |
| |
| 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, |
| base::Bind(&SafeWebstoreResponseParser::StartWorkOnIOThread, this)); |
| } |
| |
| void StartWorkOnIOThread() { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| utility_host_ = new UtilityProcessHost(this, BrowserThread::IO); |
| utility_host_->set_use_linux_zygote(true); |
| 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, |
| base::Bind(&SafeWebstoreResponseParser::ReportResultOnUIThread, this)); |
| } |
| |
| 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, |
| int install_id, |
| std::string webstore_item_id, |
| GURL requestor_url, |
| Delegate* delegate) |
| : content::WebContentsObserver(tab_contents), |
| install_id_(install_id), |
| id_(webstore_item_id), |
| requestor_url_(requestor_url), |
| delegate_(delegate), |
| average_rating_(0.0), |
| rating_count_(0) {} |
| |
| WebstoreInlineInstaller::~WebstoreInlineInstaller() { |
| } |
| |
| void WebstoreInlineInstaller::BeginInstall() { |
| AddRef(); // Balanced in CompleteInstall or TabContentsDestroyed. |
| |
| if (!Extension::IdIsValid(id_)) { |
| CompleteInstall(kInvalidWebstoreItemId); |
| return; |
| } |
| |
| GURL webstore_data_url(extension_urls::GetWebstoreItemJsonDataURL(id_)); |
| |
| webstore_data_url_fetcher_.reset(content::URLFetcher::Create( |
| webstore_data_url, content::URLFetcher::GET, this)); |
| Profile* profile = Profile::FromBrowserContext( |
| tab_contents()->GetBrowserContext()); |
| webstore_data_url_fetcher_->SetRequestContext( |
| profile->GetRequestContext()); |
| // Use the requesting page as the referrer both since that is more correct |
| // (it is the page that caused this request to happen) and so that we can |
| // track top sites that trigger inline install requests. |
| webstore_data_url_fetcher_->SetReferrer(requestor_url_.spec()); |
| webstore_data_url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | |
| net::LOAD_DO_NOT_SAVE_COOKIES | |
| net::LOAD_DISABLE_CACHE); |
| webstore_data_url_fetcher_->Start(); |
| } |
| |
| void WebstoreInlineInstaller::OnURLFetchComplete( |
| const content::URLFetcher* source) { |
| CHECK_EQ(webstore_data_url_fetcher_.get(), source); |
| // We shouldn't be getting UrlFetcher callbacks if the TabContents has gone |
| // away; we stop any in in-progress fetches in TabContentsDestroyed. |
| CHECK(tab_contents()); |
| |
| if (!webstore_data_url_fetcher_->GetStatus().is_success() || |
| webstore_data_url_fetcher_->GetResponseCode() != 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) { |
| // Check if the tab has gone away in the meantime. |
| if (!tab_contents()) { |
| CompleteInstall(""); |
| return; |
| } |
| |
| webstore_data_.reset(webstore_data); |
| |
| // The store may not support inline installs for this item, in which case |
| // we open the store-provided redirect URL in a new tab and abort the |
| // installation process. |
| bool inline_install_not_supported = false; |
| if (webstore_data->HasKey(kInlineInstallNotSupportedKey) && |
| !webstore_data->GetBoolean( |
| kInlineInstallNotSupportedKey, &inline_install_not_supported)) { |
| CompleteInstall(kInvalidWebstoreResponseError); |
| return; |
| } |
| if (inline_install_not_supported) { |
| std::string redirect_url; |
| if (!webstore_data->GetString(kRedirectUrlKey, &redirect_url)) { |
| CompleteInstall(kInvalidWebstoreResponseError); |
| return; |
| } |
| |
| tab_contents()->OpenURL(OpenURLParams( |
| GURL(redirect_url), |
| content::Referrer(tab_contents()->GetURL(), |
| WebKit::WebReferrerPolicyDefault), |
| NEW_FOREGROUND_TAB, |
| content::PAGE_TRANSITION_AUTO_BOOKMARK, |
| false)); |
| CompleteInstall(kInlineInstallSupportedError); |
| return; |
| } |
| |
| // Manifest, number of users, average rating and rating count are required. |
| std::string manifest; |
| if (!webstore_data->GetString(kManifestKey, &manifest) || |
| !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; |
| } |
| } |
| |
| // Verified site is required |
| if (webstore_data->HasKey(kVerifiedSiteKey)) { |
| std::string verified_site_domain; |
| if (!webstore_data->GetString(kVerifiedSiteKey, &verified_site_domain)) { |
| CompleteInstall(kInvalidWebstoreResponseError); |
| return; |
| } |
| |
| URLPattern verified_site_pattern(URLPattern::SCHEME_ALL); |
| verified_site_pattern.SetScheme("*"); |
| verified_site_pattern.SetHost(verified_site_domain); |
| verified_site_pattern.SetMatchSubdomains(true); |
| verified_site_pattern.SetPath("/*"); |
| |
| if (!verified_site_pattern.MatchesURL(requestor_url_)) { |
| CompleteInstall(kNotFromVerifiedSiteError); |
| return; |
| } |
| } else { |
| CompleteInstall(kNoVerifiedSiteError); |
| return; |
| } |
| |
| scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper( |
| this, |
| id_, |
| manifest, |
| "", // We don't have any icon data. |
| icon_url, |
| Profile::FromBrowserContext(tab_contents()->GetBrowserContext())-> |
| 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 std::string& id, |
| const SkBitmap& icon, |
| base::DictionaryValue* manifest) { |
| // Check if the tab has gone away in the meantime. |
| if (!tab_contents()) { |
| CompleteInstall(""); |
| return; |
| } |
| |
| CHECK_EQ(id_, id); |
| manifest_.reset(manifest); |
| icon_ = icon; |
| |
| Profile* profile = Profile::FromBrowserContext( |
| tab_contents()->GetBrowserContext()); |
| |
| ExtensionInstallUI::Prompt prompt(ExtensionInstallUI::INLINE_INSTALL_PROMPT); |
| prompt.SetInlineInstallWebstoreData(localized_user_count_, |
| average_rating_, |
| rating_count_); |
| |
| if (!ShowExtensionInstallDialogForManifest(profile, |
| this, |
| manifest, |
| id_, |
| localized_name_, |
| localized_description_, |
| &icon_, |
| prompt, |
| &dummy_extension_)) { |
| CompleteInstall(kInvalidManifestError); |
| return; |
| } |
| |
| // Control flow finishes up in InstallUIProceed or InstallUIAbort. |
| } |
| |
| void WebstoreInlineInstaller::OnWebstoreParseFailure( |
| const std::string& id, |
| InstallHelperResultCode result_code, |
| const std::string& error_message) { |
| CompleteInstall(error_message); |
| } |
| |
| void WebstoreInlineInstaller::InstallUIProceed() { |
| // Check if the tab has gone away in the meantime. |
| if (!tab_contents()) { |
| CompleteInstall(""); |
| return; |
| } |
| |
| 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); |
| |
| Profile* profile = Profile::FromBrowserContext( |
| tab_contents()->GetBrowserContext()); |
| |
| scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller( |
| profile, this, &(tab_contents()->GetController()), id_, |
| WebstoreInstaller::FLAG_INLINE_INSTALL); |
| installer->Start(); |
| } |
| |
| void WebstoreInlineInstaller::InstallUIAbort(bool user_initiated) { |
| CompleteInstall(kUserCancelledError); |
| } |
| |
| void WebstoreInlineInstaller::TabContentsDestroyed(TabContents* tab_contents) { |
| // Abort any in-progress fetches. |
| if (webstore_data_url_fetcher_.get()) { |
| webstore_data_url_fetcher_.reset(); |
| Release(); // Matches the AddRef in BeginInstall. |
| } |
| } |
| |
| void WebstoreInlineInstaller::OnExtensionInstallSuccess(const std::string& id) { |
| CHECK_EQ(id_, id); |
| CompleteInstall(""); |
| } |
| |
| void WebstoreInlineInstaller::OnExtensionInstallFailure( |
| const std::string& id, const std::string& error) { |
| CHECK_EQ(id_, id); |
| CompleteInstall(error); |
| } |
| |
| void WebstoreInlineInstaller::CompleteInstall(const std::string& error) { |
| // Only bother responding if there's still a tab contents to send back the |
| // response to. |
| if (tab_contents()) { |
| if (error.empty()) { |
| delegate_->OnInlineInstallSuccess(install_id_); |
| } else { |
| delegate_->OnInlineInstallFailure(install_id_, error); |
| } |
| } |
| |
| Release(); // Matches the AddRef in BeginInstall. |
| } |