// Copyright 2016 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 "extensions/browser/extension_navigation_throttle.h" #include "components/guest_view/browser/guest_view_base.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/navigation_handle.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" #include "content/public/common/browser_side_navigation_policy.h" #include "content/public/common/url_constants.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/guest_view/web_view/web_view_guest.h" #include "extensions/browser/url_request_util.h" #include "extensions/common/constants.h" #include "extensions/common/extension.h" #include "extensions/common/extension_set.h" #include "extensions/common/manifest_handlers/web_accessible_resources_info.h" #include "extensions/common/manifest_handlers/webview_info.h" #include "extensions/common/permissions/api_permission.h" #include "extensions/common/permissions/permissions_data.h" #include "ui/base/page_transition_types.h" namespace extensions { ExtensionNavigationThrottle::ExtensionNavigationThrottle( content::NavigationHandle* navigation_handle) : content::NavigationThrottle(navigation_handle) {} ExtensionNavigationThrottle::~ExtensionNavigationThrottle() {} content::NavigationThrottle::ThrottleCheckResult ExtensionNavigationThrottle::WillStartRequest() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); GURL url(navigation_handle()->GetURL()); content::WebContents* web_contents = navigation_handle()->GetWebContents(); ExtensionRegistry* registry = ExtensionRegistry::Get(web_contents->GetBrowserContext()); if (navigation_handle()->IsInMainFrame()) { // Block top-level navigations to blob: or filesystem: URLs with extension // origin from non-extension processes. See https://crbug.com/645028. bool is_nested_url = url.SchemeIsFileSystem() || url.SchemeIsBlob(); bool is_extension = false; if (registry) { is_extension = !!registry->enabled_extensions().GetExtensionOrAppByURL( navigation_handle()->GetStartingSiteInstance()->GetSiteURL()); } url::Origin origin(url); if (is_nested_url && origin.scheme() == extensions::kExtensionScheme && !is_extension) { // Relax this restriction for apps that use . See // https://crbug.com/652077. const extensions::Extension* extension = registry->enabled_extensions().GetByID(origin.host()); bool has_webview_permission = extension && extension->permissions_data()->HasAPIPermission( extensions::APIPermission::kWebView); if (!has_webview_permission) return content::NavigationThrottle::CANCEL; } if (content::IsBrowserSideNavigationEnabled() && url.scheme() == extensions::kExtensionScheme) { // This logic is performed for PlzNavigate sub-resources and for // non-PlzNavigate in // extensions::url_request_util::AllowCrossRendererResourceLoad. const Extension* extension = registry->enabled_extensions().GetExtensionOrAppByURL(url); guest_view::GuestViewBase* guest = guest_view::GuestViewBase::FromWebContents(web_contents); if (guest) { std::string owner_extension_id = guest->owner_host(); const Extension* owner_extension = registry->enabled_extensions().GetByID(owner_extension_id); std::string partition_domain, partition_id; bool in_memory; std::string resource_path = url.path(); bool is_guest = WebViewGuest::GetGuestPartitionConfigForSite( navigation_handle()->GetStartingSiteInstance()->GetSiteURL(), &partition_domain, &partition_id, &in_memory); bool allowed = true; url_request_util::AllowCrossRendererResourceLoadHelper( is_guest, extension, owner_extension, partition_id, resource_path, navigation_handle()->GetPageTransition(), &allowed); if (!allowed) return content::NavigationThrottle::BLOCK_REQUEST; } } return content::NavigationThrottle::PROCEED; } // Now enforce web_accessible_resources for navigations. Top-level navigations // should always be allowed. // If the navigation is not to a chrome-extension:// URL, no need to perform // any more checks. if (!url.SchemeIs(extensions::kExtensionScheme)) return content::NavigationThrottle::PROCEED; // The subframe which is navigated needs to have all of its ancestors be // at the same origin, otherwise the resource needs to be explicitly listed // in web_accessible_resources. // Since the RenderFrameHost is not known until navigation has committed, // we can't get it from NavigationHandle. However, this code only cares about // the ancestor chain, so find the current RenderFrameHost and use it to // traverse up to the main frame. content::RenderFrameHost* navigating_frame = nullptr; for (auto* frame : web_contents->GetAllFrames()) { if (frame->GetFrameTreeNodeId() == navigation_handle()->GetFrameTreeNodeId()) { navigating_frame = frame; break; } } DCHECK(navigating_frame); // Traverse the chain of parent frames, checking if they are the same origin // as the URL of this navigation. content::RenderFrameHost* ancestor = navigating_frame->GetParent(); bool external_ancestor = false; while (ancestor) { if (ancestor->GetLastCommittedURL().GetOrigin() != url.GetOrigin()) { // Ignore DevTools, as it is allowed to embed extension pages. if (!ancestor->GetLastCommittedURL().SchemeIs( content::kChromeDevToolsScheme)) { external_ancestor = true; break; } } ancestor = ancestor->GetParent(); } if (!external_ancestor) return content::NavigationThrottle::PROCEED; // Since there was at least one origin different than the navigation URL, // explicitly check for the resource in web_accessible_resources. std::string resource_path = url.path(); if (!registry) return content::NavigationThrottle::BLOCK_REQUEST; const extensions::Extension* extension = registry->enabled_extensions().GetByID(url.host()); if (!extension) return content::NavigationThrottle::BLOCK_REQUEST; if (WebAccessibleResourcesInfo::IsResourceWebAccessible(extension, resource_path)) { return content::NavigationThrottle::PROCEED; } return content::NavigationThrottle::BLOCK_REQUEST; } } // namespace extensions