/* 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 "PermissionsPolicyParser.h" #include "mozilla/BasePrincipal.h" #include "mozilla/dom/Feature.h" #include "mozilla/dom/PermissionsPolicyUtils.h" #include "mozilla/dom/PolicyTokenizer.h" #include "mozilla/net/SFV.h" #include "nsIScriptError.h" #include "nsIURI.h" #include "nsNetUtil.h" namespace mozilla::dom { namespace { void ReportToConsoleUnsupportedFeature(Document* aDocument, const nsString& aFeatureName) { if (!aDocument) { return; } AutoTArray params = {aFeatureName}; nsContentUtils::ReportToConsole( nsIScriptError::warningFlag, "Permissions Policy"_ns, aDocument, PropertiesFile::SECURITY_PROPERTIES, "PermissionsPolicyUnsupportedFeatureName", params); } void ReportToConsoleInvalidEmptyAllowValue(Document* aDocument, const nsString& aFeatureName) { if (!aDocument) { return; } AutoTArray params = {aFeatureName}; nsContentUtils::ReportToConsole( nsIScriptError::warningFlag, "Permissions Policy"_ns, aDocument, PropertiesFile::SECURITY_PROPERTIES, "PermissionsPolicyInvalidEmptyAllowValue", params); } void ReportToConsoleInvalidAllowValue(Document* aDocument, const nsString& aValue) { if (!aDocument) { return; } AutoTArray params = {aValue}; nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Permissions Policy"_ns, aDocument, PropertiesFile::SECURITY_PROPERTIES, "PermissionsPolicyInvalidAllowValue", params); } } // namespace /* static */ bool PermissionsPolicyParser::ParsePolicyFromAttribute( const nsAString& aPolicy, Document* aDocument, nsIPrincipal* aSelfOrigin, nsIPrincipal* aSrcOrigin, nsTArray& aParsedFeatures) { MOZ_ASSERT(aSelfOrigin); nsTArray> tokens; PolicyTokenizer::tokenizePolicy(aPolicy, tokens); nsTArray parsedFeatures; for (const nsTArray& featureTokens : tokens) { if (featureTokens.IsEmpty()) { continue; } if (!PermissionsPolicyUtils::IsSupportedFeature(featureTokens[0])) { ReportToConsoleUnsupportedFeature(aDocument, featureTokens[0]); continue; } Feature feature(featureTokens[0]); if (featureTokens.Length() == 1) { if (aSrcOrigin) { feature.AppendToAllowList(aSrcOrigin); } else { ReportToConsoleInvalidEmptyAllowValue(aDocument, featureTokens[0]); continue; } } else { // we gotta start at 1 here for (uint32_t i = 1; i < featureTokens.Length(); ++i) { const nsString& curVal = featureTokens[i]; if (curVal.LowerCaseEqualsASCII("'none'")) { feature.SetAllowsNone(); break; } if (curVal.EqualsLiteral("*")) { feature.SetAllowsAll(); break; } if (curVal.LowerCaseEqualsASCII("'self'")) { feature.AppendToAllowList(aSelfOrigin); continue; } if (aSrcOrigin && curVal.LowerCaseEqualsASCII("'src'")) { feature.AppendToAllowList(aSrcOrigin); continue; } nsCOMPtr uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), curVal); if (NS_FAILED(rv)) { ReportToConsoleInvalidAllowValue(aDocument, curVal); continue; } nsCOMPtr origin = BasePrincipal::CreateContentPrincipal( uri, BasePrincipal::Cast(aSelfOrigin)->OriginAttributesRef()); if (NS_WARN_IF(!origin)) { ReportToConsoleInvalidAllowValue(aDocument, curVal); continue; } feature.AppendToAllowList(origin); } } // No duplicate! bool found = false; for (const Feature& parsedFeature : parsedFeatures) { if (parsedFeature.Name() == feature.Name()) { found = true; break; } } if (!found) { parsedFeatures.AppendElement(feature); } } aParsedFeatures = std::move(parsedFeatures); return true; } // Temporary: this supports only exact origins. Bug 2068536 will replace it // with Permissions-Policy source-expression parsing. static bool AppendOriginToFeature(const nsACString& aValue, Document* aDocument, nsIPrincipal* aSelfOrigin, Feature& aFeature) { nsCOMPtr uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), aValue); if (NS_FAILED(rv)) { ReportToConsoleInvalidAllowValue(aDocument, NS_ConvertUTF8toUTF16(aValue)); return false; } nsCOMPtr origin = BasePrincipal::CreateContentPrincipal( uri, BasePrincipal::Cast(aSelfOrigin)->OriginAttributesRef()); if (NS_WARN_IF(!origin)) { ReportToConsoleInvalidAllowValue(aDocument, NS_ConvertUTF8toUTF16(aValue)); return false; } aFeature.AppendToAllowList(origin); return true; } /* static */ bool PermissionsPolicyParser::ParsePolicyFromHeader( const nsACString& aPolicy, Document* aDocument, nsIPrincipal* aSelfOrigin, nsTArray& aParsedFeatures) { MOZ_ASSERT(aSelfOrigin); // 1. treats a malformed structured header as an empty policy. // https://w3c.github.io/webappsec-permissions-policy/#algo-process-response-policy aParsedFeatures.Clear(); auto dictionary = net::SFV::ParseDict(aPolicy); if (!dictionary.IsValid()) { return false; } nsTArray keys; if (NS_FAILED(dictionary.GetKeys(keys))) { return false; } nsTArray parsedFeatures; for (const nsCString& key : keys) { nsString featureName = NS_ConvertUTF8toUTF16(key); if (!PermissionsPolicyUtils::IsSupportedFeature(featureName)) { ReportToConsoleUnsupportedFeature(aDocument, featureName); continue; } Feature feature(featureName); auto innerList = dictionary.GetInnerList(key); // 2 handle SFV lists, e.g. camera=(self) if (innerList.IsValid()) { for (size_t i = 0; i < innerList.Length(); ++i) { auto item = innerList.GetItemAt(i); // 2.1 handle tokens such as * and self nsAutoCString token; if (NS_SUCCEEDED(item.GetValue(token))) { if (token.EqualsLiteral("*")) { feature.SetAllowsAll(); break; } if (token.EqualsLiteral("self")) { feature.AppendToAllowList(aSelfOrigin); } continue; } // 2.2 Parse quoted allowlist entries including origins // TODO Bug 2068536: Support non-origin source expressions. nsAutoCString source; if (NS_SUCCEEDED(item.GetValue(source))) { AppendOriginToFeature(source, aDocument, aSelfOrigin, feature); } } parsedFeatures.AppendElement(std::move(feature)); continue; } // 3. handle SFV individual values, e.g. camera=self nsAutoCString value; bool validValue = false; if (NS_SUCCEEDED(dictionary.GetItem(key, value))) { if (value.EqualsLiteral("*")) { feature.SetAllowsAll(); validValue = true; } else if (value.EqualsLiteral("self")) { feature.AppendToAllowList(aSelfOrigin); validValue = true; } } else if (NS_SUCCEEDED( dictionary.GetItem(key, value))) { validValue = AppendOriginToFeature(value, aDocument, aSelfOrigin, feature); } if (validValue) { parsedFeatures.AppendElement(std::move(feature)); } } aParsedFeatures = std::move(parsedFeatures); return true; } } // namespace mozilla::dom