| // Copyright (c) 2012 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 <utility> |
| |
| #include "base/json/json_writer.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/extensions/webstore_data_fetcher.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h" |
| #include "chrome/browser/safe_browsing/safe_browsing_service.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/web_contents.h" |
| |
| using content::WebContents; |
| using safe_browsing::SafeBrowsingNavigationObserverManager; |
| using safe_browsing::ReferrerChain; |
| |
| namespace { |
| |
| // The number of user gestures to trace back for CWS pings. |
| const int kExtensionReferrerUserGestureLimit = 2; |
| } |
| |
| namespace extensions { |
| |
| const char kInvalidWebstoreResponseError[] = |
| "Invalid Chrome Web Store response."; |
| const char kNoVerifiedSitesError[] = |
| "Inline installs can only be initiated for Chrome Web Store items that " |
| "have one or more verified sites."; |
| const char kNotFromVerifiedSitesError[] = |
| "Installs can only be initiated by one of the Chrome Web Store item's " |
| "verified sites."; |
| const char kInlineInstallSupportedError[] = |
| "Inline installation is not supported for this item. The user will be " |
| "redirected to the Chrome Web Store."; |
| const char kInitiatedFromPopupError[] = |
| "Inline installs can not be initiated from pop-up windows."; |
| const char kInitiatedFromFullscreenError[] = |
| "Inline installs can not be initiated from fullscreen."; |
| |
| WebstoreInlineInstaller::WebstoreInlineInstaller( |
| content::WebContents* web_contents, |
| content::RenderFrameHost* host, |
| const std::string& webstore_item_id, |
| const GURL& requestor_url, |
| const Callback& callback) |
| : WebstoreStandaloneInstaller( |
| webstore_item_id, |
| Profile::FromBrowserContext(web_contents->GetBrowserContext()), |
| callback), |
| content::WebContentsObserver(web_contents), |
| host_(host), |
| requestor_url_(requestor_url) {} |
| |
| WebstoreInlineInstaller::~WebstoreInlineInstaller() {} |
| |
| // static |
| bool WebstoreInlineInstaller::IsRequestorPermitted( |
| const base::DictionaryValue& webstore_data, |
| const GURL& requestor_url, |
| std::string* error) { |
| // Ensure that there is at least one verified site present. |
| const bool data_has_single_site = webstore_data.HasKey(kVerifiedSiteKey); |
| const bool data_has_site_list = webstore_data.HasKey(kVerifiedSitesKey); |
| if (!data_has_single_site && !data_has_site_list) { |
| *error = kNoVerifiedSitesError; |
| return false; |
| } |
| bool requestor_is_ok = false; |
| // Handle the deprecated single-site case. |
| if (!data_has_site_list) { |
| std::string verified_site; |
| if (!webstore_data.GetString(kVerifiedSiteKey, &verified_site)) { |
| *error = kInvalidWebstoreResponseError; |
| return false; |
| } |
| requestor_is_ok = IsRequestorURLInVerifiedSite(requestor_url, |
| verified_site); |
| } else { |
| const base::ListValue* verified_sites = NULL; |
| if (!webstore_data.GetList(kVerifiedSitesKey, &verified_sites)) { |
| *error = kInvalidWebstoreResponseError; |
| return false; |
| } |
| for (base::ListValue::const_iterator it = verified_sites->begin(); |
| it != verified_sites->end() && !requestor_is_ok; ++it) { |
| std::string verified_site; |
| if (!it->GetAsString(&verified_site)) { |
| *error = kInvalidWebstoreResponseError; |
| return false; |
| } |
| if (IsRequestorURLInVerifiedSite(requestor_url, verified_site)) { |
| requestor_is_ok = true; |
| } |
| } |
| } |
| if (!requestor_is_ok) { |
| *error = kNotFromVerifiedSitesError; |
| return false; |
| } |
| *error = ""; |
| return true; |
| } |
| |
| bool WebstoreInlineInstaller::SafeBrowsingNavigationEventsEnabled() const { |
| return SafeBrowsingNavigationObserverManager::IsEnabledAndReady(profile()); |
| } |
| |
| std::string WebstoreInlineInstaller::GetJsonPostData() { |
| // web_contents() might return null during tab destruction. This object would |
| // also be destroyed shortly thereafter but check to be on the safe side. |
| if (!web_contents()) |
| return std::string(); |
| |
| // Report extra data only when SafeBrowsing is enabled for the current |
| // profile. |
| if (!profile()->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) |
| return std::string(); |
| |
| auto redirect_chain = base::MakeUnique<base::ListValue>(); |
| |
| if (SafeBrowsingNavigationEventsEnabled()) { |
| // If we have it, use the new referrer checker. |
| safe_browsing::SafeBrowsingService* safe_browsing_service = |
| g_browser_process->safe_browsing_service(); |
| // May be null in some tests. |
| if (!safe_browsing_service) |
| return std::string(); |
| |
| scoped_refptr<SafeBrowsingNavigationObserverManager> |
| navigation_observer_manager = |
| safe_browsing_service->navigation_observer_manager(); |
| // This may be null if the navigation observer manager feature is |
| // disabled by experiment. |
| if (!navigation_observer_manager) |
| return std::string(); |
| |
| ReferrerChain referrer_chain; |
| SafeBrowsingNavigationObserverManager::AttributionResult result = |
| navigation_observer_manager->IdentifyReferrerChainByWebContents( |
| web_contents(), kExtensionReferrerUserGestureLimit, |
| &referrer_chain); |
| if (result != |
| SafeBrowsingNavigationObserverManager::NAVIGATION_EVENT_NOT_FOUND) { |
| // For now the CWS post data is JSON encoded. Consider moving it to a |
| // proto. |
| for (const auto& referrer_chain_entry : referrer_chain) { |
| // Referrer chain entries are a list of URLs in reverse chronological |
| // order, so the final URL is the last thing in the list and the initial |
| // landing page is the first thing in the list. |
| // Furthermore each entry may contain a series of server redirects |
| // stored in the same order. |
| redirect_chain->AppendString(referrer_chain_entry.url()); |
| for (const auto& server_side_redirect : |
| referrer_chain_entry.server_redirect_chain()) { |
| redirect_chain->AppendString(server_side_redirect.url()); |
| } |
| } |
| } |
| } else { |
| content::NavigationController& navigation_controller = |
| web_contents()->GetController(); |
| content::NavigationEntry* navigation_entry = |
| navigation_controller.GetLastCommittedEntry(); |
| if (navigation_entry) { |
| const std::vector<GURL>& redirect_urls = |
| navigation_entry->GetRedirectChain(); |
| for (const GURL& url : redirect_urls) { |
| redirect_chain->AppendString(url.spec()); |
| } |
| } |
| } |
| |
| if (!redirect_chain->empty()) { |
| base::DictionaryValue dictionary; |
| dictionary.SetString("id", id()); |
| dictionary.SetString("referrer", requestor_url_.spec()); |
| dictionary.Set("redirect_chain", std::move(redirect_chain)); |
| |
| std::string json; |
| base::JSONWriter::Write(dictionary, &json); |
| return json; |
| } |
| |
| return std::string(); |
| } |
| |
| bool WebstoreInlineInstaller::CheckRequestorAlive() const { |
| // The frame or tab may have gone away - cancel installation in that case. |
| return host_ != nullptr && web_contents() != nullptr && |
| chrome::FindBrowserWithWebContents(web_contents()) != nullptr; |
| } |
| |
| const GURL& WebstoreInlineInstaller::GetRequestorURL() const { |
| return requestor_url_; |
| } |
| |
| std::unique_ptr<ExtensionInstallPrompt::Prompt> |
| WebstoreInlineInstaller::CreateInstallPrompt() const { |
| std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt( |
| new ExtensionInstallPrompt::Prompt( |
| ExtensionInstallPrompt::INLINE_INSTALL_PROMPT)); |
| |
| // crbug.com/260742: Don't display the user count if it's zero. The reason |
| // it's zero is very often that the number isn't actually being counted |
| // (intentionally), which means that it's unlikely to be correct. |
| prompt->SetWebstoreData(localized_user_count(), |
| show_user_count(), |
| average_rating(), |
| rating_count()); |
| return prompt; |
| } |
| |
| bool WebstoreInlineInstaller::ShouldShowPostInstallUI() const { |
| return true; |
| } |
| |
| bool WebstoreInlineInstaller::ShouldShowAppInstalledBubble() const { |
| return true; |
| } |
| |
| WebContents* WebstoreInlineInstaller::GetWebContents() const { |
| return web_contents(); |
| } |
| |
| bool WebstoreInlineInstaller::CheckInlineInstallPermitted( |
| const base::DictionaryValue& webstore_data, |
| std::string* error) const { |
| Browser* browser = chrome::FindBrowserWithWebContents(web_contents()); |
| DCHECK(browser); |
| if (browser->is_type_popup()) { |
| *error = kInitiatedFromPopupError; |
| return false; |
| } |
| FullscreenController* controller = |
| browser->exclusive_access_manager()->fullscreen_controller(); |
| if (controller->IsTabFullscreen()) { |
| *error = kInitiatedFromFullscreenError; |
| return false; |
| } |
| // 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)) { |
| *error = kInvalidWebstoreResponseError; |
| return false; |
| } |
| if (inline_install_not_supported) { |
| std::string redirect_url; |
| if (!webstore_data.GetString(kRedirectUrlKey, &redirect_url)) { |
| *error = kInvalidWebstoreResponseError; |
| return false; |
| } |
| web_contents()->OpenURL(content::OpenURLParams( |
| GURL(redirect_url), |
| content::Referrer::SanitizeForRequest( |
| GURL(redirect_url), |
| content::Referrer(web_contents()->GetURL(), |
| blink::kWebReferrerPolicyDefault)), |
| WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK, false)); |
| *error = kInlineInstallSupportedError; |
| return false; |
| } |
| *error = ""; |
| return true; |
| } |
| |
| bool WebstoreInlineInstaller::CheckRequestorPermitted( |
| const base::DictionaryValue& webstore_data, |
| std::string* error) const { |
| return IsRequestorPermitted(webstore_data, requestor_url_, error); |
| } |
| |
| // |
| // Private implementation. |
| // |
| |
| void WebstoreInlineInstaller::DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (navigation_handle->HasCommitted() && |
| !navigation_handle->IsSameDocument() && |
| (navigation_handle->GetRenderFrameHost() == host_ || |
| navigation_handle->IsInMainFrame())) { |
| host_ = nullptr; |
| } |
| } |
| |
| void WebstoreInlineInstaller::WebContentsDestroyed() { |
| AbortInstall(); |
| } |
| |
| // static |
| bool WebstoreInlineInstaller::IsRequestorURLInVerifiedSite( |
| const GURL& requestor_url, |
| const std::string& verified_site) { |
| // Turn the verified site into a URL that can be parsed by URLPattern. |
| // |verified_site| must follow the format: |
| // |
| // [scheme://]host[:port][/path/specifier] |
| // |
| // If scheme is omitted, URLPattern will match against either an |
| // HTTP or HTTPS requestor. If scheme is specified, it must be either HTTP |
| // or HTTPS, and URLPattern will only match the scheme specified. |
| GURL verified_site_url(verified_site); |
| int valid_schemes = URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS; |
| if (!verified_site_url.is_valid() || !verified_site_url.IsStandard()) |
| // If no scheme is specified, GURL will fail to parse the string correctly. |
| // It will either determine that the URL is invalid, or parse a |
| // host:port/path as scheme:host/path. |
| verified_site_url = GURL("http://" + verified_site); |
| else if (verified_site_url.SchemeIs("http")) |
| valid_schemes = URLPattern::SCHEME_HTTP; |
| else if (verified_site_url.SchemeIs("https")) |
| valid_schemes = URLPattern::SCHEME_HTTPS; |
| else |
| return false; |
| |
| std::string port_spec = |
| verified_site_url.has_port() ? ":" + verified_site_url.port() : ""; |
| std::string path_spec = verified_site_url.path() + "*"; |
| std::string verified_site_pattern_spec = |
| base::StringPrintf( |
| "%s://*.%s%s%s", |
| verified_site_url.scheme().c_str(), |
| verified_site_url.host().c_str(), |
| port_spec.c_str(), |
| path_spec.c_str()); |
| |
| URLPattern verified_site_pattern(valid_schemes); |
| URLPattern::ParseResult parse_result = |
| verified_site_pattern.Parse(verified_site_pattern_spec); |
| if (parse_result != URLPattern::PARSE_SUCCESS) { |
| DLOG(WARNING) << "Could not parse " << verified_site_pattern_spec << |
| " as URL pattern " << parse_result; |
| return false; |
| } |
| verified_site_pattern.SetScheme("*"); |
| |
| return verified_site_pattern.MatchesURL(requestor_url); |
| } |
| |
| } // namespace extensions |