blob: 2472bbb4e77ee33d68b924602ab409928190f073 [file] [log] [blame]
// Copyright 2014 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/chrome_content_browser_client_extensions_part.h"
#include <stddef.h>
#include <memory>
#include <set>
#include "base/command_line.h"
#include "base/debug/alias.h"
#include "base/debug/dump_without_crashing.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_web_ui.h"
#include "chrome/browser/extensions/extension_webkit_preferences.h"
#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_io_data.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/renderer_host/chrome_extension_message_filter.h"
#include "chrome/browser/sync_file_system/local/sync_file_system_backend.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/url_constants.h"
#include "components/dom_distiller/core/url_constants.h"
#include "components/guest_view/browser/guest_view_message_filter.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/browser_url_handler.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/resource_dispatcher_host.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/vpn_service_proxy.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/api/web_request/web_request_api.h"
#include "extensions/browser/api/web_request/web_request_api_helpers.h"
#include "extensions/browser/bad_message.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_message_filter.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_service_worker_message_filter.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/guest_view/extensions_guest_view_message_filter.h"
#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
#include "extensions/browser/info_map.h"
#include "extensions/browser/io_thread_extension_message_filter.h"
#include "extensions/browser/view_type_utils.h"
#include "extensions/common/constants.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/app_isolation_info.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/switches.h"
#include "url/origin.h"
#if defined(OS_CHROMEOS)
#include "extensions/browser/api/vpn_provider/vpn_service.h"
#include "extensions/browser/api/vpn_provider/vpn_service_factory.h"
#endif // defined(OS_CHROMEOS)
using content::BrowserContext;
using content::BrowserThread;
using content::BrowserURLHandler;
using content::RenderViewHost;
using content::SiteInstance;
using content::WebContents;
using content::WebPreferences;
namespace extensions {
namespace {
// Used by the GetPrivilegeRequiredByUrl() and GetProcessPrivilege() functions
// below. Extension, and isolated apps require different privileges to be
// granted to their RenderProcessHosts. This classification allows us to make
// sure URLs are served by hosts with the right set of privileges.
enum RenderProcessHostPrivilege {
PRIV_NORMAL,
PRIV_HOSTED,
PRIV_ISOLATED,
PRIV_EXTENSION,
};
// Specifies the scheme of the SiteInstance responsible for a failed
// web-accessible resource check in ShouldAllowOpenURL.
//
// This enum backs an UMA histogram. The order of existing values
// should not be changed. Add any new values before SCHEME_LAST, and also run
// update_should_allow_open_url_histograms.py to update the corresponding enum
// in histograms.xml. This enum must also be synchronized to kSchemeNames in
// RecordShouldAllowOpenURLFailure.
enum ShouldAllowOpenURLFailureScheme {
SCHEME_UNKNOWN,
SCHEME_EMPTY,
SCHEME_HTTP,
SCHEME_HTTPS,
SCHEME_FILE,
SCHEME_FTP,
SCHEME_DATA,
SCHEME_JAVASCRIPT,
SCHEME_ABOUT,
SCHEME_CHROME,
SCHEME_DEVTOOLS,
SCHEME_GUEST,
SCHEME_VIEWSOURCE,
SCHEME_CHROME_SEARCH,
SCHEME_CHROME_NATIVE,
SCHEME_DOM_DISTILLER,
SCHEME_CHROME_EXTENSION,
SCHEME_CONTENT,
SCHEME_BLOB,
SCHEME_FILESYSTEM,
// Add new entries above and make sure to update histograms.xml by running
// update_should_allow_open_url_histograms.py.
SCHEME_LAST,
};
RenderProcessHostPrivilege GetPrivilegeRequiredByUrl(
const GURL& url,
ExtensionRegistry* registry) {
// Default to a normal renderer cause it is lower privileged. This should only
// occur if the URL on a site instance is either malformed, or uninitialized.
// If it is malformed, then there is no need for better privileges anyways.
// If it is uninitialized, but eventually settles on being an a scheme other
// than normal webrenderer, the navigation logic will correct us out of band
// anyways.
if (!url.is_valid())
return PRIV_NORMAL;
if (!url.SchemeIs(kExtensionScheme))
return PRIV_NORMAL;
const Extension* extension =
registry->enabled_extensions().GetByID(url.host());
if (extension && AppIsolationInfo::HasIsolatedStorage(extension))
return PRIV_ISOLATED;
if (extension && extension->is_hosted_app())
return PRIV_HOSTED;
return PRIV_EXTENSION;
}
RenderProcessHostPrivilege GetProcessPrivilege(
content::RenderProcessHost* process_host,
ProcessMap* process_map,
ExtensionRegistry* registry) {
std::set<std::string> extension_ids =
process_map->GetExtensionsInProcess(process_host->GetID());
if (extension_ids.empty())
return PRIV_NORMAL;
for (const std::string& extension_id : extension_ids) {
const Extension* extension =
registry->enabled_extensions().GetByID(extension_id);
if (extension && AppIsolationInfo::HasIsolatedStorage(extension))
return PRIV_ISOLATED;
if (extension && extension->is_hosted_app())
return PRIV_HOSTED;
}
return PRIV_EXTENSION;
}
// Determines whether the extension |origin| is legal to use in an Origin header
// from the process identified by |child_id|. Returns CONTINUE if so, FAIL if
// the extension is not recognized (and may recently have been uninstalled), and
// KILL if the origin is from a platform app but the request does not come from
// that app.
content::HeaderInterceptorResult CheckOriginHeader(
content::ResourceContext* resource_context,
int child_id,
const GURL& origin) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Consider non-extension URLs safe; they will be checked elsewhere.
if (!origin.SchemeIs(kExtensionScheme))
return content::HeaderInterceptorResult::CONTINUE;
// If there is no extension installed for the origin, it may be from a
// recently uninstalled extension. The tabs of such extensions are
// automatically closed, but subframes and content scripts may stick around.
// Fail such requests without killing the process.
// TODO(rdevlin.cronin, creis): Track which extensions have been uninstalled
// and use HeaderInterceptorResult::KILL for anything not on the list.
// See https://crbug.com/705128.
ProfileIOData* io_data = ProfileIOData::FromResourceContext(resource_context);
InfoMap* extension_info_map = io_data->GetExtensionInfoMap();
const Extension* extension =
extension_info_map->extensions().GetExtensionOrAppByURL(origin);
if (!extension)
return content::HeaderInterceptorResult::FAIL;
// Check for platform app origins. These can only be committed by the app
// itself, or by one if its guests if it has the webview permission.
// Processes that incorrectly claim to be an app should be killed.
const ProcessMap& process_map = extension_info_map->process_map();
if (extension->is_platform_app() &&
!process_map.Contains(extension->id(), child_id)) {
// This is a platform app origin not in the app's own process. If it cannot
// create webviews, this is illegal.
if (!extension->permissions_data()->HasAPIPermission(
extensions::APIPermission::kWebView)) {
return content::HeaderInterceptorResult::KILL;
}
// If there are accessible resources, the origin is only legal if the
// given process is a guest of the app.
std::string owner_extension_id;
int owner_process_id;
WebViewRendererState::GetInstance()->GetOwnerInfo(
child_id, &owner_process_id, &owner_extension_id);
const Extension* owner_extension =
extension_info_map->extensions().GetByID(owner_extension_id);
if (!owner_extension || owner_extension != extension)
return content::HeaderInterceptorResult::KILL;
// It's a valid guest of the app, so allow it to proceed.
return content::HeaderInterceptorResult::CONTINUE;
}
// With only the origin and not the full URL, we don't have enough
// information to validate hosted apps or web_accessible_resources in normal
// extensions. Assume they're legal.
return content::HeaderInterceptorResult::CONTINUE;
}
// This callback is registered on the ResourceDispatcherHost for the chrome
// extension Origin scheme. We determine whether the extension origin is
// valid. Please see the CheckOriginHeader() function.
void OnHttpHeaderReceived(const std::string& header,
const std::string& value,
int child_id,
content::ResourceContext* resource_context,
content::OnHeaderProcessedCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
GURL origin(value);
DCHECK(origin.SchemeIs(extensions::kExtensionScheme));
callback.Run(CheckOriginHeader(resource_context, child_id, origin));
}
} // namespace
ChromeContentBrowserClientExtensionsPart::
ChromeContentBrowserClientExtensionsPart() {
}
ChromeContentBrowserClientExtensionsPart::
~ChromeContentBrowserClientExtensionsPart() {
}
// static
GURL ChromeContentBrowserClientExtensionsPart::GetEffectiveURL(
Profile* profile,
const GURL& url) {
// If the input |url| is part of an installed app, the effective URL is an
// extension URL with the ID of that extension as the host. This has the
// effect of grouping apps together in a common SiteInstance.
ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
if (!registry)
return url;
const Extension* extension =
registry->enabled_extensions().GetHostedAppByURL(url);
if (!extension)
return url;
// Bookmark apps do not use the hosted app process model, and should be
// treated as normal URLs.
if (extension->from_bookmark())
return url;
// If |url| corresponds to both an isolated origin and a hosted app,
// determine whether to use the effective URL, which also determines whether
// the isolated origin should take precedence over a matching hosted app:
// - Chrome Web Store should always be resolved to its effective URL, so that
// the CWS process gets proper bindings.
// - for other hosted apps, if the isolated origin covers the app's entire
// web extent (i.e., *all* URLs matched by the hosted app will have this
// isolated origin), allow the hosted app to take effect and return an
// effective URL.
// - for other cases, disallow effective URLs, as otherwise this would allow
// the isolated origin to share the hosted app process with other origins
// it does not trust, due to https://crbug.com/791796.
//
// TODO(alexmos): Revisit and possibly remove this once
// https://crbug.com/791796 is fixed.
url::Origin isolated_origin;
auto* policy = content::ChildProcessSecurityPolicy::GetInstance();
bool is_isolated_origin = policy->GetMatchingIsolatedOrigin(
url::Origin::Create(url), &isolated_origin);
if (is_isolated_origin && extension->id() != kWebStoreAppId &&
!DoesOriginMatchAllURLsInWebExtent(isolated_origin,
extension->web_extent())) {
return url;
}
// If the URL is part of an extension's web extent, convert it to an
// extension URL.
return extension->GetResourceURL(url.path());
}
// static
bool ChromeContentBrowserClientExtensionsPart::ShouldUseProcessPerSite(
Profile* profile, const GURL& effective_url) {
if (!effective_url.SchemeIs(kExtensionScheme))
return false;
ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
if (!registry)
return false;
const Extension* extension =
registry->enabled_extensions().GetByID(effective_url.host());
if (!extension)
return false;
// If the URL is part of a hosted app that does not have the background
// permission, or that does not allow JavaScript access to the background
// page, we want to give each instance its own process to improve
// responsiveness.
if (extension->GetType() == Manifest::TYPE_HOSTED_APP) {
if (!extension->permissions_data()->HasAPIPermission(
APIPermission::kBackground) ||
!BackgroundInfo::AllowJSAccess(extension)) {
return false;
}
}
// Hosted apps that have script access to their background page must use
// process per site, since all instances can make synchronous calls to the
// background window. Other extensions should use process per site as well.
return true;
}
// static
bool ChromeContentBrowserClientExtensionsPart::DoesSiteRequireDedicatedProcess(
content::BrowserContext* browser_context,
const GURL& effective_site_url) {
const Extension* extension = ExtensionRegistry::Get(browser_context)
->enabled_extensions()
.GetExtensionOrAppByURL(effective_site_url);
if (!extension)
return false;
// Always isolate Chrome Web Store.
if (extension->id() == kWebStoreAppId)
return true;
// Extensions should be isolated, except for hosted apps. Isolating hosted
// apps is a good idea, but ought to be a separate knob.
if (extension->is_hosted_app())
return false;
// Isolate all extensions.
return true;
}
// static
bool ChromeContentBrowserClientExtensionsPart::ShouldLockToOrigin(
content::BrowserContext* browser_context,
const GURL& effective_site_url) {
// https://crbug.com/160576 workaround: Origin lock to the chrome-extension://
// scheme for a hosted app would kill processes on legitimate requests for the
// app's cookies.
if (effective_site_url.SchemeIs(extensions::kExtensionScheme)) {
const Extension* extension =
ExtensionRegistry::Get(browser_context)
->enabled_extensions()
.GetExtensionOrAppByURL(effective_site_url);
if (extension && extension->is_hosted_app())
return false;
// http://crbug.com/600441 workaround: Extension process reuse, implemented
// in ShouldTryToUseExistingProcessHost(), means that extension processes
// aren't always actually dedicated to a single origin.
// TODO(nick): Fix this.
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
::switches::kSitePerProcess))
return false;
}
return true;
}
bool ChromeContentBrowserClientExtensionsPart::ShouldBypassDocumentBlocking(
const url::Origin& initiator) {
// Don't block responses for extension processes or for content scripts.
// TODO(creis): This check can be made stricter by checking what the extension
// has access to.
return initiator.scheme() == extensions::kExtensionScheme;
}
// static
bool ChromeContentBrowserClientExtensionsPart::CanCommitURL(
content::RenderProcessHost* process_host, const GURL& url) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// We need to let most extension URLs commit in any process, since this can
// be allowed due to web_accessible_resources. Most hosted app URLs may also
// load in any process (e.g., in an iframe). However, the Chrome Web Store
// cannot be loaded in iframes and should never be requested outside its
// process.
ExtensionRegistry* registry =
ExtensionRegistry::Get(process_host->GetBrowserContext());
if (!registry)
return true;
const Extension* new_extension =
registry->enabled_extensions().GetExtensionOrAppByURL(url);
if (new_extension && new_extension->is_hosted_app() &&
new_extension->id() == kWebStoreAppId &&
!ProcessMap::Get(process_host->GetBrowserContext())
->Contains(new_extension->id(), process_host->GetID())) {
return false;
}
return true;
}
// static
bool ChromeContentBrowserClientExtensionsPart::IsSuitableHost(
Profile* profile,
content::RenderProcessHost* process_host,
const GURL& site_url) {
DCHECK(profile);
ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
ProcessMap* process_map = ProcessMap::Get(profile);
// These may be NULL during tests. In that case, just assume any site can
// share any host.
if (!registry || !process_map)
return true;
// Otherwise, just make sure the process privilege matches the privilege
// required by the site.
RenderProcessHostPrivilege privilege_required =
GetPrivilegeRequiredByUrl(site_url, registry);
return GetProcessPrivilege(process_host, process_map, registry) ==
privilege_required;
}
// static
bool
ChromeContentBrowserClientExtensionsPart::ShouldTryToUseExistingProcessHost(
Profile* profile, const GURL& url) {
// This function is trying to limit the amount of processes used by extensions
// with background pages. It uses a globally set percentage of processes to
// run such extensions and if the limit is exceeded, it returns true, to
// indicate to the content module to group extensions together.
ExtensionRegistry* registry =
profile ? ExtensionRegistry::Get(profile) : NULL;
if (!registry)
return false;
// We have to have a valid extension with background page to proceed.
const Extension* extension =
registry->enabled_extensions().GetExtensionOrAppByURL(url);
if (!extension)
return false;
if (!BackgroundInfo::HasBackgroundPage(extension))
return false;
std::set<int> process_ids;
size_t max_process_count =
content::RenderProcessHost::GetMaxRendererProcessCount();
// Go through all profiles to ensure we have total count of extension
// processes containing background pages, otherwise one profile can
// starve the other.
std::vector<Profile*> profiles = g_browser_process->profile_manager()->
GetLoadedProfiles();
for (size_t i = 0; i < profiles.size(); ++i) {
ProcessManager* epm = ProcessManager::Get(profiles[i]);
for (ExtensionHost* host : epm->background_hosts())
process_ids.insert(host->render_process_host()->GetID());
}
return (process_ids.size() >
(max_process_count * chrome::kMaxShareOfExtensionProcesses));
}
// static
bool ChromeContentBrowserClientExtensionsPart::
ShouldSwapBrowsingInstancesForNavigation(SiteInstance* site_instance,
const GURL& current_url,
const GURL& new_url) {
// If we don't have an ExtensionRegistry, then rely on the SiteInstance logic
// in RenderFrameHostManager to decide when to swap.
ExtensionRegistry* registry =
ExtensionRegistry::Get(site_instance->GetBrowserContext());
if (!registry)
return false;
// We must use a new BrowsingInstance (forcing a process swap and disabling
// scripting by existing tabs) if one of the URLs corresponds to the Chrome
// Web Store hosted app, and the other does not.
//
// We don't force a BrowsingInstance swap in other cases (i.e., when opening
// a popup from one extension to a different extension, or to a non-extension
// URL) to preserve script connections and allow use cases like postMessage
// via window.opener. Those cases would still force a SiteInstance swap in
// RenderFrameHostManager. This behavior is similar to how extension
// subframes on a web main frame are also placed in the same BrowsingInstance
// (by the content/ part of ShouldSwapBrowsingInstancesForNavigation); this
// check is just doing the same for top-level frames. See
// https://crbug.com/590068.
const Extension* current_extension =
registry->enabled_extensions().GetExtensionOrAppByURL(current_url);
bool is_current_url_for_web_store =
current_extension && current_extension->id() == kWebStoreAppId;
const Extension* new_extension =
registry->enabled_extensions().GetExtensionOrAppByURL(new_url);
bool is_new_url_for_web_store =
new_extension && new_extension->id() == kWebStoreAppId;
// First do a process check. We should force a BrowsingInstance swap if we
// are going to Chrome Web Store, but the current process doesn't know about
// CWS, even if current_extension somehow corresponds to CWS.
ProcessMap* process_map = ProcessMap::Get(site_instance->GetBrowserContext());
if (is_new_url_for_web_store && site_instance->HasProcess() &&
!process_map->Contains(new_extension->id(),
site_instance->GetProcess()->GetID()))
return true;
// Otherwise, swap BrowsingInstances when transitioning to/from Chrome Web
// Store.
return is_current_url_for_web_store != is_new_url_for_web_store;
}
// static
bool ChromeContentBrowserClientExtensionsPart::AllowServiceWorker(
const GURL& scope,
const GURL& first_party_url,
content::ResourceContext* context) {
// We only care about extension urls.
if (!first_party_url.SchemeIs(kExtensionScheme))
return true;
ProfileIOData* io_data = ProfileIOData::FromResourceContext(context);
InfoMap* extension_info_map = io_data->GetExtensionInfoMap();
const Extension* extension =
extension_info_map->extensions().GetExtensionOrAppByURL(first_party_url);
// Don't allow a service worker for an extension url with no extension (this
// could happen in the case of, e.g., an unloaded extension).
return extension != nullptr;
}
// static
void ChromeContentBrowserClientExtensionsPart::OverrideNavigationParams(
content::SiteInstance* site_instance,
ui::PageTransition* transition,
bool* is_renderer_initiated,
content::Referrer* referrer) {
const Extension* extension =
ExtensionRegistry::Get(site_instance->GetBrowserContext())
->enabled_extensions()
.GetExtensionOrAppByURL(site_instance->GetSiteURL());
if (!extension)
return;
if (extension->id() == extension_misc::kBookmarkManagerId &&
ui::PageTransitionCoreTypeIs(*transition, ui::PAGE_TRANSITION_LINK)) {
// Link clicks in the bookmark manager count as bookmarks and as browser-
// initiated navigations.
*transition = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
*is_renderer_initiated = false;
}
// Hide the referrer for extension pages. We don't want sites to see a
// referrer of chrome-extension://<...>.
if (extension->is_extension())
*referrer = content::Referrer();
}
// static
bool ChromeContentBrowserClientExtensionsPart::ShouldAllowOpenURL(
content::SiteInstance* site_instance,
const GURL& to_url,
bool* result) {
DCHECK(result);
// Using url::Origin is important to properly handle blob: and filesystem:
// URLs.
url::Origin to_origin = url::Origin::Create(to_url);
if (to_origin.scheme() != kExtensionScheme) {
// We're not responsible for protecting this resource. Note that hosted
// apps fall into this category.
return false;
}
// Do not allow pages from the web or other extensions navigate to
// non-web-accessible extension resources.
ExtensionRegistry* registry =
ExtensionRegistry::Get(site_instance->GetBrowserContext());
const Extension* to_extension =
registry->enabled_extensions().GetByID(to_origin.host());
if (!to_extension) {
// Treat non-existent extensions the same as an extension without accessible
// resources.
*result = false;
return true;
}
GURL site_url(site_instance->GetSiteURL());
const Extension* from_extension =
registry->enabled_extensions().GetExtensionOrAppByURL(site_url);
if (from_extension && from_extension == to_extension) {
*result = true;
return true;
}
// Blob and filesystem URLs are never considered web-accessible. See
// https://crbug.com/656752.
if (to_url.SchemeIsFileSystem() || to_url.SchemeIsBlob()) {
if (to_url.SchemeIsFileSystem())
RecordShouldAllowOpenURLFailure(FAILURE_FILE_SYSTEM_URL, site_url);
else
RecordShouldAllowOpenURLFailure(FAILURE_BLOB_URL, site_url);
// TODO(alexmos): Temporary instrumentation to find any regressions for
// this blocking. Remove after verifying that this is not breaking any
// legitimate use cases.
DEBUG_ALIAS_FOR_GURL(site_url_copy, site_url);
DEBUG_ALIAS_FOR_ORIGIN(to_origin_copy, to_origin);
base::debug::DumpWithoutCrashing();
*result = false;
return true;
}
// Navigations from chrome://, chrome-search:// and chrome-devtools:// pages
// need to be allowed, even if |to_url| is not web-accessible. See
// https://crbug.com/662602.
//
// Note that this is intentionally done after the check for blob: and
// filesystem: URLs above, for consistency with the renderer-side checks
// which already disallow navigations from chrome URLs to blob/filesystem
// URLs.
if (site_url.SchemeIs(content::kChromeUIScheme) ||
site_url.SchemeIs(content::kChromeDevToolsScheme) ||
site_url.SchemeIs(chrome::kChromeSearchScheme)) {
*result = true;
return true;
}
// <webview> guests should be allowed to load only webview-accessible
// resources, but that check is done later in
// AllowCrossRendererResourceLoadHelper, so allow <webview> guests to proceed
// here and rely on that check instead. See https://crbug.com/691941.
if (site_url.SchemeIs(content::kGuestScheme)) {
*result = true;
return true;
}
if (WebAccessibleResourcesInfo::IsResourceWebAccessible(to_extension,
to_url.path())) {
*result = true;
return true;
}
if (!site_url.SchemeIsHTTPOrHTTPS() && !site_url.SchemeIs(kExtensionScheme)) {
// This function used to incorrectly skip the web-accessible resource
// checks in this case. Measure how often this happens. See also
// https://crbug.com/696034.
RecordShouldAllowOpenURLFailure(
FAILURE_SCHEME_NOT_HTTP_OR_HTTPS_OR_EXTENSION, site_url);
} else {
RecordShouldAllowOpenURLFailure(FAILURE_RESOURCE_NOT_WEB_ACCESSIBLE,
site_url);
}
*result = false;
return true;
}
// static
std::unique_ptr<content::VpnServiceProxy>
ChromeContentBrowserClientExtensionsPart::GetVpnServiceProxy(
content::BrowserContext* browser_context) {
#if defined(OS_CHROMEOS)
chromeos::VpnService* vpn_service =
chromeos::VpnServiceFactory::GetForBrowserContext(browser_context);
if (!vpn_service)
return nullptr;
return vpn_service->GetVpnServiceProxy();
#else
return nullptr;
#endif
}
// static
bool ChromeContentBrowserClientExtensionsPart::
ShouldFrameShareParentSiteInstanceDespiteTopDocumentIsolation(
const GURL& subframe_url,
content::SiteInstance* parent_site_instance) {
const Extension* extension =
ExtensionRegistry::Get(parent_site_instance->GetBrowserContext())
->enabled_extensions()
.GetExtensionOrAppByURL(parent_site_instance->GetSiteURL());
return extension && extension->is_hosted_app();
}
// static
void ChromeContentBrowserClientExtensionsPart::RecordShouldAllowOpenURLFailure(
ShouldAllowOpenURLFailureReason reason,
const GURL& site_url) {
UMA_HISTOGRAM_ENUMERATION("Extensions.ShouldAllowOpenURL.Failure", reason,
FAILURE_LAST);
// Must be kept in sync with the ShouldAllowOpenURLFailureScheme enum.
static const char* const kSchemeNames[] = {
"unknown",
"",
url::kHttpScheme,
url::kHttpsScheme,
url::kFileScheme,
url::kFtpScheme,
url::kDataScheme,
url::kJavaScriptScheme,
url::kAboutScheme,
content::kChromeUIScheme,
content::kChromeDevToolsScheme,
content::kGuestScheme,
content::kViewSourceScheme,
chrome::kChromeSearchScheme,
chrome::kChromeNativeScheme,
dom_distiller::kDomDistillerScheme,
extensions::kExtensionScheme,
url::kContentScheme,
url::kBlobScheme,
url::kFileSystemScheme,
"last",
};
static_assert(arraysize(kSchemeNames) == SCHEME_LAST + 1,
"kSchemeNames should have SCHEME_LAST + 1 elements");
ShouldAllowOpenURLFailureScheme scheme = SCHEME_UNKNOWN;
for (int i = 1; i < SCHEME_LAST; i++) {
if (site_url.SchemeIs(kSchemeNames[i])) {
scheme = static_cast<ShouldAllowOpenURLFailureScheme>(i);
break;
}
}
UMA_HISTOGRAM_ENUMERATION("Extensions.ShouldAllowOpenURL.Failure.Scheme",
scheme, SCHEME_LAST);
}
// static
bool ChromeContentBrowserClientExtensionsPart::
DoesOriginMatchAllURLsInWebExtent(const url::Origin& origin,
const URLPatternSet& web_extent) {
// This function assumes |origin| is an isolated origin, which can only have
// an HTTP or HTTPS scheme (see IsolatedOriginUtil::IsValidIsolatedOrigin()),
// so these are the only schemes allowed to be matched below.
DCHECK(origin.scheme() == url::kHttpsScheme ||
origin.scheme() == url::kHttpScheme);
URLPattern origin_pattern(URLPattern::SCHEME_HTTPS | URLPattern::SCHEME_HTTP);
// TODO(alexmos): Temporarily disable precise scheme matching on
// |origin_pattern| to allow apps that use *://foo.com/ in their web extent
// to still work with isolated origins. See https://crbug.com/799638. We
// should use SetScheme(origin.scheme()) here once https://crbug.com/791796
// is fixed.
origin_pattern.SetScheme("*");
origin_pattern.SetHost(origin.host());
origin_pattern.SetPath("/*");
// We allow matching subdomains here because |origin| is the precise origin
// retrieved from site isolation policy. Thus, we'll only allow an extent of
// foo.example.com and bar.example.com if the isolated origin was
// example.com; if the isolated origin is foo.example.com, this will
// correctly fail.
origin_pattern.SetMatchSubdomains(true);
URLPatternSet origin_pattern_list;
origin_pattern_list.AddPattern(origin_pattern);
return origin_pattern_list.Contains(web_extent);
}
void ChromeContentBrowserClientExtensionsPart::RenderProcessWillLaunch(
content::RenderProcessHost* host) {
int id = host->GetID();
Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext());
host->AddFilter(new ChromeExtensionMessageFilter(id, profile));
host->AddFilter(new ExtensionMessageFilter(id, profile));
host->AddFilter(new IOThreadExtensionMessageFilter(id, profile));
host->AddFilter(new ExtensionsGuestViewMessageFilter(id, profile));
if (extensions::ExtensionsClient::Get()
->ExtensionAPIEnabledInExtensionServiceWorkers()) {
host->AddFilter(new ExtensionServiceWorkerMessageFilter(
id, profile, host->GetStoragePartition()->GetServiceWorkerContext()));
}
}
void ChromeContentBrowserClientExtensionsPart::SiteInstanceGotProcess(
SiteInstance* site_instance) {
BrowserContext* context = site_instance->GetProcess()->GetBrowserContext();
ExtensionRegistry* registry = ExtensionRegistry::Get(context);
if (!registry)
return;
// Only add the process to the map if the SiteInstance's site URL is already
// a chrome-extension:// URL. This includes hosted apps, except in rare cases
// that a URL in the hosted app's extent is not treated as a hosted app (e.g.,
// for isolated origins or cross-site iframes). For that case, don't look up
// the hosted app's Extension from the site URL using GetExtensionOrAppByURL,
// since it isn't treated as a hosted app.
if (!site_instance->GetSiteURL().SchemeIs(kExtensionScheme))
return;
const Extension* extension = registry->enabled_extensions().GetByID(
site_instance->GetSiteURL().host());
if (!extension)
return;
ProcessMap::Get(context)->Insert(extension->id(),
site_instance->GetProcess()->GetID(),
site_instance->GetId());
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&InfoMap::RegisterExtensionProcess,
ExtensionSystem::Get(context)->info_map(), extension->id(),
site_instance->GetProcess()->GetID(),
site_instance->GetId()));
}
void ChromeContentBrowserClientExtensionsPart::SiteInstanceDeleting(
SiteInstance* site_instance) {
BrowserContext* context = site_instance->GetBrowserContext();
ExtensionRegistry* registry = ExtensionRegistry::Get(context);
if (!registry)
return;
const Extension* extension =
registry->enabled_extensions().GetExtensionOrAppByURL(
site_instance->GetSiteURL());
if (!extension)
return;
ProcessMap::Get(context)->Remove(extension->id(),
site_instance->GetProcess()->GetID(),
site_instance->GetId());
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&InfoMap::UnregisterExtensionProcess,
ExtensionSystem::Get(context)->info_map(), extension->id(),
site_instance->GetProcess()->GetID(),
site_instance->GetId()));
}
void ChromeContentBrowserClientExtensionsPart::OverrideWebkitPrefs(
RenderViewHost* rvh,
WebPreferences* web_prefs) {
const ExtensionRegistry* registry =
ExtensionRegistry::Get(rvh->GetProcess()->GetBrowserContext());
if (!registry)
return;
// Note: it's not possible for kExtensionsScheme to change during the lifetime
// of the process.
//
// Ensure that we are only granting extension preferences to URLs with
// the correct scheme. Without this check, chrome-guest:// schemes used by
// webview tags as well as hosts that happen to match the id of an
// installed extension would get the wrong preferences.
const GURL& site_url = rvh->GetSiteInstance()->GetSiteURL();
if (!site_url.SchemeIs(kExtensionScheme))
return;
WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
ViewType view_type = GetViewType(web_contents);
const Extension* extension =
registry->enabled_extensions().GetByID(site_url.host());
extension_webkit_preferences::SetPreferences(extension, view_type, web_prefs);
}
void ChromeContentBrowserClientExtensionsPart::BrowserURLHandlerCreated(
BrowserURLHandler* handler) {
handler->AddHandlerPair(&ExtensionWebUI::HandleChromeURLOverride,
BrowserURLHandler::null_handler());
handler->AddHandlerPair(BrowserURLHandler::null_handler(),
&ExtensionWebUI::HandleChromeURLOverrideReverse);
}
void ChromeContentBrowserClientExtensionsPart::
GetAdditionalAllowedSchemesForFileSystem(
std::vector<std::string>* additional_allowed_schemes) {
additional_allowed_schemes->push_back(kExtensionScheme);
}
void ChromeContentBrowserClientExtensionsPart::GetURLRequestAutoMountHandlers(
std::vector<storage::URLRequestAutoMountHandler>* handlers) {
handlers->push_back(
base::Bind(MediaFileSystemBackend::AttemptAutoMountForURLRequest));
}
void ChromeContentBrowserClientExtensionsPart::GetAdditionalFileSystemBackends(
content::BrowserContext* browser_context,
const base::FilePath& storage_partition_path,
std::vector<std::unique_ptr<storage::FileSystemBackend>>*
additional_backends) {
additional_backends->push_back(
std::make_unique<MediaFileSystemBackend>(storage_partition_path));
additional_backends->push_back(
std::make_unique<sync_file_system::SyncFileSystemBackend>(
Profile::FromBrowserContext(browser_context)));
}
void ChromeContentBrowserClientExtensionsPart::
AppendExtraRendererCommandLineSwitches(base::CommandLine* command_line,
content::RenderProcessHost* process,
Profile* profile) {
if (!process)
return;
DCHECK(profile);
if (ProcessMap::Get(profile)->Contains(process->GetID())) {
command_line->AppendSwitch(switches::kExtensionProcess);
}
}
void ChromeContentBrowserClientExtensionsPart::ResourceDispatcherHostCreated() {
content::ResourceDispatcherHost::Get()->RegisterInterceptor(
"Origin", kExtensionScheme, base::Bind(&OnHttpHeaderReceived));
}
} // namespace extensions