blob: ee60b12d53d92d8dbe4b922e3f740a015c5c41bf [file] [log] [blame]
/*
* Copyright (C) 2011 Google 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.
* 3. Neither the name of Google, Inc. ("Google") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY GOOGLE AND ITS CONTRIBUTORS "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 OR ITS 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 "platform/weborigin/SecurityPolicy.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/weborigin/KURL.h"
#include "platform/weborigin/OriginAccessEntry.h"
#include "platform/weborigin/SchemeRegistry.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "wtf/HashMap.h"
#include "wtf/HashSet.h"
#include "wtf/PtrUtil.h"
#include "wtf/Threading.h"
#include "wtf/text/StringHash.h"
#include <memory>
namespace blink {
using OriginAccessWhiteList = Vector<OriginAccessEntry>;
using OriginAccessMap = HashMap<String, std::unique_ptr<OriginAccessWhiteList>>;
using OriginSet = HashSet<String>;
enum ReferrerPolicyLegacyKeywordsSupport {
SupportReferrerPolicyLegacyKeywords,
DoNotSupportReferrerPolicyLegacyKeywords,
};
static OriginAccessMap& originAccessMap() {
DEFINE_STATIC_LOCAL(OriginAccessMap, originAccessMap, ());
return originAccessMap;
}
static OriginSet& trustworthyOriginSet() {
DEFINE_STATIC_LOCAL(OriginSet, trustworthyOriginSet, ());
return trustworthyOriginSet;
}
static bool referrerPolicyFromStringImpl(
const String& policy,
ReferrerPolicyLegacyKeywordsSupport legacyKeywordsSupport,
ReferrerPolicy* result) {
DCHECK(!policy.isNull());
bool supportLegacyKeywords =
(legacyKeywordsSupport == SupportReferrerPolicyLegacyKeywords);
if (equalIgnoringASCIICase(policy, "no-referrer") ||
(supportLegacyKeywords && equalIgnoringASCIICase(policy, "never"))) {
*result = ReferrerPolicyNever;
return true;
}
if (equalIgnoringASCIICase(policy, "unsafe-url") ||
(supportLegacyKeywords && equalIgnoringASCIICase(policy, "always"))) {
*result = ReferrerPolicyAlways;
return true;
}
if (equalIgnoringASCIICase(policy, "origin")) {
*result = ReferrerPolicyOrigin;
return true;
}
if (equalIgnoringASCIICase(policy, "origin-when-cross-origin") ||
(supportLegacyKeywords &&
equalIgnoringASCIICase(policy, "origin-when-crossorigin"))) {
*result = ReferrerPolicyOriginWhenCrossOrigin;
return true;
}
if (equalIgnoringASCIICase(policy, "no-referrer-when-downgrade") ||
(supportLegacyKeywords && equalIgnoringASCIICase(policy, "default"))) {
*result = ReferrerPolicyNoReferrerWhenDowngrade;
return true;
}
return false;
}
void SecurityPolicy::init() {
originAccessMap();
trustworthyOriginSet();
}
bool SecurityPolicy::shouldHideReferrer(const KURL& url,
const String& referrer) {
bool referrerIsSecureURL = protocolIs(referrer, "https");
String scheme = KURL(KURL(), referrer).protocol();
bool schemeIsAllowed =
SchemeRegistry::shouldTreatURLSchemeAsAllowedForReferrer(scheme);
if (!schemeIsAllowed)
return true;
if (!referrerIsSecureURL)
return false;
bool URLIsSecureURL = url.protocolIs("https");
return !URLIsSecureURL;
}
Referrer SecurityPolicy::generateReferrer(ReferrerPolicy referrerPolicy,
const KURL& url,
const String& referrer) {
ReferrerPolicy referrerPolicyNoDefault = referrerPolicy;
if (referrerPolicyNoDefault == ReferrerPolicyDefault) {
if (RuntimeEnabledFeatures::reducedReferrerGranularityEnabled()) {
referrerPolicyNoDefault =
ReferrerPolicyNoReferrerWhenDowngradeOriginWhenCrossOrigin;
} else {
referrerPolicyNoDefault = ReferrerPolicyNoReferrerWhenDowngrade;
}
}
if (referrer == Referrer::noReferrer())
return Referrer(Referrer::noReferrer(), referrerPolicyNoDefault);
ASSERT(!referrer.isEmpty());
String scheme = KURL(KURL(), referrer).protocol();
if (!SchemeRegistry::shouldTreatURLSchemeAsAllowedForReferrer(scheme))
return Referrer(Referrer::noReferrer(), referrerPolicyNoDefault);
if (SecurityOrigin::shouldUseInnerURL(url))
return Referrer(Referrer::noReferrer(), referrerPolicyNoDefault);
switch (referrerPolicyNoDefault) {
case ReferrerPolicyNever:
return Referrer(Referrer::noReferrer(), referrerPolicyNoDefault);
case ReferrerPolicyAlways:
return Referrer(referrer, referrerPolicyNoDefault);
case ReferrerPolicyOrigin: {
String origin = SecurityOrigin::createFromString(referrer)->toString();
// A security origin is not a canonical URL as it lacks a path. Add /
// to turn it into a canonical URL we can use as referrer.
return Referrer(origin + "/", referrerPolicyNoDefault);
}
case ReferrerPolicyOriginWhenCrossOrigin: {
RefPtr<SecurityOrigin> referrerOrigin =
SecurityOrigin::createFromString(referrer);
RefPtr<SecurityOrigin> urlOrigin = SecurityOrigin::create(url);
if (!urlOrigin->isSameSchemeHostPort(referrerOrigin.get())) {
String origin = referrerOrigin->toString();
return Referrer(origin + "/", referrerPolicyNoDefault);
}
break;
}
case ReferrerPolicyNoReferrerWhenDowngradeOriginWhenCrossOrigin: {
// If the flag is enabled, and we're dealing with a cross-origin request,
// strip it. Otherwise fall through to NoReferrerWhenDowngrade behavior.
RefPtr<SecurityOrigin> referrerOrigin =
SecurityOrigin::createFromString(referrer);
RefPtr<SecurityOrigin> urlOrigin = SecurityOrigin::create(url);
if (!urlOrigin->isSameSchemeHostPort(referrerOrigin.get())) {
String origin = referrerOrigin->toString();
return Referrer(shouldHideReferrer(url, referrer)
? Referrer::noReferrer()
: origin + "/",
referrerPolicyNoDefault);
}
break;
}
case ReferrerPolicyNoReferrerWhenDowngrade:
break;
case ReferrerPolicyDefault:
ASSERT_NOT_REACHED();
break;
}
return Referrer(
shouldHideReferrer(url, referrer) ? Referrer::noReferrer() : referrer,
referrerPolicyNoDefault);
}
void SecurityPolicy::addOriginTrustworthyWhiteList(
PassRefPtr<SecurityOrigin> origin) {
// Must be called before we start other threads.
ASSERT(WTF::isBeforeThreadCreated());
if (origin->isUnique())
return;
trustworthyOriginSet().add(origin->toRawString());
}
bool SecurityPolicy::isOriginWhiteListedTrustworthy(
const SecurityOrigin& origin) {
if (origin.isUnique())
return false;
return trustworthyOriginSet().contains(origin.toRawString());
}
bool SecurityPolicy::isAccessWhiteListed(const SecurityOrigin* activeOrigin,
const SecurityOrigin* targetOrigin) {
if (OriginAccessWhiteList* list =
originAccessMap().get(activeOrigin->toString())) {
for (size_t i = 0; i < list->size(); ++i) {
if (list->at(i).matchesOrigin(*targetOrigin) !=
OriginAccessEntry::DoesNotMatchOrigin)
return true;
}
}
return false;
}
bool SecurityPolicy::isAccessToURLWhiteListed(
const SecurityOrigin* activeOrigin,
const KURL& url) {
RefPtr<SecurityOrigin> targetOrigin = SecurityOrigin::create(url);
return isAccessWhiteListed(activeOrigin, targetOrigin.get());
}
void SecurityPolicy::addOriginAccessWhitelistEntry(
const SecurityOrigin& sourceOrigin,
const String& destinationProtocol,
const String& destinationDomain,
bool allowDestinationSubdomains) {
ASSERT(isMainThread());
ASSERT(!sourceOrigin.isUnique());
if (sourceOrigin.isUnique())
return;
String sourceString = sourceOrigin.toString();
OriginAccessMap::AddResult result =
originAccessMap().add(sourceString, nullptr);
if (result.isNewEntry)
result.storedValue->value = wrapUnique(new OriginAccessWhiteList);
OriginAccessWhiteList* list = result.storedValue->value.get();
list->append(OriginAccessEntry(destinationProtocol, destinationDomain,
allowDestinationSubdomains
? OriginAccessEntry::AllowSubdomains
: OriginAccessEntry::DisallowSubdomains));
}
void SecurityPolicy::removeOriginAccessWhitelistEntry(
const SecurityOrigin& sourceOrigin,
const String& destinationProtocol,
const String& destinationDomain,
bool allowDestinationSubdomains) {
ASSERT(isMainThread());
ASSERT(!sourceOrigin.isUnique());
if (sourceOrigin.isUnique())
return;
String sourceString = sourceOrigin.toString();
OriginAccessMap& map = originAccessMap();
OriginAccessMap::iterator it = map.find(sourceString);
if (it == map.end())
return;
OriginAccessWhiteList* list = it->value.get();
size_t index = list->find(OriginAccessEntry(
destinationProtocol, destinationDomain,
allowDestinationSubdomains ? OriginAccessEntry::AllowSubdomains
: OriginAccessEntry::DisallowSubdomains));
if (index == kNotFound)
return;
list->remove(index);
if (list->isEmpty())
map.remove(it);
}
void SecurityPolicy::resetOriginAccessWhitelists() {
ASSERT(isMainThread());
originAccessMap().clear();
}
bool SecurityPolicy::referrerPolicyFromString(const String& policy,
ReferrerPolicy* result) {
return referrerPolicyFromStringImpl(
policy, DoNotSupportReferrerPolicyLegacyKeywords, result);
}
bool SecurityPolicy::referrerPolicyFromStringWithLegacyKeywords(
const String& policy,
ReferrerPolicy* result) {
return referrerPolicyFromStringImpl(
policy, SupportReferrerPolicyLegacyKeywords, result);
}
} // namespace blink