Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "FeaturePolicy.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/dom/Feature.h"
#include "mozilla/dom/FeaturePolicyBinding.h"
#include "mozilla/dom/FeaturePolicyParser.h"
#include "mozilla/dom/FeaturePolicyUtils.h"
#include "mozilla/StaticPrefs_dom.h"
#include "nsContentUtils.h"
#include "nsNetUtil.h"
namespace mozilla::dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FeaturePolicy)
NS_IMPL_CYCLE_COLLECTING_ADDREF(FeaturePolicy)
NS_IMPL_CYCLE_COLLECTING_RELEASE(FeaturePolicy)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FeaturePolicy)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
FeaturePolicy::FeaturePolicy(nsINode* aNode) : mParentNode(aNode) {}
void FeaturePolicy::InheritPolicy(FeaturePolicy* aParentPolicy) {
MOZ_ASSERT(aParentPolicy);
mInheritedDeniedFeatureNames.Clear();
RefPtr<FeaturePolicy> dest = this;
RefPtr<FeaturePolicy> src = aParentPolicy;
// Inherit origins which explicitly declared policy in chain
for (const Feature& featureInChain :
aParentPolicy->mDeclaredFeaturesInAncestorChain) {
dest->AppendToDeclaredAllowInAncestorChain(featureInChain);
}
FeaturePolicyUtils::ForEachFeature([dest, src](const char* aFeatureName) {
nsString featureName;
featureName.AppendASCII(aFeatureName);
// Store unsafe allows all (allow=*)
if (src->HasFeatureUnsafeAllowsAll(featureName)) {
dest->mParentAllowedAllFeatures.AppendElement(featureName);
}
// If the destination has a declared feature (via the HTTP header or 'allow'
// attribute) we allow the feature if the destination allows it and the
// parent allows its origin or the destinations' one.
if (dest->HasDeclaredFeature(featureName) &&
dest->AllowsFeatureInternal(featureName, dest->mDefaultOrigin)) {
if (!src->AllowsFeatureInternal(featureName, src->mDefaultOrigin) &&
!src->AllowsFeatureInternal(featureName, dest->mDefaultOrigin)) {
dest->SetInheritedDeniedFeature(featureName);
}
return;
}
// If there was not a declared feature, we allow the feature if the parent
// FeaturePolicy allows the current origin.
if (!src->AllowsFeatureInternal(featureName, dest->mDefaultOrigin)) {
dest->SetInheritedDeniedFeature(featureName);
}
});
}
void FeaturePolicy::SetInheritedDeniedFeature(const nsAString& aFeatureName) {
MOZ_ASSERT(!HasInheritedDeniedFeature(aFeatureName));
mInheritedDeniedFeatureNames.AppendElement(aFeatureName);
}
bool FeaturePolicy::HasInheritedDeniedFeature(
const nsAString& aFeatureName) const {
return mInheritedDeniedFeatureNames.Contains(aFeatureName);
}
bool FeaturePolicy::HasDeclaredFeature(const nsAString& aFeatureName) const {
for (const Feature& feature : mFeatures) {
if (feature.Name().Equals(aFeatureName)) {
return true;
}
}
return false;
}
bool FeaturePolicy::HasFeatureUnsafeAllowsAll(
const nsAString& aFeatureName) const {
for (const Feature& feature : mFeatures) {
if (feature.AllowsAll() && feature.Name().Equals(aFeatureName)) {
return true;
}
}
// We should look into parent too (for example, document of iframe which
// allows all, would be unsafe)
return mParentAllowedAllFeatures.Contains(aFeatureName);
}
void FeaturePolicy::AppendToDeclaredAllowInAncestorChain(
const Feature& aFeature) {
for (Feature& featureInChain : mDeclaredFeaturesInAncestorChain) {
if (featureInChain.Name().Equals(aFeature.Name())) {
MOZ_ASSERT(featureInChain.HasAllowList());
nsTArray<nsCOMPtr<nsIPrincipal>> list;
aFeature.GetAllowList(list);
for (nsIPrincipal* principal : list) {
featureInChain.AppendToAllowList(principal);
}
continue;
}
}
mDeclaredFeaturesInAncestorChain.AppendElement(aFeature);
}
bool FeaturePolicy::IsSameOriginAsSrc(nsIPrincipal* aPrincipal) const {
MOZ_ASSERT(aPrincipal);
if (!mSrcOrigin) {
return false;
}
return BasePrincipal::Cast(mSrcOrigin)
->Subsumes(aPrincipal, BasePrincipal::ConsiderDocumentDomain);
}
void FeaturePolicy::SetDeclaredPolicy(Document* aDocument,
const nsAString& aPolicyString,
nsIPrincipal* aSelfOrigin,
nsIPrincipal* aSrcOrigin) {
ResetDeclaredPolicy();
mDeclaredString = aPolicyString;
mSelfOrigin = aSelfOrigin;
mSrcOrigin = aSrcOrigin;
Unused << NS_WARN_IF(!FeaturePolicyParser::ParseString(
aPolicyString, aDocument, aSelfOrigin, aSrcOrigin, mFeatures));
// Only store explicitly declared allowlist
for (const Feature& feature : mFeatures) {
if (feature.HasAllowList()) {
AppendToDeclaredAllowInAncestorChain(feature);
}
}
}
void FeaturePolicy::ResetDeclaredPolicy() {
mFeatures.Clear();
mDeclaredString.Truncate();
mSelfOrigin = nullptr;
mSrcOrigin = nullptr;
mDeclaredFeaturesInAncestorChain.Clear();
mAttributeEnabledFeatureNames.Clear();
}
JSObject* FeaturePolicy::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return FeaturePolicy_Binding::Wrap(aCx, this, aGivenProto);
}
bool FeaturePolicy::AllowsFeature(const nsAString& aFeatureName,
const Optional<nsAString>& aOrigin) const {
nsCOMPtr<nsIPrincipal> origin;
if (aOrigin.WasPassed()) {
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aOrigin.Value());
if (NS_FAILED(rv)) {
return false;
}
origin = BasePrincipal::CreateContentPrincipal(
uri, BasePrincipal::Cast(mDefaultOrigin)->OriginAttributesRef());
} else {
origin = mDefaultOrigin;
}
if (NS_WARN_IF(!origin)) {
return false;
}
return AllowsFeatureInternal(aFeatureName, origin);
}
bool FeaturePolicy::AllowsFeatureExplicitlyInAncestorChain(
const nsAString& aFeatureName, nsIPrincipal* aOrigin) const {
MOZ_ASSERT(aOrigin);
for (const Feature& feature : mDeclaredFeaturesInAncestorChain) {
if (feature.Name().Equals(aFeatureName)) {
return feature.AllowListContains(aOrigin);
}
}
return false;
}
bool FeaturePolicy::AllowsFeatureInternal(const nsAString& aFeatureName,
nsIPrincipal* aOrigin) const {
MOZ_ASSERT(aOrigin);
// Let's see if have to disable this feature because inherited policy.
if (HasInheritedDeniedFeature(aFeatureName)) {
return false;
}
for (const Feature& feature : mFeatures) {
if (feature.Name().Equals(aFeatureName)) {
return feature.Allows(aOrigin);
}
}
switch (FeaturePolicyUtils::DefaultAllowListFeature(aFeatureName)) {
case FeaturePolicyUtils::FeaturePolicyValue::eAll:
return true;
case FeaturePolicyUtils::FeaturePolicyValue::eSelf:
return BasePrincipal::Cast(mDefaultOrigin)
->Subsumes(aOrigin, BasePrincipal::ConsiderDocumentDomain);
case FeaturePolicyUtils::FeaturePolicyValue::eNone:
return false;
default:
MOZ_CRASH("Unknown default value");
}
return false;
}
void FeaturePolicy::Features(nsTArray<nsString>& aFeatures) {
RefPtr<FeaturePolicy> self = this;
FeaturePolicyUtils::ForEachFeature(
[self, &aFeatures](const char* aFeatureName) {
nsString featureName;
featureName.AppendASCII(aFeatureName);
aFeatures.AppendElement(featureName);
});
}
void FeaturePolicy::AllowedFeatures(nsTArray<nsString>& aAllowedFeatures) {
RefPtr<FeaturePolicy> self = this;
FeaturePolicyUtils::ForEachFeature(
[self, &aAllowedFeatures](const char* aFeatureName) {
nsString featureName;
featureName.AppendASCII(aFeatureName);
if (self->AllowsFeatureInternal(featureName, self->mDefaultOrigin)) {
aAllowedFeatures.AppendElement(featureName);
}
});
}
void FeaturePolicy::GetAllowlistForFeature(const nsAString& aFeatureName,
nsTArray<nsString>& aList) const {
if (!AllowsFeatureInternal(aFeatureName, mDefaultOrigin)) {
return;
}
for (const Feature& feature : mFeatures) {
if (feature.Name().Equals(aFeatureName)) {
if (feature.AllowsAll()) {
aList.AppendElement(u"*"_ns);
return;
}
nsTArray<nsCOMPtr<nsIPrincipal>> list;
feature.GetAllowList(list);
for (nsIPrincipal* principal : list) {
nsAutoCString originNoSuffix;
nsresult rv = principal->GetOriginNoSuffix(originNoSuffix);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
aList.AppendElement(NS_ConvertUTF8toUTF16(originNoSuffix));
}
return;
}
}
switch (FeaturePolicyUtils::DefaultAllowListFeature(aFeatureName)) {
case FeaturePolicyUtils::FeaturePolicyValue::eAll:
aList.AppendElement(u"*"_ns);
return;
case FeaturePolicyUtils::FeaturePolicyValue::eSelf: {
nsAutoCString originNoSuffix;
nsresult rv = mDefaultOrigin->GetOriginNoSuffix(originNoSuffix);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
aList.AppendElement(NS_ConvertUTF8toUTF16(originNoSuffix));
return;
}
case FeaturePolicyUtils::FeaturePolicyValue::eNone:
return;
default:
MOZ_CRASH("Unknown default value");
}
}
void FeaturePolicy::MaybeSetAllowedPolicy(const nsAString& aFeatureName) {
MOZ_ASSERT(FeaturePolicyUtils::IsSupportedFeature(aFeatureName) ||
FeaturePolicyUtils::IsExperimentalFeature(aFeatureName));
// Skip if feature is in experimental phase
if (!StaticPrefs::dom_security_featurePolicy_experimental_enabled() &&
FeaturePolicyUtils::IsExperimentalFeature(aFeatureName)) {
return;
}
if (HasDeclaredFeature(aFeatureName)) {
return;
}
Feature feature(aFeatureName);
feature.SetAllowsAll();
mFeatures.AppendElement(feature);
mAttributeEnabledFeatureNames.AppendElement(aFeatureName);
}
} // namespace mozilla::dom