blob: 473b91706d91d51f8e98e15fbb982129e4aaf8ae [file] [log] [blame]
/*
* Copyright (C) 2010 Apple Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/thread_specific.h"
#include "third_party/blink/renderer/platform/wtf/threading.h"
#include "third_party/blink/renderer/platform/wtf/threading_primitives.h"
#include "url/url_util.h"
namespace blink {
// Function defined in third_party/blink/public/web/blink.h.
void SetDomainRelaxationForbiddenForTest(bool forbidden,
const WebString& scheme) {
SchemeRegistry::SetDomainRelaxationForbiddenForURLScheme(forbidden,
String(scheme));
}
// Function defined in third_party/blink/public/web/blink.h.
void ResetDomainRelaxationForTest() {
SchemeRegistry::ResetDomainRelaxation();
}
namespace {
struct PolicyAreasHashTraits : HashTraits<SchemeRegistry::PolicyAreas> {
static const bool kEmptyValueIsZero = true;
static SchemeRegistry::PolicyAreas EmptyValue() {
return SchemeRegistry::kPolicyAreaNone;
}
};
class URLSchemesRegistry final {
USING_FAST_MALLOC(URLSchemesRegistry);
public:
URLSchemesRegistry()
: // For ServiceWorker schemes: HTTP is required because http://localhost
// is considered secure. Additional checks are performed to ensure that
// other http pages are filtered out.
service_worker_schemes({"http", "https"}),
fetch_api_schemes({"http", "https"}),
allowed_in_referrer_schemes({"http", "https"}) {
for (auto& scheme : url::GetCorsEnabledSchemes())
cors_enabled_schemes.insert(scheme.c_str());
for (auto& scheme : url::GetCSPBypassingSchemes()) {
content_security_policy_bypassing_schemes.insert(
scheme.c_str(), SchemeRegistry::kPolicyAreaAll);
}
for (auto& scheme : url::GetEmptyDocumentSchemes())
empty_document_schemes.insert(scheme.c_str());
}
~URLSchemesRegistry() = default;
// As URLSchemesRegistry is accessed from multiple threads, be very careful to
// ensure that
// - URLSchemesRegistry is initialized/modified through
// GetMutableURLSchemesRegistry() before threads can be created, and
// - The URLSchemesRegistry members below aren't modified when accessed after
// initialization.
// Particularly, Strings inside them shouldn't be copied, as it modifies
// reference counts of StringImpls (IsolatedCopy() should be taken instead).
URLSchemesSet display_isolated_url_schemes;
URLSchemesSet empty_document_schemes;
URLSchemesSet schemes_forbidden_from_domain_relaxation;
URLSchemesSet not_allowing_javascript_urls_schemes;
URLSchemesSet cors_enabled_schemes;
URLSchemesSet service_worker_schemes;
URLSchemesSet fetch_api_schemes;
URLSchemesSet first_party_when_top_level_schemes;
URLSchemesSet first_party_when_top_level_with_secure_embedded_schemes;
URLSchemesMap<SchemeRegistry::PolicyAreas, PolicyAreasHashTraits>
content_security_policy_bypassing_schemes;
URLSchemesSet secure_context_bypassing_schemes;
URLSchemesSet allowed_in_referrer_schemes;
URLSchemesSet error_schemes;
URLSchemesSet wasm_eval_csp_schemes;
private:
friend const URLSchemesRegistry& GetURLSchemesRegistry();
friend URLSchemesRegistry& GetMutableURLSchemesRegistry();
static URLSchemesRegistry& GetInstance() {
DEFINE_STATIC_LOCAL(URLSchemesRegistry, schemes, ());
return schemes;
}
};
const URLSchemesRegistry& GetURLSchemesRegistry() {
return URLSchemesRegistry::GetInstance();
}
URLSchemesRegistry& GetMutableURLSchemesRegistry() {
#if DCHECK_IS_ON()
DCHECK(WTF::IsBeforeThreadCreated());
#endif
return URLSchemesRegistry::GetInstance();
}
} // namespace
void SchemeRegistry::RegisterURLSchemeAsDisplayIsolated(const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
GetMutableURLSchemesRegistry().display_isolated_url_schemes.insert(scheme);
}
bool SchemeRegistry::ShouldTreatURLSchemeAsDisplayIsolated(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
if (scheme.IsEmpty())
return false;
return GetURLSchemesRegistry().display_isolated_url_schemes.Contains(scheme);
}
bool SchemeRegistry::ShouldTreatURLSchemeAsRestrictingMixedContent(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
return scheme == "https";
}
bool SchemeRegistry::ShouldLoadURLSchemeAsEmptyDocument(const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
if (scheme.IsEmpty())
return false;
return GetURLSchemesRegistry().empty_document_schemes.Contains(scheme);
}
void SchemeRegistry::SetDomainRelaxationForbiddenForURLScheme(
bool forbidden,
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
if (scheme.IsEmpty())
return;
if (forbidden) {
GetMutableURLSchemesRegistry()
.schemes_forbidden_from_domain_relaxation.insert(scheme);
} else {
GetMutableURLSchemesRegistry()
.schemes_forbidden_from_domain_relaxation.erase(scheme);
}
}
void SchemeRegistry::ResetDomainRelaxation() {
GetMutableURLSchemesRegistry()
.schemes_forbidden_from_domain_relaxation.clear();
}
bool SchemeRegistry::IsDomainRelaxationForbiddenForURLScheme(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
if (scheme.IsEmpty())
return false;
return GetURLSchemesRegistry()
.schemes_forbidden_from_domain_relaxation.Contains(scheme);
}
bool SchemeRegistry::CanDisplayOnlyIfCanRequest(const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
return scheme == "blob" || scheme == "filesystem";
}
void SchemeRegistry::RegisterURLSchemeAsNotAllowingJavascriptURLs(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
GetMutableURLSchemesRegistry().not_allowing_javascript_urls_schemes.insert(
scheme);
}
void SchemeRegistry::RemoveURLSchemeAsNotAllowingJavascriptURLs(
const String& scheme) {
GetMutableURLSchemesRegistry().not_allowing_javascript_urls_schemes.erase(
scheme);
}
bool SchemeRegistry::ShouldTreatURLSchemeAsNotAllowingJavascriptURLs(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
if (scheme.IsEmpty())
return false;
return GetURLSchemesRegistry().not_allowing_javascript_urls_schemes.Contains(
scheme);
}
bool SchemeRegistry::ShouldTreatURLSchemeAsCorsEnabled(const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
if (scheme.IsEmpty())
return false;
return GetURLSchemesRegistry().cors_enabled_schemes.Contains(scheme);
}
String SchemeRegistry::ListOfCorsEnabledURLSchemes() {
StringBuilder builder;
bool add_separator = false;
for (const auto& scheme : GetURLSchemesRegistry().cors_enabled_schemes) {
if (add_separator)
builder.Append(", ");
else
add_separator = true;
// As |cors_enabled_schemes| can be accessed from multiple threads, we need
// IsolatedCopy() here before passing it to |StringBuilder| that can
// ref/deref Strings.
builder.Append(scheme.IsolatedCopy());
}
return builder.ToString();
}
bool SchemeRegistry::ShouldTreatURLSchemeAsLegacy(const String& scheme) {
return scheme == "ftp";
}
bool SchemeRegistry::ShouldTrackUsageMetricsForScheme(const String& scheme) {
// This SchemeRegistry is primarily used by Blink UseCounter, which aims to
// match the tracking policy of page_load_metrics (see
// pageTrackDecider::ShouldTrack() for more details).
// The scheme represents content which likely cannot be easily updated.
// Specifically this includes internal pages such as about, devtools,
// etc.
// "chrome-extension" is not included because they have a single deployment
// point (the webstore) and are designed specifically for Chrome.
// "data" is not included because real sites shouldn't be using it for
// top-level pages and Chrome does use it internally (eg. PluginPlaceholder).
// "file" is not included because file:// navigations have different loading
// behaviors.
return scheme == "http" || scheme == "https";
}
void SchemeRegistry::RegisterURLSchemeAsAllowingServiceWorkers(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
GetMutableURLSchemesRegistry().service_worker_schemes.insert(scheme);
}
bool SchemeRegistry::ShouldTreatURLSchemeAsAllowingServiceWorkers(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
if (scheme.IsEmpty())
return false;
return GetURLSchemesRegistry().service_worker_schemes.Contains(scheme);
}
void SchemeRegistry::RegisterURLSchemeAsSupportingFetchAPI(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
GetMutableURLSchemesRegistry().fetch_api_schemes.insert(scheme);
}
bool SchemeRegistry::ShouldTreatURLSchemeAsSupportingFetchAPI(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
if (scheme.IsEmpty())
return false;
return GetURLSchemesRegistry().fetch_api_schemes.Contains(scheme);
}
// https://url.spec.whatwg.org/#special-scheme
bool SchemeRegistry::IsSpecialScheme(const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
return scheme == "ftp" || scheme == "file" || scheme == "http" ||
scheme == "https" || scheme == "ws" || scheme == "wss";
}
void SchemeRegistry::RegisterURLSchemeAsFirstPartyWhenTopLevel(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
GetMutableURLSchemesRegistry().first_party_when_top_level_schemes.insert(
scheme);
}
void SchemeRegistry::RemoveURLSchemeAsFirstPartyWhenTopLevel(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
GetMutableURLSchemesRegistry().first_party_when_top_level_schemes.erase(
scheme);
}
bool SchemeRegistry::ShouldTreatURLSchemeAsFirstPartyWhenTopLevel(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
if (scheme.IsEmpty())
return false;
return GetURLSchemesRegistry().first_party_when_top_level_schemes.Contains(
scheme);
}
void SchemeRegistry::RegisterURLSchemeAsFirstPartyWhenTopLevelEmbeddingSecure(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
GetMutableURLSchemesRegistry()
.first_party_when_top_level_with_secure_embedded_schemes.insert(scheme);
}
bool SchemeRegistry::
ShouldTreatURLSchemeAsFirstPartyWhenTopLevelEmbeddingSecure(
const String& top_level_scheme,
const String& child_scheme) {
DCHECK_EQ(top_level_scheme, top_level_scheme.LowerASCII());
DCHECK_EQ(child_scheme, child_scheme.LowerASCII());
// Matches GURL::SchemeIsCryptographic used by
// RenderFrameHostImpl::ComputeIsolationInfoInternal
if (child_scheme != "https" && child_scheme != "wss")
return false;
if (top_level_scheme.IsEmpty())
return false;
return GetURLSchemesRegistry()
.first_party_when_top_level_with_secure_embedded_schemes.Contains(
top_level_scheme);
}
void SchemeRegistry::RegisterURLSchemeAsAllowedForReferrer(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
GetMutableURLSchemesRegistry().allowed_in_referrer_schemes.insert(scheme);
}
void SchemeRegistry::RemoveURLSchemeAsAllowedForReferrer(const String& scheme) {
GetMutableURLSchemesRegistry().allowed_in_referrer_schemes.erase(scheme);
}
bool SchemeRegistry::ShouldTreatURLSchemeAsAllowedForReferrer(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
if (scheme.IsEmpty())
return false;
return GetURLSchemesRegistry().allowed_in_referrer_schemes.Contains(scheme);
}
void SchemeRegistry::RegisterURLSchemeAsError(const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
GetMutableURLSchemesRegistry().error_schemes.insert(scheme);
}
bool SchemeRegistry::ShouldTreatURLSchemeAsError(const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
if (scheme.IsEmpty())
return false;
return GetURLSchemesRegistry().error_schemes.Contains(scheme);
}
void SchemeRegistry::RegisterURLSchemeAsBypassingContentSecurityPolicy(
const String& scheme,
PolicyAreas policy_areas) {
DCHECK_EQ(scheme, scheme.LowerASCII());
GetMutableURLSchemesRegistry()
.content_security_policy_bypassing_schemes.insert(scheme, policy_areas);
}
void SchemeRegistry::RemoveURLSchemeRegisteredAsBypassingContentSecurityPolicy(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
GetMutableURLSchemesRegistry()
.content_security_policy_bypassing_schemes.erase(scheme);
}
bool SchemeRegistry::SchemeShouldBypassContentSecurityPolicy(
const String& scheme,
PolicyAreas policy_areas) {
DCHECK_NE(policy_areas, kPolicyAreaNone);
if (scheme.IsEmpty() || policy_areas == kPolicyAreaNone)
return false;
// get() returns 0 (PolicyAreaNone) if there is no entry in the map.
// Thus by default, schemes do not bypass CSP.
return (GetURLSchemesRegistry().content_security_policy_bypassing_schemes.at(
scheme) &
policy_areas) == policy_areas;
}
void SchemeRegistry::RegisterURLSchemeBypassingSecureContextCheck(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
GetMutableURLSchemesRegistry().secure_context_bypassing_schemes.insert(
scheme);
}
bool SchemeRegistry::SchemeShouldBypassSecureContextCheck(
const String& scheme) {
if (scheme.IsEmpty())
return false;
DCHECK_EQ(scheme, scheme.LowerASCII());
return GetURLSchemesRegistry().secure_context_bypassing_schemes.Contains(
scheme);
}
void SchemeRegistry::RegisterURLSchemeAsAllowingWasmEvalCSP(
const String& scheme) {
DCHECK_EQ(scheme, scheme.LowerASCII());
GetMutableURLSchemesRegistry().wasm_eval_csp_schemes.insert(scheme);
}
bool SchemeRegistry::SchemeSupportsWasmEvalCSP(const String& scheme) {
if (scheme.IsEmpty())
return false;
DCHECK_EQ(scheme, scheme.LowerASCII());
return GetURLSchemesRegistry().wasm_eval_csp_schemes.Contains(scheme);
}
} // namespace blink