summaryrefslogtreecommitdiffstats
path: root/caps
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /caps
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'caps')
-rw-r--r--caps/BasePrincipal.cpp1625
-rw-r--r--caps/BasePrincipal.h472
-rw-r--r--caps/ContentPrincipal.cpp709
-rw-r--r--caps/ContentPrincipal.h94
-rw-r--r--caps/ContentPrincipalInfoHashKey.h56
-rw-r--r--caps/DomainPolicy.cpp222
-rw-r--r--caps/DomainPolicy.h64
-rw-r--r--caps/ExpandedPrincipal.cpp401
-rw-r--r--caps/ExpandedPrincipal.h105
-rw-r--r--caps/NullPrincipal.cpp333
-rw-r--r--caps/NullPrincipal.h127
-rw-r--r--caps/OriginAttributes.cpp494
-rw-r--r--caps/OriginAttributes.h289
-rw-r--r--caps/PrincipalHashKey.h58
-rw-r--r--caps/SystemPrincipal.cpp118
-rw-r--r--caps/SystemPrincipal.h83
-rw-r--r--caps/moz.build80
-rw-r--r--caps/nsIAddonPolicyService.idl102
-rw-r--r--caps/nsIDomainPolicy.idl76
-rw-r--r--caps/nsIPrincipal.idl767
-rw-r--r--caps/nsIScriptSecurityManager.idl335
-rw-r--r--caps/nsJSPrincipals.cpp369
-rw-r--r--caps/nsJSPrincipals.h82
-rw-r--r--caps/nsScriptSecurityManager.cpp1852
-rw-r--r--caps/nsScriptSecurityManager.h142
-rw-r--r--caps/tests/gtest/TestBackgroundThreadPrincipal.cpp94
-rw-r--r--caps/tests/gtest/TestNullPrincipalPrecursor.cpp56
-rw-r--r--caps/tests/gtest/TestOriginAttributes.cpp118
-rw-r--r--caps/tests/gtest/TestPrincipalAttributes.cpp39
-rw-r--r--caps/tests/gtest/TestPrincipalSerialization.cpp214
-rw-r--r--caps/tests/gtest/TestRedirectChainURITruncation.cpp231
-rw-r--r--caps/tests/gtest/moz.build18
-rw-r--r--caps/tests/mochitest/browser.ini2
-rw-r--r--caps/tests/mochitest/browser_aboutOrigin.js12
-rw-r--r--caps/tests/mochitest/browser_checkloaduri.js393
-rw-r--r--caps/tests/mochitest/chrome.ini12
-rw-r--r--caps/tests/mochitest/file_bug1367586-followon.html1
-rw-r--r--caps/tests/mochitest/file_bug1367586-redirect.sjs8
-rw-r--r--caps/tests/mochitest/file_bug1367586-target.html6
-rw-r--r--caps/tests/mochitest/file_data.txt1
-rw-r--r--caps/tests/mochitest/file_disableScript.html11
-rw-r--r--caps/tests/mochitest/mochitest.ini18
-rw-r--r--caps/tests/mochitest/resource_test_file.html2
-rw-r--r--caps/tests/mochitest/test_addonMayLoad.html95
-rw-r--r--caps/tests/mochitest/test_bug1367586.html50
-rw-r--r--caps/tests/mochitest/test_bug246699.html60
-rw-r--r--caps/tests/mochitest/test_bug292789.html121
-rw-r--r--caps/tests/mochitest/test_bug423375.html43
-rw-r--r--caps/tests/mochitest/test_bug470804.html41
-rw-r--r--caps/tests/mochitest/test_bug995943.xhtml111
-rw-r--r--caps/tests/mochitest/test_disableScript.xhtml330
-rw-r--r--caps/tests/mochitest/test_disallowInheritPrincipal.html58
-rw-r--r--caps/tests/unit/test_ipv6_host_literal.js38
-rw-r--r--caps/tests/unit/test_oa_partitionKey_pattern.js159
-rw-r--r--caps/tests/unit/test_origin.js323
-rw-r--r--caps/tests/unit/test_precursor_principal.js259
-rw-r--r--caps/tests/unit/test_site_origin.js184
-rw-r--r--caps/tests/unit/test_uri_escaping.js28
-rw-r--r--caps/tests/unit/xpcshell.ini11
59 files changed, 12202 insertions, 0 deletions
diff --git a/caps/BasePrincipal.cpp b/caps/BasePrincipal.cpp
new file mode 100644
index 0000000000..97924b23c5
--- /dev/null
+++ b/caps/BasePrincipal.cpp
@@ -0,0 +1,1625 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et 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 "mozilla/BasePrincipal.h"
+
+#include "nsDocShell.h"
+
+#include "ExpandedPrincipal.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+#include "nsIOService.h"
+#include "nsIURIWithSpecialOrigin.h"
+#include "nsScriptSecurityManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsAboutProtocolUtils.h"
+#include "ThirdPartyUtil.h"
+#include "mozilla/ContentPrincipal.h"
+#include "mozilla/ExtensionPolicyService.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/ChromeUtils.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/StorageUtils.h"
+#include "mozilla/dom/StorageUtils.h"
+#include "nsIURL.h"
+#include "nsEffectiveTLDService.h"
+#include "nsIURIMutator.h"
+#include "mozilla/StaticPrefs_permissions.h"
+#include "nsIURIMutator.h"
+#include "nsMixedContentBlocker.h"
+#include "prnetdb.h"
+#include "nsIURIFixup.h"
+#include "mozilla/dom/StorageUtils.h"
+#include "mozilla/StorageAccess.h"
+#include "nsPIDOMWindow.h"
+#include "nsIURIMutator.h"
+#include "mozilla/PermissionManager.h"
+
+#include "json/json.h"
+#include "nsSerializationHelper.h"
+
+namespace mozilla {
+
+const char* BasePrincipal::JSONEnumKeyStrings[4] = {
+ "0",
+ "1",
+ "2",
+ "3",
+};
+
+BasePrincipal::BasePrincipal(PrincipalKind aKind,
+ const nsACString& aOriginNoSuffix,
+ const OriginAttributes& aOriginAttributes)
+ : mOriginNoSuffix(NS_Atomize(aOriginNoSuffix)),
+ mOriginSuffix(aOriginAttributes.CreateSuffixAtom()),
+ mOriginAttributes(aOriginAttributes),
+ mKind(aKind),
+ mHasExplicitDomain(false) {}
+
+BasePrincipal::BasePrincipal(BasePrincipal* aOther,
+ const OriginAttributes& aOriginAttributes)
+ : mOriginNoSuffix(aOther->mOriginNoSuffix),
+ mOriginSuffix(aOriginAttributes.CreateSuffixAtom()),
+ mOriginAttributes(aOriginAttributes),
+ mKind(aOther->mKind),
+ mHasExplicitDomain(aOther->mHasExplicitDomain.load()) {}
+
+BasePrincipal::~BasePrincipal() = default;
+
+NS_IMETHODIMP
+BasePrincipal::GetOrigin(nsACString& aOrigin) {
+ nsresult rv = GetOriginNoSuffix(aOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString suffix;
+ rv = GetOriginSuffix(suffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aOrigin.Append(suffix);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetAsciiOrigin(nsACString& aOrigin) {
+ aOrigin.Truncate();
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return nsContentUtils::GetASCIIOrigin(prinURI, aOrigin);
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetHostPort(nsACString& aRes) {
+ aRes.Truncate();
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+ return prinURI->GetHostPort(aRes);
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetHost(nsACString& aRes) {
+ aRes.Truncate();
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+ return prinURI->GetHost(aRes);
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetOriginNoSuffix(nsACString& aOrigin) {
+ mOriginNoSuffix->ToUTF8String(aOrigin);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetSiteOrigin(nsACString& aSiteOrigin) {
+ nsresult rv = GetSiteOriginNoSuffix(aSiteOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString suffix;
+ rv = GetOriginSuffix(suffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aSiteOrigin.Append(suffix);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetSiteOriginNoSuffix(nsACString& aSiteOrigin) {
+ return GetOriginNoSuffix(aSiteOrigin);
+}
+
+// Returns the inner Json::value of the serialized principal
+// Example input and return values:
+// Null principal:
+// {"0":{"0":"moz-nullprincipal:{56cac540-864d-47e7-8e25-1614eab5155e}"}} ->
+// {"0":"moz-nullprincipal:{56cac540-864d-47e7-8e25-1614eab5155e}"}
+//
+// Content principal:
+// {"1":{"0":"https://mozilla.com"}} -> {"0":"https://mozilla.com"}
+//
+// Expanded principal:
+// {"2":{"0":"<base64principal1>,<base64principal2>"}} ->
+// {"0":"<base64principal1>,<base64principal2>"}
+//
+// System principal:
+// {"3":{}} -> {}
+// The aKey passed in also returns the corresponding PrincipalKind enum
+//
+// Warning: The Json::Value* pointer is into the aRoot object
+static const Json::Value* GetPrincipalObject(const Json::Value& aRoot,
+ int& aOutPrincipalKind) {
+ const Json::Value::Members members = aRoot.getMemberNames();
+ // We only support one top level key in the object
+ if (members.size() != 1) {
+ return nullptr;
+ }
+ // members[0] here is the "0", "1", "2", "3" principalKind
+ // that is the top level of the serialized JSON principal
+ const std::string stringPrincipalKind = members[0];
+
+ // Next we take the string value from the JSON
+ // and convert it into the int for the BasePrincipal::PrincipalKind enum
+
+ // Verify that the key is within the valid range
+ int principalKind = std::stoi(stringPrincipalKind);
+ MOZ_ASSERT(BasePrincipal::eNullPrincipal == 0,
+ "We need to rely on 0 being a bounds check for the first "
+ "principal kind.");
+ if (principalKind < 0 || principalKind > BasePrincipal::eKindMax) {
+ return nullptr;
+ }
+ MOZ_ASSERT(principalKind == BasePrincipal::eNullPrincipal ||
+ principalKind == BasePrincipal::eContentPrincipal ||
+ principalKind == BasePrincipal::eExpandedPrincipal ||
+ principalKind == BasePrincipal::eSystemPrincipal);
+ aOutPrincipalKind = principalKind;
+
+ if (!aRoot[stringPrincipalKind].isObject()) {
+ return nullptr;
+ }
+
+ // Return the inner value of the principal object
+ return &aRoot[stringPrincipalKind];
+}
+
+// Accepts the JSON inner object without the wrapping principalKind
+// (See GetPrincipalObject for the inner object response examples)
+// Creates an array of KeyVal objects that are all defined on the principal
+// Each principal type (null, content, expanded) has a KeyVal that stores the
+// fields of the JSON
+//
+// This simplifies deserializing elsewhere as we do the checking for presence
+// and string values here for the complete set of serializable keys that the
+// corresponding principal supports.
+//
+// The KeyVal object has the following fields:
+// - valueWasSerialized: is true if the deserialized JSON contained a string
+// value
+// - value: The string that was serialized for this key
+// - key: an SerializableKeys enum value specific to the principal.
+// For example content principal is an enum of: eURI, eDomain,
+// eSuffix, eCSP
+//
+//
+// Given an inner content principal:
+// {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"}
+// | | | |
+// ----------------------------- |
+// | | |
+// Key ----------------------
+// |
+// Value
+//
+// They Key "0" corresponds to ContentPrincipal::eURI
+// They Key "1" corresponds to ContentPrincipal::eSuffix
+template <typename T>
+static nsTArray<typename T::KeyVal> GetJSONKeys(const Json::Value* aInput) {
+ int size = T::eMax + 1;
+ nsTArray<typename T::KeyVal> fields;
+ for (int i = 0; i != size; i++) {
+ typename T::KeyVal* field = fields.AppendElement();
+ // field->valueWasSerialized returns if the field was found in the
+ // deserialized code. This simplifies the consumers from having to check
+ // length.
+ field->valueWasSerialized = false;
+ field->key = static_cast<typename T::SerializableKeys>(i);
+ const std::string key = std::to_string(field->key);
+ if (aInput->isMember(key)) {
+ const Json::Value& val = (*aInput)[key];
+ if (val.isString()) {
+ field->value.Append(nsDependentCString(val.asCString()));
+ field->valueWasSerialized = true;
+ }
+ }
+ }
+ return fields;
+}
+
+// Takes a JSON string and parses it turning it into a principal of the
+// corresponding type
+//
+// Given a content principal:
+//
+// inner JSON object
+// |
+// ---------------------------------------------------------
+// | |
+// {"1": {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"}}
+// | | | | |
+// | ----------------------------- |
+// | | | |
+// PrincipalKind | | |
+// | ----------------------------
+// SerializableKeys |
+// Value
+//
+// The string is first deserialized with jsoncpp to get the Json::Value of the
+// object. The inner JSON object is parsed with GetPrincipalObject which returns
+// a KeyVal array of the inner object's fields. PrincipalKind is returned by
+// GetPrincipalObject which is then used to decide which principal
+// implementation of FromProperties to call. The corresponding FromProperties
+// call takes the KeyVal fields and turns it into a principal.
+already_AddRefed<BasePrincipal> BasePrincipal::FromJSON(
+ const nsACString& aJSON) {
+ Json::Value root;
+ Json::CharReaderBuilder builder;
+ std::unique_ptr<Json::CharReader> const reader(builder.newCharReader());
+ bool parseSuccess =
+ reader->parse(aJSON.BeginReading(), aJSON.EndReading(), &root, nullptr);
+ if (!parseSuccess) {
+ MOZ_ASSERT(false,
+ "Unable to parse string as JSON to deserialize as a principal");
+ return nullptr;
+ }
+
+ return FromJSON(root);
+}
+
+// Checks if an ExpandedPrincipal is using the legacy format, where
+// sub-principals are Base64 encoded.
+//
+// Given a legacy expanded principal:
+//
+// *
+// {"2": {"0": "eyIxIjp7IjAiOiJodHRwczovL2EuY29tLyJ9fQ=="}}
+// | | |
+// | ---------- Value
+// | |
+// PrincipalKind |
+// |
+// SerializableKeys
+//
+// The value is a CSV list of Base64 encoded prinipcals. The new format for this
+// principal is:
+//
+// Subsumed principals
+// |
+// ------------------------------------
+// * | |
+// {"2": {"0": [{"1": {"0": https://mozilla.com"}}]}}
+// | | |
+// -------------- Value
+// |
+// PrincipalKind
+//
+// It is possible to tell these apart by checking the type of the property noted
+// in both diagrams with an asterisk. In the legacy format the type will be a
+// string and in the new format it will be an array.
+static bool IsLegacyFormat(const Json::Value& aValue) {
+ const auto& specs = std::to_string(ExpandedPrincipal::eSpecs);
+ return aValue.isMember(specs) && aValue[specs].isString();
+}
+
+/* static */
+already_AddRefed<BasePrincipal> BasePrincipal::FromJSON(
+ const Json::Value& aJSON) {
+ int principalKind = -1;
+ const Json::Value* value = GetPrincipalObject(aJSON, principalKind);
+ if (!value) {
+#ifdef DEBUG
+ fprintf(stderr, "Unexpected JSON principal %s\n",
+ aJSON.toStyledString().c_str());
+#endif
+ MOZ_ASSERT(false, "Unexpected JSON to deserialize as a principal");
+
+ return nullptr;
+ }
+ MOZ_ASSERT(principalKind != -1,
+ "PrincipalKind should always be >=0 by this point");
+
+ if (principalKind == eSystemPrincipal) {
+ RefPtr<BasePrincipal> principal =
+ BasePrincipal::Cast(nsContentUtils::GetSystemPrincipal());
+ return principal.forget();
+ }
+
+ if (principalKind == eNullPrincipal) {
+ nsTArray<NullPrincipal::KeyVal> res = GetJSONKeys<NullPrincipal>(value);
+ return NullPrincipal::FromProperties(res);
+ }
+
+ if (principalKind == eContentPrincipal) {
+ nsTArray<ContentPrincipal::KeyVal> res =
+ GetJSONKeys<ContentPrincipal>(value);
+ return ContentPrincipal::FromProperties(res);
+ }
+
+ if (principalKind == eExpandedPrincipal) {
+ // Check if expanded principals is stored in the new or the old format. See
+ // comment for `IsLegacyFormat`.
+ if (IsLegacyFormat(*value)) {
+ nsTArray<ExpandedPrincipal::KeyVal> res =
+ GetJSONKeys<ExpandedPrincipal>(value);
+ return ExpandedPrincipal::FromProperties(res);
+ }
+
+ return ExpandedPrincipal::FromProperties(*value);
+ }
+
+ MOZ_RELEASE_ASSERT(false, "Unexpected enum to deserialize as a principal");
+}
+
+nsresult BasePrincipal::PopulateJSONObject(Json::Value& aObject) {
+ return NS_OK;
+}
+
+// Returns a JSON representation of the principal.
+// Calling BasePrincipal::FromJSON will deserialize the JSON into
+// the corresponding principal type.
+nsresult BasePrincipal::ToJSON(nsACString& aJSON) {
+ MOZ_ASSERT(aJSON.IsEmpty(), "ToJSON only supports an empty result input");
+ aJSON.Truncate();
+
+ Json::Value root = Json::objectValue;
+ nsresult rv = ToJSON(root);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ static StaticAutoPtr<Json::StreamWriterBuilder> sJSONBuilderForPrincipals;
+ if (!sJSONBuilderForPrincipals) {
+ sJSONBuilderForPrincipals = new Json::StreamWriterBuilder();
+ (*sJSONBuilderForPrincipals)["indentation"] = "";
+ (*sJSONBuilderForPrincipals)["emitUTF8"] = true;
+ ClearOnShutdown(&sJSONBuilderForPrincipals);
+ }
+ std::string result = Json::writeString(*sJSONBuilderForPrincipals, root);
+ aJSON.Append(result);
+ if (aJSON.Length() == 0) {
+ MOZ_ASSERT(false, "JSON writer failed to output a principal serialization");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+nsresult BasePrincipal::ToJSON(Json::Value& aObject) {
+ static_assert(eKindMax < ArrayLength(JSONEnumKeyStrings));
+ nsresult rv = PopulateJSONObject(
+ (aObject[Json::StaticString(JSONEnumKeyStrings[Kind()])] =
+ Json::objectValue));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+bool BasePrincipal::FastSubsumesIgnoringFPD(
+ nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) {
+ MOZ_ASSERT(aOther);
+
+ if (Kind() == eContentPrincipal &&
+ !dom::ChromeUtils::IsOriginAttributesEqualIgnoringFPD(
+ mOriginAttributes, Cast(aOther)->mOriginAttributes)) {
+ return false;
+ }
+
+ return SubsumesInternal(aOther, aConsideration);
+}
+
+bool BasePrincipal::Subsumes(nsIPrincipal* aOther,
+ DocumentDomainConsideration aConsideration) {
+ MOZ_ASSERT(aOther);
+ MOZ_ASSERT_IF(Kind() == eContentPrincipal, mOriginSuffix);
+
+ // Expanded principals handle origin attributes for each of their
+ // sub-principals individually, null principals do only simple checks for
+ // pointer equality, and system principals are immune to origin attributes
+ // checks, so only do this check for content principals.
+ if (Kind() == eContentPrincipal &&
+ mOriginSuffix != Cast(aOther)->mOriginSuffix) {
+ return false;
+ }
+
+ return SubsumesInternal(aOther, aConsideration);
+}
+
+NS_IMETHODIMP
+BasePrincipal::Equals(nsIPrincipal* aOther, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aOther);
+
+ *aResult = FastEquals(aOther);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::EqualsForPermission(nsIPrincipal* aOther, bool aExactHost,
+ bool* aResult) {
+ *aResult = false;
+ NS_ENSURE_ARG_POINTER(aOther);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ auto* other = Cast(aOther);
+ if (Kind() != other->Kind()) {
+ // Principals of different kinds can't be equal.
+ return NS_OK;
+ }
+
+ if (Kind() == eSystemPrincipal) {
+ *aResult = this == other;
+ return NS_OK;
+ }
+
+ if (Kind() == eNullPrincipal) {
+ // We don't store permissions for NullPrincipals.
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(Kind() == eExpandedPrincipal || Kind() == eContentPrincipal);
+
+ // Certain origin attributes should not be used to isolate permissions.
+ // Create a stripped copy of both OA sets to compare.
+ mozilla::OriginAttributes ourAttrs = mOriginAttributes;
+ PermissionManager::MaybeStripOriginAttributes(false, ourAttrs);
+ mozilla::OriginAttributes theirAttrs = aOther->OriginAttributesRef();
+ PermissionManager::MaybeStripOriginAttributes(false, theirAttrs);
+
+ if (ourAttrs != theirAttrs) {
+ return NS_OK;
+ }
+
+ if (mOriginNoSuffix == other->mOriginNoSuffix) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ // If we are matching with an exact host, we're done now - the permissions
+ // don't match otherwise, we need to start comparing subdomains!
+ if (aExactHost) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> ourURI;
+ nsresult rv = GetURI(getter_AddRefs(ourURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Some principal types may indicate success, but still return nullptr for
+ // URI.
+ NS_ENSURE_TRUE(ourURI, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIURI> otherURI;
+ rv = other->GetURI(getter_AddRefs(otherURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(otherURI, NS_ERROR_FAILURE);
+
+ // Compare schemes
+ nsAutoCString otherScheme;
+ rv = otherURI->GetScheme(otherScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString ourScheme;
+ rv = ourURI->GetScheme(ourScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (otherScheme != ourScheme) {
+ return NS_OK;
+ }
+
+ // Compare ports
+ int32_t otherPort;
+ rv = otherURI->GetPort(&otherPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t ourPort;
+ rv = ourURI->GetPort(&ourPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (otherPort != ourPort) {
+ return NS_OK;
+ }
+
+ // Check if the host or any subdomain of their host matches.
+ nsAutoCString otherHost;
+ rv = otherURI->GetHost(otherHost);
+ if (NS_FAILED(rv) || otherHost.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsAutoCString ourHost;
+ rv = ourURI->GetHost(ourHost);
+ if (NS_FAILED(rv) || ourHost.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!tldService) {
+ NS_ERROR("Should have a tld service!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // This loop will not loop forever, as GetNextSubDomain will eventually fail
+ // with NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS.
+ while (otherHost != ourHost) {
+ rv = tldService->GetNextSubDomain(otherHost, otherHost);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+ return NS_OK;
+ }
+ return rv;
+ }
+ }
+
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::EqualsConsideringDomain(nsIPrincipal* aOther, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aOther);
+
+ *aResult = FastEqualsConsideringDomain(aOther);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::EqualsURI(nsIURI* aOtherURI, bool* aResult) {
+ *aResult = false;
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+ return prinURI->EqualsExceptRef(aOtherURI, aResult);
+}
+
+NS_IMETHODIMP
+BasePrincipal::Subsumes(nsIPrincipal* aOther, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aOther);
+
+ *aResult = FastSubsumes(aOther);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::SubsumesConsideringDomain(nsIPrincipal* aOther, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aOther);
+
+ *aResult = FastSubsumesConsideringDomain(aOther);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::SubsumesConsideringDomainIgnoringFPD(nsIPrincipal* aOther,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aOther);
+
+ *aResult = FastSubsumesConsideringDomainIgnoringFPD(aOther);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::CheckMayLoad(nsIURI* aURI, bool aAllowIfInheritsPrincipal) {
+ AssertIsOnMainThread();
+ return CheckMayLoadHelper(aURI, aAllowIfInheritsPrincipal, false, 0);
+}
+
+NS_IMETHODIMP
+BasePrincipal::CheckMayLoadWithReporting(nsIURI* aURI,
+ bool aAllowIfInheritsPrincipal,
+ uint64_t aInnerWindowID) {
+ AssertIsOnMainThread();
+ return CheckMayLoadHelper(aURI, aAllowIfInheritsPrincipal, true,
+ aInnerWindowID);
+}
+
+nsresult BasePrincipal::CheckMayLoadHelper(nsIURI* aURI,
+ bool aAllowIfInheritsPrincipal,
+ bool aReport,
+ uint64_t aInnerWindowID) {
+ AssertIsOnMainThread(); // Accesses non-threadsafe URI flags and the
+ // non-threadsafe ExtensionPolicyService
+ NS_ENSURE_ARG_POINTER(aURI);
+ MOZ_ASSERT(
+ aReport || aInnerWindowID == 0,
+ "Why do we have an inner window id if we're not supposed to report?");
+
+ // Check the internal method first, which allows us to quickly approve loads
+ // for the System Principal.
+ if (MayLoadInternal(aURI)) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ if (aAllowIfInheritsPrincipal) {
+ // If the caller specified to allow loads of URIs that inherit
+ // our principal, allow the load if this URI inherits its principal.
+ bool doesInheritSecurityContext;
+ rv = NS_URIChainHasFlags(aURI,
+ nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
+ &doesInheritSecurityContext);
+ if (NS_SUCCEEDED(rv) && doesInheritSecurityContext) {
+ return NS_OK;
+ }
+ }
+
+ // Web Accessible Resources in MV2 Extensions are marked with
+ // URI_FETCHABLE_BY_ANYONE
+ bool fetchableByAnyone;
+ rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FETCHABLE_BY_ANYONE,
+ &fetchableByAnyone);
+ if (NS_SUCCEEDED(rv) && fetchableByAnyone) {
+ return NS_OK;
+ }
+
+ // Get the principal uri for the last flag check or error.
+ nsCOMPtr<nsIURI> prinURI;
+ rv = GetURI(getter_AddRefs(prinURI));
+ if (!(NS_SUCCEEDED(rv) && prinURI)) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // If MV3 Extension uris are web accessible by this principal it is allowed to
+ // load.
+ bool maybeWebAccessible = false;
+ NS_URIChainHasFlags(aURI, nsIProtocolHandler::WEBEXT_URI_WEB_ACCESSIBLE,
+ &maybeWebAccessible);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (maybeWebAccessible) {
+ bool isWebAccessible = false;
+ rv = ExtensionPolicyService::GetSingleton().SourceMayLoadExtensionURI(
+ prinURI, aURI, &isWebAccessible);
+ if (NS_SUCCEEDED(rv) && isWebAccessible) {
+ return NS_OK;
+ }
+ }
+
+ if (aReport) {
+ nsScriptSecurityManager::ReportError(
+ "CheckSameOriginError", prinURI, aURI,
+ mOriginAttributes.mPrivateBrowsingId > 0, aInnerWindowID);
+ }
+
+ return NS_ERROR_DOM_BAD_URI;
+}
+
+NS_IMETHODIMP
+BasePrincipal::IsThirdPartyURI(nsIURI* aURI, bool* aRes) {
+ if (IsSystemPrincipal() || (AddonPolicyCore() && AddonAllowsLoad(aURI))) {
+ *aRes = false;
+ return NS_OK;
+ }
+
+ *aRes = true;
+ // If we do not have a URI its always 3rd party.
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+ ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+ return thirdPartyUtil->IsThirdPartyURI(prinURI, aURI, aRes);
+}
+
+NS_IMETHODIMP
+BasePrincipal::IsThirdPartyPrincipal(nsIPrincipal* aPrin, bool* aRes) {
+ *aRes = true;
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+ return aPrin->IsThirdPartyURI(prinURI, aRes);
+}
+
+NS_IMETHODIMP
+BasePrincipal::IsThirdPartyChannel(nsIChannel* aChan, bool* aRes) {
+ AssertIsOnMainThread();
+ if (IsSystemPrincipal()) {
+ // Nothing is 3rd party to the system principal.
+ *aRes = false;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> prinURI;
+ GetURI(getter_AddRefs(prinURI));
+ ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+ return thirdPartyUtil->IsThirdPartyChannel(aChan, prinURI, aRes);
+}
+
+NS_IMETHODIMP
+BasePrincipal::IsSameOrigin(nsIURI* aURI, bool* aRes) {
+ *aRes = false;
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ // Note that expanded and system principals return here, because they have
+ // no URI.
+ return NS_OK;
+ }
+ *aRes = nsScriptSecurityManager::SecurityCompareURIs(prinURI, aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::IsL10nAllowed(nsIURI* aURI, bool* aRes) {
+ AssertIsOnMainThread(); // URI_DANGEROUS_TO_LOAD is not threadsafe to query.
+ *aRes = false;
+
+ if (nsContentUtils::IsErrorPage(aURI)) {
+ *aRes = true;
+ return NS_OK;
+ }
+
+ // The system principal is always allowed.
+ if (IsSystemPrincipal()) {
+ *aRes = true;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ bool hasFlags;
+
+ // Allow access to uris that cannot be loaded by web content.
+ rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
+ &hasFlags);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ if (hasFlags) {
+ *aRes = true;
+ return NS_OK;
+ }
+
+ // UI resources also get access.
+ rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
+ &hasFlags);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ if (hasFlags) {
+ *aRes = true;
+ return NS_OK;
+ }
+
+ auto policy = AddonPolicyCore();
+ *aRes = (policy && policy->IsPrivileged());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::AllowsRelaxStrictFileOriginPolicy(nsIURI* aURI, bool* aRes) {
+ *aRes = false;
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+ *aRes = NS_RelaxStrictFileOriginPolicy(aURI, prinURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetPrefLightCacheKey(nsIURI* aURI, bool aWithCredentials,
+ const OriginAttributes& aOriginAttributes,
+ nsACString& _retval) {
+ _retval.Truncate();
+ constexpr auto space = " "_ns;
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString scheme, host, port;
+ if (uri) {
+ uri->GetScheme(scheme);
+ uri->GetHost(host);
+ port.AppendInt(NS_GetRealPort(uri));
+ }
+
+ if (aWithCredentials) {
+ _retval.AssignLiteral("cred");
+ } else {
+ _retval.AssignLiteral("nocred");
+ }
+
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString originAttributesSuffix;
+ aOriginAttributes.CreateSuffix(originAttributesSuffix);
+
+ _retval.Append(space + scheme + space + host + space + port + space + spec +
+ space + originAttributesSuffix);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::HasFirstpartyStorageAccess(mozIDOMWindow* aCheckWindow,
+ uint32_t* aRejectedReason,
+ bool* aOutAllowed) {
+ AssertIsOnMainThread();
+ *aRejectedReason = 0;
+ *aOutAllowed = false;
+
+ nsPIDOMWindowInner* win = nsPIDOMWindowInner::From(aCheckWindow);
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aOutAllowed = ShouldAllowAccessFor(win, uri, aRejectedReason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetIsNullPrincipal(bool* aResult) {
+ *aResult = Kind() == eNullPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetIsContentPrincipal(bool* aResult) {
+ *aResult = Kind() == eContentPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetIsExpandedPrincipal(bool* aResult) {
+ *aResult = Kind() == eExpandedPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetAsciiSpec(nsACString& aSpec) {
+ aSpec.Truncate();
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+ return prinURI->GetAsciiSpec(aSpec);
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetSpec(nsACString& aSpec) {
+ aSpec.Truncate();
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+ return prinURI->GetSpec(aSpec);
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetAsciiHost(nsACString& aHost) {
+ aHost.Truncate();
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+ return prinURI->GetAsciiHost(aHost);
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetExposablePrePath(nsACString& aPrepath) {
+ aPrepath.Truncate();
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> exposableURI = net::nsIOService::CreateExposableURI(prinURI);
+ return exposableURI->GetDisplayPrePath(aPrepath);
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetExposableSpec(nsACString& aSpec) {
+ aSpec.Truncate();
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIURI> clone;
+ rv = NS_MutateURI(prinURI)
+ .SetQuery(""_ns)
+ .SetRef(""_ns)
+ .SetUserPass(""_ns)
+ .Finalize(clone);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return clone->GetAsciiSpec(aSpec);
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetPrePath(nsACString& aPath) {
+ aPath.Truncate();
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+ return prinURI->GetPrePath(aPath);
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetFilePath(nsACString& aPath) {
+ aPath.Truncate();
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+ return prinURI->GetFilePath(aPath);
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetIsSystemPrincipal(bool* aResult) {
+ *aResult = IsSystemPrincipal();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetIsAddonOrExpandedAddonPrincipal(bool* aResult) {
+ *aResult = AddonPolicyCore() || ContentScriptAddonPolicyCore();
+ return NS_OK;
+}
+
+NS_IMETHODIMP BasePrincipal::GetIsOnion(bool* aIsOnion) {
+ *aIsOnion = false;
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+
+ nsAutoCString host;
+ rv = prinURI->GetHost(host);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+ *aIsOnion = StringEndsWith(host, ".onion"_ns);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BasePrincipal::GetIsIpAddress(bool* aIsIpAddress) {
+ *aIsIpAddress = false;
+
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+
+ nsAutoCString host;
+ rv = prinURI->GetHost(host);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ PRNetAddr prAddr;
+ memset(&prAddr, 0, sizeof(prAddr));
+
+ if (PR_StringToNetAddr(host.get(), &prAddr) == PR_SUCCESS) {
+ *aIsIpAddress = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP BasePrincipal::GetIsLocalIpAddress(bool* aIsIpAddress) {
+ *aIsIpAddress = false;
+
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_FAILED(rv) || !ioService) {
+ return NS_OK;
+ }
+ rv = ioService->HostnameIsLocalIPAddress(prinURI, aIsIpAddress);
+ if (NS_FAILED(rv)) {
+ *aIsIpAddress = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetScheme(nsACString& aScheme) {
+ aScheme.Truncate();
+
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+
+ return prinURI->GetScheme(aScheme);
+}
+
+NS_IMETHODIMP
+BasePrincipal::SchemeIs(const char* aScheme, bool* aResult) {
+ *aResult = false;
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_WARN_IF(NS_FAILED(rv)) || !prinURI) {
+ return NS_OK;
+ }
+ *aResult = prinURI->SchemeIs(aScheme);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::IsURIInPrefList(const char* aPref, bool* aResult) {
+ AssertIsOnMainThread();
+ *aResult = false;
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+ *aResult = nsContentUtils::IsURIInPrefList(prinURI, aPref);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::IsURIInList(const nsACString& aList, bool* aResult) {
+ *aResult = false;
+ nsCOMPtr<nsIURI> prinURI;
+
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+
+ *aResult = nsContentUtils::IsURIInList(prinURI, nsCString(aList));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::IsContentAccessibleAboutURI(bool* aResult) {
+ *aResult = false;
+
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+
+ if (!prinURI->SchemeIs("about")) {
+ return NS_OK;
+ }
+
+ *aResult = NS_IsContentAccessibleAboutURI(prinURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetIsOriginPotentiallyTrustworthy(bool* aResult) {
+ AssertIsOnMainThread();
+ *aResult = false;
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv) || !uri) {
+ return NS_OK;
+ }
+
+ *aResult = nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetIsLoopbackHost(bool* aRes) {
+ AssertIsOnMainThread();
+ *aRes = false;
+ nsAutoCString host;
+ nsresult rv = GetHost(host);
+ // Swallow potential failure as this method is infallible.
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ *aRes = nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(host);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetAboutModuleFlags(uint32_t* flags) {
+ AssertIsOnMainThread();
+ *flags = 0;
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (!prinURI->SchemeIs("about")) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAboutModule> aboutModule;
+ rv = NS_GetAboutModule(prinURI, getter_AddRefs(aboutModule));
+ if (NS_FAILED(rv) || !aboutModule) {
+ return rv;
+ }
+ return aboutModule->GetURIFlags(prinURI, flags);
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aVal) {
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetOriginSuffix(nsACString& aOriginAttributes) {
+ MOZ_ASSERT(mOriginSuffix);
+ mOriginSuffix->ToUTF8String(aOriginAttributes);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetUserContextId(uint32_t* aUserContextId) {
+ *aUserContextId = UserContextId();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) {
+ *aPrivateBrowsingId = PrivateBrowsingId();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetIsInIsolatedMozBrowserElement(
+ bool* aIsInIsolatedMozBrowserElement) {
+ *aIsInIsolatedMozBrowserElement = IsInIsolatedMozBrowserElement();
+ return NS_OK;
+}
+
+nsresult BasePrincipal::GetAddonPolicy(
+ extensions::WebExtensionPolicy** aResult) {
+ AssertIsOnMainThread();
+ RefPtr<extensions::WebExtensionPolicy> policy(AddonPolicy());
+ policy.forget(aResult);
+ return NS_OK;
+}
+
+nsresult BasePrincipal::GetContentScriptAddonPolicy(
+ extensions::WebExtensionPolicy** aResult) {
+ RefPtr<extensions::WebExtensionPolicy> policy(ContentScriptAddonPolicy());
+ policy.forget(aResult);
+ return NS_OK;
+}
+
+extensions::WebExtensionPolicy* BasePrincipal::AddonPolicy() {
+ AssertIsOnMainThread();
+ RefPtr<extensions::WebExtensionPolicyCore> core = AddonPolicyCore();
+ return core ? core->GetMainThreadPolicy() : nullptr;
+}
+
+RefPtr<extensions::WebExtensionPolicyCore> BasePrincipal::AddonPolicyCore() {
+ if (Is<ContentPrincipal>()) {
+ return As<ContentPrincipal>()->AddonPolicyCore();
+ }
+ return nullptr;
+}
+
+bool BasePrincipal::AddonHasPermission(const nsAtom* aPerm) {
+ if (auto policy = AddonPolicyCore()) {
+ return policy->HasPermission(aPerm);
+ }
+ return false;
+}
+
+nsIPrincipal* BasePrincipal::PrincipalToInherit(nsIURI* aRequestedURI) {
+ if (Is<ExpandedPrincipal>()) {
+ return As<ExpandedPrincipal>()->PrincipalToInherit(aRequestedURI);
+ }
+ return this;
+}
+
+bool BasePrincipal::OverridesCSP(nsIPrincipal* aDocumentPrincipal) {
+ MOZ_ASSERT(aDocumentPrincipal);
+
+ // Expanded principals override CSP if and only if they subsume the document
+ // principal.
+ if (mKind == eExpandedPrincipal) {
+ return FastSubsumes(aDocumentPrincipal);
+ }
+ // Extension principals always override the CSP of non-extension principals.
+ // This is primarily for the sake of their stylesheets, which are usually
+ // loaded from channels and cannot have expanded principals.
+ return (AddonPolicyCore() &&
+ !BasePrincipal::Cast(aDocumentPrincipal)->AddonPolicyCore());
+}
+
+already_AddRefed<BasePrincipal> BasePrincipal::CreateContentPrincipal(
+ nsIURI* aURI, const OriginAttributes& aAttrs, nsIURI* aInitialDomain) {
+ MOZ_ASSERT(aURI);
+
+ nsAutoCString originNoSuffix;
+ nsresult rv =
+ ContentPrincipal::GenerateOriginNoSuffixFromURI(aURI, originNoSuffix);
+ if (NS_FAILED(rv)) {
+ // If the generation of the origin fails, we still want to have a valid
+ // principal. Better to return a null principal here.
+ return NullPrincipal::Create(aAttrs);
+ }
+
+ return CreateContentPrincipal(aURI, aAttrs, originNoSuffix, aInitialDomain);
+}
+
+already_AddRefed<BasePrincipal> BasePrincipal::CreateContentPrincipal(
+ nsIURI* aURI, const OriginAttributes& aAttrs,
+ const nsACString& aOriginNoSuffix, nsIURI* aInitialDomain) {
+ MOZ_ASSERT(aURI);
+ MOZ_ASSERT(!aOriginNoSuffix.IsEmpty());
+
+ // If the URI is supposed to inherit the security context of whoever loads it,
+ // we shouldn't make a content principal for it.
+ bool inheritsPrincipal;
+ nsresult rv = NS_URIChainHasFlags(
+ aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
+ &inheritsPrincipal);
+ if (NS_FAILED(rv) || inheritsPrincipal) {
+ return NullPrincipal::Create(aAttrs);
+ }
+
+ // Check whether the URI knows what its principal is supposed to be.
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+ nsCOMPtr<nsIURIWithSpecialOrigin> uriWithSpecialOrigin =
+ do_QueryInterface(aURI);
+ if (uriWithSpecialOrigin) {
+ nsCOMPtr<nsIURI> origin;
+ rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ MOZ_ASSERT(origin);
+ OriginAttributes attrs;
+ RefPtr<BasePrincipal> principal =
+ CreateContentPrincipal(origin, attrs, aInitialDomain);
+ return principal.forget();
+ }
+#endif
+
+ nsCOMPtr<nsIPrincipal> blobPrincipal;
+ if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal(
+ aURI, getter_AddRefs(blobPrincipal))) {
+ MOZ_ASSERT(blobPrincipal);
+ MOZ_ASSERT(!aInitialDomain,
+ "an initial domain for a blob URI makes no sense");
+ RefPtr<BasePrincipal> principal = Cast(blobPrincipal);
+ return principal.forget();
+ }
+
+ // Mint a content principal.
+ RefPtr<ContentPrincipal> principal =
+ new ContentPrincipal(aURI, aAttrs, aOriginNoSuffix, aInitialDomain);
+ return principal.forget();
+}
+
+already_AddRefed<BasePrincipal> BasePrincipal::CreateContentPrincipal(
+ const nsACString& aOrigin) {
+ MOZ_ASSERT(!StringBeginsWith(aOrigin, "["_ns),
+ "CreateContentPrincipal does not support System and Expanded "
+ "principals");
+
+ MOZ_ASSERT(
+ !StringBeginsWith(aOrigin, nsLiteralCString(NS_NULLPRINCIPAL_SCHEME ":")),
+ "CreateContentPrincipal does not support NullPrincipal");
+
+ nsAutoCString originNoSuffix;
+ OriginAttributes attrs;
+ if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return BasePrincipal::CreateContentPrincipal(uri, attrs);
+}
+
+already_AddRefed<BasePrincipal> BasePrincipal::CloneForcingOriginAttributes(
+ const OriginAttributes& aOriginAttributes) {
+ if (NS_WARN_IF(!IsContentPrincipal())) {
+ return nullptr;
+ }
+
+ nsAutoCString originNoSuffix;
+ nsresult rv = GetOriginNoSuffix(originNoSuffix);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(GetURI(getter_AddRefs(uri)));
+
+ // XXX: This does not copy over the domain. Should it?
+ RefPtr<ContentPrincipal> copy =
+ new ContentPrincipal(uri, aOriginAttributes, originNoSuffix, nullptr);
+ return copy.forget();
+}
+
+extensions::WebExtensionPolicy* BasePrincipal::ContentScriptAddonPolicy() {
+ AssertIsOnMainThread();
+ RefPtr<extensions::WebExtensionPolicyCore> core =
+ ContentScriptAddonPolicyCore();
+ return core ? core->GetMainThreadPolicy() : nullptr;
+}
+
+RefPtr<extensions::WebExtensionPolicyCore>
+BasePrincipal::ContentScriptAddonPolicyCore() {
+ if (!Is<ExpandedPrincipal>()) {
+ return nullptr;
+ }
+
+ auto* expanded = As<ExpandedPrincipal>();
+ for (const auto& prin : expanded->AllowList()) {
+ if (RefPtr<extensions::WebExtensionPolicyCore> policy =
+ BasePrincipal::Cast(prin)->AddonPolicyCore()) {
+ return policy;
+ }
+ }
+
+ return nullptr;
+}
+
+bool BasePrincipal::AddonAllowsLoad(nsIURI* aURI,
+ bool aExplicit /* = false */) {
+ if (Is<ExpandedPrincipal>()) {
+ return As<ExpandedPrincipal>()->AddonAllowsLoad(aURI, aExplicit);
+ }
+ if (auto policy = AddonPolicyCore()) {
+ return policy->CanAccessURI(aURI, aExplicit);
+ }
+ return false;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetLocalStorageQuotaKey(nsACString& aKey) {
+ aKey.Truncate();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
+
+ // The special handling of the file scheme should be consistent with
+ // GetStorageOriginKey.
+
+ nsAutoCString baseDomain;
+ rv = uri->GetAsciiHost(baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (baseDomain.IsEmpty() && uri->SchemeIs("file")) {
+ nsCOMPtr<nsIURL> url = do_QueryInterface(uri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->GetDirectory(baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsCOMPtr<nsIEffectiveTLDService> eTLDService(
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString eTLDplusOne;
+ rv = eTLDService->GetBaseDomain(uri, 0, eTLDplusOne);
+ if (NS_SUCCEEDED(rv)) {
+ baseDomain = eTLDplusOne;
+ } else if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
+ rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+ rv = NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ OriginAttributesRef().CreateSuffix(aKey);
+
+ nsAutoCString subdomainsDBKey;
+ rv = dom::StorageUtils::CreateReversedDomain(baseDomain, subdomainsDBKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aKey.Append(':');
+ aKey.Append(subdomainsDBKey);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetNextSubDomainPrincipal(
+ nsIPrincipal** aNextSubDomainPrincipal) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv) || !uri) {
+ return NS_OK;
+ }
+
+ nsAutoCString host;
+ rv = uri->GetHost(host);
+ if (NS_FAILED(rv) || host.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsCString subDomain;
+ rv = nsEffectiveTLDService::GetInstance()->GetNextSubDomain(host, subDomain);
+
+ if (NS_FAILED(rv) || subDomain.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> subDomainURI;
+ rv = NS_MutateURI(uri).SetHost(subDomain).Finalize(subDomainURI);
+ if (NS_FAILED(rv) || !subDomainURI) {
+ return NS_OK;
+ }
+ // Copy the attributes over
+ mozilla::OriginAttributes attrs = OriginAttributesRef();
+
+ if (!StaticPrefs::permissions_isolateBy_userContext()) {
+ // Disable userContext for permissions.
+ attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID);
+ }
+ RefPtr<nsIPrincipal> principal =
+ mozilla::BasePrincipal::CreateContentPrincipal(subDomainURI, attrs);
+
+ if (!principal) {
+ return NS_OK;
+ }
+ principal.forget(aNextSubDomainPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetStorageOriginKey(nsACString& aOriginKey) {
+ aOriginKey.Truncate();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
+
+ // The special handling of the file scheme should be consistent with
+ // GetLocalStorageQuotaKey.
+
+ nsAutoCString domainOrigin;
+ rv = uri->GetAsciiHost(domainOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (domainOrigin.IsEmpty()) {
+ // For the file:/// protocol use the exact directory as domain.
+ if (uri->SchemeIs("file")) {
+ nsCOMPtr<nsIURL> url = do_QueryInterface(uri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = url->GetDirectory(domainOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // Append reversed domain
+ nsAutoCString reverseDomain;
+ rv = dom::StorageUtils::CreateReversedDomain(domainOrigin, reverseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aOriginKey.Append(reverseDomain);
+
+ // Append scheme
+ nsAutoCString scheme;
+ rv = uri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aOriginKey.Append(':');
+ aOriginKey.Append(scheme);
+
+ // Append port if any
+ int32_t port = NS_GetRealPort(uri);
+ if (port != -1) {
+ aOriginKey.Append(nsPrintfCString(":%d", port));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetIsScriptAllowedByPolicy(bool* aIsScriptAllowedByPolicy) {
+ AssertIsOnMainThread();
+ *aIsScriptAllowedByPolicy = false;
+ nsCOMPtr<nsIURI> prinURI;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ return NS_OK;
+ }
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ if (!ssm) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return ssm->PolicyAllowsScript(prinURI, aIsScriptAllowedByPolicy);
+}
+
+bool SiteIdentifier::Equals(const SiteIdentifier& aOther) const {
+ MOZ_ASSERT(IsInitialized());
+ MOZ_ASSERT(aOther.IsInitialized());
+ return mPrincipal->FastEquals(aOther.mPrincipal);
+}
+
+NS_IMETHODIMP
+BasePrincipal::CreateReferrerInfo(mozilla::dom::ReferrerPolicy aReferrerPolicy,
+ nsIReferrerInfo** _retval) {
+ nsCOMPtr<nsIURI> prinURI;
+ RefPtr<dom::ReferrerInfo> info;
+ nsresult rv = GetURI(getter_AddRefs(prinURI));
+ if (NS_FAILED(rv) || !prinURI) {
+ info = new dom::ReferrerInfo(nullptr);
+ info.forget(_retval);
+ return NS_OK;
+ }
+ info = new dom::ReferrerInfo(prinURI, aReferrerPolicy);
+ info.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetPrecursorPrincipal(nsIPrincipal** aPrecursor) {
+ *aPrecursor = nullptr;
+ return NS_OK;
+}
+
+NS_IMPL_ADDREF(BasePrincipal::Deserializer)
+NS_IMPL_RELEASE(BasePrincipal::Deserializer)
+
+NS_INTERFACE_MAP_BEGIN(BasePrincipal::Deserializer)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsISerializable)
+ if (mPrincipal) {
+ return mPrincipal->QueryInterface(aIID, aInstancePtr);
+ } else
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+BasePrincipal::Deserializer::Write(nsIObjectOutputStream* aStream) {
+ // Read is used still for legacy principals
+ MOZ_RELEASE_ASSERT(false, "Old style serialization is removed");
+ return NS_OK;
+}
+
+/* static */
+void BasePrincipal::SetJSONValue(Json::Value& aObject, const char* aKey,
+ const nsCString& aValue) {
+ aObject[Json::StaticString(aKey)] =
+ Json::Value(aValue.BeginReading(), aValue.EndReading());
+}
+
+} // namespace mozilla
diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h
new file mode 100644
index 0000000000..8b4a9ffee0
--- /dev/null
+++ b/caps/BasePrincipal.h
@@ -0,0 +1,472 @@
+/* -*- 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/. */
+
+#ifndef mozilla_BasePrincipal_h
+#define mozilla_BasePrincipal_h
+
+#include <stdint.h>
+#include "ErrorList.h"
+#include "js/TypeDecls.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/RefPtr.h"
+#include "nsAtom.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIPrincipal.h"
+#include "nsJSPrincipals.h"
+#include "nsStringFwd.h"
+#include "nscore.h"
+
+class ExpandedPrincipal;
+class mozIDOMWindow;
+class nsIChannel;
+class nsIReferrerInfo;
+class nsISupports;
+class nsIURI;
+namespace Json {
+class Value;
+}
+
+namespace mozilla {
+
+namespace dom {
+enum class ReferrerPolicy : uint8_t;
+}
+
+namespace extensions {
+class WebExtensionPolicy;
+class WebExtensionPolicyCore;
+} // namespace extensions
+
+class BasePrincipal;
+
+// Content principals (and content principals embedded within expanded
+// principals) stored in SiteIdentifier are guaranteed to contain only the
+// eTLD+1 part of the original domain. This is used to determine whether two
+// origins are same-site: if it's possible for two origins to access each other
+// (maybe after mutating document.domain), then they must have the same site
+// identifier.
+class SiteIdentifier {
+ public:
+ void Init(BasePrincipal* aPrincipal) {
+ MOZ_ASSERT(aPrincipal);
+ mPrincipal = aPrincipal;
+ }
+
+ bool IsInitialized() const { return !!mPrincipal; }
+
+ bool Equals(const SiteIdentifier& aOther) const;
+
+ private:
+ friend class ::ExpandedPrincipal;
+
+ BasePrincipal* GetPrincipal() const {
+ MOZ_ASSERT(IsInitialized());
+ return mPrincipal;
+ }
+
+ RefPtr<BasePrincipal> mPrincipal;
+};
+
+/*
+ * Base class from which all nsIPrincipal implementations inherit. Use this for
+ * default implementations and other commonalities between principal
+ * implementations.
+ *
+ * We should merge nsJSPrincipals into this class at some point.
+ */
+class BasePrincipal : public nsJSPrincipals {
+ public:
+ // Warning: this enum impacts Principal serialization into JSON format.
+ // Only update if you know exactly what you are doing
+ enum PrincipalKind {
+ eNullPrincipal = 0,
+ eContentPrincipal,
+ eExpandedPrincipal,
+ eSystemPrincipal,
+ eKindMax = eSystemPrincipal
+ };
+
+ template <typename T>
+ bool Is() const {
+ return mKind == T::Kind();
+ }
+
+ template <typename T>
+ T* As() {
+ MOZ_ASSERT(Is<T>());
+ return static_cast<T*>(this);
+ }
+
+ enum DocumentDomainConsideration {
+ DontConsiderDocumentDomain,
+ ConsiderDocumentDomain
+ };
+ bool Subsumes(nsIPrincipal* aOther,
+ DocumentDomainConsideration aConsideration);
+
+ NS_IMETHOD GetOrigin(nsACString& aOrigin) final;
+ NS_IMETHOD GetAsciiOrigin(nsACString& aOrigin) override;
+ NS_IMETHOD GetOriginNoSuffix(nsACString& aOrigin) final;
+ NS_IMETHOD Equals(nsIPrincipal* other, bool* _retval) final;
+ NS_IMETHOD EqualsConsideringDomain(nsIPrincipal* other, bool* _retval) final;
+ NS_IMETHOD EqualsURI(nsIURI* aOtherURI, bool* _retval) override;
+ NS_IMETHOD EqualsForPermission(nsIPrincipal* other, bool aExactHost,
+ bool* _retval) final;
+ NS_IMETHOD Subsumes(nsIPrincipal* other, bool* _retval) final;
+ NS_IMETHOD SubsumesConsideringDomain(nsIPrincipal* other,
+ bool* _retval) final;
+ NS_IMETHOD SubsumesConsideringDomainIgnoringFPD(nsIPrincipal* other,
+ bool* _retval) final;
+ NS_IMETHOD CheckMayLoad(nsIURI* uri, bool allowIfInheritsPrincipal) final;
+ NS_IMETHOD CheckMayLoadWithReporting(nsIURI* uri,
+ bool allowIfInheritsPrincipal,
+ uint64_t innerWindowID) final;
+ NS_IMETHOD GetAddonPolicy(extensions::WebExtensionPolicy** aResult) final;
+ NS_IMETHOD GetContentScriptAddonPolicy(
+ extensions::WebExtensionPolicy** aResult) final;
+ NS_IMETHOD GetIsNullPrincipal(bool* aResult) override;
+ NS_IMETHOD GetIsContentPrincipal(bool* aResult) override;
+ NS_IMETHOD GetIsExpandedPrincipal(bool* aResult) override;
+ NS_IMETHOD GetIsSystemPrincipal(bool* aResult) override;
+ NS_IMETHOD GetScheme(nsACString& aScheme) override;
+ NS_IMETHOD SchemeIs(const char* aScheme, bool* aResult) override;
+ NS_IMETHOD IsURIInPrefList(const char* aPref, bool* aResult) override;
+ NS_IMETHOD IsURIInList(const nsACString& aList, bool* aResult) override;
+ NS_IMETHOD IsContentAccessibleAboutURI(bool* aResult) override;
+ NS_IMETHOD IsL10nAllowed(nsIURI* aURI, bool* aResult) override;
+ NS_IMETHOD GetAboutModuleFlags(uint32_t* flags) override;
+ NS_IMETHOD GetIsAddonOrExpandedAddonPrincipal(bool* aResult) override;
+ NS_IMETHOD GetOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aVal) final;
+ NS_IMETHOD GetAsciiSpec(nsACString& aSpec) override;
+ NS_IMETHOD GetSpec(nsACString& aSpec) override;
+ NS_IMETHOD GetExposablePrePath(nsACString& aResult) override;
+ NS_IMETHOD GetExposableSpec(nsACString& aSpec) override;
+ NS_IMETHOD GetHostPort(nsACString& aRes) override;
+ NS_IMETHOD GetHost(nsACString& aRes) override;
+ NS_IMETHOD GetPrePath(nsACString& aResult) override;
+ NS_IMETHOD GetFilePath(nsACString& aResult) override;
+ NS_IMETHOD GetOriginSuffix(nsACString& aOriginSuffix) final;
+ NS_IMETHOD GetIsIpAddress(bool* aIsIpAddress) override;
+ NS_IMETHOD GetIsLocalIpAddress(bool* aIsIpAddress) override;
+ NS_IMETHOD GetIsOnion(bool* aIsOnion) override;
+ NS_IMETHOD GetIsInIsolatedMozBrowserElement(
+ bool* aIsInIsolatedMozBrowserElement) final;
+ NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final;
+ NS_IMETHOD GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) final;
+ NS_IMETHOD GetSiteOrigin(nsACString& aSiteOrigin) final;
+ NS_IMETHOD GetSiteOriginNoSuffix(nsACString& aSiteOrigin) override;
+ NS_IMETHOD IsThirdPartyURI(nsIURI* uri, bool* aRes) override;
+ NS_IMETHOD IsThirdPartyPrincipal(nsIPrincipal* uri, bool* aRes) override;
+ NS_IMETHOD IsThirdPartyChannel(nsIChannel* aChannel, bool* aRes) override;
+ NS_IMETHOD GetIsOriginPotentiallyTrustworthy(bool* aResult) override;
+ NS_IMETHOD GetIsLoopbackHost(bool* aResult) override;
+ NS_IMETHOD IsSameOrigin(nsIURI* aURI, bool* aRes) override;
+ NS_IMETHOD GetPrefLightCacheKey(nsIURI* aURI, bool aWithCredentials,
+ const OriginAttributes& aOriginAttributes,
+ nsACString& _retval) override;
+ NS_IMETHOD HasFirstpartyStorageAccess(mozIDOMWindow* aCheckWindow,
+ uint32_t* aRejectedReason,
+ bool* aOutAllowed) override;
+ NS_IMETHOD GetAsciiHost(nsACString& aAsciiHost) override;
+ NS_IMETHOD GetLocalStorageQuotaKey(nsACString& aRes) override;
+ NS_IMETHOD AllowsRelaxStrictFileOriginPolicy(nsIURI* aURI,
+ bool* aRes) override;
+ NS_IMETHOD CreateReferrerInfo(mozilla::dom::ReferrerPolicy aReferrerPolicy,
+ nsIReferrerInfo** _retval) override;
+ NS_IMETHOD GetIsScriptAllowedByPolicy(
+ bool* aIsScriptAllowedByPolicy) override;
+ NS_IMETHOD GetStorageOriginKey(nsACString& aOriginKey) override;
+
+ NS_IMETHOD GetNextSubDomainPrincipal(
+ nsIPrincipal** aNextSubDomainPrincipal) override;
+
+ NS_IMETHOD GetPrecursorPrincipal(nsIPrincipal** aPrecursor) override;
+
+ nsresult ToJSON(nsACString& aJSON);
+ nsresult ToJSON(Json::Value& aObject);
+
+ static already_AddRefed<BasePrincipal> FromJSON(const nsACString& aJSON);
+ static already_AddRefed<BasePrincipal> FromJSON(const Json::Value& aJSON);
+ // Method populates a passed Json::Value with serializable fields
+ // which represent all of the fields to deserialize the principal
+ virtual nsresult PopulateJSONObject(Json::Value& aObject);
+
+ virtual bool AddonHasPermission(const nsAtom* aPerm);
+
+ virtual bool IsContentPrincipal() const { return false; };
+
+ static BasePrincipal* Cast(nsIPrincipal* aPrin) {
+ return static_cast<BasePrincipal*>(aPrin);
+ }
+
+ static BasePrincipal& Cast(nsIPrincipal& aPrin) {
+ return *static_cast<BasePrincipal*>(&aPrin);
+ }
+
+ static const BasePrincipal* Cast(const nsIPrincipal* aPrin) {
+ return static_cast<const BasePrincipal*>(aPrin);
+ }
+
+ static const BasePrincipal& Cast(const nsIPrincipal& aPrin) {
+ return *static_cast<const BasePrincipal*>(&aPrin);
+ }
+
+ static already_AddRefed<BasePrincipal> CreateContentPrincipal(
+ const nsACString& aOrigin);
+
+ // This method may not create a content principal in case it's not possible to
+ // generate a correct origin from the passed URI. If this happens, a
+ // NullPrincipal is returned.
+ //
+ // If `aInitialDomain` is specified, and a ContentPrincipal is set, it will
+ // initially have its domain set to the given value, without re-computing js
+ // wrappers. Unlike `SetDomain()` this is safe to do off-main-thread.
+
+ static already_AddRefed<BasePrincipal> CreateContentPrincipal(
+ nsIURI* aURI, const OriginAttributes& aAttrs,
+ nsIURI* aInitialDomain = nullptr);
+
+ const OriginAttributes& OriginAttributesRef() final {
+ return mOriginAttributes;
+ }
+ extensions::WebExtensionPolicy* AddonPolicy();
+ RefPtr<extensions::WebExtensionPolicyCore> AddonPolicyCore();
+ uint32_t UserContextId() const { return mOriginAttributes.mUserContextId; }
+ uint32_t PrivateBrowsingId() const {
+ return mOriginAttributes.mPrivateBrowsingId;
+ }
+ bool IsInIsolatedMozBrowserElement() const {
+ return mOriginAttributes.mInIsolatedMozBrowser;
+ }
+
+ PrincipalKind Kind() const { return mKind; }
+
+ already_AddRefed<BasePrincipal> CloneForcingOriginAttributes(
+ const OriginAttributes& aOriginAttributes);
+
+ // If this is an add-on content script principal, returns its AddonPolicy.
+ // Otherwise returns null.
+ extensions::WebExtensionPolicy* ContentScriptAddonPolicy();
+ RefPtr<extensions::WebExtensionPolicyCore> ContentScriptAddonPolicyCore();
+
+ // Helper to check whether this principal is associated with an addon that
+ // allows unprivileged code to load aURI. aExplicit == true will prevent
+ // use of all_urls permission, requiring the domain in its permissions.
+ bool AddonAllowsLoad(nsIURI* aURI, bool aExplicit = false);
+
+ // Call these to avoid the cost of virtual dispatch.
+ inline bool FastEquals(nsIPrincipal* aOther);
+ inline bool FastEqualsConsideringDomain(nsIPrincipal* aOther);
+ inline bool FastSubsumes(nsIPrincipal* aOther);
+ inline bool FastSubsumesConsideringDomain(nsIPrincipal* aOther);
+ inline bool FastSubsumesIgnoringFPD(nsIPrincipal* aOther);
+ inline bool FastSubsumesConsideringDomainIgnoringFPD(nsIPrincipal* aOther);
+
+ // Fast way to check whether we have a system principal.
+ inline bool IsSystemPrincipal() const;
+
+ // Returns the principal to inherit when a caller with this principal loads
+ // the given URI.
+ //
+ // For most principal types, this returns the principal itself. For expanded
+ // principals, it returns the first sub-principal which subsumes the given URI
+ // (or, if no URI is given, the last allowlist principal).
+ nsIPrincipal* PrincipalToInherit(nsIURI* aRequestedURI = nullptr);
+
+ /* Returns true if this principal's CSP should override a document's CSP for
+ * loads that it triggers. Currently true for expanded principals which
+ * subsume the document principal, and add-on content principals regardless
+ * of whether they subsume the document principal.
+ */
+ bool OverridesCSP(nsIPrincipal* aDocumentPrincipal);
+
+ uint32_t GetOriginNoSuffixHash() const { return mOriginNoSuffix->hash(); }
+ uint32_t GetOriginSuffixHash() const { return mOriginSuffix->hash(); }
+
+ virtual nsresult GetSiteIdentifier(SiteIdentifier& aSite) = 0;
+
+ protected:
+ BasePrincipal(PrincipalKind aKind, const nsACString& aOriginNoSuffix,
+ const OriginAttributes& aOriginAttributes);
+ BasePrincipal(BasePrincipal* aOther,
+ const OriginAttributes& aOriginAttributes);
+
+ virtual ~BasePrincipal();
+
+ // Note that this does not check OriginAttributes. Callers that depend on
+ // those must call Subsumes instead.
+ virtual bool SubsumesInternal(nsIPrincipal* aOther,
+ DocumentDomainConsideration aConsider) = 0;
+
+ // Internal, side-effect-free check to determine whether the concrete
+ // principal would allow the load ignoring any common behavior implemented in
+ // BasePrincipal::CheckMayLoad.
+ //
+ // Safe to call from any thread, unlike CheckMayLoad.
+ virtual bool MayLoadInternal(nsIURI* aURI) = 0;
+ friend class ::ExpandedPrincipal;
+
+ // Helper for implementing CheckMayLoad and CheckMayLoadWithReporting.
+ nsresult CheckMayLoadHelper(nsIURI* aURI, bool aAllowIfInheritsPrincipal,
+ bool aReport, uint64_t aInnerWindowID);
+
+ void SetHasExplicitDomain() { mHasExplicitDomain = true; }
+ bool GetHasExplicitDomain() { return mHasExplicitDomain; }
+
+ // KeyValT holds a principal subtype-specific key value and the associated
+ // parsed value after JSON parsing.
+ template <typename SerializedKey>
+ struct KeyValT {
+ static_assert(sizeof(SerializedKey) == 1,
+ "SerializedKey should be a uint8_t");
+ SerializedKey key;
+ bool valueWasSerialized;
+ nsCString value;
+ };
+
+ // Common base class for all Deserializer implementations in concrete
+ // subclasses. Subclasses will initialize `mPrincipal` in `Read`, and then
+ // calls to `QueryInterface` will QI on the target object.
+ class Deserializer : public nsISerializable {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD Write(nsIObjectOutputStream* aStream) override;
+
+ protected:
+ virtual ~Deserializer() = default;
+ RefPtr<BasePrincipal> mPrincipal;
+ };
+
+ private:
+ static const char* JSONEnumKeyStrings[4];
+
+ static void SetJSONValue(Json::Value& aObject, const char* aKey,
+ const nsCString& aValue);
+
+ protected:
+ template <size_t EnumValue>
+ static inline constexpr const char* JSONEnumKeyString() {
+ static_assert(EnumValue < ArrayLength(JSONEnumKeyStrings));
+ return JSONEnumKeyStrings[EnumValue];
+ }
+ template <size_t EnumValue>
+ static void SetJSONValue(Json::Value& aObject, const nsCString& aValue) {
+ SetJSONValue(aObject, JSONEnumKeyString<EnumValue>(), aValue);
+ }
+
+ private:
+ static already_AddRefed<BasePrincipal> CreateContentPrincipal(
+ nsIURI* aURI, const OriginAttributes& aAttrs,
+ const nsACString& aOriginNoSuffix, nsIURI* aInitialDomain);
+
+ bool FastSubsumesIgnoringFPD(nsIPrincipal* aOther,
+ DocumentDomainConsideration aConsideration);
+
+ const RefPtr<nsAtom> mOriginNoSuffix;
+ const RefPtr<nsAtom> mOriginSuffix;
+
+ const OriginAttributes mOriginAttributes;
+ const PrincipalKind mKind;
+ std::atomic<bool> mHasExplicitDomain;
+};
+
+inline bool BasePrincipal::FastEquals(nsIPrincipal* aOther) {
+ MOZ_ASSERT(aOther);
+
+ auto other = Cast(aOther);
+ if (Kind() != other->Kind()) {
+ // Principals of different kinds can't be equal.
+ return false;
+ }
+
+ // Two principals are considered to be equal if their origins are the same.
+ // If the two principals are content principals, their origin attributes
+ // (aka the origin suffix) must also match.
+ if (Kind() == eSystemPrincipal) {
+ return this == other;
+ }
+
+ if (Kind() == eContentPrincipal || Kind() == eNullPrincipal) {
+ return mOriginNoSuffix == other->mOriginNoSuffix &&
+ mOriginSuffix == other->mOriginSuffix;
+ }
+
+ MOZ_ASSERT(Kind() == eExpandedPrincipal);
+ return mOriginNoSuffix == other->mOriginNoSuffix;
+}
+
+inline bool BasePrincipal::FastEqualsConsideringDomain(nsIPrincipal* aOther) {
+ MOZ_ASSERT(aOther);
+
+ // If neither of the principals have document.domain set, we use the fast path
+ // in Equals(). Otherwise, we fall back to the slow path below.
+ auto other = Cast(aOther);
+ if (!mHasExplicitDomain && !other->mHasExplicitDomain) {
+ return FastEquals(aOther);
+ }
+
+ // Principals of different kinds can't be equal.
+ if (Kind() != other->Kind()) {
+ return false;
+ }
+
+ // Only ContentPrincipals should have mHasExplicitDomain set to true, so test
+ // that we haven't ended up here instead of FastEquals by mistake.
+ MOZ_ASSERT(IsContentPrincipal(),
+ "Only content principals can set mHasExplicitDomain");
+
+ return Subsumes(aOther, ConsiderDocumentDomain) &&
+ other->Subsumes(this, ConsiderDocumentDomain);
+}
+
+inline bool BasePrincipal::FastSubsumes(nsIPrincipal* aOther) {
+ MOZ_ASSERT(aOther);
+
+ // If two principals are equal, then they both subsume each other.
+ if (FastEquals(aOther)) {
+ return true;
+ }
+
+ // Otherwise, fall back to the slow path.
+ return Subsumes(aOther, DontConsiderDocumentDomain);
+}
+
+inline bool BasePrincipal::FastSubsumesConsideringDomain(nsIPrincipal* aOther) {
+ MOZ_ASSERT(aOther);
+
+ // If neither of the principals have document.domain set, we hand off to
+ // FastSubsumes() which has fast paths for some special cases. Otherwise, we
+ // fall back to the slow path below.
+ if (!mHasExplicitDomain && !Cast(aOther)->mHasExplicitDomain) {
+ return FastSubsumes(aOther);
+ }
+
+ return Subsumes(aOther, ConsiderDocumentDomain);
+}
+
+inline bool BasePrincipal::FastSubsumesIgnoringFPD(nsIPrincipal* aOther) {
+ return FastSubsumesIgnoringFPD(aOther, DontConsiderDocumentDomain);
+}
+
+inline bool BasePrincipal::FastSubsumesConsideringDomainIgnoringFPD(
+ nsIPrincipal* aOther) {
+ return FastSubsumesIgnoringFPD(aOther, ConsiderDocumentDomain);
+}
+
+inline bool BasePrincipal::IsSystemPrincipal() const {
+ return Kind() == eSystemPrincipal;
+}
+
+} // namespace mozilla
+
+inline bool nsIPrincipal::IsSystemPrincipal() const {
+ return mozilla::BasePrincipal::Cast(this)->IsSystemPrincipal();
+}
+
+#endif /* mozilla_BasePrincipal_h */
diff --git a/caps/ContentPrincipal.cpp b/caps/ContentPrincipal.cpp
new file mode 100644
index 0000000000..5aeecaf2bd
--- /dev/null
+++ b/caps/ContentPrincipal.cpp
@@ -0,0 +1,709 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et 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 "ContentPrincipal.h"
+
+#include "mozIThirdPartyUtil.h"
+#include "nsContentUtils.h"
+#include "nscore.h"
+#include "nsScriptSecurityManager.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "pratom.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsIStandardURL.h"
+#include "nsIURIWithSpecialOrigin.h"
+#include "nsIURIMutator.h"
+#include "nsJSPrincipals.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIProtocolHandler.h"
+#include "nsError.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsNetCID.h"
+#include "js/RealmIterators.h"
+#include "js/Wrapper.h"
+
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ExtensionPolicyService.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/HashFunctions.h"
+
+#include "nsSerializationHelper.h"
+#include "json/json.h"
+
+using namespace mozilla;
+
+NS_IMPL_CLASSINFO(ContentPrincipal, nullptr, 0, NS_PRINCIPAL_CID)
+NS_IMPL_QUERY_INTERFACE_CI(ContentPrincipal, nsIPrincipal)
+NS_IMPL_CI_INTERFACE_GETTER(ContentPrincipal, nsIPrincipal)
+
+ContentPrincipal::ContentPrincipal(nsIURI* aURI,
+ const OriginAttributes& aOriginAttributes,
+ const nsACString& aOriginNoSuffix,
+ nsIURI* aInitialDomain)
+ : BasePrincipal(eContentPrincipal, aOriginNoSuffix, aOriginAttributes),
+ mURI(aURI),
+ mDomain(aInitialDomain) {
+ if (mDomain) {
+ // We're just creating the principal, so no need to re-compute wrappers.
+ SetHasExplicitDomain();
+ }
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ // Assert that the URI we get here isn't any of the schemes that we know we
+ // should not get here. These schemes always either inherit their principal
+ // or fall back to a null principal. These are schemes which return
+ // URI_INHERITS_SECURITY_CONTEXT from their protocol handler's
+ // GetProtocolFlags function.
+ bool hasFlag = false;
+ MOZ_DIAGNOSTIC_ASSERT(
+ NS_SUCCEEDED(NS_URIChainHasFlags(
+ aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &hasFlag)) &&
+ !hasFlag);
+#endif
+}
+
+ContentPrincipal::ContentPrincipal(ContentPrincipal* aOther,
+ const OriginAttributes& aOriginAttributes)
+ : BasePrincipal(aOther, aOriginAttributes),
+ mURI(aOther->mURI),
+ mDomain(aOther->mDomain),
+ mAddon(aOther->mAddon) {}
+
+ContentPrincipal::~ContentPrincipal() = default;
+
+nsresult ContentPrincipal::GetScriptLocation(nsACString& aStr) {
+ return mURI->GetSpec(aStr);
+}
+
+/* static */
+nsresult ContentPrincipal::GenerateOriginNoSuffixFromURI(
+ nsIURI* aURI, nsACString& aOriginNoSuffix) {
+ if (!aURI) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> origin = NS_GetInnermostURI(aURI);
+ if (!origin) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(!NS_IsAboutBlank(origin),
+ "The inner URI for about:blank must be moz-safe-about:blank");
+
+ // Handle non-strict file:// uris.
+ if (!nsScriptSecurityManager::GetStrictFileOriginPolicy() &&
+ NS_URIIsLocalFile(origin)) {
+ // If strict file origin policy is not in effect, all local files are
+ // considered to be same-origin, so return a known dummy origin here.
+ aOriginNoSuffix.AssignLiteral("file://UNIVERSAL_FILE_URI_ORIGIN");
+ return NS_OK;
+ }
+
+ nsresult rv;
+// NB: This is only compiled for Thunderbird/Suite.
+#if IS_ORIGIN_IS_FULL_SPEC_DEFINED
+ bool fullSpec = false;
+ rv = NS_URIChainHasFlags(origin, nsIProtocolHandler::ORIGIN_IS_FULL_SPEC,
+ &fullSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (fullSpec) {
+ return origin->GetAsciiSpec(aOriginNoSuffix);
+ }
+#endif
+
+ // We want the invariant that prinA.origin == prinB.origin i.f.f.
+ // prinA.equals(prinB). However, this requires that we impose certain
+ // constraints on the behavior and origin semantics of principals, and in
+ // particular, forbid creating origin strings for principals whose equality
+ // constraints are not expressible as strings (i.e. object equality).
+ // Moreover, we want to forbid URIs containing the magic "^" we use as a
+ // separating character for origin attributes.
+ //
+ // These constraints can generally be achieved by restricting .origin to
+ // nsIStandardURL-based URIs, but there are a few other URI schemes that we
+ // need to handle.
+ if (origin->SchemeIs("about") ||
+ (origin->SchemeIs("moz-safe-about") &&
+ // We generally consider two about:foo origins to be same-origin, but
+ // about:blank is special since it can be generated from different
+ // sources. We check for moz-safe-about:blank since origin is an
+ // innermost URI.
+ !StringBeginsWith(origin->GetSpecOrDefault(),
+ "moz-safe-about:blank"_ns))) {
+ rv = origin->GetAsciiSpec(aOriginNoSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t pos = aOriginNoSuffix.FindChar('?');
+ int32_t hashPos = aOriginNoSuffix.FindChar('#');
+
+ if (hashPos != kNotFound && (pos == kNotFound || hashPos < pos)) {
+ pos = hashPos;
+ }
+
+ if (pos != kNotFound) {
+ aOriginNoSuffix.Truncate(pos);
+ }
+
+ // These URIs could technically contain a '^', but they never should.
+ if (NS_WARN_IF(aOriginNoSuffix.FindChar('^', 0) != -1)) {
+ aOriginNoSuffix.Truncate();
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+ }
+
+ // This URL can be a blobURL. In this case, we should use the 'parent'
+ // principal instead.
+ nsCOMPtr<nsIPrincipal> blobPrincipal;
+ if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal(
+ origin, getter_AddRefs(blobPrincipal))) {
+ MOZ_ASSERT(blobPrincipal);
+ return blobPrincipal->GetOriginNoSuffix(aOriginNoSuffix);
+ }
+
+ // If we reached this branch, we can only create an origin if we have a
+ // nsIStandardURL. So, we query to a nsIStandardURL, and fail if we aren't
+ // an instance of an nsIStandardURL nsIStandardURLs have the good property
+ // of escaping the '^' character in their specs, which means that we can be
+ // sure that the caret character (which is reserved for delimiting the end
+ // of the spec, and the beginning of the origin attributes) is not present
+ // in the origin string
+ nsCOMPtr<nsIStandardURL> standardURL = do_QueryInterface(origin);
+ if (!standardURL) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // See whether we have a useful hostPort. If we do, use that.
+ nsAutoCString hostPort;
+ if (!origin->SchemeIs("chrome")) {
+ rv = origin->GetAsciiHostPort(hostPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (!hostPort.IsEmpty()) {
+ rv = origin->GetScheme(aOriginNoSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aOriginNoSuffix.AppendLiteral("://");
+ aOriginNoSuffix.Append(hostPort);
+ return NS_OK;
+ }
+
+ rv = aURI->GetAsciiSpec(aOriginNoSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The origin, when taken from the spec, should not contain the ref part of
+ // the URL.
+
+ int32_t pos = aOriginNoSuffix.FindChar('?');
+ int32_t hashPos = aOriginNoSuffix.FindChar('#');
+
+ if (hashPos != kNotFound && (pos == kNotFound || hashPos < pos)) {
+ pos = hashPos;
+ }
+
+ if (pos != kNotFound) {
+ aOriginNoSuffix.Truncate(pos);
+ }
+
+ return NS_OK;
+}
+
+bool ContentPrincipal::SubsumesInternal(
+ nsIPrincipal* aOther,
+ BasePrincipal::DocumentDomainConsideration aConsideration) {
+ MOZ_ASSERT(aOther);
+
+ // For ContentPrincipal, Subsumes is equivalent to Equals.
+ if (aOther == this) {
+ return true;
+ }
+
+ // If either the subject or the object has changed its principal by
+ // explicitly setting document.domain then the other must also have
+ // done so in order to be considered the same origin. This prevents
+ // DNS spoofing based on document.domain (154930)
+ if (aConsideration == ConsiderDocumentDomain) {
+ // Get .domain on each principal.
+ nsCOMPtr<nsIURI> thisDomain, otherDomain;
+ GetDomain(getter_AddRefs(thisDomain));
+ aOther->GetDomain(getter_AddRefs(otherDomain));
+
+ // If either has .domain set, we have equality i.f.f. the domains match.
+ // Otherwise, we fall through to the non-document-domain-considering case.
+ if (thisDomain || otherDomain) {
+ bool isMatch =
+ nsScriptSecurityManager::SecurityCompareURIs(thisDomain, otherDomain);
+#ifdef DEBUG
+ if (isMatch) {
+ nsAutoCString thisSiteOrigin, otherSiteOrigin;
+ MOZ_ALWAYS_SUCCEEDS(GetSiteOrigin(thisSiteOrigin));
+ MOZ_ALWAYS_SUCCEEDS(aOther->GetSiteOrigin(otherSiteOrigin));
+ MOZ_ASSERT(
+ thisSiteOrigin == otherSiteOrigin,
+ "SubsumesConsideringDomain passed with mismatched siteOrigin!");
+ }
+#endif
+ return isMatch;
+ }
+ }
+
+ // Do a fast check (including origin attributes) or a slow uri comparison.
+ return FastEquals(aOther) || aOther->IsSameOrigin(mURI);
+}
+
+NS_IMETHODIMP
+ContentPrincipal::GetURI(nsIURI** aURI) {
+ *aURI = do_AddRef(mURI).take();
+ return NS_OK;
+}
+
+bool ContentPrincipal::MayLoadInternal(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+ nsCOMPtr<nsIURIWithSpecialOrigin> uriWithSpecialOrigin =
+ do_QueryInterface(aURI);
+ if (uriWithSpecialOrigin) {
+ nsCOMPtr<nsIURI> origin;
+ nsresult rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ MOZ_ASSERT(origin);
+ OriginAttributes attrs;
+ RefPtr<BasePrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(origin, attrs);
+ return nsIPrincipal::Subsumes(principal);
+ }
+#endif
+
+ nsCOMPtr<nsIPrincipal> blobPrincipal;
+ if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal(
+ aURI, getter_AddRefs(blobPrincipal))) {
+ MOZ_ASSERT(blobPrincipal);
+ return nsIPrincipal::Subsumes(blobPrincipal);
+ }
+
+ // If this principal is associated with an addon, check whether that addon
+ // has been given permission to load from this domain.
+ if (AddonAllowsLoad(aURI)) {
+ return true;
+ }
+
+ if (nsScriptSecurityManager::SecurityCompareURIs(mURI, aURI)) {
+ return true;
+ }
+
+ // If strict file origin policy is in effect, local files will always fail
+ // SecurityCompareURIs unless they are identical. Explicitly check file origin
+ // policy, in that case.
+ if (nsScriptSecurityManager::GetStrictFileOriginPolicy() &&
+ NS_URIIsLocalFile(aURI) && NS_RelaxStrictFileOriginPolicy(aURI, mURI)) {
+ return true;
+ }
+
+ return false;
+}
+
+uint32_t ContentPrincipal::GetHashValue() {
+ MOZ_ASSERT(mURI, "Need a principal URI");
+
+ nsCOMPtr<nsIURI> uri;
+ GetDomain(getter_AddRefs(uri));
+ if (!uri) {
+ GetURI(getter_AddRefs(uri));
+ };
+ return NS_SecurityHashURI(uri);
+}
+
+NS_IMETHODIMP
+ContentPrincipal::GetDomain(nsIURI** aDomain) {
+ if (!GetHasExplicitDomain()) {
+ *aDomain = nullptr;
+ return NS_OK;
+ }
+
+ mozilla::MutexAutoLock lock(mMutex);
+ NS_ADDREF(*aDomain = mDomain);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentPrincipal::SetDomain(nsIURI* aDomain) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aDomain);
+
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ mDomain = aDomain;
+ SetHasExplicitDomain();
+ }
+
+ // Set the changed-document-domain flag on compartments containing realms
+ // using this principal.
+ auto cb = [](JSContext*, void*, JS::Realm* aRealm,
+ const JS::AutoRequireNoGC& nogc) {
+ JS::Compartment* comp = JS::GetCompartmentForRealm(aRealm);
+ xpc::SetCompartmentChangedDocumentDomain(comp);
+ };
+ JSPrincipals* principals =
+ nsJSPrincipals::get(static_cast<nsIPrincipal*>(this));
+
+ dom::AutoJSAPI jsapi;
+ jsapi.Init();
+ JS::IterateRealmsWithPrincipals(jsapi.cx(), principals, nullptr, cb);
+
+ return NS_OK;
+}
+
+static nsresult GetSpecialBaseDomain(const nsCOMPtr<nsIURI>& aURI,
+ bool* aHandled, nsACString& aBaseDomain) {
+ *aHandled = false;
+
+ // Special handling for a file URI.
+ if (NS_URIIsLocalFile(aURI)) {
+ // If strict file origin policy is not in effect, all local files are
+ // considered to be same-origin, so return a known dummy domain here.
+ if (!nsScriptSecurityManager::GetStrictFileOriginPolicy()) {
+ *aHandled = true;
+ aBaseDomain.AssignLiteral("UNIVERSAL_FILE_URI_ORIGIN");
+ return NS_OK;
+ }
+
+ // Otherwise, we return the file path.
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
+
+ if (url) {
+ *aHandled = true;
+ return url->GetFilePath(aBaseDomain);
+ }
+ }
+
+ bool hasNoRelativeFlag;
+ nsresult rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_NORELATIVE,
+ &hasNoRelativeFlag);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // In case of FTP we want to get base domain via TLD service even if FTP
+ // protocol handler is disabled and the scheme is handled by external protocol
+ // handler which returns URI_NORELATIVE flag.
+ if (hasNoRelativeFlag && !aURI->SchemeIs("ftp")) {
+ *aHandled = true;
+ return aURI->GetSpec(aBaseDomain);
+ }
+
+ if (aURI->SchemeIs("indexeddb")) {
+ *aHandled = true;
+ return aURI->GetSpec(aBaseDomain);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentPrincipal::GetBaseDomain(nsACString& aBaseDomain) {
+ // Handle some special URIs first.
+ bool handled;
+ nsresult rv = GetSpecialBaseDomain(mURI, &handled, aBaseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (handled) {
+ return NS_OK;
+ }
+
+ // For everything else, we ask the TLD service via the ThirdPartyUtil.
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ do_GetService(THIRDPARTYUTIL_CONTRACTID);
+ if (!thirdPartyUtil) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return thirdPartyUtil->GetBaseDomain(mURI, aBaseDomain);
+}
+
+NS_IMETHODIMP
+ContentPrincipal::GetSiteOriginNoSuffix(nsACString& aSiteOrigin) {
+ nsresult rv = GetOriginNoSuffix(aSiteOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // It is possible for two principals with the same origin to have different
+ // mURI values. In order to ensure that two principals with matching origins
+ // also have matching siteOrigins, we derive the siteOrigin entirely from the
+ // origin string and do not rely on mURI at all here.
+ nsCOMPtr<nsIURI> origin;
+ rv = NS_NewURI(getter_AddRefs(origin), aSiteOrigin);
+ if (NS_FAILED(rv)) {
+ // We got an error parsing the origin as a URI? siteOrigin == origin
+ // aSiteOrigin was already filled with `OriginNoSuffix`
+ return rv;
+ }
+
+ // Handle some special URIs first.
+ nsAutoCString baseDomain;
+ bool handled;
+ rv = GetSpecialBaseDomain(origin, &handled, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (handled) {
+ // This is a special URI ("file:", "about:", "view-source:", etc). Just
+ // return the origin.
+ return NS_OK;
+ }
+
+ // For everything else, we ask the TLD service. Note that, unlike in
+ // GetBaseDomain, we don't use ThirdPartyUtil.getBaseDomain because if the
+ // host is an IP address that returns the raw address and we can't use it with
+ // SetHost below because SetHost expects '[' and ']' around IPv6 addresses.
+ // See bug 1491728.
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!tldService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool gotBaseDomain = false;
+ rv = tldService->GetBaseDomain(origin, 0, baseDomain);
+ if (NS_SUCCEEDED(rv)) {
+ gotBaseDomain = true;
+ } else {
+ // If this is an IP address or something like "localhost", we just continue
+ // with gotBaseDomain = false.
+ if (rv != NS_ERROR_HOST_IS_IP_ADDRESS &&
+ rv != NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS &&
+ rv != NS_ERROR_INVALID_ARG) {
+ return rv;
+ }
+ }
+
+ // NOTE: Calling `SetHostPort` with a portless domain is insufficient to clear
+ // the port, so an extra `SetPort` call has to be made.
+ nsCOMPtr<nsIURI> siteUri;
+ NS_MutateURI mutator(origin);
+ mutator.SetUserPass(""_ns).SetPort(-1);
+ if (gotBaseDomain) {
+ mutator.SetHost(baseDomain);
+ }
+ rv = mutator.Finalize(siteUri);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create siteUri");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aSiteOrigin.Truncate();
+ rv = GenerateOriginNoSuffixFromURI(siteUri, aSiteOrigin);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create siteOriginNoSuffix");
+ return rv;
+}
+
+nsresult ContentPrincipal::GetSiteIdentifier(SiteIdentifier& aSite) {
+ nsCString siteOrigin;
+ nsresult rv = GetSiteOrigin(siteOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<BasePrincipal> principal = CreateContentPrincipal(siteOrigin);
+ if (!principal) {
+ NS_WARNING("could not instantiate content principal");
+ return NS_ERROR_FAILURE;
+ }
+
+ aSite.Init(principal);
+ return NS_OK;
+}
+
+RefPtr<extensions::WebExtensionPolicyCore> ContentPrincipal::AddonPolicyCore() {
+ mozilla::MutexAutoLock lock(mMutex);
+ if (!mAddon.isSome()) {
+ NS_ENSURE_TRUE(mURI, nullptr);
+
+ RefPtr<extensions::WebExtensionPolicyCore> core;
+ if (mURI->SchemeIs("moz-extension")) {
+ nsCString host;
+ NS_ENSURE_SUCCESS(mURI->GetHost(host), nullptr);
+ core = ExtensionPolicyService::GetCoreByHost(host);
+ }
+
+ mAddon.emplace(core);
+ }
+ return *mAddon;
+}
+
+NS_IMETHODIMP
+ContentPrincipal::GetAddonId(nsAString& aAddonId) {
+ if (RefPtr<extensions::WebExtensionPolicyCore> policy = AddonPolicyCore()) {
+ policy->Id()->ToString(aAddonId);
+ } else {
+ aAddonId.Truncate();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentPrincipal::Deserializer::Read(nsIObjectInputStream* aStream) {
+ MOZ_ASSERT(!mPrincipal);
+
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIURI> principalURI;
+ nsresult rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ principalURI = do_QueryInterface(supports);
+ // Enforce re-parsing about: URIs so that if they change, we continue to use
+ // their new principals correctly.
+ if (principalURI->SchemeIs("about")) {
+ nsAutoCString spec;
+ principalURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(NS_NewURI(getter_AddRefs(principalURI), spec),
+ NS_ERROR_FAILURE);
+ }
+
+ nsCOMPtr<nsIURI> domain;
+ rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ domain = do_QueryInterface(supports);
+
+ nsAutoCString suffix;
+ rv = aStream->ReadCString(suffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ OriginAttributes attrs;
+ bool ok = attrs.PopulateFromSuffix(suffix);
+ NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
+
+ // Since Bug 965637 we do not serialize the CSP within the
+ // Principal anymore. Nevertheless there might still be
+ // serialized Principals that do have a serialized CSP.
+ // For now, we just read the CSP here but do not actually
+ // consume it. Please note that we deliberately ignore
+ // the return value to avoid CSP deserialization problems.
+ // After Bug 1508939 we will have a new serialization for
+ // Principals which allows us to update the code here.
+ // Additionally, the format for serialized CSPs changed
+ // within Bug 965637 which also can cause failures within
+ // the CSP deserialization code.
+ Unused << NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports));
+
+ nsAutoCString originNoSuffix;
+ rv = GenerateOriginNoSuffixFromURI(principalURI, originNoSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mPrincipal =
+ new ContentPrincipal(principalURI, attrs, originNoSuffix, domain);
+ return NS_OK;
+}
+
+nsresult ContentPrincipal::PopulateJSONObject(Json::Value& aObject) {
+ nsAutoCString principalURI;
+ nsresult rv = mURI->GetSpec(principalURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We turn each int enum field into a JSON string key of the object
+ // aObject is the inner JSON object that has stringified enum keys
+ // An example aObject might be:
+ //
+ // eURI eSuffix
+ // | |
+ // {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"}
+ // | | | |
+ // ----------------------------- |
+ // | | |
+ // Key ----------------------
+ // |
+ // Value
+ SetJSONValue<eURI>(aObject, principalURI);
+
+ if (GetHasExplicitDomain()) {
+ nsAutoCString domainStr;
+ {
+ MutexAutoLock lock(mMutex);
+ rv = mDomain->GetSpec(domainStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ SetJSONValue<eDomain>(aObject, domainStr);
+ }
+
+ nsAutoCString suffix;
+ OriginAttributesRef().CreateSuffix(suffix);
+ if (suffix.Length() > 0) {
+ SetJSONValue<eSuffix>(aObject, suffix);
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<BasePrincipal> ContentPrincipal::FromProperties(
+ nsTArray<ContentPrincipal::KeyVal>& aFields) {
+ MOZ_ASSERT(aFields.Length() == eMax + 1, "Must have all the keys");
+ nsresult rv;
+ nsCOMPtr<nsIURI> principalURI;
+ nsCOMPtr<nsIURI> domain;
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ OriginAttributes attrs;
+
+ // The odd structure here is to make the code to not compile
+ // if all the switch enum cases haven't been codified
+ for (const auto& field : aFields) {
+ switch (field.key) {
+ case ContentPrincipal::eURI:
+ if (!field.valueWasSerialized) {
+ MOZ_ASSERT(
+ false,
+ "Content principals require a principal URI in serialized JSON");
+ return nullptr;
+ }
+ rv = NS_NewURI(getter_AddRefs(principalURI), field.value.get());
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ {
+ // Enforce re-parsing about: URIs so that if they change, we
+ // continue to use their new principals correctly.
+ if (principalURI->SchemeIs("about")) {
+ nsAutoCString spec;
+ principalURI->GetSpec(spec);
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(principalURI), spec))) {
+ return nullptr;
+ }
+ }
+ }
+ break;
+ case ContentPrincipal::eDomain:
+ if (field.valueWasSerialized) {
+ rv = NS_NewURI(getter_AddRefs(domain), field.value.get());
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ }
+ break;
+ case ContentPrincipal::eSuffix:
+ if (field.valueWasSerialized) {
+ bool ok = attrs.PopulateFromSuffix(field.value);
+ if (!ok) {
+ return nullptr;
+ }
+ }
+ break;
+ }
+ }
+ nsAutoCString originNoSuffix;
+ rv = ContentPrincipal::GenerateOriginNoSuffixFromURI(principalURI,
+ originNoSuffix);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ RefPtr<ContentPrincipal> principal =
+ new ContentPrincipal(principalURI, attrs, originNoSuffix, domain);
+
+ return principal.forget();
+}
diff --git a/caps/ContentPrincipal.h b/caps/ContentPrincipal.h
new file mode 100644
index 0000000000..d2f9d7ed83
--- /dev/null
+++ b/caps/ContentPrincipal.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_ContentPrincipal_h
+#define mozilla_ContentPrincipal_h
+
+#include "nsCOMPtr.h"
+#include "nsJSPrincipals.h"
+#include "nsTArray.h"
+#include "nsNetUtil.h"
+#include "nsScriptSecurityManager.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/extensions/WebExtensionPolicy.h"
+
+namespace Json {
+class Value;
+}
+
+namespace mozilla {
+
+class ContentPrincipal final : public BasePrincipal {
+ public:
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+ uint32_t GetHashValue() override;
+ NS_IMETHOD GetURI(nsIURI** aURI) override;
+ NS_IMETHOD GetDomain(nsIURI** aDomain) override;
+ NS_IMETHOD SetDomain(nsIURI* aDomain) override;
+ NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
+ NS_IMETHOD GetAddonId(nsAString& aAddonId) override;
+ NS_IMETHOD GetSiteOriginNoSuffix(nsACString& aSiteOrigin) override;
+ bool IsContentPrincipal() const override { return true; }
+
+ ContentPrincipal(nsIURI* aURI, const OriginAttributes& aOriginAttributes,
+ const nsACString& aOriginNoSuffix, nsIURI* aInitialDomain);
+ ContentPrincipal(ContentPrincipal* aOther,
+ const OriginAttributes& aOriginAttributes);
+
+ static PrincipalKind Kind() { return eContentPrincipal; }
+
+ virtual nsresult GetScriptLocation(nsACString& aStr) override;
+
+ nsresult GetSiteIdentifier(SiteIdentifier& aSite) override;
+
+ static nsresult GenerateOriginNoSuffixFromURI(nsIURI* aURI,
+ nsACString& aOrigin);
+
+ RefPtr<extensions::WebExtensionPolicyCore> AddonPolicyCore();
+
+ virtual nsresult PopulateJSONObject(Json::Value& aObject) override;
+ // Serializable keys are the valid enum fields the serialization supports
+ enum SerializableKeys : uint8_t {
+ eURI = 0,
+ eDomain,
+ eSuffix,
+ eMax = eSuffix
+ };
+ typedef mozilla::BasePrincipal::KeyValT<SerializableKeys> KeyVal;
+
+ static already_AddRefed<BasePrincipal> FromProperties(
+ nsTArray<ContentPrincipal::KeyVal>& aFields);
+
+ class Deserializer : public BasePrincipal::Deserializer {
+ public:
+ NS_IMETHOD Read(nsIObjectInputStream* aStream) override;
+ };
+
+ protected:
+ virtual ~ContentPrincipal();
+
+ bool SubsumesInternal(nsIPrincipal* aOther,
+ DocumentDomainConsideration aConsideration) override;
+ bool MayLoadInternal(nsIURI* aURI) override;
+
+ private:
+ const nsCOMPtr<nsIURI> mURI;
+ mozilla::Mutex mMutex{"ContentPrincipal::mMutex"};
+ nsCOMPtr<nsIURI> mDomain MOZ_GUARDED_BY(mMutex);
+ Maybe<RefPtr<extensions::WebExtensionPolicyCore>> mAddon
+ MOZ_GUARDED_BY(mMutex);
+};
+
+} // namespace mozilla
+
+#define NS_PRINCIPAL_CID \
+ { \
+ 0x653e0e4d, 0x3ee4, 0x45fa, { \
+ 0xb2, 0x72, 0x97, 0xc2, 0x0b, 0xc0, 0x1e, 0xb8 \
+ } \
+ }
+
+#endif // mozilla_ContentPrincipal_h
diff --git a/caps/ContentPrincipalInfoHashKey.h b/caps/ContentPrincipalInfoHashKey.h
new file mode 100644
index 0000000000..ed1f79852f
--- /dev/null
+++ b/caps/ContentPrincipalInfoHashKey.h
@@ -0,0 +1,56 @@
+/* -*- 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/. */
+
+#ifndef CAPS_PRINCIPALHASHKEY_H_
+#define CAPS_PRINCIPALHASHKEY_H_
+
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "PLDHashTable.h"
+#include "nsHashKeys.h"
+#include "nsUnicharUtils.h"
+
+namespace mozilla {
+
+class ContentPrincipalInfoHashKey : public PLDHashEntryHdr {
+ public:
+ using KeyType = const ipc::ContentPrincipalInfo&;
+ using KeyTypePointer = const ipc::ContentPrincipalInfo*;
+
+ explicit ContentPrincipalInfoHashKey(KeyTypePointer aKey)
+ : mPrincipalInfo(*aKey) {
+ MOZ_COUNT_CTOR(ContentPrincipalInfoHashKey);
+ }
+ ContentPrincipalInfoHashKey(ContentPrincipalInfoHashKey&& aOther) noexcept
+ : mPrincipalInfo(aOther.mPrincipalInfo) {
+ MOZ_COUNT_CTOR(ContentPrincipalInfoHashKey);
+ }
+
+ MOZ_COUNTED_DTOR(ContentPrincipalInfoHashKey)
+
+ KeyType GetKey() const { return mPrincipalInfo; }
+
+ bool KeyEquals(KeyTypePointer aKey) const {
+ // Mocks BasePrincipal::FastEquals()
+ return mPrincipalInfo.originNoSuffix() == aKey->originNoSuffix() &&
+ mPrincipalInfo.attrs() == aKey->attrs();
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ nsAutoCString suffix;
+ aKey->attrs().CreateSuffix(suffix);
+ return HashGeneric(HashString(aKey->originNoSuffix()), HashString(suffix));
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ protected:
+ const ipc::ContentPrincipalInfo mPrincipalInfo;
+};
+
+} // namespace mozilla
+
+#endif // CAPS_PRINCIPALHASHKEY_H_
diff --git a/caps/DomainPolicy.cpp b/caps/DomainPolicy.cpp
new file mode 100644
index 0000000000..65ad3d8df4
--- /dev/null
+++ b/caps/DomainPolicy.cpp
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 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 "DomainPolicy.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/Unused.h"
+#include "nsIURIMutator.h"
+#include "nsScriptSecurityManager.h"
+
+namespace mozilla {
+
+using namespace ipc;
+using namespace dom;
+
+NS_IMPL_ISUPPORTS(DomainPolicy, nsIDomainPolicy)
+
+static nsresult BroadcastDomainSetChange(DomainSetType aSetType,
+ DomainSetChangeType aChangeType,
+ nsIURI* aDomain = nullptr) {
+ MOZ_ASSERT(XRE_IsParentProcess(),
+ "DomainPolicy should only be exposed to the chrome process.");
+
+ nsTArray<ContentParent*> parents;
+ ContentParent::GetAll(parents);
+ if (!parents.Length()) {
+ return NS_OK;
+ }
+
+ for (uint32_t i = 0; i < parents.Length(); i++) {
+ Unused << parents[i]->SendDomainSetChanged(aSetType, aChangeType, aDomain);
+ }
+ return NS_OK;
+}
+
+DomainPolicy::DomainPolicy()
+ : mBlocklist(new DomainSet(BLOCKLIST)),
+ mSuperBlocklist(new DomainSet(SUPER_BLOCKLIST)),
+ mAllowlist(new DomainSet(ALLOWLIST)),
+ mSuperAllowlist(new DomainSet(SUPER_ALLOWLIST)) {
+ if (XRE_IsParentProcess()) {
+ BroadcastDomainSetChange(NO_TYPE, ACTIVATE_POLICY);
+ }
+}
+
+DomainPolicy::~DomainPolicy() {
+ // The SSM holds a strong ref to the DomainPolicy until Deactivate() is
+ // invoked, so we should never hit the destructor until that happens.
+ MOZ_ASSERT(!mBlocklist && !mSuperBlocklist && !mAllowlist &&
+ !mSuperAllowlist);
+}
+
+NS_IMETHODIMP
+DomainPolicy::GetBlocklist(nsIDomainSet** aSet) {
+ nsCOMPtr<nsIDomainSet> set = mBlocklist.get();
+ set.forget(aSet);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainPolicy::GetSuperBlocklist(nsIDomainSet** aSet) {
+ nsCOMPtr<nsIDomainSet> set = mSuperBlocklist.get();
+ set.forget(aSet);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainPolicy::GetAllowlist(nsIDomainSet** aSet) {
+ nsCOMPtr<nsIDomainSet> set = mAllowlist.get();
+ set.forget(aSet);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainPolicy::GetSuperAllowlist(nsIDomainSet** aSet) {
+ nsCOMPtr<nsIDomainSet> set = mSuperAllowlist.get();
+ set.forget(aSet);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainPolicy::Deactivate() {
+ // Clear the hashtables first to free up memory, since script might
+ // hold the doomed sets alive indefinitely.
+ mBlocklist->Clear();
+ mSuperBlocklist->Clear();
+ mAllowlist->Clear();
+ mSuperAllowlist->Clear();
+
+ // Null them out.
+ mBlocklist = nullptr;
+ mSuperBlocklist = nullptr;
+ mAllowlist = nullptr;
+ mSuperAllowlist = nullptr;
+
+ // Inform the SSM.
+ nsScriptSecurityManager* ssm =
+ nsScriptSecurityManager::GetScriptSecurityManager();
+ if (ssm) {
+ ssm->DeactivateDomainPolicy();
+ }
+ if (XRE_IsParentProcess()) {
+ BroadcastDomainSetChange(NO_TYPE, DEACTIVATE_POLICY);
+ }
+ return NS_OK;
+}
+
+void DomainPolicy::CloneDomainPolicy(DomainPolicyClone* aClone) {
+ aClone->active() = true;
+ mBlocklist->CloneSet(&aClone->blocklist());
+ mSuperBlocklist->CloneSet(&aClone->superBlocklist());
+ mAllowlist->CloneSet(&aClone->allowlist());
+ mSuperAllowlist->CloneSet(&aClone->superAllowlist());
+}
+
+static void CopyURIs(const nsTArray<RefPtr<nsIURI>>& aDomains,
+ nsIDomainSet* aSet) {
+ for (uint32_t i = 0; i < aDomains.Length(); i++) {
+ if (NS_WARN_IF(!aDomains[i])) {
+ continue;
+ }
+ aSet->Add(aDomains[i]);
+ }
+}
+
+void DomainPolicy::ApplyClone(const DomainPolicyClone* aClone) {
+ CopyURIs(aClone->blocklist(), mBlocklist);
+ CopyURIs(aClone->allowlist(), mAllowlist);
+ CopyURIs(aClone->superBlocklist(), mSuperBlocklist);
+ CopyURIs(aClone->superAllowlist(), mSuperAllowlist);
+}
+
+static already_AddRefed<nsIURI> GetCanonicalClone(nsIURI* aURI) {
+ nsCOMPtr<nsIURI> clone;
+ nsresult rv =
+ NS_MutateURI(aURI).SetUserPass(""_ns).SetPathQueryRef(""_ns).Finalize(
+ clone);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ return clone.forget();
+}
+
+NS_IMPL_ISUPPORTS(DomainSet, nsIDomainSet)
+
+NS_IMETHODIMP
+DomainSet::Add(nsIURI* aDomain) {
+ nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain);
+ NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE);
+ mHashTable.Insert(clone);
+ if (XRE_IsParentProcess()) {
+ return BroadcastDomainSetChange(mType, ADD_DOMAIN, aDomain);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainSet::Remove(nsIURI* aDomain) {
+ nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain);
+ NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE);
+ mHashTable.Remove(clone);
+ if (XRE_IsParentProcess()) {
+ return BroadcastDomainSetChange(mType, REMOVE_DOMAIN, aDomain);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainSet::Clear() {
+ mHashTable.Clear();
+ if (XRE_IsParentProcess()) {
+ return BroadcastDomainSetChange(mType, CLEAR_DOMAINS);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainSet::Contains(nsIURI* aDomain, bool* aContains) {
+ *aContains = false;
+ nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain);
+ NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE);
+ *aContains = mHashTable.Contains(clone);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainSet::ContainsSuperDomain(nsIURI* aDomain, bool* aContains) {
+ *aContains = false;
+ nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain);
+ NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE);
+ nsAutoCString domain;
+ nsresult rv = clone->GetHost(domain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ while (true) {
+ // Check the current domain.
+ if (mHashTable.Contains(clone)) {
+ *aContains = true;
+ return NS_OK;
+ }
+
+ // Chop off everything before the first dot, or break if there are no
+ // dots left.
+ int32_t index = domain.Find(".");
+ if (index == kNotFound) break;
+ domain.Assign(Substring(domain, index + 1));
+ rv = NS_MutateURI(clone).SetHost(domain).Finalize(clone);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // No match.
+ return NS_OK;
+}
+
+void DomainSet::CloneSet(nsTArray<RefPtr<nsIURI>>* aDomains) {
+ AppendToArray(*aDomains, mHashTable);
+}
+
+} /* namespace mozilla */
diff --git a/caps/DomainPolicy.h b/caps/DomainPolicy.h
new file mode 100644
index 0000000000..24fe3b9395
--- /dev/null
+++ b/caps/DomainPolicy.h
@@ -0,0 +1,64 @@
+/* -*- 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/. */
+
+#ifndef DomainPolicy_h__
+#define DomainPolicy_h__
+
+#include "nsIDomainPolicy.h"
+#include "nsTHashSet.h"
+#include "nsURIHashKey.h"
+
+namespace mozilla {
+
+enum DomainSetChangeType {
+ ACTIVATE_POLICY,
+ DEACTIVATE_POLICY,
+ ADD_DOMAIN,
+ REMOVE_DOMAIN,
+ CLEAR_DOMAINS
+};
+
+enum DomainSetType {
+ NO_TYPE,
+ BLOCKLIST,
+ SUPER_BLOCKLIST,
+ ALLOWLIST,
+ SUPER_ALLOWLIST
+};
+
+class DomainSet final : public nsIDomainSet {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMAINSET
+
+ explicit DomainSet(DomainSetType aType) : mType(aType) {}
+
+ void CloneSet(nsTArray<RefPtr<nsIURI>>* aDomains);
+
+ protected:
+ virtual ~DomainSet() {}
+ nsTHashSet<nsURIHashKey> mHashTable;
+ DomainSetType mType;
+};
+
+class DomainPolicy final : public nsIDomainPolicy {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMAINPOLICY
+ DomainPolicy();
+
+ private:
+ virtual ~DomainPolicy();
+
+ RefPtr<DomainSet> mBlocklist;
+ RefPtr<DomainSet> mSuperBlocklist;
+ RefPtr<DomainSet> mAllowlist;
+ RefPtr<DomainSet> mSuperAllowlist;
+};
+
+} /* namespace mozilla */
+
+#endif /* DomainPolicy_h__ */
diff --git a/caps/ExpandedPrincipal.cpp b/caps/ExpandedPrincipal.cpp
new file mode 100644
index 0000000000..ce8ccd8b82
--- /dev/null
+++ b/caps/ExpandedPrincipal.cpp
@@ -0,0 +1,401 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et 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 "ExpandedPrincipal.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIObjectInputStream.h"
+#include "nsReadableUtils.h"
+#include "mozilla/Base64.h"
+#include "mozilla/extensions/WebExtensionPolicy.h"
+#include "json/json.h"
+
+using namespace mozilla;
+
+NS_IMPL_CLASSINFO(ExpandedPrincipal, nullptr, 0, NS_EXPANDEDPRINCIPAL_CID)
+NS_IMPL_QUERY_INTERFACE_CI(ExpandedPrincipal, nsIPrincipal,
+ nsIExpandedPrincipal)
+NS_IMPL_CI_INTERFACE_GETTER(ExpandedPrincipal, nsIPrincipal,
+ nsIExpandedPrincipal)
+
+ExpandedPrincipal::ExpandedPrincipal(
+ nsTArray<nsCOMPtr<nsIPrincipal>>&& aPrincipals,
+ const nsACString& aOriginNoSuffix, const OriginAttributes& aAttrs)
+ : BasePrincipal(eExpandedPrincipal, aOriginNoSuffix, aAttrs),
+ mPrincipals(std::move(aPrincipals)) {}
+
+ExpandedPrincipal::~ExpandedPrincipal() = default;
+
+already_AddRefed<ExpandedPrincipal> ExpandedPrincipal::Create(
+ const nsTArray<nsCOMPtr<nsIPrincipal>>& aAllowList,
+ const OriginAttributes& aAttrs) {
+ nsTArray<nsCOMPtr<nsIPrincipal>> principals;
+ for (size_t i = 0; i < aAllowList.Length(); ++i) {
+ principals.AppendElement(aAllowList[i]);
+ }
+
+ nsAutoCString origin;
+ origin.AssignLiteral("[Expanded Principal [");
+ StringJoinAppend(
+ origin, ", "_ns, principals,
+ [](nsACString& dest, const nsCOMPtr<nsIPrincipal>& principal) {
+ nsAutoCString subOrigin;
+ DebugOnly<nsresult> rv = principal->GetOrigin(subOrigin);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ dest.Append(subOrigin);
+ });
+ origin.AppendLiteral("]]");
+
+ RefPtr<ExpandedPrincipal> ep =
+ new ExpandedPrincipal(std::move(principals), origin, aAttrs);
+ return ep.forget();
+}
+
+NS_IMETHODIMP
+ExpandedPrincipal::GetDomain(nsIURI** aDomain) {
+ *aDomain = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExpandedPrincipal::SetDomain(nsIURI* aDomain) { return NS_OK; }
+
+bool ExpandedPrincipal::SubsumesInternal(
+ nsIPrincipal* aOther,
+ BasePrincipal::DocumentDomainConsideration aConsideration) {
+ // If aOther is an ExpandedPrincipal too, we break it down into its component
+ // nsIPrincipals, and check subsumes on each one.
+ if (Cast(aOther)->Is<ExpandedPrincipal>()) {
+ auto* expanded = Cast(aOther)->As<ExpandedPrincipal>();
+
+ for (auto& other : expanded->AllowList()) {
+ // Use SubsumesInternal rather than Subsumes here, since OriginAttribute
+ // checks are only done between non-expanded sub-principals, and we don't
+ // need to incur the extra virtual call overhead.
+ if (!SubsumesInternal(other, aConsideration)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // We're dealing with a regular principal. One of our principals must subsume
+ // it.
+ for (uint32_t i = 0; i < mPrincipals.Length(); ++i) {
+ if (Cast(mPrincipals[i])->Subsumes(aOther, aConsideration)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ExpandedPrincipal::MayLoadInternal(nsIURI* uri) {
+ for (uint32_t i = 0; i < mPrincipals.Length(); ++i) {
+ if (BasePrincipal::Cast(mPrincipals[i])->MayLoadInternal(uri)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+uint32_t ExpandedPrincipal::GetHashValue() {
+ MOZ_CRASH("extended principal should never be used as key in a hash map");
+}
+
+NS_IMETHODIMP
+ExpandedPrincipal::GetURI(nsIURI** aURI) {
+ *aURI = nullptr;
+ return NS_OK;
+}
+
+const nsTArray<nsCOMPtr<nsIPrincipal>>& ExpandedPrincipal::AllowList() {
+ return mPrincipals;
+}
+
+NS_IMETHODIMP
+ExpandedPrincipal::GetBaseDomain(nsACString& aBaseDomain) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+ExpandedPrincipal::GetAddonId(nsAString& aAddonId) {
+ aAddonId.Truncate();
+ return NS_OK;
+};
+
+bool ExpandedPrincipal::AddonHasPermission(const nsAtom* aPerm) {
+ for (size_t i = 0; i < mPrincipals.Length(); ++i) {
+ if (BasePrincipal::Cast(mPrincipals[i])->AddonHasPermission(aPerm)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ExpandedPrincipal::AddonAllowsLoad(nsIURI* aURI,
+ bool aExplicit /* = false */) {
+ for (const auto& principal : mPrincipals) {
+ if (Cast(principal)->AddonAllowsLoad(aURI, aExplicit)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void ExpandedPrincipal::SetCsp(nsIContentSecurityPolicy* aCSP) {
+ AssertIsOnMainThread();
+ mCSP = new nsMainThreadPtrHolder<nsIContentSecurityPolicy>(
+ "ExpandedPrincipal::mCSP", aCSP);
+}
+
+NS_IMETHODIMP
+ExpandedPrincipal::GetCsp(nsIContentSecurityPolicy** aCsp) {
+ AssertIsOnMainThread();
+ NS_IF_ADDREF(*aCsp = mCSP);
+ return NS_OK;
+}
+
+nsIPrincipal* ExpandedPrincipal::PrincipalToInherit(nsIURI* aRequestedURI) {
+ if (aRequestedURI) {
+ // If a given sub-principal subsumes the given URI, use that principal for
+ // inheritance. In general, this only happens with certain CORS modes, loads
+ // with forced principal inheritance, and creation of XML documents from
+ // XMLHttpRequests or fetch requests. For URIs that normally inherit a
+ // principal (such as data: URIs), we fall back to the last principal in the
+ // allowlist.
+ for (const auto& principal : mPrincipals) {
+ if (Cast(principal)->MayLoadInternal(aRequestedURI)) {
+ return principal;
+ }
+ }
+ }
+ return mPrincipals.LastElement();
+}
+
+nsresult ExpandedPrincipal::GetScriptLocation(nsACString& aStr) {
+ aStr.AssignLiteral("[Expanded Principal [");
+ for (size_t i = 0; i < mPrincipals.Length(); ++i) {
+ if (i != 0) {
+ aStr.AppendLiteral(", ");
+ }
+
+ nsAutoCString spec;
+ nsresult rv =
+ nsJSPrincipals::get(mPrincipals.ElementAt(i))->GetScriptLocation(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aStr.Append(spec);
+ }
+ aStr.AppendLiteral("]]");
+ return NS_OK;
+}
+
+//////////////////////////////////////////
+// Methods implementing nsISerializable //
+//////////////////////////////////////////
+
+// We've had way too many issues with unversioned serializations, so
+// explicitly version this one.
+static const uint32_t kSerializationVersion = 1;
+
+NS_IMETHODIMP
+ExpandedPrincipal::Deserializer::Read(nsIObjectInputStream* aStream) {
+ uint32_t version;
+ nsresult rv = aStream->Read32(&version);
+ if (version != kSerializationVersion) {
+ MOZ_ASSERT(false,
+ "We really need to add handling of the old(?) version here");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ uint32_t count;
+ rv = aStream->Read32(&count);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsTArray<nsCOMPtr<nsIPrincipal>> principals;
+ if (!principals.SetCapacity(count, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsISupports> read;
+ rv = aStream->ReadObject(true, getter_AddRefs(read));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(read);
+ if (!principal) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ principals.AppendElement(std::move(principal));
+ }
+
+ mPrincipal = ExpandedPrincipal::Create(principals, OriginAttributes());
+ return NS_OK;
+}
+
+nsresult ExpandedPrincipal::GetSiteIdentifier(SiteIdentifier& aSite) {
+ // Call GetSiteIdentifier on each of our principals and return a new
+ // ExpandedPrincipal.
+
+ nsTArray<nsCOMPtr<nsIPrincipal>> allowlist;
+ for (const auto& principal : mPrincipals) {
+ SiteIdentifier site;
+ nsresult rv = Cast(principal)->GetSiteIdentifier(site);
+ NS_ENSURE_SUCCESS(rv, rv);
+ allowlist.AppendElement(site.GetPrincipal());
+ }
+
+ RefPtr<ExpandedPrincipal> expandedPrincipal =
+ ExpandedPrincipal::Create(allowlist, OriginAttributesRef());
+ MOZ_ASSERT(expandedPrincipal, "ExpandedPrincipal::Create returned nullptr?");
+
+ aSite.Init(expandedPrincipal);
+ return NS_OK;
+}
+
+nsresult ExpandedPrincipal::PopulateJSONObject(Json::Value& aObject) {
+ Json::Value& principalList =
+ aObject[Json::StaticString(JSONEnumKeyString<eSpecs>())] =
+ Json::arrayValue;
+ for (const auto& principal : mPrincipals) {
+ Json::Value object = Json::objectValue;
+ nsresult rv = BasePrincipal::Cast(principal)->ToJSON(object);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ principalList.append(std::move(object));
+ }
+
+ nsAutoCString suffix;
+ OriginAttributesRef().CreateSuffix(suffix);
+ if (suffix.Length() > 0) {
+ SetJSONValue<eSuffix>(aObject, suffix);
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<BasePrincipal> ExpandedPrincipal::FromProperties(
+ nsTArray<ExpandedPrincipal::KeyVal>& aFields) {
+ MOZ_ASSERT(aFields.Length() == eMax + 1, "Must have all the keys");
+ nsTArray<nsCOMPtr<nsIPrincipal>> allowList;
+ OriginAttributes attrs;
+ // The odd structure here is to make the code to not compile
+ // if all the switch enum cases haven't been codified
+
+ for (const auto& field : aFields) {
+ switch (field.key) {
+ case ExpandedPrincipal::eSpecs:
+ if (!field.valueWasSerialized) {
+ MOZ_ASSERT(false,
+ "Expanded principals require specs in serialized JSON");
+ return nullptr;
+ }
+ for (const nsACString& each : field.value.Split(',')) {
+ nsAutoCString result;
+ nsresult rv;
+ rv = Base64Decode(each, result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to decode");
+
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ nsCOMPtr<nsIPrincipal> principal = BasePrincipal::FromJSON(result);
+ allowList.AppendElement(principal);
+ }
+ break;
+ case ExpandedPrincipal::eSuffix:
+ if (field.valueWasSerialized) {
+ bool ok = attrs.PopulateFromSuffix(field.value);
+ if (!ok) {
+ return nullptr;
+ }
+ }
+ break;
+ }
+ }
+
+ if (allowList.Length() == 0) {
+ return nullptr;
+ }
+
+ RefPtr<ExpandedPrincipal> expandedPrincipal =
+ ExpandedPrincipal::Create(allowList, attrs);
+
+ return expandedPrincipal.forget();
+}
+
+/* static */
+already_AddRefed<BasePrincipal> ExpandedPrincipal::FromProperties(
+ const Json::Value& aJSON) {
+ MOZ_ASSERT(aJSON.size() <= eMax + 1, "Must have at most, all the properties");
+ const std::string specs = std::to_string(eSpecs);
+ const std::string suffix = std::to_string(eSuffix);
+ MOZ_ASSERT(aJSON.isMember(specs), "The eSpecs member is required");
+ MOZ_ASSERT(aJSON.size() == 1 || aJSON.isMember(suffix),
+ "eSuffix is optional");
+
+ const auto* specsValue =
+ aJSON.find(specs.c_str(), specs.c_str() + specs.length());
+ if (!specsValue) {
+ MOZ_ASSERT(false, "Expanded principals require specs in serialized JSON");
+ return nullptr;
+ }
+
+ nsTArray<nsCOMPtr<nsIPrincipal>> allowList;
+ for (const auto& principalJSON : *specsValue) {
+ if (nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::FromJSON(principalJSON)) {
+ allowList.AppendElement(principal);
+ }
+ }
+
+ if (allowList.Length() == 0) {
+ return nullptr;
+ }
+
+ OriginAttributes attrs;
+ if (aJSON.isMember(suffix)) {
+ const auto& value = aJSON[suffix];
+ if (!value.isString()) {
+ return nullptr;
+ }
+
+ bool ok = attrs.PopulateFromSuffix(nsDependentCString(value.asCString()));
+ if (!ok) {
+ return nullptr;
+ }
+ }
+
+ RefPtr<ExpandedPrincipal> expandedPrincipal =
+ ExpandedPrincipal::Create(allowList, attrs);
+
+ return expandedPrincipal.forget();
+}
+
+NS_IMETHODIMP
+ExpandedPrincipal::IsThirdPartyURI(nsIURI* aURI, bool* aRes) {
+ // ExpandedPrincipal for extension content scripts consist of two principals,
+ // the document's principal and the extension's principal.
+ // To make sure that the third-party check behaves like the web page on which
+ // the content script is running, ignore the extension's principal.
+
+ for (const auto& principal : mPrincipals) {
+ if (!Cast(principal)->AddonPolicyCore()) {
+ return Cast(principal)->IsThirdPartyURI(aURI, aRes);
+ }
+ }
+
+ if (mPrincipals.IsEmpty()) {
+ *aRes = true;
+ return NS_OK;
+ }
+
+ return Cast(mPrincipals[0])->IsThirdPartyURI(aURI, aRes);
+}
diff --git a/caps/ExpandedPrincipal.h b/caps/ExpandedPrincipal.h
new file mode 100644
index 0000000000..748a0c4138
--- /dev/null
+++ b/caps/ExpandedPrincipal.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef ExpandedPrincipal_h
+#define ExpandedPrincipal_h
+
+#include "nsCOMPtr.h"
+#include "nsJSPrincipals.h"
+#include "nsProxyRelease.h"
+#include "nsTArray.h"
+#include "nsNetUtil.h"
+#include "mozilla/BasePrincipal.h"
+
+class nsIContentSecurityPolicy;
+
+namespace Json {
+class Value;
+}
+
+class ExpandedPrincipal : public nsIExpandedPrincipal,
+ public mozilla::BasePrincipal {
+ public:
+ static already_AddRefed<ExpandedPrincipal> Create(
+ const nsTArray<nsCOMPtr<nsIPrincipal>>& aAllowList,
+ const mozilla::OriginAttributes& aAttrs);
+
+ static PrincipalKind Kind() { return eExpandedPrincipal; }
+
+ NS_DECL_NSIEXPANDEDPRINCIPAL
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef() override {
+ return nsJSPrincipals::AddRef();
+ };
+ NS_IMETHOD_(MozExternalRefCountType) Release() override {
+ return nsJSPrincipals::Release();
+ };
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+ uint32_t GetHashValue() override;
+ NS_IMETHOD GetURI(nsIURI** aURI) override;
+ NS_IMETHOD GetDomain(nsIURI** aDomain) override;
+ NS_IMETHOD SetDomain(nsIURI* aDomain) override;
+ NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
+ NS_IMETHOD GetAddonId(nsAString& aAddonId) override;
+ NS_IMETHOD IsThirdPartyURI(nsIURI* uri, bool* aRes) override;
+ virtual bool AddonHasPermission(const nsAtom* aPerm) override;
+ virtual nsresult GetScriptLocation(nsACString& aStr) override;
+
+ bool AddonAllowsLoad(nsIURI* aURI, bool aExplicit = false);
+
+ void SetCsp(nsIContentSecurityPolicy* aCSP);
+
+ // Returns the principal to inherit when this principal requests the given
+ // URL. See BasePrincipal::PrincipalToInherit.
+ nsIPrincipal* PrincipalToInherit(nsIURI* aRequestedURI = nullptr);
+
+ nsresult GetSiteIdentifier(mozilla::SiteIdentifier& aSite) override;
+
+ virtual nsresult PopulateJSONObject(Json::Value& aObject) override;
+ // Serializable keys are the valid enum fields the serialization supports
+ enum SerializableKeys : uint8_t { eSpecs = 0, eSuffix, eMax = eSuffix };
+ typedef mozilla::BasePrincipal::KeyValT<SerializableKeys> KeyVal;
+
+ // This is the legacy serializer for expanded principals. See note for
+ // `IsLegacyFormat` in BasePrincipal.cpp.
+ static already_AddRefed<BasePrincipal> FromProperties(
+ nsTArray<ExpandedPrincipal::KeyVal>& aFields);
+
+ // This is the new serializer for expanded principals. See note for
+ // `IsLegacyFormat` in BasePrincipal.cpp.
+ static already_AddRefed<BasePrincipal> FromProperties(
+ const Json::Value& aJSON);
+
+ class Deserializer : public BasePrincipal::Deserializer {
+ public:
+ NS_IMETHOD Read(nsIObjectInputStream* aStream) override;
+ };
+
+ protected:
+ explicit ExpandedPrincipal(nsTArray<nsCOMPtr<nsIPrincipal>>&& aPrincipals,
+ const nsACString& aOriginNoSuffix,
+ const mozilla::OriginAttributes& aAttrs);
+
+ virtual ~ExpandedPrincipal();
+
+ bool SubsumesInternal(nsIPrincipal* aOther,
+ DocumentDomainConsideration aConsideration) override;
+
+ bool MayLoadInternal(nsIURI* aURI) override;
+
+ private:
+ const nsTArray<nsCOMPtr<nsIPrincipal>> mPrincipals;
+ nsMainThreadPtrHandle<nsIContentSecurityPolicy> mCSP
+ MOZ_GUARDED_BY(mozilla::sMainThreadCapability);
+};
+
+#define NS_EXPANDEDPRINCIPAL_CID \
+ { \
+ 0xe8ee88b0, 0x5571, 0x4086, { \
+ 0xa4, 0x5b, 0x39, 0xa7, 0x16, 0x90, 0x6b, 0xdb \
+ } \
+ }
+
+#endif // ExpandedPrincipal_h
diff --git a/caps/NullPrincipal.cpp b/caps/NullPrincipal.cpp
new file mode 100644
index 0000000000..f0a0ad4708
--- /dev/null
+++ b/caps/NullPrincipal.cpp
@@ -0,0 +1,333 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 sts=2 ts=2 et 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/. */
+
+/**
+ * This is the principal that has no rights and can't be accessed by
+ * anything other than itself and chrome; null principals are not
+ * same-origin with anything but themselves.
+ */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsDocShell.h"
+#include "NullPrincipal.h"
+#include "DefaultURI.h"
+#include "nsSimpleURI.h"
+#include "nsIClassInfoImpl.h"
+#include "nsNetCID.h"
+#include "nsError.h"
+#include "nsEscape.h"
+#include "ContentPrincipal.h"
+#include "nsScriptSecurityManager.h"
+#include "pratom.h"
+#include "nsIObjectInputStream.h"
+
+#include "json/json.h"
+
+using namespace mozilla;
+
+NS_IMPL_CLASSINFO(NullPrincipal, nullptr, 0, NS_NULLPRINCIPAL_CID)
+NS_IMPL_QUERY_INTERFACE_CI(NullPrincipal, nsIPrincipal)
+NS_IMPL_CI_INTERFACE_GETTER(NullPrincipal, nsIPrincipal)
+
+NullPrincipal::NullPrincipal(nsIURI* aURI, const nsACString& aOriginNoSuffix,
+ const OriginAttributes& aOriginAttributes)
+ : BasePrincipal(eNullPrincipal, aOriginNoSuffix, aOriginAttributes),
+ mURI(aURI) {}
+
+/* static */
+already_AddRefed<NullPrincipal> NullPrincipal::CreateWithInheritedAttributes(
+ nsIPrincipal* aInheritFrom) {
+ MOZ_ASSERT(aInheritFrom);
+ nsCOMPtr<nsIURI> uri = CreateURI(aInheritFrom);
+ return Create(Cast(aInheritFrom)->OriginAttributesRef(), uri);
+}
+
+/* static */
+already_AddRefed<NullPrincipal> NullPrincipal::Create(
+ const OriginAttributes& aOriginAttributes, nsIURI* aNullPrincipalURI) {
+ nsCOMPtr<nsIURI> uri = aNullPrincipalURI;
+ if (!uri) {
+ uri = NullPrincipal::CreateURI(nullptr);
+ }
+
+ MOZ_RELEASE_ASSERT(uri->SchemeIs(NS_NULLPRINCIPAL_SCHEME));
+
+ nsAutoCString originNoSuffix;
+ DebugOnly<nsresult> rv = uri->GetSpec(originNoSuffix);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ RefPtr<NullPrincipal> nullPrin =
+ new NullPrincipal(uri, originNoSuffix, aOriginAttributes);
+ return nullPrin.forget();
+}
+
+/* static */
+already_AddRefed<NullPrincipal> NullPrincipal::CreateWithoutOriginAttributes() {
+ return NullPrincipal::Create(OriginAttributes(), nullptr);
+}
+
+void NullPrincipal::EscapePrecursorQuery(nsACString& aPrecursorQuery) {
+ // origins should not contain existing escape sequences, so set `esc_Forced`
+ // to force any `%` in the input to be escaped in addition to non-ascii,
+ // control characters and DEL.
+ nsCString modified;
+ if (NS_EscapeURLSpan(aPrecursorQuery, esc_Query | esc_Forced, modified)) {
+ aPrecursorQuery.Assign(std::move(modified));
+ }
+}
+
+void NullPrincipal::UnescapePrecursorQuery(nsACString& aPrecursorQuery) {
+ nsCString modified;
+ if (NS_UnescapeURL(aPrecursorQuery.BeginReading(), aPrecursorQuery.Length(),
+ /* aFlags */ 0, modified)) {
+ aPrecursorQuery.Assign(std::move(modified));
+ }
+}
+
+already_AddRefed<nsIURI> NullPrincipal::CreateURI(
+ nsIPrincipal* aPrecursor, const nsID* aNullPrincipalID) {
+ nsCOMPtr<nsIURIMutator> iMutator;
+ if (StaticPrefs::network_url_useDefaultURI()) {
+ iMutator = new mozilla::net::DefaultURI::Mutator();
+ } else {
+ iMutator = new mozilla::net::nsSimpleURI::Mutator();
+ }
+
+ nsID uuid = aNullPrincipalID ? *aNullPrincipalID : nsID::GenerateUUID();
+
+ NS_MutateURI mutator(iMutator);
+ mutator.SetSpec(NS_NULLPRINCIPAL_SCHEME ":"_ns +
+ nsDependentCString(nsIDToCString(uuid).get()));
+
+ // If there's a precursor URI, encode it in the null principal URI's query.
+ if (aPrecursor) {
+ nsAutoCString precursorOrigin;
+ switch (BasePrincipal::Cast(aPrecursor)->Kind()) {
+ case eNullPrincipal: {
+ // If the precursor null principal has a precursor, inherit it.
+ if (nsCOMPtr<nsIURI> nullPrecursorURI = aPrecursor->GetURI()) {
+ MOZ_ALWAYS_SUCCEEDS(nullPrecursorURI->GetQuery(precursorOrigin));
+ }
+ break;
+ }
+ case eContentPrincipal: {
+ MOZ_ALWAYS_SUCCEEDS(aPrecursor->GetOriginNoSuffix(precursorOrigin));
+#ifdef DEBUG
+ nsAutoCString original(precursorOrigin);
+#endif
+ EscapePrecursorQuery(precursorOrigin);
+#ifdef DEBUG
+ nsAutoCString unescaped(precursorOrigin);
+ UnescapePrecursorQuery(unescaped);
+ MOZ_ASSERT(unescaped == original,
+ "cannot recover original precursor origin after escape");
+#endif
+ break;
+ }
+
+ // For now, we won't track expanded or system principal precursors. We may
+ // want to track expanded principal precursors in the future, but it's
+ // unlikely we'll want to track system principal precursors.
+ case eExpandedPrincipal:
+ case eSystemPrincipal:
+ break;
+ }
+ if (!precursorOrigin.IsEmpty()) {
+ mutator.SetQuery(precursorOrigin);
+ }
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(mutator.Finalize(getter_AddRefs(uri)));
+ return uri.forget();
+}
+
+nsresult NullPrincipal::GetScriptLocation(nsACString& aStr) {
+ return mURI->GetSpec(aStr);
+}
+
+/**
+ * nsIPrincipal implementation
+ */
+
+uint32_t NullPrincipal::GetHashValue() { return (NS_PTR_TO_INT32(this) >> 2); }
+
+NS_IMETHODIMP
+NullPrincipal::GetURI(nsIURI** aURI) {
+ nsCOMPtr<nsIURI> uri = mURI;
+ uri.forget(aURI);
+ return NS_OK;
+}
+NS_IMETHODIMP
+NullPrincipal::GetIsOriginPotentiallyTrustworthy(bool* aResult) {
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullPrincipal::GetDomain(nsIURI** aDomain) {
+ nsCOMPtr<nsIURI> uri = mURI;
+ uri.forget(aDomain);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullPrincipal::SetDomain(nsIURI* aDomain) {
+ // I think the right thing to do here is to just throw... Silently failing
+ // seems counterproductive.
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+bool NullPrincipal::MayLoadInternal(nsIURI* aURI) {
+ // Also allow the load if we are the principal of the URI being checked.
+ nsCOMPtr<nsIPrincipal> blobPrincipal;
+ if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal(
+ aURI, getter_AddRefs(blobPrincipal))) {
+ MOZ_ASSERT(blobPrincipal);
+ return SubsumesInternal(blobPrincipal,
+ BasePrincipal::ConsiderDocumentDomain);
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+NullPrincipal::GetBaseDomain(nsACString& aBaseDomain) {
+ // For a null principal, we use our unique uuid as the base domain.
+ return mURI->GetPathQueryRef(aBaseDomain);
+}
+
+NS_IMETHODIMP
+NullPrincipal::GetAddonId(nsAString& aAddonId) {
+ aAddonId.Truncate();
+ return NS_OK;
+};
+
+/**
+ * nsISerializable implementation
+ */
+NS_IMETHODIMP
+NullPrincipal::Deserializer::Read(nsIObjectInputStream* aStream) {
+ nsAutoCString spec;
+ nsresult rv = aStream->ReadCString(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString suffix;
+ rv = aStream->ReadCString(suffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ OriginAttributes attrs;
+ bool ok = attrs.PopulateFromSuffix(suffix);
+ NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
+
+ mPrincipal = NullPrincipal::Create(attrs, uri);
+ NS_ENSURE_TRUE(mPrincipal, NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+nsresult NullPrincipal::PopulateJSONObject(Json::Value& aObject) {
+ nsAutoCString principalURI;
+ nsresult rv = mURI->GetSpec(principalURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetJSONValue<eSpec>(aObject, principalURI);
+
+ nsAutoCString suffix;
+ OriginAttributesRef().CreateSuffix(suffix);
+ if (suffix.Length() > 0) {
+ SetJSONValue<eSuffix>(aObject, suffix);
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<BasePrincipal> NullPrincipal::FromProperties(
+ nsTArray<NullPrincipal::KeyVal>& aFields) {
+ MOZ_ASSERT(aFields.Length() == eMax + 1, "Must have all the keys");
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+ OriginAttributes attrs;
+
+ // The odd structure here is to make the code to not compile
+ // if all the switch enum cases haven't been codified
+ for (const auto& field : aFields) {
+ switch (field.key) {
+ case NullPrincipal::eSpec:
+ if (!field.valueWasSerialized) {
+ MOZ_ASSERT(false,
+ "Null principals require a spec URI in serialized JSON");
+ return nullptr;
+ }
+ rv = NS_NewURI(getter_AddRefs(uri), field.value);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ break;
+ case NullPrincipal::eSuffix:
+ bool ok = attrs.PopulateFromSuffix(field.value);
+ if (!ok) {
+ return nullptr;
+ }
+ break;
+ }
+ }
+
+ if (!uri) {
+ MOZ_ASSERT(false, "No URI deserialized");
+ return nullptr;
+ }
+
+ return NullPrincipal::Create(attrs, uri);
+}
+
+NS_IMETHODIMP
+NullPrincipal::GetPrecursorPrincipal(nsIPrincipal** aPrincipal) {
+ *aPrincipal = nullptr;
+
+ nsAutoCString query;
+ if (NS_FAILED(mURI->GetQuery(query)) || query.IsEmpty()) {
+ return NS_OK;
+ }
+ UnescapePrecursorQuery(query);
+
+ nsCOMPtr<nsIURI> precursorURI;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(precursorURI), query))) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Failed to parse precursor from nullprincipal query");
+ return NS_OK;
+ }
+
+ // If our precursor is another null principal, re-construct it. This can
+ // happen if a null principal without a precursor causes another principal to
+ // be created.
+ if (precursorURI->SchemeIs(NS_NULLPRINCIPAL_SCHEME)) {
+#ifdef DEBUG
+ nsAutoCString precursorQuery;
+ precursorURI->GetQuery(precursorQuery);
+ MOZ_ASSERT(precursorQuery.IsEmpty(),
+ "Null principal with nested precursors?");
+#endif
+ *aPrincipal =
+ NullPrincipal::Create(OriginAttributesRef(), precursorURI).take();
+ return NS_OK;
+ }
+
+ RefPtr<BasePrincipal> contentPrincipal =
+ BasePrincipal::CreateContentPrincipal(precursorURI,
+ OriginAttributesRef());
+ // If `CreateContentPrincipal` failed, it will create a new NullPrincipal and
+ // return that instead. We only want to return real content principals here.
+ if (!contentPrincipal || !contentPrincipal->Is<ContentPrincipal>()) {
+ return NS_OK;
+ }
+ contentPrincipal.forget(aPrincipal);
+ return NS_OK;
+}
diff --git a/caps/NullPrincipal.h b/caps/NullPrincipal.h
new file mode 100644
index 0000000000..cbce27c700
--- /dev/null
+++ b/caps/NullPrincipal.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/**
+ * This is the principal that has no rights and can't be accessed by
+ * anything other than itself and chrome; null principals are not
+ * same-origin with anything but themselves.
+ */
+
+#ifndef mozilla_NullPrincipal_h
+#define mozilla_NullPrincipal_h
+
+#include "nsIPrincipal.h"
+#include "nsJSPrincipals.h"
+#include "nsCOMPtr.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "gtest/MozGtestFriend.h"
+
+class nsIDocShell;
+class nsIURI;
+namespace Json {
+class Value;
+}
+
+#define NS_NULLPRINCIPAL_CID \
+ { \
+ 0xbd066e5f, 0x146f, 0x4472, { \
+ 0x83, 0x31, 0x7b, 0xfd, 0x05, 0xb1, 0xed, 0x90 \
+ } \
+ }
+
+#define NS_NULLPRINCIPAL_SCHEME "moz-nullprincipal"
+
+namespace mozilla {
+
+class NullPrincipal final : public BasePrincipal {
+ public:
+ static PrincipalKind Kind() { return eNullPrincipal; }
+
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+ uint32_t GetHashValue() override;
+ NS_IMETHOD GetURI(nsIURI** aURI) override;
+ NS_IMETHOD GetIsOriginPotentiallyTrustworthy(bool* aResult) override;
+ NS_IMETHOD GetDomain(nsIURI** aDomain) override;
+ NS_IMETHOD SetDomain(nsIURI* aDomain) override;
+ NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
+ NS_IMETHOD GetAddonId(nsAString& aAddonId) override;
+ NS_IMETHOD GetPrecursorPrincipal(nsIPrincipal** aPrecursor) override;
+
+ // Create a NullPrincipal, inheriting origin attributes from the given
+ // principal.
+ // If aInheritFrom is a content principal, or has a content principal
+ // precursor, it will be used as the precursor for this principal.
+ static already_AddRefed<NullPrincipal> CreateWithInheritedAttributes(
+ nsIPrincipal* aInheritFrom);
+
+ // Create a new NullPrincipal with the specified OriginAttributes.
+ //
+ // If `aNullPrincipalURI` is specified, it must be a NS_NULLPRINCIPAL_SCHEME
+ // URI previously created using `NullPrincipal::CreateURI`, and will be used
+ // as the origin URI for this principal.
+ static already_AddRefed<NullPrincipal> Create(
+ const OriginAttributes& aOriginAttributes,
+ nsIURI* aNullPrincipalURI = nullptr);
+
+ static already_AddRefed<NullPrincipal> CreateWithoutOriginAttributes();
+
+ // Generates a new unique `moz-nullprincipal:` URI. If `aPrecursor` is
+ // specified, it will be included in the generated URI as the null principal's
+ // precursor.
+ //
+ // The `aPrincipalID` attribute is used to force the creation of a
+ // deterministic NullPrincipal in situations where that is required. Avoid
+ // using this parameter unless absolutely necessary.
+ static already_AddRefed<nsIURI> CreateURI(nsIPrincipal* aPrecursor = nullptr,
+ const nsID* aPrincipalID = nullptr);
+
+ virtual nsresult GetScriptLocation(nsACString& aStr) override;
+
+ nsresult GetSiteIdentifier(SiteIdentifier& aSite) override {
+ aSite.Init(this);
+ return NS_OK;
+ }
+
+ virtual nsresult PopulateJSONObject(Json::Value& aObject) override;
+
+ // Serializable keys are the valid enum fields the serialization supports
+ enum SerializableKeys : uint8_t { eSpec = 0, eSuffix, eMax = eSuffix };
+ typedef mozilla::BasePrincipal::KeyValT<SerializableKeys> KeyVal;
+
+ static already_AddRefed<BasePrincipal> FromProperties(
+ nsTArray<NullPrincipal::KeyVal>& aFields);
+
+ class Deserializer : public BasePrincipal::Deserializer {
+ public:
+ NS_IMETHOD Read(nsIObjectInputStream* aStream) override;
+ };
+
+ protected:
+ NullPrincipal(nsIURI* aURI, const nsACString& aOriginNoSuffix,
+ const OriginAttributes& aOriginAttributes);
+
+ virtual ~NullPrincipal() = default;
+
+ bool SubsumesInternal(nsIPrincipal* aOther,
+ DocumentDomainConsideration aConsideration) override {
+ MOZ_ASSERT(aOther);
+ return FastEquals(aOther);
+ }
+
+ bool MayLoadInternal(nsIURI* aURI) override;
+
+ const nsCOMPtr<nsIURI> mURI;
+
+ private:
+ FRIEND_TEST(NullPrincipalPrecursor, EscapingRoundTrips);
+
+ static void EscapePrecursorQuery(nsACString& aPrecursorQuery);
+ static void UnescapePrecursorQuery(nsACString& aPrecursorQuery);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_NullPrincipal_h
diff --git a/caps/OriginAttributes.cpp b/caps/OriginAttributes.cpp
new file mode 100644
index 0000000000..734a230495
--- /dev/null
+++ b/caps/OriginAttributes.cpp
@@ -0,0 +1,494 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et 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 "mozilla/OriginAttributes.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "nsURLHelper.h"
+
+static const char kSourceChar = ':';
+static const char kSanitizedChar = '+';
+
+namespace mozilla {
+
+static void MakeTopLevelInfo(const nsACString& aScheme, const nsACString& aHost,
+ int32_t aPort, bool aUseSite,
+ nsAString& aTopLevelInfo) {
+ if (!aUseSite) {
+ aTopLevelInfo.Assign(NS_ConvertUTF8toUTF16(aHost));
+ return;
+ }
+
+ // Note: If you change the serialization of the partition-key, please update
+ // StoragePrincipalHelper.cpp too.
+
+ nsAutoCString site;
+ site.AssignLiteral("(");
+ site.Append(aScheme);
+ site.Append(",");
+ site.Append(aHost);
+ if (aPort != -1) {
+ site.Append(",");
+ site.AppendInt(aPort);
+ }
+ site.AppendLiteral(")");
+
+ aTopLevelInfo.Assign(NS_ConvertUTF8toUTF16(site));
+}
+
+static void MakeTopLevelInfo(const nsACString& aScheme, const nsACString& aHost,
+ bool aUseSite, nsAString& aTopLevelInfo) {
+ MakeTopLevelInfo(aScheme, aHost, -1, aUseSite, aTopLevelInfo);
+}
+
+static void PopulateTopLevelInfoFromURI(const bool aIsTopLevelDocument,
+ nsIURI* aURI, bool aIsFirstPartyEnabled,
+ bool aForced, bool aUseSite,
+ nsString OriginAttributes::*aTarget,
+ OriginAttributes& aOriginAttributes) {
+ nsresult rv;
+
+ if (!aURI) {
+ return;
+ }
+
+ // If the prefs are off or this is not a top level load, bail out.
+ if ((!aIsFirstPartyEnabled || !aIsTopLevelDocument) && !aForced) {
+ return;
+ }
+
+ nsAString& topLevelInfo = aOriginAttributes.*aTarget;
+
+ nsAutoCString scheme;
+ nsCOMPtr<nsIURI> uri = aURI;
+ // The URI could be nested (for example view-source:http://example.com), in
+ // that case we want to get the innermost URI (http://example.com).
+ nsCOMPtr<nsINestedURI> nestedURI;
+ do {
+ NS_ENSURE_SUCCESS_VOID(uri->GetScheme(scheme));
+ nestedURI = do_QueryInterface(uri);
+ // We can't just use GetInnermostURI on the nested URI, since that would
+ // also unwrap some about: URIs to hidden moz-safe-about: URIs, which we do
+ // not want. Thus we loop through with GetInnerURI until the URI isn't
+ // nested anymore or we encounter a about: scheme.
+ } while (nestedURI && !scheme.EqualsLiteral("about") &&
+ NS_SUCCEEDED(nestedURI->GetInnerURI(getter_AddRefs(uri))));
+
+ if (scheme.EqualsLiteral("about")) {
+ MakeTopLevelInfo(scheme, nsLiteralCString(ABOUT_URI_FIRST_PARTY_DOMAIN),
+ aUseSite, topLevelInfo);
+ return;
+ }
+
+ // If a null principal URI was provided, extract the UUID portion of the URI
+ // to use for the first-party domain.
+ if (scheme.EqualsLiteral("moz-nullprincipal")) {
+ // Get the UUID portion of the URI, ignoring the precursor principal.
+ nsAutoCString filePath;
+ rv = uri->GetFilePath(filePath);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ // Remove the `{}` characters from both ends.
+ filePath.Mid(filePath, 1, filePath.Length() - 2);
+ filePath.AppendLiteral(".mozilla");
+ // Store the generated file path.
+ topLevelInfo = NS_ConvertUTF8toUTF16(filePath);
+ return;
+ }
+
+ // Add-on principals should never get any first-party domain
+ // attributes in order to guarantee their storage integrity when switching
+ // FPI on and off.
+ if (scheme.EqualsLiteral("moz-extension")) {
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> blobPrincipal;
+ if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal(
+ uri, getter_AddRefs(blobPrincipal))) {
+ MOZ_ASSERT(blobPrincipal);
+ topLevelInfo = blobPrincipal->OriginAttributesRef().*aTarget;
+ return;
+ }
+
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ MOZ_ASSERT(tldService);
+ NS_ENSURE_TRUE_VOID(tldService);
+
+ nsAutoCString baseDomain;
+ rv = tldService->GetBaseDomain(uri, 0, baseDomain);
+ if (NS_SUCCEEDED(rv)) {
+ MakeTopLevelInfo(scheme, baseDomain, aUseSite, topLevelInfo);
+ return;
+ }
+
+ // Saving before rv is overwritten.
+ bool isIpAddress = (rv == NS_ERROR_HOST_IS_IP_ADDRESS);
+ bool isInsufficientDomainLevels = (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
+
+ int32_t port;
+ rv = uri->GetPort(&port);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsAutoCString host;
+ rv = uri->GetHost(host);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (isIpAddress) {
+ // If the host is an IPv4/IPv6 address, we still accept it as a
+ // valid topLevelInfo.
+ nsAutoCString ipAddr;
+
+ if (net_IsValidIPv6Addr(host)) {
+ // According to RFC2732, the host of an IPv6 address should be an
+ // IPv6reference. The GetHost() of nsIURI will only return the IPv6
+ // address. So, we need to convert it back to IPv6reference here.
+ ipAddr.AssignLiteral("[");
+ ipAddr.Append(host);
+ ipAddr.AppendLiteral("]");
+ } else {
+ ipAddr = host;
+ }
+
+ MakeTopLevelInfo(scheme, ipAddr, port, aUseSite, topLevelInfo);
+ return;
+ }
+
+ if (aUseSite) {
+ MakeTopLevelInfo(scheme, host, port, aUseSite, topLevelInfo);
+ return;
+ }
+
+ if (isInsufficientDomainLevels) {
+ nsAutoCString publicSuffix;
+ rv = tldService->GetPublicSuffix(uri, publicSuffix);
+ if (NS_SUCCEEDED(rv)) {
+ MakeTopLevelInfo(scheme, publicSuffix, port, aUseSite, topLevelInfo);
+ return;
+ }
+ }
+}
+
+void OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument,
+ nsIURI* aURI, bool aForced) {
+ PopulateTopLevelInfoFromURI(
+ aIsTopLevelDocument, aURI, IsFirstPartyEnabled(), aForced,
+ StaticPrefs::privacy_firstparty_isolate_use_site(),
+ &OriginAttributes::mFirstPartyDomain, *this);
+}
+
+void OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument,
+ const nsACString& aDomain) {
+ SetFirstPartyDomain(aIsTopLevelDocument, NS_ConvertUTF8toUTF16(aDomain));
+}
+
+void OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument,
+ const nsAString& aDomain,
+ bool aForced) {
+ // If the pref is off or this is not a top level load, bail out.
+ if ((!IsFirstPartyEnabled() || !aIsTopLevelDocument) && !aForced) {
+ return;
+ }
+
+ mFirstPartyDomain = aDomain;
+}
+
+void OriginAttributes::SetPartitionKey(nsIURI* aURI) {
+ PopulateTopLevelInfoFromURI(
+ false /* aIsTopLevelDocument */, aURI, IsFirstPartyEnabled(),
+ true /* aForced */, StaticPrefs::privacy_dynamic_firstparty_use_site(),
+ &OriginAttributes::mPartitionKey, *this);
+}
+
+void OriginAttributes::SetPartitionKey(const nsACString& aDomain) {
+ SetPartitionKey(NS_ConvertUTF8toUTF16(aDomain));
+}
+
+void OriginAttributes::SetPartitionKey(const nsAString& aDomain) {
+ mPartitionKey = aDomain;
+}
+
+void OriginAttributes::CreateSuffix(nsACString& aStr) const {
+ URLParams params;
+ nsAutoString value;
+
+ //
+ // Important: While serializing any string-valued attributes, perform a
+ // release-mode assertion to make sure that they don't contain characters that
+ // will break the quota manager when it uses the serialization for file
+ // naming.
+ //
+
+ if (mInIsolatedMozBrowser) {
+ params.Set(u"inBrowser"_ns, u"1"_ns);
+ }
+
+ if (mUserContextId != nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) {
+ value.Truncate();
+ value.AppendInt(mUserContextId);
+ params.Set(u"userContextId"_ns, value);
+ }
+
+ if (mPrivateBrowsingId) {
+ value.Truncate();
+ value.AppendInt(mPrivateBrowsingId);
+ params.Set(u"privateBrowsingId"_ns, value);
+ }
+
+ if (!mFirstPartyDomain.IsEmpty()) {
+ nsAutoString sanitizedFirstPartyDomain(mFirstPartyDomain);
+ sanitizedFirstPartyDomain.ReplaceChar(kSourceChar, kSanitizedChar);
+
+ params.Set(u"firstPartyDomain"_ns, sanitizedFirstPartyDomain);
+ }
+
+ if (!mGeckoViewSessionContextId.IsEmpty()) {
+ nsAutoString sanitizedGeckoViewUserContextId(mGeckoViewSessionContextId);
+ sanitizedGeckoViewUserContextId.ReplaceChar(
+ dom::quota::QuotaManager::kReplaceChars16, kSanitizedChar);
+
+ params.Set(u"geckoViewUserContextId"_ns, sanitizedGeckoViewUserContextId);
+ }
+
+ if (!mPartitionKey.IsEmpty()) {
+ nsAutoString sanitizedPartitionKey(mPartitionKey);
+ sanitizedPartitionKey.ReplaceChar(kSourceChar, kSanitizedChar);
+
+ params.Set(u"partitionKey"_ns, sanitizedPartitionKey);
+ }
+
+ aStr.Truncate();
+
+ params.Serialize(value, true);
+ if (!value.IsEmpty()) {
+ aStr.AppendLiteral("^");
+ aStr.Append(NS_ConvertUTF16toUTF8(value));
+ }
+
+// In debug builds, check the whole string for illegal characters too (just in
+// case).
+#ifdef DEBUG
+ nsAutoCString str;
+ str.Assign(aStr);
+ MOZ_ASSERT(str.FindCharInSet(dom::quota::QuotaManager::kReplaceChars) ==
+ kNotFound);
+#endif
+}
+
+already_AddRefed<nsAtom> OriginAttributes::CreateSuffixAtom() const {
+ nsAutoCString suffix;
+ CreateSuffix(suffix);
+ return NS_Atomize(suffix);
+}
+
+void OriginAttributes::CreateAnonymizedSuffix(nsACString& aStr) const {
+ OriginAttributes attrs = *this;
+
+ if (!attrs.mFirstPartyDomain.IsEmpty()) {
+ attrs.mFirstPartyDomain.AssignLiteral("_anonymizedFirstPartyDomain_");
+ }
+
+ if (!attrs.mPartitionKey.IsEmpty()) {
+ attrs.mPartitionKey.AssignLiteral("_anonymizedPartitionKey_");
+ }
+
+ attrs.CreateSuffix(aStr);
+}
+
+bool OriginAttributes::PopulateFromSuffix(const nsACString& aStr) {
+ if (aStr.IsEmpty()) {
+ return true;
+ }
+
+ if (aStr[0] != '^') {
+ return false;
+ }
+
+ // If a non-default mPrivateBrowsingId is passed and is not present in the
+ // suffix, then it will retain the id when it should be default according
+ // to the suffix. Set to default before iterating to fix this.
+ mPrivateBrowsingId = nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID;
+
+ // Checking that we are in a pristine state
+
+ MOZ_RELEASE_ASSERT(mUserContextId == 0);
+ MOZ_RELEASE_ASSERT(mPrivateBrowsingId == 0);
+ MOZ_RELEASE_ASSERT(mFirstPartyDomain.IsEmpty());
+ MOZ_RELEASE_ASSERT(mGeckoViewSessionContextId.IsEmpty());
+ MOZ_RELEASE_ASSERT(mPartitionKey.IsEmpty());
+
+ return URLParams::Parse(
+ Substring(aStr, 1, aStr.Length() - 1),
+ [this](const nsAString& aName, const nsAString& aValue) {
+ if (aName.EqualsLiteral("inBrowser")) {
+ if (!aValue.EqualsLiteral("1")) {
+ return false;
+ }
+
+ mInIsolatedMozBrowser = true;
+ return true;
+ }
+
+ if (aName.EqualsLiteral("addonId") || aName.EqualsLiteral("appId")) {
+ // No longer supported. Silently ignore so that legacy origin strings
+ // don't cause failures.
+ return true;
+ }
+
+ if (aName.EqualsLiteral("userContextId")) {
+ nsresult rv;
+ int64_t val = aValue.ToInteger64(&rv);
+ NS_ENSURE_SUCCESS(rv, false);
+ NS_ENSURE_TRUE(val <= UINT32_MAX, false);
+ mUserContextId = static_cast<uint32_t>(val);
+
+ return true;
+ }
+
+ if (aName.EqualsLiteral("privateBrowsingId")) {
+ nsresult rv;
+ int64_t val = aValue.ToInteger64(&rv);
+ NS_ENSURE_SUCCESS(rv, false);
+ NS_ENSURE_TRUE(val >= 0 && val <= UINT32_MAX, false);
+ mPrivateBrowsingId = static_cast<uint32_t>(val);
+
+ return true;
+ }
+
+ if (aName.EqualsLiteral("firstPartyDomain")) {
+ nsAutoString firstPartyDomain(aValue);
+ firstPartyDomain.ReplaceChar(kSanitizedChar, kSourceChar);
+ mFirstPartyDomain.Assign(firstPartyDomain);
+ return true;
+ }
+
+ if (aName.EqualsLiteral("geckoViewUserContextId")) {
+ mGeckoViewSessionContextId.Assign(aValue);
+ return true;
+ }
+
+ if (aName.EqualsLiteral("partitionKey")) {
+ nsAutoString partitionKey(aValue);
+ partitionKey.ReplaceChar(kSanitizedChar, kSourceChar);
+ mPartitionKey.Assign(partitionKey);
+ return true;
+ }
+
+ // No other attributes are supported.
+ return false;
+ });
+}
+
+bool OriginAttributes::PopulateFromOrigin(const nsACString& aOrigin,
+ nsACString& aOriginNoSuffix) {
+ // RFindChar is only available on nsCString.
+ nsCString origin(aOrigin);
+ int32_t pos = origin.RFindChar('^');
+
+ if (pos == kNotFound) {
+ aOriginNoSuffix = origin;
+ return true;
+ }
+
+ aOriginNoSuffix = Substring(origin, 0, pos);
+ return PopulateFromSuffix(Substring(origin, pos));
+}
+
+void OriginAttributes::SyncAttributesWithPrivateBrowsing(
+ bool aInPrivateBrowsing) {
+ mPrivateBrowsingId = aInPrivateBrowsing ? 1 : 0;
+}
+
+/* static */
+bool OriginAttributes::IsPrivateBrowsing(const nsACString& aOrigin) {
+ nsAutoCString dummy;
+ OriginAttributes attrs;
+ if (NS_WARN_IF(!attrs.PopulateFromOrigin(aOrigin, dummy))) {
+ return false;
+ }
+
+ return !!attrs.mPrivateBrowsingId;
+}
+
+/* static */
+bool OriginAttributes::ParsePartitionKey(const nsAString& aPartitionKey,
+ nsAString& outScheme,
+ nsAString& outBaseDomain,
+ int32_t& outPort) {
+ outScheme.Truncate();
+ outBaseDomain.Truncate();
+ outPort = -1;
+
+ // Partition keys have the format "(<scheme>,<baseDomain>,[port])". The port
+ // is optional. For example: "(https,example.com,8443)" or
+ // "(http,example.org)".
+ // When privacy.dynamic_firstparty.use_site = false, the partitionKey contains
+ // only the host, e.g. "example.com".
+ // See MakeTopLevelInfo for the partitionKey serialization code.
+
+ if (aPartitionKey.IsEmpty()) {
+ return true;
+ }
+
+ // PartitionKey contains only the host.
+ if (!StaticPrefs::privacy_dynamic_firstparty_use_site()) {
+ outBaseDomain = aPartitionKey;
+ return true;
+ }
+
+ // Smallest possible partitionKey is "(x,x)". Scheme and base domain are
+ // mandatory.
+ if (NS_WARN_IF(aPartitionKey.Length() < 5)) {
+ return false;
+ }
+
+ if (NS_WARN_IF(aPartitionKey.First() != '(' || aPartitionKey.Last() != ')')) {
+ return false;
+ }
+
+ // Remove outer brackets so we can string split.
+ nsAutoString str(Substring(aPartitionKey, 1, aPartitionKey.Length() - 2));
+
+ uint32_t fieldIndex = 0;
+ for (const nsAString& field : str.Split(',')) {
+ if (NS_WARN_IF(field.IsEmpty())) {
+ // There cannot be empty fields.
+ return false;
+ }
+
+ if (fieldIndex == 0) {
+ outScheme.Assign(field);
+ } else if (fieldIndex == 1) {
+ outBaseDomain.Assign(field);
+ } else if (fieldIndex == 2) {
+ // Parse the port which is represented in the partitionKey string as a
+ // decimal (base 10) number.
+ long port = strtol(NS_ConvertUTF16toUTF8(field).get(), nullptr, 10);
+ // Invalid port.
+ if (NS_WARN_IF(port == 0)) {
+ return false;
+ }
+ outPort = static_cast<int32_t>(port);
+ } else {
+ NS_WARNING("Invalid partitionKey. Too many tokens");
+ return false;
+ }
+
+ fieldIndex++;
+ }
+
+ // scheme and base domain are required.
+ return fieldIndex > 1;
+}
+
+} // namespace mozilla
diff --git a/caps/OriginAttributes.h b/caps/OriginAttributes.h
new file mode 100644
index 0000000000..02e52f0799
--- /dev/null
+++ b/caps/OriginAttributes.h
@@ -0,0 +1,289 @@
+/* -*- 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/. */
+
+#ifndef mozilla_OriginAttributes_h
+#define mozilla_OriginAttributes_h
+
+#include "mozilla/dom/ChromeUtilsBinding.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "nsIScriptSecurityManager.h"
+
+namespace mozilla {
+
+class OriginAttributes : public dom::OriginAttributesDictionary {
+ public:
+ OriginAttributes() = default;
+
+ explicit OriginAttributes(bool aInIsolatedMozBrowser) {
+ mInIsolatedMozBrowser = aInIsolatedMozBrowser;
+ }
+
+ explicit OriginAttributes(const OriginAttributesDictionary& aOther)
+ : OriginAttributesDictionary(aOther) {}
+
+ void SetFirstPartyDomain(const bool aIsTopLevelDocument, nsIURI* aURI,
+ bool aForced = false);
+ void SetFirstPartyDomain(const bool aIsTopLevelDocument,
+ const nsACString& aDomain);
+ void SetFirstPartyDomain(const bool aIsTopLevelDocument,
+ const nsAString& aDomain, bool aForced = false);
+
+ void SetPartitionKey(nsIURI* aURI);
+ void SetPartitionKey(const nsACString& aDomain);
+ void SetPartitionKey(const nsAString& aDomain);
+
+ enum {
+ STRIP_FIRST_PARTY_DOMAIN = 0x01,
+ STRIP_USER_CONTEXT_ID = 0x02,
+ STRIP_PRIVATE_BROWSING_ID = 0x04,
+ STRIP_PARITION_KEY = 0x08,
+ };
+
+ inline void StripAttributes(uint32_t aFlags) {
+ if (aFlags & STRIP_FIRST_PARTY_DOMAIN) {
+ mFirstPartyDomain.Truncate();
+ }
+
+ if (aFlags & STRIP_USER_CONTEXT_ID) {
+ mUserContextId = nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
+ }
+
+ if (aFlags & STRIP_PRIVATE_BROWSING_ID) {
+ mPrivateBrowsingId =
+ nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID;
+ }
+
+ if (aFlags & STRIP_PARITION_KEY) {
+ mPartitionKey.Truncate();
+ }
+ }
+
+ bool operator==(const OriginAttributes& aOther) const {
+ return EqualsIgnoringFPD(aOther) &&
+ mFirstPartyDomain == aOther.mFirstPartyDomain &&
+ // FIXME(emilio, bug 1667440): Should this be part of
+ // EqualsIgnoringFPD instead?
+ mPartitionKey == aOther.mPartitionKey;
+ }
+
+ bool operator!=(const OriginAttributes& aOther) const {
+ return !(*this == aOther);
+ }
+
+ [[nodiscard]] bool EqualsIgnoringFPD(const OriginAttributes& aOther) const {
+ return mInIsolatedMozBrowser == aOther.mInIsolatedMozBrowser &&
+ mUserContextId == aOther.mUserContextId &&
+ mPrivateBrowsingId == aOther.mPrivateBrowsingId &&
+ mGeckoViewSessionContextId == aOther.mGeckoViewSessionContextId;
+ }
+
+ [[nodiscard]] bool EqualsIgnoringPartitionKey(
+ const OriginAttributes& aOther) const {
+ return EqualsIgnoringFPD(aOther) &&
+ mFirstPartyDomain == aOther.mFirstPartyDomain;
+ }
+
+ // Serializes/Deserializes non-default values into the suffix format, i.e.
+ // |^key1=value1&key2=value2|. If there are no non-default attributes, this
+ // returns an empty string.
+ void CreateSuffix(nsACString& aStr) const;
+
+ // Like CreateSuffix, but returns an atom instead of producing a string.
+ already_AddRefed<nsAtom> CreateSuffixAtom() const;
+
+ // Don't use this method for anything else than debugging!
+ void CreateAnonymizedSuffix(nsACString& aStr) const;
+
+ [[nodiscard]] bool PopulateFromSuffix(const nsACString& aStr);
+
+ // Populates the attributes from a string like
+ // |uri^key1=value1&key2=value2| and returns the uri without the suffix.
+ [[nodiscard]] bool PopulateFromOrigin(const nsACString& aOrigin,
+ nsACString& aOriginNoSuffix);
+
+ // Helper function to match mIsPrivateBrowsing to existing private browsing
+ // flags. Once all other flags are removed, this can be removed too.
+ void SyncAttributesWithPrivateBrowsing(bool aInPrivateBrowsing);
+
+ // check if "privacy.firstparty.isolate" is enabled.
+ static inline bool IsFirstPartyEnabled() {
+ return StaticPrefs::privacy_firstparty_isolate();
+ }
+
+ // check if the access of window.opener across different FPDs is restricted.
+ // We only restrict the access of window.opener when first party isolation
+ // is enabled and "privacy.firstparty.isolate.restrict_opener_access" is on.
+ static inline bool IsRestrictOpenerAccessForFPI() {
+ // We always want to restrict window.opener if first party isolation is
+ // disabled.
+ return !StaticPrefs::privacy_firstparty_isolate() ||
+ StaticPrefs::privacy_firstparty_isolate_restrict_opener_access();
+ }
+
+ // Check whether we block the postMessage across different FPDs when the
+ // targetOrigin is '*'.
+ [[nodiscard]] static inline bool IsBlockPostMessageForFPI() {
+ return StaticPrefs::privacy_firstparty_isolate() &&
+ StaticPrefs::privacy_firstparty_isolate_block_post_message();
+ }
+
+ // returns true if the originAttributes suffix has mPrivateBrowsingId value
+ // different than 0.
+ static bool IsPrivateBrowsing(const nsACString& aOrigin);
+
+ // Parse a partitionKey of the format "(<scheme>,<baseDomain>,[port])" into
+ // its components.
+ // Returns false if the partitionKey cannot be parsed because the format is
+ // invalid.
+ static bool ParsePartitionKey(const nsAString& aPartitionKey,
+ nsAString& outScheme, nsAString& outBaseDomain,
+ int32_t& outPort);
+};
+
+class OriginAttributesPattern : public dom::OriginAttributesPatternDictionary {
+ public:
+ // To convert a JSON string to an OriginAttributesPattern, do the following:
+ //
+ // OriginAttributesPattern pattern;
+ // if (!pattern.Init(aJSONString)) {
+ // ... // handle failure.
+ // }
+ OriginAttributesPattern() = default;
+
+ explicit OriginAttributesPattern(
+ const OriginAttributesPatternDictionary& aOther)
+ : OriginAttributesPatternDictionary(aOther) {}
+
+ // Performs a match of |aAttrs| against this pattern.
+ bool Matches(const OriginAttributes& aAttrs) const {
+ if (mInIsolatedMozBrowser.WasPassed() &&
+ mInIsolatedMozBrowser.Value() != aAttrs.mInIsolatedMozBrowser) {
+ return false;
+ }
+
+ if (mUserContextId.WasPassed() &&
+ mUserContextId.Value() != aAttrs.mUserContextId) {
+ return false;
+ }
+
+ if (mPrivateBrowsingId.WasPassed() &&
+ mPrivateBrowsingId.Value() != aAttrs.mPrivateBrowsingId) {
+ return false;
+ }
+
+ if (mFirstPartyDomain.WasPassed() &&
+ mFirstPartyDomain.Value() != aAttrs.mFirstPartyDomain) {
+ return false;
+ }
+
+ if (mGeckoViewSessionContextId.WasPassed() &&
+ mGeckoViewSessionContextId.Value() !=
+ aAttrs.mGeckoViewSessionContextId) {
+ return false;
+ }
+
+ // If both mPartitionKey and mPartitionKeyPattern are passed, mPartitionKey
+ // takes precedence.
+ if (mPartitionKey.WasPassed()) {
+ if (mPartitionKey.Value() != aAttrs.mPartitionKey) {
+ return false;
+ }
+ } else if (mPartitionKeyPattern.WasPassed()) {
+ auto& pkPattern = mPartitionKeyPattern.Value();
+
+ if (pkPattern.mScheme.WasPassed() || pkPattern.mBaseDomain.WasPassed() ||
+ pkPattern.mPort.WasPassed()) {
+ if (aAttrs.mPartitionKey.IsEmpty()) {
+ return false;
+ }
+
+ nsString scheme;
+ nsString baseDomain;
+ int32_t port;
+ bool success = OriginAttributes::ParsePartitionKey(
+ aAttrs.mPartitionKey, scheme, baseDomain, port);
+ if (!success) {
+ return false;
+ }
+
+ if (pkPattern.mScheme.WasPassed() &&
+ pkPattern.mScheme.Value() != scheme) {
+ return false;
+ }
+ if (pkPattern.mBaseDomain.WasPassed() &&
+ pkPattern.mBaseDomain.Value() != baseDomain) {
+ return false;
+ }
+ if (pkPattern.mPort.WasPassed() && pkPattern.mPort.Value() != port) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ bool Overlaps(const OriginAttributesPattern& aOther) const {
+ if (mInIsolatedMozBrowser.WasPassed() &&
+ aOther.mInIsolatedMozBrowser.WasPassed() &&
+ mInIsolatedMozBrowser.Value() != aOther.mInIsolatedMozBrowser.Value()) {
+ return false;
+ }
+
+ if (mUserContextId.WasPassed() && aOther.mUserContextId.WasPassed() &&
+ mUserContextId.Value() != aOther.mUserContextId.Value()) {
+ return false;
+ }
+
+ if (mPrivateBrowsingId.WasPassed() &&
+ aOther.mPrivateBrowsingId.WasPassed() &&
+ mPrivateBrowsingId.Value() != aOther.mPrivateBrowsingId.Value()) {
+ return false;
+ }
+
+ if (mFirstPartyDomain.WasPassed() && aOther.mFirstPartyDomain.WasPassed() &&
+ mFirstPartyDomain.Value() != aOther.mFirstPartyDomain.Value()) {
+ return false;
+ }
+
+ if (mGeckoViewSessionContextId.WasPassed() &&
+ aOther.mGeckoViewSessionContextId.WasPassed() &&
+ mGeckoViewSessionContextId.Value() !=
+ aOther.mGeckoViewSessionContextId.Value()) {
+ return false;
+ }
+
+ if (mPartitionKey.WasPassed() && aOther.mPartitionKey.WasPassed() &&
+ mPartitionKey.Value() != aOther.mPartitionKey.Value()) {
+ return false;
+ }
+
+ if (mPartitionKeyPattern.WasPassed() &&
+ aOther.mPartitionKeyPattern.WasPassed()) {
+ auto& self = mPartitionKeyPattern.Value();
+ auto& other = aOther.mPartitionKeyPattern.Value();
+
+ if (self.mScheme.WasPassed() && other.mScheme.WasPassed() &&
+ self.mScheme.Value() != other.mScheme.Value()) {
+ return false;
+ }
+ if (self.mBaseDomain.WasPassed() && other.mBaseDomain.WasPassed() &&
+ self.mBaseDomain.Value() != other.mBaseDomain.Value()) {
+ return false;
+ }
+ if (self.mPort.WasPassed() && other.mPort.WasPassed() &&
+ self.mPort.Value() != other.mPort.Value()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_OriginAttributes_h */
diff --git a/caps/PrincipalHashKey.h b/caps/PrincipalHashKey.h
new file mode 100644
index 0000000000..1c245532a6
--- /dev/null
+++ b/caps/PrincipalHashKey.h
@@ -0,0 +1,58 @@
+/* -*- 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/. */
+
+#ifndef mozilla_PrincipalHashKey_h
+#define mozilla_PrincipalHashKey_h
+
+#include "BasePrincipal.h"
+#include "PLDHashTable.h"
+#include "mozilla/Unused.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+
+namespace mozilla {
+
+class PrincipalHashKey : public PLDHashEntryHdr {
+ public:
+ using KeyType = nsIPrincipal*;
+ using KeyTypePointer = const nsIPrincipal*;
+
+ explicit PrincipalHashKey(const nsIPrincipal* aKey)
+ : mPrincipal(const_cast<nsIPrincipal*>(aKey)) {
+ MOZ_ASSERT(aKey);
+ MOZ_COUNT_CTOR(PrincipalHashKey);
+ }
+ PrincipalHashKey(PrincipalHashKey&& aKey)
+ : mPrincipal(std::move(aKey.mPrincipal)) {
+ MOZ_COUNT_CTOR(PrincipalHashKey);
+ }
+
+ MOZ_COUNTED_DTOR(PrincipalHashKey)
+
+ nsIPrincipal* GetKey() const { return mPrincipal; }
+
+ bool KeyEquals(const nsIPrincipal* aKey) const {
+ return BasePrincipal::Cast(mPrincipal)
+ ->FastEquals(const_cast<nsIPrincipal*>(aKey));
+ }
+
+ static const nsIPrincipal* KeyToPointer(const nsIPrincipal* aKey) {
+ return aKey;
+ }
+ static PLDHashNumber HashKey(const nsIPrincipal* aKey) {
+ auto* bp = BasePrincipal::Cast(aKey);
+ return HashGeneric(bp->GetOriginNoSuffixHash(), bp->GetOriginSuffixHash());
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ protected:
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/caps/SystemPrincipal.cpp b/caps/SystemPrincipal.cpp
new file mode 100644
index 0000000000..5c2ed63a9a
--- /dev/null
+++ b/caps/SystemPrincipal.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/* The privileged system principal. */
+
+#include "nscore.h"
+#include "SystemPrincipal.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsCOMPtr.h"
+#include "nsReadableUtils.h"
+#include "nsCRT.h"
+#include "nsString.h"
+#include "nsIClassInfoImpl.h"
+#include "pratom.h"
+
+using namespace mozilla;
+
+NS_IMPL_CLASSINFO(SystemPrincipal, nullptr, nsIClassInfo::SINGLETON,
+ NS_SYSTEMPRINCIPAL_CID)
+NS_IMPL_QUERY_INTERFACE_CI(SystemPrincipal, nsIPrincipal, nsISerializable)
+NS_IMPL_CI_INTERFACE_GETTER(SystemPrincipal, nsIPrincipal, nsISerializable)
+
+static constexpr nsLiteralCString kSystemPrincipalSpec =
+ "[System Principal]"_ns;
+
+SystemPrincipal::SystemPrincipal()
+ : BasePrincipal(eSystemPrincipal, kSystemPrincipalSpec,
+ OriginAttributes()) {}
+
+static StaticMutex sSystemPrincipalMutex;
+static StaticRefPtr<SystemPrincipal> sSystemPrincipal
+ MOZ_GUARDED_BY(sSystemPrincipalMutex);
+
+already_AddRefed<SystemPrincipal> SystemPrincipal::Get() {
+ StaticMutexAutoLock lock(sSystemPrincipalMutex);
+ return do_AddRef(sSystemPrincipal);
+}
+
+already_AddRefed<SystemPrincipal> SystemPrincipal::Init() {
+ AssertIsOnMainThread();
+ StaticMutexAutoLock lock(sSystemPrincipalMutex);
+ if (MOZ_UNLIKELY(sSystemPrincipal)) {
+ MOZ_ASSERT_UNREACHABLE("SystemPrincipal::Init() may only be called once");
+ } else {
+ sSystemPrincipal = new SystemPrincipal();
+ }
+ return do_AddRef(sSystemPrincipal);
+}
+
+void SystemPrincipal::Shutdown() {
+ AssertIsOnMainThread();
+ StaticMutexAutoLock lock(sSystemPrincipalMutex);
+ MOZ_ASSERT(sSystemPrincipal);
+ sSystemPrincipal = nullptr;
+}
+
+nsresult SystemPrincipal::GetScriptLocation(nsACString& aStr) {
+ aStr.Assign(kSystemPrincipalSpec);
+ return NS_OK;
+}
+
+///////////////////////////////////////
+// Methods implementing nsIPrincipal //
+///////////////////////////////////////
+
+uint32_t SystemPrincipal::GetHashValue() { return NS_PTR_TO_INT32(this); }
+
+NS_IMETHODIMP
+SystemPrincipal::GetURI(nsIURI** aURI) {
+ *aURI = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SystemPrincipal::GetIsOriginPotentiallyTrustworthy(bool* aResult) {
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SystemPrincipal::GetDomain(nsIURI** aDomain) {
+ *aDomain = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SystemPrincipal::SetDomain(nsIURI* aDomain) { return NS_OK; }
+
+NS_IMETHODIMP
+SystemPrincipal::GetBaseDomain(nsACString& aBaseDomain) {
+ // No base domain for chrome.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SystemPrincipal::GetAddonId(nsAString& aAddonId) {
+ aAddonId.Truncate();
+ return NS_OK;
+};
+
+//////////////////////////////////////////
+// Methods implementing nsISerializable //
+//////////////////////////////////////////
+
+NS_IMETHODIMP
+SystemPrincipal::Read(nsIObjectInputStream* aStream) {
+ // no-op: CID is sufficient to identify the mSystemPrincipal singleton
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SystemPrincipal::Write(nsIObjectOutputStream* aStream) {
+ // Read is used still for legacy principals
+ MOZ_RELEASE_ASSERT(false, "Old style serialization is removed");
+ return NS_OK;
+}
diff --git a/caps/SystemPrincipal.h b/caps/SystemPrincipal.h
new file mode 100644
index 0000000000..7b89d7ddff
--- /dev/null
+++ b/caps/SystemPrincipal.h
@@ -0,0 +1,83 @@
+/* -*- 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/. */
+
+/* The privileged system principal. */
+
+#ifndef mozilla_SystemPrincipal_h
+#define mozilla_SystemPrincipal_h
+
+#include "nsIPrincipal.h"
+#include "nsJSPrincipals.h"
+
+#include "mozilla/BasePrincipal.h"
+
+#define NS_SYSTEMPRINCIPAL_CID \
+ { \
+ 0x4a6212db, 0xaccb, 0x11d3, { \
+ 0xb7, 0x65, 0x0, 0x60, 0xb0, 0xb6, 0xce, 0xcb \
+ } \
+ }
+#define NS_SYSTEMPRINCIPAL_CONTRACTID "@mozilla.org/systemprincipal;1"
+
+class nsScriptSecurityManager;
+
+namespace Json {
+class Value;
+}
+
+namespace mozilla {
+
+class SystemPrincipal final : public BasePrincipal, public nsISerializable {
+ SystemPrincipal();
+
+ public:
+ static already_AddRefed<SystemPrincipal> Get();
+
+ static PrincipalKind Kind() { return eSystemPrincipal; }
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef() override {
+ return nsJSPrincipals::AddRef();
+ };
+ NS_IMETHOD_(MozExternalRefCountType) Release() override {
+ return nsJSPrincipals::Release();
+ };
+
+ NS_DECL_NSISERIALIZABLE
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+ uint32_t GetHashValue() override;
+ NS_IMETHOD GetURI(nsIURI** aURI) override;
+ NS_IMETHOD GetDomain(nsIURI** aDomain) override;
+ NS_IMETHOD SetDomain(nsIURI* aDomain) override;
+ NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
+ NS_IMETHOD GetAddonId(nsAString& aAddonId) override;
+ NS_IMETHOD GetIsOriginPotentiallyTrustworthy(bool* aResult) override;
+
+ virtual nsresult GetScriptLocation(nsACString& aStr) override;
+
+ nsresult GetSiteIdentifier(SiteIdentifier& aSite) override {
+ aSite.Init(this);
+ return NS_OK;
+ }
+
+ protected:
+ friend class ::nsScriptSecurityManager;
+
+ virtual ~SystemPrincipal() = default;
+
+ static already_AddRefed<SystemPrincipal> Init();
+ static void Shutdown();
+
+ bool SubsumesInternal(nsIPrincipal* aOther,
+ DocumentDomainConsideration aConsideration) override {
+ return true;
+ }
+
+ bool MayLoadInternal(nsIURI* aURI) override { return true; }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SystemPrincipal_h
diff --git a/caps/moz.build b/caps/moz.build
new file mode 100644
index 0000000000..6d5d95f934
--- /dev/null
+++ b/caps/moz.build
@@ -0,0 +1,80 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+MOCHITEST_MANIFESTS += ["tests/mochitest/mochitest.ini"]
+MOCHITEST_CHROME_MANIFESTS += ["tests/mochitest/chrome.ini"]
+BROWSER_CHROME_MANIFESTS += ["tests/mochitest/browser.ini"]
+XPCSHELL_TESTS_MANIFESTS += ["tests/unit/xpcshell.ini"]
+
+# Hack to make this file available as a resource:// URI.
+TESTING_JS_MODULES += [
+ "tests/mochitest/resource_test_file.html",
+]
+
+XPIDL_SOURCES += [
+ "nsIAddonPolicyService.idl",
+ "nsIDomainPolicy.idl",
+ "nsIPrincipal.idl",
+ "nsIScriptSecurityManager.idl",
+]
+
+XPIDL_MODULE = "caps"
+
+EXPORTS += [
+ "nsJSPrincipals.h",
+ "nsScriptSecurityManager.h",
+]
+
+EXPORTS.mozilla = [
+ "BasePrincipal.h",
+ "ContentPrincipal.h",
+ "ContentPrincipalInfoHashKey.h",
+ "ExpandedPrincipal.h",
+ "NullPrincipal.h",
+ "OriginAttributes.h",
+ "PrincipalHashKey.h",
+ "SystemPrincipal.h",
+]
+
+SOURCES += [
+ # Compile this separately since nsExceptionHandler.h conflicts
+ # with something from NullPrincipal.cpp.
+ "BasePrincipal.cpp",
+]
+
+UNIFIED_SOURCES += [
+ "ContentPrincipal.cpp",
+ "DomainPolicy.cpp",
+ "ExpandedPrincipal.cpp",
+ "nsJSPrincipals.cpp",
+ "nsScriptSecurityManager.cpp",
+ "NullPrincipal.cpp",
+ "OriginAttributes.cpp",
+ "SystemPrincipal.cpp",
+]
+
+USE_LIBS += [
+ "jsoncpp",
+]
+
+LOCAL_INCLUDES += [
+ "/docshell/base",
+ "/dom/base",
+ "/js/xpconnect/src",
+ "/netwerk/base",
+ "/netwerk/cookie",
+ "/toolkit/components/jsoncpp/include",
+]
+
+if CONFIG["ENABLE_TESTS"]:
+ DIRS += ["tests/gtest"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Security: CAPS")
diff --git a/caps/nsIAddonPolicyService.idl b/caps/nsIAddonPolicyService.idl
new file mode 100644
index 0000000000..de218ff70c
--- /dev/null
+++ b/caps/nsIAddonPolicyService.idl
@@ -0,0 +1,102 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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 "nsISupports.idl"
+#include "nsIURI.idl"
+
+/**
+ * This interface allows the security manager to query custom per-addon security
+ * policy.
+ */
+[scriptable, uuid(8a034ef9-9d14-4c5d-8319-06c1ab574baa)]
+interface nsIAddonPolicyService : nsISupports
+{
+ /**
+ * Returns the default content security policy which applies to extension
+ * documents which do not specify any custom policies.
+ */
+ readonly attribute AString defaultCSP;
+
+ /**
+ * Same as above, but used for extensions using manifest v3.
+ */
+ readonly attribute AString defaultCSPV3;
+
+ /**
+ * Returns the base content security policy which applies to all extension resources.
+ */
+ AString getBaseCSP(in AString aAddonId);
+
+ /**
+ * Returns the content security policy which applies to documents belonging
+ * to the extension with the given ID. This may be either a custom policy,
+ * if one was supplied, or the default policy if one was not.
+ */
+ AString getExtensionPageCSP(in AString aAddonId);
+
+ /**
+ * Returns the generated background page as a data-URI, if any. If the addon
+ * does not have an auto-generated background page, an empty string is
+ * returned.
+ */
+ ACString getGeneratedBackgroundPageUrl(in ACString aAddonId);
+
+ /**
+ * Returns true if the addon was granted the |aPerm| API permission.
+ */
+ boolean addonHasPermission(in AString aAddonId, in AString aPerm);
+
+ /**
+ * Returns true if unprivileged code associated with the given addon may load
+ * data from |aURI|. If |aExplicit| is true, the <all_urls> permission and
+ * permissive host globs are ignored when checking for a match.
+ */
+ boolean addonMayLoadURI(in AString aAddonId, in nsIURI aURI, [optional] in boolean aExplicit);
+
+ /**
+ * Returns the name of the WebExtension with the given ID, or the ID string
+ * if no matching add-on can be found.
+ */
+ AString getExtensionName(in AString aAddonId);
+
+ /**
+ * Returns true if a given extension:// URI is web-accessible and loadable by the source.
+ * This should be called if the protocol flags for the extension URI has URI_WEB_ACCESSIBLE.
+ */
+ boolean sourceMayLoadExtensionURI(in nsIURI aSourceURI, in nsIURI aExtensionURI);
+
+ /**
+ * Maps an extension URI to the ID of the addon it belongs to.
+ */
+ AString extensionURIToAddonId(in nsIURI aURI);
+};
+
+/**
+ * This interface exposes functionality related to add-on content policy
+ * enforcement.
+ */
+[scriptable, uuid(7a4fe60b-9131-45f5-83f3-dc63b5d71a5d)]
+interface nsIAddonContentPolicy : nsISupports
+{
+ /* options to pass to validateAddonCSP
+ *
+ * Manifest V2 uses CSP_ALLOW_ANY.
+ * In Manifest V3, extension_pages would use CSP_ALLOW_WASM
+ * and sandbox would use CSP_ALLOW_EVAL.
+ */
+ const unsigned long CSP_ALLOW_ANY = 0xFFFF;
+ const unsigned long CSP_ALLOW_LOCALHOST = (1<<0);
+ const unsigned long CSP_ALLOW_EVAL = (1<<1);
+ const unsigned long CSP_ALLOW_REMOTE = (1<<2);
+ const unsigned long CSP_ALLOW_WASM = (1<<3);
+
+ /**
+ * Checks a custom content security policy string, to ensure that it meets
+ * minimum security requirements. Returns null for valid policies, or a
+ * string describing the error for invalid policies.
+ */
+ AString validateAddonCSP(in AString aPolicyString, in unsigned long aPermittedPolicy);
+};
diff --git a/caps/nsIDomainPolicy.idl b/caps/nsIDomainPolicy.idl
new file mode 100644
index 0000000000..74d7f3b656
--- /dev/null
+++ b/caps/nsIDomainPolicy.idl
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+interface nsIURI;
+interface nsIDomainSet;
+
+%{ C++
+namespace mozilla {
+namespace dom {
+class DomainPolicyClone;
+}
+}
+%}
+
+[ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone);
+[ptr] native DomainPolicyCloneConstPtr(const mozilla::dom::DomainPolicyClone);
+
+/*
+ * When a domain policy is instantiated by invoking activateDomainPolicy() on
+ * nsIScriptSecurityManager, these domain sets are consulted when each new
+ * global is created (they have no effect on already-created globals).
+ * If javascript is globally enabled with |javascript.enabled|, the blocklists
+ * are consulted. If globally disabled, the allowlists are consulted. Lookups
+ * on blocklist and allowlist happen with contains(), and lookups on
+ * superBlocklist and superAllowlist happen with containsSuperDomain().
+ *
+ * When deactivate() is invoked, the domain sets are emptied, and the
+ * nsIDomainPolicy ceases to have any effect on the system.
+ */
+[scriptable, builtinclass, uuid(82b24a20-6701-4d40-a0f9-f5dc7321b555)]
+interface nsIDomainPolicy : nsISupports
+{
+ readonly attribute nsIDomainSet blocklist;
+ readonly attribute nsIDomainSet superBlocklist;
+ readonly attribute nsIDomainSet allowlist;
+ readonly attribute nsIDomainSet superAllowlist;
+
+ void deactivate();
+
+ [noscript, notxpcom] void cloneDomainPolicy(in DomainPolicyClonePtr aClone);
+ [noscript, notxpcom] void applyClone(in DomainPolicyCloneConstPtr aClone);
+};
+
+[scriptable, builtinclass, uuid(665c981b-0a0f-4229-ac06-a826e02d4f69)]
+interface nsIDomainSet : nsISupports
+{
+ /*
+ * Add a domain to the set. No-op if it already exists.
+ */
+ void add(in nsIURI aDomain);
+
+ /*
+ * Remove a domain from the set. No-op if it doesn't exist.
+ */
+ void remove(in nsIURI aDomain);
+
+ /*
+ * Remove all entries from the set.
+ */
+ void clear();
+
+ /*
+ * Returns true if a given domain is in the set.
+ */
+ bool contains(in nsIURI aDomain);
+
+ /*
+ * Returns true if a given domain is a subdomain of one of the entries in
+ * the set.
+ */
+ bool containsSuperDomain(in nsIURI aDomain);
+};
diff --git a/caps/nsIPrincipal.idl b/caps/nsIPrincipal.idl
new file mode 100644
index 0000000000..09d16e1bad
--- /dev/null
+++ b/caps/nsIPrincipal.idl
@@ -0,0 +1,767 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/* Defines the abstract interface for a principal. */
+
+#include "nsIContentSecurityPolicy.idl"
+#include "nsISerializable.idl"
+#include "nsIAboutModule.idl"
+#include "nsIReferrerInfo.idl"
+interface nsIChannel;
+#include "mozIDOMWindow.idl"
+
+%{C++
+struct JSPrincipals;
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "mozilla/DebugOnly.h"
+namespace mozilla {
+class OriginAttributes;
+}
+
+/**
+ * Some methods have a fast path for the case when we're comparing a principal
+ * to itself. The situation may happen for example with about:blank documents.
+ */
+
+#define DECL_FAST_INLINE_HELPER(method_) \
+ inline bool method_(nsIPrincipal* aOther) \
+ { \
+ mozilla::DebugOnly<bool> val = false; \
+ MOZ_ASSERT_IF(this == aOther, \
+ NS_SUCCEEDED(method_(aOther, &val)) && val); \
+ \
+ bool retVal = false; \
+ return \
+ this == aOther || \
+ (NS_SUCCEEDED(method_(aOther, &retVal)) && retVal); \
+ }
+
+%}
+
+interface nsIURI;
+
+webidl WebExtensionPolicy;
+
+[ptr] native JSContext(JSContext);
+[ptr] native JSPrincipals(JSPrincipals);
+[ref] native PrincipalArray(const nsTArray<nsCOMPtr<nsIPrincipal>>);
+[ref] native const_OriginAttributes(const mozilla::OriginAttributes);
+native ReferrerPolicy(mozilla::dom::ReferrerPolicy);
+
+[scriptable, builtinclass, uuid(f75f502d-79fd-48be-a079-e5a7b8f80c8b)]
+interface nsIPrincipal : nsISupports
+{
+ /**
+ * Returns whether the other principal is equivalent to this principal.
+ * Principals are considered equal if they are the same principal, or
+ * they have the same origin.
+ *
+ * May be called from any thread.
+ */
+ boolean equals(in nsIPrincipal other);
+
+ /**
+ * Returns whether the other principal is equivalent to this principal
+ * for permission purposes
+ * Matches {originAttributes ,equalsURIForPermission}
+ *
+ * May be called from any thread.
+ */
+
+ boolean equalsForPermission(in nsIPrincipal other, in bool aExactHost);
+
+ /**
+ * Like equals, but takes document.domain changes into account.
+ *
+ * May be called from any thread, though document.domain may racily change
+ * during the comparison when called from off-main-thread.
+ */
+ boolean equalsConsideringDomain(in nsIPrincipal other);
+
+ %{C++
+ DECL_FAST_INLINE_HELPER(Equals)
+ DECL_FAST_INLINE_HELPER(EqualsConsideringDomain)
+ %}
+
+ /*
+ * Returns whether the Principals URI is equal to the other URI
+ *
+ * May be called from any thread.
+ */
+ boolean equalsURI(in nsIURI aOtherURI);
+
+ /**
+ * Returns a hash value for the principal.
+ *
+ * May be called from any thread.
+ */
+ [notxpcom, nostdcall] readonly attribute unsigned long hashValue;
+
+ /**
+ * The principal URI to which this principal pertains. This is
+ * generally the document URI.
+ *
+ * May be called from any thread.
+ */
+ [infallible] readonly attribute nsIURI URI;
+
+ /**
+ * The domain URI to which this principal pertains.
+ * This is null unless script successfully sets document.domain to our URI
+ * or a superdomain of our URI.
+ * Setting this has no effect on the URI.
+ * See https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Changing_origin
+ *
+ * The getter may be called from any thread, but may only be set on the main thread.
+ */
+ [noscript] attribute nsIURI domain;
+
+ /**
+ * Returns whether the other principal is equal to or weaker than this
+ * principal. Principals are equal if they are the same object or they
+ * have the same origin.
+ *
+ * Thus a principal always subsumes itself.
+ *
+ * The system principal subsumes itself and all other principals.
+ *
+ * A null principal (corresponding to an unknown, hence assumed minimally
+ * privileged, security context) is not equal to any other principal
+ * (including other null principals), and therefore does not subsume
+ * anything but itself.
+ *
+ * May be called from any thread.
+ */
+ boolean subsumes(in nsIPrincipal other);
+
+ /**
+ * Same as the previous method, subsumes(), but takes document.domain into
+ * account.
+ *
+ * May be called from any thread, though document.domain may racily change
+ * during the comparison when called from off-main-thread.
+ */
+ boolean subsumesConsideringDomain(in nsIPrincipal other);
+
+ /**
+ * Same as the subsumesConsideringDomain(), but ignores the first party
+ * domain in its originAttributes.
+ *
+ * May be called from any thread, though document.domain may racily change
+ * during the comparison when called from off-main-thread.
+ */
+ boolean subsumesConsideringDomainIgnoringFPD(in nsIPrincipal other);
+
+ %{C++
+ DECL_FAST_INLINE_HELPER(Subsumes)
+ DECL_FAST_INLINE_HELPER(SubsumesConsideringDomain)
+ DECL_FAST_INLINE_HELPER(SubsumesConsideringDomainIgnoringFPD)
+#undef DECL_FAST_INLINE_HELPER
+ %}
+
+ /**
+ * Checks whether this principal is allowed to load the network resource
+ * located at the given URI under the same-origin policy. This means that
+ * content principals are only allowed to load resources from the same
+ * domain, the system principal is allowed to load anything, and null
+ * principals can only load URIs where they are the principal. This is
+ * changed by the optional flag allowIfInheritsPrincipal (which defaults to
+ * false) which allows URIs that inherit their loader's principal.
+ *
+ * If the load is allowed this function does nothing. If the load is not
+ * allowed the function throws NS_ERROR_DOM_BAD_URI.
+ *
+ * NOTE: Other policies might override this, such as the Access-Control
+ * specification.
+ * NOTE: The 'domain' attribute has no effect on the behaviour of this
+ * function.
+ * NOTE: Main-Thread Only.
+ *
+ *
+ * @param uri The URI about to be loaded.
+ * @param allowIfInheritsPrincipal If true, the load is allowed if the
+ * loadee inherits the principal of the
+ * loader.
+ * @throws NS_ERROR_DOM_BAD_URI if the load is not allowed.
+ */
+ void checkMayLoad(in nsIURI uri,
+ in boolean allowIfInheritsPrincipal);
+
+ /**
+ * Like checkMayLoad, but if returning an error will also report that error
+ * to the console, using the provided window id. The window id may be 0 to
+ * report to just the browser console, not web consoles.
+ *
+ * NOTE: Main-Thread Only.
+ */
+ void checkMayLoadWithReporting(in nsIURI uri,
+ in boolean allowIfInheritsPrincipal,
+ in unsigned long long innerWindowID);
+
+ /**
+ * Checks if the provided URI is considered third-party to the
+ * URI of the principal.
+ * Returns true if the URI is third-party.
+ *
+ * May be called from any thread.
+ *
+ * @param uri - The URI to check
+ */
+ boolean isThirdPartyURI(in nsIURI uri);
+
+ /**
+ * Checks if the provided principal is considered third-party to the
+ * URI of the Principal.
+ * Returns true if the principal is third-party.
+ *
+ * May be called from any thread.
+ *
+ * @param principal - The principal to check
+ */
+ boolean isThirdPartyPrincipal(in nsIPrincipal principal);
+
+ /**
+ * Checks if the provided channel is considered third-party to the
+ * URI of the principal.
+ * Returns true if the channel is third-party.
+ * Returns false if the Principal is a System Principal
+ *
+ * NOTE: Main-Thread Only.
+ *
+ * @param channel - The Channel to check
+ */
+ boolean isThirdPartyChannel(in nsIChannel channel);
+
+ /**
+ * A dictionary of the non-default origin attributes associated with this
+ * nsIPrincipal.
+ *
+ * Attributes are tokens that are taken into account when determining whether
+ * two principals are same-origin - if any attributes differ, the principals
+ * are cross-origin, even if the scheme, host, and port are the same.
+ * Attributes should also be considered for all security and bucketing decisions,
+ * even those which make non-standard comparisons (like cookies, which ignore
+ * scheme, or quotas, which ignore subdomains).
+ *
+ * If you're looking for an easy-to-use canonical stringification of the origin
+ * attributes, see |originSuffix| below.
+ */
+ [implicit_jscontext]
+ readonly attribute jsval originAttributes;
+
+ // May be called from any thread.
+ [noscript, notxpcom, nostdcall, binaryname(OriginAttributesRef)]
+ const_OriginAttributes OriginAttributesRef();
+
+ /**
+ * A canonical representation of the origin for this principal. This
+ * consists of a base string (which, for content principals, is of the
+ * format scheme://host:port), concatenated with |originAttributes| (see
+ * below).
+ *
+ * We maintain the invariant that principalA.equals(principalB) if and only
+ * if principalA.origin == principalB.origin.
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString origin;
+
+ /**
+ * Returns an ASCII compatible representation
+ * of the principals Origin
+ *
+ * May be called from any thread.
+ */
+ [noscript] readonly attribute ACString asciiOrigin;
+
+ /**
+ * Returns the "host:port" portion of the
+ * Principals URI, if any.
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString hostPort;
+
+ /**
+ * Returns the "host:port" portion of the
+ * Principals URI, if any.
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString asciiHost;
+
+ /**
+ * Returns the "host" portion of the
+ * Principals URI, if any.
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString host;
+
+ /**
+ * Returns the prePath of the principals uri
+ * follows the format scheme:
+ * "scheme://username:password@hostname:portnumber/"
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString prePath;
+
+ /**
+ * Returns the filePath of the principals uri. See nsIURI.
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString filePath;
+
+ /**
+ * Returns the ASCII Spec from the Principals URI.
+ * Might return the empty string, e.g. for the case of
+ * a SystemPrincipal or an EpxandedPrincipal.
+ *
+ * May be called from any thread.
+ *
+ * WARNING: DO NOT USE FOR SECURITY CHECKS.
+ * just for logging purposes!
+ */
+ readonly attribute ACString asciiSpec;
+
+ /**
+ * Returns the Spec from the Principals URI.
+ * Might return the empty string, e.g. for the case of
+ * a SystemPrincipal or an EpxandedPrincipal.
+ *
+ * May be called from any thread.
+ *
+ * WARNING: Do not land new Code using, as this will be removed soon
+ */
+ readonly attribute ACString spec;
+
+ /**
+ * Returns the Pre Path of the Principals URI with
+ * user:pass stripped for privacy and spoof prevention
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString exposablePrePath;
+
+ /**
+ * Returns the Spec of the Principals URI with
+ * user/pass/ref/query stripped for privacy and spoof prevention
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString exposableSpec;
+
+ /**
+ * Return the scheme of the principals URI
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString scheme;
+
+ /**
+ * Checks if the Principal's URI Scheme matches with the parameter
+ *
+ * May be called from any thread.
+ *
+ * @param scheme The scheme to be checked
+ */
+ [infallible]
+ boolean schemeIs(in string scheme);
+
+ /*
+ * Checks if the Principal's URI is contained in the given Pref
+ *
+ * NOTE: Main-Thread Only.
+ *
+ * @param pref The pref to be checked
+ */
+ [infallible]
+ boolean isURIInPrefList(in string pref);
+
+ /**
+ * Check if the Principal's URI is contained in the given list
+ *
+ * May be called from any thread.
+ *
+ * @param list The list to be checked
+ */
+ [infallible]
+ boolean isURIInList(in ACString list);
+
+ /**
+ * Check if the Principal's URI is a content-accessible about: page
+ *
+ * May be called from any thread.
+ */
+ [infallible]
+ boolean isContentAccessibleAboutURI();
+
+ /**
+ * Uses NS_Security Compare to determine if the
+ * other URI is same-origin as the uri of the Principal
+ *
+ * May be called from any thread.
+ */
+ [infallible]
+ boolean isSameOrigin(in nsIURI otherURI);
+
+ /*
+ * Checks if the Principal is allowed to load the Provided file:// URI
+ * using NS_RelaxStrictFileOriginPolicy
+ *
+ * May be called from any thread.
+ */
+ bool allowsRelaxStrictFileOriginPolicy(in nsIURI aURI);
+
+
+ /*
+ * Generates a Cache-Key for the Cors-Preflight Cache
+ *
+ * May be called from any thread.
+ */
+ [noscript]
+ ACString getPrefLightCacheKey(in nsIURI aURI ,in bool aWithCredentials,
+ in const_OriginAttributes aOriginAttributes);
+
+
+ /*
+ * Checks if the Principals URI has first party storage access
+ * when loaded inside the provided 3rd party resource window.
+ * See also: ContentBlocking::ShouldAllowAccessFor
+ *
+ * NOTE: Main-Thread Only.
+ */
+ bool hasFirstpartyStorageAccess(in mozIDOMWindow aWindow, out uint32_t rejectedReason);
+
+
+ /*
+ * Returns a Key for the LocalStorage Manager, used to
+ * check the Principals Origin Storage usage.
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString localStorageQuotaKey;
+
+ /**
+ * Implementation of
+ * https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
+ *
+ * The value returned by this method feeds into the the Secure Context
+ * algorithm that determins the value of Window.isSecureContext and
+ * WorkerGlobalScope.isSecureContext.
+ *
+ * This method returns false instead of throwing upon errors.
+ *
+ * NOTE: Main-Thread Only.
+ */
+ [infallible]
+ readonly attribute boolean isOriginPotentiallyTrustworthy;
+
+ /**
+ * NOTE: Main-Thread Only.
+ */
+ [infallible]
+ readonly attribute boolean isLoopbackHost;
+
+ /**
+ * Returns the Flags of the Principals
+ * associated AboutModule, in case there is one.
+ *
+ * NOTE: Main-Thread Only.
+ */
+ uint32_t getAboutModuleFlags();
+
+ /**
+ * Returns the Key to access the Principals
+ * Origin Local/Session Storage
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString storageOriginKey;
+
+ /**
+ * Creates and Returns a new ReferrerInfo with the
+ * Principals URI
+ *
+ * May be called from any thread.
+ */
+ nsIReferrerInfo createReferrerInfo(in ReferrerPolicy aReferrerPolicy);
+
+ /**
+ * The base part of |origin| without the concatenation with |originSuffix|.
+ * This doesn't have the important invariants described above with |origin|,
+ * and as such should only be used for legacy situations.
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString originNoSuffix;
+
+ /**
+ * A string of the form ^key1=value1&key2=value2, where each pair represents
+ * an attribute with a non-default value. If all attributes have default
+ * values, this is the empty string.
+ *
+ * The value of .originSuffix is automatically serialized into .origin, so any
+ * consumers using that are automatically origin-attribute-aware. Consumers with
+ * special requirements must inspect and compare .originSuffix manually.
+ *
+ * May be called from any thread.
+ */
+ readonly attribute AUTF8String originSuffix;
+
+ /**
+ * A canonical representation of the site-origin for this principal.
+ * This string has the same format as |origin| (see above). Two principals
+ * with differing |siteOrigin| values will never compare equal, even when
+ * considering domain mutations.
+ *
+ * For most principals, |siteOrigin| matches |origin| precisely. Only
+ * principals which allow mutating |domain|, such as ContentPrincipal,
+ * override the default implementation in BasePrincipal.
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString siteOrigin;
+
+ /**
+ * The base part of |siteOrigin| without the concatenation with
+ * |originSuffix|.
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString siteOriginNoSuffix;
+
+ /**
+ * The base domain of the principal URI to which this principal pertains
+ * (generally the document URI), handling null principals and
+ * non-hierarchical schemes correctly.
+ *
+ * May be called from any thread.
+ */
+ readonly attribute ACString baseDomain;
+
+ /**
+ * Gets the ID of the add-on this principal belongs to.
+ *
+ * May be called from any thread.
+ */
+ readonly attribute AString addonId;
+
+ /**
+ * Gets the WebExtensionPolicy of the add-on this principal belongs to.
+ *
+ * NOTE: Main-Thread Only.
+ */
+ readonly attribute WebExtensionPolicy addonPolicy;
+ readonly attribute WebExtensionPolicy contentScriptAddonPolicy;
+
+ /**
+ * Gets the id of the user context this principal is inside. If this
+ * principal is inside the default userContext, this returns
+ * nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID.
+ *
+ * May be called from any thread.
+ */
+ [infallible] readonly attribute unsigned long userContextId;
+
+ /**
+ * Gets the id of the private browsing state of the context containing
+ * this principal. If the principal has a private browsing value of 0, it
+ * is not in private browsing.
+ *
+ * May be called from any thread.
+ */
+ [infallible] readonly attribute unsigned long privateBrowsingId;
+
+ /**
+ * Returns true iff the principal is inside an isolated mozbrowser element.
+ * <xul:browser> is not considered to be a mozbrowser element.
+ * <iframe mozbrowser noisolation> does not count as isolated since
+ * isolation is disabled. Isolation can only be disabled if the
+ * containing document is chrome.
+ *
+ * May be called from any thread.
+ */
+ [infallible] readonly attribute boolean isInIsolatedMozBrowserElement;
+
+ /**
+ * Returns true iff this is a null principal (corresponding to an
+ * unknown, hence assumed minimally privileged, security context).
+ *
+ * May be called from any thread.
+ */
+ [infallible] readonly attribute boolean isNullPrincipal;
+
+ /**
+ * Returns true iff this principal corresponds to a principal origin.
+ *
+ * May be called from any thread.
+ */
+ [infallible] readonly attribute boolean isContentPrincipal;
+
+ /**
+ * Returns true iff this is an expanded principal.
+ *
+ * May be called from any thread.
+ */
+ [infallible] readonly attribute boolean isExpandedPrincipal;
+
+ /**
+ * Returns true iff this is the system principal. C++ callers should use
+ * IsSystemPrincipal() instead of this scriptable accessor.
+ *
+ * May be called from any thread.
+ */
+ readonly attribute boolean isSystemPrincipal;
+
+ /**
+ * Faster and nicer version callable from C++. Callers must include
+ * BasePrincipal.h, where it's implemented.
+ *
+ * May be called from any thread.
+ */
+ %{C++
+ inline bool IsSystemPrincipal() const;
+ %}
+
+ /**
+ * Returns true iff the principal is either an addon principal or
+ * an expanded principal, which contains at least one addon principal.
+ *
+ * May be called from any thread.
+ */
+ [infallible] readonly attribute boolean isAddonOrExpandedAddonPrincipal;
+
+ %{C++
+ // MOZ_DBG support (threadsafe)
+ friend std::ostream& operator<<(std::ostream& aOut, const nsIPrincipal& aPrincipal) {
+ nsIPrincipal* principal = const_cast<nsIPrincipal*>(&aPrincipal);
+ nsAutoCString origin;
+ mozilla::DebugOnly<nsresult> rv = principal->GetOrigin(origin);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return aOut << "nsIPrincipal { " << origin << " }";
+ }
+ %}
+
+ /**
+ * Returns true if the URI is an Onion URI.
+ *
+ * May be called from any thread.
+ */
+ [infallible] readonly attribute boolean isOnion;
+
+ /**
+ * Returns true if the Domain Policy allows js execution
+ * for the Principal's URI
+ *
+ * NOTE: Main-Thread Only.
+ */
+ readonly attribute boolean isScriptAllowedByPolicy;
+
+
+ /**
+ * Returns true if the Principal can acess l10n
+ * features for the Provided DocumentURI
+ *
+ * NOTE: Main-Thread Only.
+ */
+ boolean isL10nAllowed(in nsIURI aDocumentURI);
+
+ /**
+ * Returns a nsIPrincipal, with one less Subdomain Segment
+ * Returns `nullptr` if there are no more segments to remove.
+ *
+ * May be called from any thread.
+ */
+ [infallible] readonly attribute nsIPrincipal nextSubDomainPrincipal;
+
+ /**
+ * Returns if the principal is for an IP address.
+ *
+ * May be called from any thread.
+ */
+ [infallible] readonly attribute boolean isIpAddress;
+
+ /**
+ * Returns if the principal is for a local IP address.
+ *
+ * May be called from any thread.
+ */
+ [infallible] readonly attribute boolean isLocalIpAddress;
+
+ /**
+ * If this principal is a null principal, reconstruct the precursor
+ * principal which this null principal was derived from. This may be null,
+ * in which case this is not a null principal, there is no known precursor
+ * to this null principal, it was created by a privileged context, or there
+ * was a bugged origin in the precursor string.
+ *
+ * May be called from any thread.
+ *
+ * WARNING: Be careful when using this principal, as it is not part of the
+ * security properties of the null principal, and should NOT be used to
+ * grant a resource with a null principal access to resources from its
+ * precursor origin. This is only to be used for places where tracking how
+ * null principals were created is necessary.
+ */
+ [infallible] readonly attribute nsIPrincipal precursorPrincipal;
+};
+
+/**
+ * If SystemPrincipal is too risky to use, but we want a principal to access
+ * more than one origin, ExpandedPrincipals letting us define an array of
+ * principals it subsumes. So script with an ExpandedPrincipals will gain
+ * same origin access when at least one of its principals it contains gained
+ * sameorigin acccess. An ExpandedPrincipal will be subsumed by the system
+ * principal, and by another ExpandedPrincipal that has all its principals.
+ * It is added for jetpack content-scripts to let them interact with the
+ * content and a well defined set of other domains, without the risk of
+ * leaking out a system principal to the content. See: Bug 734891
+ */
+[uuid(f3e177Df-6a5e-489f-80a7-2dd1481471d8)]
+interface nsIExpandedPrincipal : nsISupports
+{
+ /**
+ * An array of principals that the expanded principal subsumes.
+ *
+ * When an expanded principal is used as a triggering principal for a
+ * request that inherits a security context, one of its constitutent
+ * principals is inherited rather than the expanded principal itself. The
+ * last principal in the allowlist is the default principal to inherit.
+ *
+ * Note: this list is not reference counted, it is shared, so
+ * should not be changed and should only be used ephemerally.
+ *
+ * May be called from any thread.
+ */
+ [noscript, notxpcom, nostdcall]
+ PrincipalArray AllowList();
+
+
+ /**
+ * Bug 1548468: Move CSP off ExpandedPrincipal.
+ *
+ * A Content Security Policy associated with this principal. Use this function
+ * to query the associated CSP with this principal.
+ *
+ * NOTE: Main-Thread Only.
+ */
+ readonly attribute nsIContentSecurityPolicy csp;
+
+%{ C++
+ inline already_AddRefed<nsIContentSecurityPolicy> GetCsp()
+ {
+ nsCOMPtr<nsIContentSecurityPolicy> result;
+ mozilla::DebugOnly<nsresult> rv = GetCsp(getter_AddRefs(result));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return result.forget();
+ }
+%}
+
+};
diff --git a/caps/nsIScriptSecurityManager.idl b/caps/nsIScriptSecurityManager.idl
new file mode 100644
index 0000000000..e0497db336
--- /dev/null
+++ b/caps/nsIScriptSecurityManager.idl
@@ -0,0 +1,335 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsISupports.idl"
+#include "nsIPrincipal.idl"
+interface nsIURI;
+interface nsIChannel;
+interface nsIClassInfo;
+interface nsIDocShell;
+interface nsIDomainPolicy;
+interface nsILoadContext;
+
+%{ C++
+#include "jspubtd.h"
+
+namespace mozilla {
+namespace dom {
+class DomainPolicyClone;
+}
+}
+%}
+
+[ptr] native JSContextPtr(JSContext);
+[ptr] native JSObjectPtr(JSObject);
+[ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone);
+
+[scriptable, builtinclass, uuid(51daad87-3a0c-44cc-b620-7356801c9022)]
+interface nsIScriptSecurityManager : nsISupports
+{
+ /**
+ * For each of these hooks returning NS_OK means 'let the action continue'.
+ * Returning an error code means 'veto the action'. XPConnect will return
+ * false to the js engine if the action is vetoed. The implementor of this
+ * interface is responsible for setting a JS exception into the JSContext
+ * if that is appropriate.
+ */
+ [noscript] void canCreateWrapper(in JSContextPtr aJSContext,
+ in nsIIDRef aIID,
+ in nsISupports aObj,
+ in nsIClassInfo aClassInfo);
+
+ [noscript] void canCreateInstance(in JSContextPtr aJSContext,
+ in nsCIDRef aCID);
+
+ [noscript] void canGetService(in JSContextPtr aJSContext,
+ in nsCIDRef aCID);
+
+ /**
+ * Check that the script currently running in context "cx" can load "uri".
+ *
+ * Will return error code NS_ERROR_DOM_BAD_URI if the load request
+ * should be denied.
+ *
+ * @param cx the JSContext of the script causing the load
+ * @param uri the URI that is being loaded
+ */
+ [noscript] void checkLoadURIFromScript(in JSContextPtr cx, in nsIURI uri);
+
+ /**
+ * Default CheckLoadURI permissions
+ */
+ // Default permissions
+ const unsigned long STANDARD = 0;
+
+ // Indicate that the load is a load of a new document that is not
+ // user-triggered. Here "user-triggered" could be broadly interpreted --
+ // for example, scripted sets of window.location.href might be treated as
+ // "user-triggered" in some circumstances. A typical example of a load
+ // that is not user-triggered is a <meta> refresh load. If this flag is
+ // set, the load will be denied if the originating principal's URI has the
+ // nsIProtocolHandler::URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT flag set.
+ const unsigned long LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT = 1 << 0;
+
+ // Allow the loading of chrome URLs by non-chrome URLs. Use with great
+ // care! This will actually allow the loading of any URI which has the
+ // nsIProtocolHandler::URI_IS_UI_RESOURCE protocol handler flag set. Ths
+ // probably means at least chrome: and resource:.
+ const unsigned long ALLOW_CHROME = 1 << 1;
+
+ // Don't allow URLs which would inherit the caller's principal (such as
+ // javascript: or data:) to load. See
+ // nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT.
+ const unsigned long DISALLOW_INHERIT_PRINCIPAL = 1 << 2;
+
+ // Alias for DISALLOW_INHERIT_PRINCIPAL for backwards compat with
+ // JS-implemented extensions.
+ const unsigned long DISALLOW_SCRIPT_OR_DATA = DISALLOW_INHERIT_PRINCIPAL;
+
+ // Don't allow javascript: URLs to load
+ // WARNING: Support for this value was added in Mozilla 1.7.8 and
+ // Firefox 1.0.4. Use in prior versions WILL BE IGNORED.
+ // When using this, make sure that you actually want DISALLOW_SCRIPT, not
+ // DISALLOW_INHERIT_PRINCIPAL
+ const unsigned long DISALLOW_SCRIPT = 1 << 3;
+
+ // Do not report errors if we just want to check if a principal can load
+ // a URI to not unnecessarily spam the error console.
+ const unsigned long DONT_REPORT_ERRORS = 1 << 4;
+
+ /**
+ * Check that content with principal aPrincipal can load "uri".
+ *
+ * Will return error code NS_ERROR_DOM_BAD_URI if the load request
+ * should be denied.
+ *
+ * @param aPrincipal the principal identifying the actor causing the load
+ * @param uri the URI that is being loaded
+ * @param flags the permission set, see above
+ * @param innerWindowID the window ID for error reporting. If this is 0
+ * (which happens automatically if it's not passed from JS), errors
+ * will only appear in the browser console, not window-associated
+ * consoles like the web console.
+ */
+ [binaryname(CheckLoadURIWithPrincipal)]
+ void checkLoadURIWithPrincipalXPCOM(in nsIPrincipal aPrincipal,
+ in nsIURI uri,
+ in unsigned long flags,
+ [optional] in unsigned long long innerWindowID);
+
+ /**
+ * Same as the above, but when called from JS, raises exceptions with more
+ * useful messages, including both the tested URI and the principal string.
+ */
+ [implicit_jscontext, binaryname(CheckLoadURIWithPrincipalFromJS)]
+ void checkLoadURIWithPrincipal(in nsIPrincipal aPrincipal,
+ in nsIURI uri,
+ [optional] in unsigned long flags,
+ [optional] in unsigned long long innerWindowID);
+
+ /**
+ * Similar to checkLoadURIWithPrincipal but there are two differences:
+ *
+ * 1) The URI is a string, not a URI object.
+ * 2) This function assumes that the URI may still be subject to fixup (and
+ * hence will check whether fixed-up versions of the URI are allowed to
+ * load as well); if any of the versions of this URI is not allowed, this
+ * function will return error code NS_ERROR_DOM_BAD_URI.
+ */
+ [binaryname(CheckLoadURIStrWithPrincipal)]
+ void checkLoadURIStrWithPrincipalXPCOM(in nsIPrincipal aPrincipal,
+ in AUTF8String uri,
+ in unsigned long flags);
+
+ /**
+ * Same as the above, but when called from JS, raises exceptions with more
+ * useful messages, including both the tested URI and the principal string.
+ */
+ [implicit_jscontext, binaryname(CheckLoadURIStrWithPrincipalFromJS)]
+ void checkLoadURIStrWithPrincipal(in nsIPrincipal aPrincipal,
+ in AUTF8String uri,
+ [optional] in unsigned long flags);
+
+ /**
+ * Returns true if the URI is from a domain that is allow-listed through
+ * prefs to be allowed to use file:// URIs.
+ * @param aUri the URI to be tested
+ */
+ bool inFileURIAllowlist(in nsIURI aUri);
+
+ ///////////////// Principals ///////////////////////
+
+ /**
+ * Return the all-powerful system principal.
+ */
+ nsIPrincipal getSystemPrincipal();
+
+ /**
+ * Returns a principal that has the OriginAttributes of the load context.
+ * @param loadContext to get the OriginAttributes from.
+ */
+ nsIPrincipal getLoadContextContentPrincipal(in nsIURI uri,
+ in nsILoadContext loadContext);
+
+ /**
+ * Returns a principal that has the OriginAttributes of the docshell.
+ * @param docShell to get the OriginAttributes from.
+ */
+ nsIPrincipal getDocShellContentPrincipal(in nsIURI uri,
+ in nsIDocShell docShell);
+
+ /**
+ * If this is a content principal, return a copy with different
+ * origin attributes.
+ */
+ [implicit_jscontext]
+ nsIPrincipal principalWithOA(in nsIPrincipal principal,
+ in jsval originAttributes);
+
+ /**
+ * Returns a principal whose origin is composed of |uri| and |originAttributes|.
+ * See nsIPrincipal.idl for a description of origin attributes, and
+ * ChromeUtils.webidl for a list of origin attributes and their defaults.
+ */
+ [implicit_jscontext]
+ nsIPrincipal createContentPrincipal(in nsIURI uri, in jsval originAttributes);
+
+ /**
+ * Returns a principal whose origin is the one we pass in.
+ * See nsIPrincipal.idl for a description of origin attributes, and
+ * ChromeUtils.webidl for a list of origin attributes and their defaults.
+ */
+ nsIPrincipal createContentPrincipalFromOrigin(in ACString origin);
+
+ /**
+ * Takes a principal and returns a string representation of it or a nullptr if it can't be serialized.
+ * Example output: `{"1": {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"}}`
+ */
+ ACString principalToJSON(in nsIPrincipal principal);
+
+ /**
+ * Takes a string of the following format:
+ * `{"1": {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"}}`
+ * and turns it into a principal or a nullptr on error.
+ */
+ nsIPrincipal JSONToPrincipal(in ACString json);
+
+ /**
+ * Returns a unique nonce principal with |originAttributes|.
+ * See nsIPrincipal.idl for a description of origin attributes, and
+ * ChromeUtils.webidl for a list of origin attributes and their defaults.
+ */
+ [implicit_jscontext]
+ nsIPrincipal createNullPrincipal(in jsval originAttributes);
+
+ /**
+ * Returns OK if aSourceURI and target have the same "origin"
+ * (scheme, host, and port).
+ * ReportError flag suppresses error reports for functions that
+ * don't need reporting.
+ * FromPrivateWindow indicates whether the error occurs in a private
+ * window or not.
+ */
+ void checkSameOriginURI(in nsIURI aSourceURI,
+ in nsIURI aTargetURI,
+ in boolean reportError,
+ in boolean fromPrivateWindow);
+
+ /**
+ * Get the principal for the given channel. This will typically be the
+ * channel owner if there is one, and the content principal for the
+ * channel's URI otherwise. aChannel must not be null.
+ */
+ nsIPrincipal getChannelResultPrincipal(in nsIChannel aChannel);
+
+ /**
+ * Get the storage principal for the given channel. This is basically the
+ * same of getChannelResultPrincipal() execept for trackers, where we
+ * return a principal with a different OriginAttributes.
+ */
+ nsIPrincipal getChannelResultStoragePrincipal(in nsIChannel aChannel);
+
+ /**
+ * This method returns 2 principals from a nsIChannel:
+ * - aPrincipal is the regular principal.
+ * - aPartitionedPrincipal is aPrincipal plus an isolation key in its
+ * originAttributes.
+ * See more in StoragePrincipalHelper.h
+ */
+ void getChannelResultPrincipals(in nsIChannel aChannel,
+ out nsIPrincipal aPrincipal,
+ out nsIPrincipal aPartitionedPrincipal);
+
+ /**
+ * Temporary API until bug 1220687 is fixed.
+ *
+ * Returns the same value as getChannelResultPrincipal, but ignoring
+ * sandboxing. Specifically, if sandboxing would have prevented the
+ * channel's triggering principal from being returned by
+ * getChannelResultPrincipal, the triggering principal will be returned
+ * by this method.
+ *
+ * Note that this method only ignores sandboxing of the channel in
+ * question, it does not ignore sandboxing of any channels further up a
+ * document chain. The triggering principal itself may still be the null
+ * principal due to sandboxing further up a document chain. In that regard
+ * the ignoring of sandboxing is limited.
+ */
+ [noscript, nostdcall]
+ nsIPrincipal getChannelResultPrincipalIfNotSandboxed(in nsIChannel aChannel);
+
+ /**
+ * Get the content principal for the channel's URI.
+ * aChannel must not be null.
+ */
+ nsIPrincipal getChannelURIPrincipal(in nsIChannel aChannel);
+
+ const unsigned long DEFAULT_USER_CONTEXT_ID = 0;
+
+ const unsigned long DEFAULT_PRIVATE_BROWSING_ID = 0;
+
+ /**
+ * Per-domain controls to enable and disable script. This system is designed
+ * to be used by at most one consumer, and enforces this with its semantics.
+ *
+ * Initially, domainPolicyActive is false. When activateDomainPolicy() is
+ * invoked, domainPolicyActive becomes true, and subsequent calls to
+ * activateDomainPolicy() will fail until deactivate() is invoked on the
+ * nsIDomainPolicy returned from activateDomainPolicy(). At this point,
+ * domainPolicyActive becomes false again, and a new consumer may acquire
+ * control of the system by invoking activateDomainPolicy().
+ */
+ nsIDomainPolicy activateDomainPolicy();
+ readonly attribute boolean domainPolicyActive;
+
+ /**
+ * Only the parent process can directly access domain policies, child
+ * processes only have a read-only mirror to the one in the parent.
+ * For child processes the mirror is updated via messages
+ * and ContentChild will hold the DomainPolicy by calling
+ * ActivateDomainPolicyInternal directly. New consumer to this
+ * function should not be addded.
+ */
+ [noscript] nsIDomainPolicy activateDomainPolicyInternal();
+
+ /**
+ * This function is for internal use only. Every time a child process is spawned, we
+ * must clone any active domain policies in the parent to the new child.
+ */
+ [noscript, notxpcom] void cloneDomainPolicy(in DomainPolicyClonePtr aClone);
+
+ /**
+ * Query mechanism for the above policy.
+ *
+ * If domainPolicyEnabled is false, this simply returns the current value
+ * of javascript.enabled. Otherwise, it returns the same value, but taking
+ * the various blocklist/allowlist exceptions into account.
+ */
+ bool policyAllowsScript(in nsIURI aDomain);
+};
+
+%{C++
+#define NS_SCRIPTSECURITYMANAGER_CONTRACTID "@mozilla.org/scriptsecuritymanager;1"
+%}
diff --git a/caps/nsJSPrincipals.cpp b/caps/nsJSPrincipals.cpp
new file mode 100644
index 0000000000..d089bffdf5
--- /dev/null
+++ b/caps/nsJSPrincipals.cpp
@@ -0,0 +1,369 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsIPrincipal.h"
+#include "xpcpublic.h"
+#include "nsString.h"
+#include "nsJSPrincipals.h"
+#include "nsCOMPtr.h"
+#include "nsStringBuffer.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsJSPrincipals::AddRef() {
+ MOZ_ASSERT(int32_t(refcount) >= 0, "illegal refcnt");
+ nsrefcnt count = ++refcount;
+ NS_LOG_ADDREF(this, count, "nsJSPrincipals", sizeof(*this));
+ return count;
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsJSPrincipals::Release() {
+ MOZ_ASSERT(0 != refcount, "dup release");
+ nsrefcnt count = --refcount;
+ NS_LOG_RELEASE(this, count, "nsJSPrincipals");
+ if (count == 0) {
+ delete this;
+ }
+
+ return count;
+}
+
+/* static */
+bool nsJSPrincipals::Subsume(JSPrincipals* jsprin, JSPrincipals* other) {
+ bool result;
+ nsresult rv = nsJSPrincipals::get(jsprin)->Subsumes(
+ nsJSPrincipals::get(other), &result);
+ return NS_SUCCEEDED(rv) && result;
+}
+
+/* static */
+void nsJSPrincipals::Destroy(JSPrincipals* jsprin) {
+ // The JS runtime can call this method during the last GC when
+ // nsScriptSecurityManager is destroyed. So we must not assume here that
+ // the security manager still exists.
+
+ nsJSPrincipals* nsjsprin = nsJSPrincipals::get(jsprin);
+
+ // We need to destroy the nsIPrincipal. We'll do this by adding
+ // to the refcount and calling release
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+ // The refcount logging considers AddRef-to-1 to indicate creation,
+ // so trick it into thinking it's otherwise, but balance the
+ // Release() we do below.
+ nsjsprin->refcount++;
+ nsjsprin->AddRef();
+ nsjsprin->refcount--;
+#else
+ nsjsprin->refcount++;
+#endif
+ nsjsprin->Release();
+}
+
+#ifdef DEBUG
+
+// Defined here so one can do principals->dump() in the debugger
+JS_PUBLIC_API void JSPrincipals::dump() {
+ if (debugToken == nsJSPrincipals::DEBUG_TOKEN) {
+ nsAutoCString str;
+ nsresult rv = static_cast<nsJSPrincipals*>(this)->GetScriptLocation(str);
+ fprintf(stderr, "nsIPrincipal (%p) = %s\n", static_cast<void*>(this),
+ NS_SUCCEEDED(rv) ? str.get() : "(unknown)");
+ } else {
+ fprintf(stderr,
+ "!!! JSPrincipals (%p) is not nsJSPrincipals instance - bad token: "
+ "actual=0x%x expected=0x%x\n",
+ this, unsigned(debugToken), unsigned(nsJSPrincipals::DEBUG_TOKEN));
+ }
+}
+
+#endif
+
+/* static */
+bool nsJSPrincipals::ReadPrincipals(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ JSPrincipals** aOutPrincipals) {
+ uint32_t tag;
+ uint32_t unused;
+ if (!JS_ReadUint32Pair(aReader, &tag, &unused)) {
+ return false;
+ }
+
+ if (tag != SCTAG_DOM_NULL_PRINCIPAL && tag != SCTAG_DOM_SYSTEM_PRINCIPAL &&
+ tag != SCTAG_DOM_CONTENT_PRINCIPAL &&
+ tag != SCTAG_DOM_EXPANDED_PRINCIPAL) {
+ xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+ return false;
+ }
+
+ return ReadKnownPrincipalType(aCx, aReader, tag, aOutPrincipals);
+}
+
+static bool ReadPrincipalInfo(JSStructuredCloneReader* aReader,
+ OriginAttributes& aAttrs, nsACString& aSpec,
+ nsACString& aOriginNoSuffix,
+ nsACString& aBaseDomain) {
+ uint32_t suffixLength, specLength;
+ if (!JS_ReadUint32Pair(aReader, &suffixLength, &specLength)) {
+ return false;
+ }
+
+ nsAutoCString suffix;
+ if (!suffix.SetLength(suffixLength, fallible)) {
+ return false;
+ }
+
+ if (!JS_ReadBytes(aReader, suffix.BeginWriting(), suffixLength)) {
+ return false;
+ }
+
+ if (!aAttrs.PopulateFromSuffix(suffix)) {
+ return false;
+ }
+
+ if (!aSpec.SetLength(specLength, fallible)) {
+ return false;
+ }
+
+ if (!JS_ReadBytes(aReader, aSpec.BeginWriting(), specLength)) {
+ return false;
+ }
+
+ uint32_t originNoSuffixLength, dummy;
+ if (!JS_ReadUint32Pair(aReader, &originNoSuffixLength, &dummy)) {
+ return false;
+ }
+
+ MOZ_ASSERT(dummy == 0);
+ if (dummy != 0) {
+ return false;
+ }
+
+ if (!aOriginNoSuffix.SetLength(originNoSuffixLength, fallible)) {
+ return false;
+ }
+
+ if (!JS_ReadBytes(aReader, aOriginNoSuffix.BeginWriting(),
+ originNoSuffixLength)) {
+ return false;
+ }
+
+ uint32_t baseDomainIsVoid, baseDomainLength;
+ if (!JS_ReadUint32Pair(aReader, &baseDomainIsVoid, &baseDomainLength)) {
+ return false;
+ }
+
+ if (baseDomainIsVoid != 0 && baseDomainIsVoid != 1) {
+ return false;
+ }
+
+ if (baseDomainIsVoid) {
+ if (baseDomainLength != 0) {
+ return false;
+ }
+
+ aBaseDomain.SetIsVoid(true);
+ return true;
+ }
+
+ if (!aBaseDomain.SetLength(baseDomainLength, fallible)) {
+ return false;
+ }
+
+ if (!JS_ReadBytes(aReader, aBaseDomain.BeginWriting(), baseDomainLength)) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool ReadPrincipalInfo(JSStructuredCloneReader* aReader, uint32_t aTag,
+ PrincipalInfo& aInfo) {
+ if (aTag == SCTAG_DOM_SYSTEM_PRINCIPAL) {
+ aInfo = SystemPrincipalInfo();
+ } else if (aTag == SCTAG_DOM_NULL_PRINCIPAL) {
+ OriginAttributes attrs;
+ nsAutoCString spec;
+ nsAutoCString originNoSuffix;
+ nsAutoCString baseDomain;
+ if (!::ReadPrincipalInfo(aReader, attrs, spec, originNoSuffix,
+ baseDomain)) {
+ return false;
+ }
+ aInfo = NullPrincipalInfo(attrs, spec);
+ } else if (aTag == SCTAG_DOM_EXPANDED_PRINCIPAL) {
+ uint32_t length, unused;
+ if (!JS_ReadUint32Pair(aReader, &length, &unused)) {
+ return false;
+ }
+
+ ExpandedPrincipalInfo expanded;
+
+ for (uint32_t i = 0; i < length; i++) {
+ uint32_t tag;
+ if (!JS_ReadUint32Pair(aReader, &tag, &unused)) {
+ return false;
+ }
+
+ PrincipalInfo sub;
+ if (!ReadPrincipalInfo(aReader, tag, sub)) {
+ return false;
+ }
+ expanded.allowlist().AppendElement(sub);
+ }
+
+ aInfo = expanded;
+ } else if (aTag == SCTAG_DOM_CONTENT_PRINCIPAL) {
+ OriginAttributes attrs;
+ nsAutoCString spec;
+ nsAutoCString originNoSuffix;
+ nsAutoCString baseDomain;
+ if (!::ReadPrincipalInfo(aReader, attrs, spec, originNoSuffix,
+ baseDomain)) {
+ return false;
+ }
+
+#ifdef FUZZING
+ if (originNoSuffix.IsEmpty()) {
+ return false;
+ }
+#endif
+
+ MOZ_DIAGNOSTIC_ASSERT(!originNoSuffix.IsEmpty());
+
+ // XXX: Do we care about mDomain for structured clone?
+ aInfo = ContentPrincipalInfo(attrs, originNoSuffix, spec, Nothing(),
+ baseDomain);
+ } else {
+#ifdef FUZZING
+ return false;
+#else
+ MOZ_CRASH("unexpected principal structured clone tag");
+#endif
+ }
+
+ return true;
+}
+
+/* static */
+bool nsJSPrincipals::ReadPrincipalInfo(JSStructuredCloneReader* aReader,
+ PrincipalInfo& aInfo) {
+ uint32_t tag, unused;
+ if (!JS_ReadUint32Pair(aReader, &tag, &unused)) {
+ return false;
+ }
+ return ::ReadPrincipalInfo(aReader, tag, aInfo);
+}
+
+/* static */
+bool nsJSPrincipals::ReadKnownPrincipalType(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ uint32_t aTag,
+ JSPrincipals** aOutPrincipals) {
+ MOZ_ASSERT(aTag == SCTAG_DOM_NULL_PRINCIPAL ||
+ aTag == SCTAG_DOM_SYSTEM_PRINCIPAL ||
+ aTag == SCTAG_DOM_CONTENT_PRINCIPAL ||
+ aTag == SCTAG_DOM_EXPANDED_PRINCIPAL);
+
+ PrincipalInfo info;
+ if (!::ReadPrincipalInfo(aReader, aTag, info)) {
+ return false;
+ }
+
+ auto principalOrErr = PrincipalInfoToPrincipal(info);
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+
+ *aOutPrincipals = get(principal.forget().take());
+ return true;
+}
+
+static bool WritePrincipalInfo(JSStructuredCloneWriter* aWriter,
+ const OriginAttributes& aAttrs,
+ const nsCString& aSpec,
+ const nsCString& aOriginNoSuffix,
+ const nsCString& aBaseDomain) {
+ nsAutoCString suffix;
+ aAttrs.CreateSuffix(suffix);
+
+ if (!(JS_WriteUint32Pair(aWriter, suffix.Length(), aSpec.Length()) &&
+ JS_WriteBytes(aWriter, suffix.get(), suffix.Length()) &&
+ JS_WriteBytes(aWriter, aSpec.get(), aSpec.Length()) &&
+ JS_WriteUint32Pair(aWriter, aOriginNoSuffix.Length(), 0) &&
+ JS_WriteBytes(aWriter, aOriginNoSuffix.get(),
+ aOriginNoSuffix.Length()))) {
+ return false;
+ }
+
+ if (aBaseDomain.IsVoid()) {
+ return JS_WriteUint32Pair(aWriter, 1, 0);
+ }
+
+ return JS_WriteUint32Pair(aWriter, 0, aBaseDomain.Length()) &&
+ JS_WriteBytes(aWriter, aBaseDomain.get(), aBaseDomain.Length());
+}
+
+/* static */
+bool nsJSPrincipals::WritePrincipalInfo(JSStructuredCloneWriter* aWriter,
+ const PrincipalInfo& aInfo) {
+ if (aInfo.type() == PrincipalInfo::TNullPrincipalInfo) {
+ const NullPrincipalInfo& nullInfo = aInfo;
+ return JS_WriteUint32Pair(aWriter, SCTAG_DOM_NULL_PRINCIPAL, 0) &&
+ ::WritePrincipalInfo(aWriter, nullInfo.attrs(), nullInfo.spec(),
+ ""_ns, ""_ns);
+ }
+ if (aInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
+ return JS_WriteUint32Pair(aWriter, SCTAG_DOM_SYSTEM_PRINCIPAL, 0);
+ }
+ if (aInfo.type() == PrincipalInfo::TExpandedPrincipalInfo) {
+ const ExpandedPrincipalInfo& expanded = aInfo;
+ if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_EXPANDED_PRINCIPAL, 0) ||
+ !JS_WriteUint32Pair(aWriter, expanded.allowlist().Length(), 0)) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < expanded.allowlist().Length(); i++) {
+ if (!WritePrincipalInfo(aWriter, expanded.allowlist()[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ MOZ_ASSERT(aInfo.type() == PrincipalInfo::TContentPrincipalInfo);
+ const ContentPrincipalInfo& cInfo = aInfo;
+ return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CONTENT_PRINCIPAL, 0) &&
+ ::WritePrincipalInfo(aWriter, cInfo.attrs(), cInfo.spec(),
+ cInfo.originNoSuffix(), cInfo.baseDomain());
+}
+
+bool nsJSPrincipals::write(JSContext* aCx, JSStructuredCloneWriter* aWriter) {
+ PrincipalInfo info;
+ if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(this, &info)))) {
+ xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+ return false;
+ }
+
+ return WritePrincipalInfo(aWriter, info);
+}
+
+bool nsJSPrincipals::isSystemOrAddonPrincipal() {
+ JS::AutoSuppressGCAnalysis suppress;
+ return this->IsSystemPrincipal() ||
+ this->GetIsAddonOrExpandedAddonPrincipal();
+}
diff --git a/caps/nsJSPrincipals.h b/caps/nsJSPrincipals.h
new file mode 100644
index 0000000000..54cf5f44c0
--- /dev/null
+++ b/caps/nsJSPrincipals.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+/* describes principals by their orginating uris*/
+
+#ifndef nsJSPrincipals_h__
+#define nsJSPrincipals_h__
+
+#include "js/Principals.h"
+#include "nsIPrincipal.h"
+
+struct JSContext;
+struct JSStructuredCloneReader;
+struct JSStructuredCloneWriter;
+
+namespace mozilla {
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+} // namespace mozilla
+
+class nsJSPrincipals : public nsIPrincipal, public JSPrincipals {
+ public:
+ /* SpiderMonkey security callbacks. */
+ static bool Subsume(JSPrincipals* jsprin, JSPrincipals* other);
+ static void Destroy(JSPrincipals* jsprin);
+
+ /* JSReadPrincipalsOp for nsJSPrincipals */
+ static bool ReadPrincipals(JSContext* aCx, JSStructuredCloneReader* aReader,
+ JSPrincipals** aOutPrincipals);
+
+ static bool ReadKnownPrincipalType(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ uint32_t aTag,
+ JSPrincipals** aOutPrincipals);
+
+ static bool ReadPrincipalInfo(JSStructuredCloneReader* aReader,
+ mozilla::ipc::PrincipalInfo& aInfo);
+
+ /* For write() implementations of off-main-thread JSPrincipals. */
+ static bool WritePrincipalInfo(JSStructuredCloneWriter* aWriter,
+ const mozilla::ipc::PrincipalInfo& aInfo);
+
+ bool write(JSContext* aCx, JSStructuredCloneWriter* aWriter) final;
+
+ bool isSystemOrAddonPrincipal() final;
+
+ /*
+ * Get a weak reference to nsIPrincipal associated with the given JS
+ * principal, and vice-versa.
+ */
+ static nsJSPrincipals* get(JSPrincipals* principals) {
+ nsJSPrincipals* self = static_cast<nsJSPrincipals*>(principals);
+ MOZ_ASSERT_IF(self, self->debugToken == DEBUG_TOKEN);
+ return self;
+ }
+ static nsJSPrincipals* get(nsIPrincipal* principal) {
+ nsJSPrincipals* self = static_cast<nsJSPrincipals*>(principal);
+ MOZ_ASSERT_IF(self, self->debugToken == DEBUG_TOKEN);
+ return self;
+ }
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override;
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) override;
+
+ nsJSPrincipals() {
+ refcount = 0;
+ setDebugToken(DEBUG_TOKEN);
+ }
+
+ /**
+ * Return a string that can be used as JS script filename in error reports.
+ */
+ virtual nsresult GetScriptLocation(nsACString& aStr) = 0;
+ static const uint32_t DEBUG_TOKEN = 0x0bf41760;
+
+ protected:
+ virtual ~nsJSPrincipals() { setDebugToken(0); }
+};
+
+#endif /* nsJSPrincipals_h__ */
diff --git a/caps/nsScriptSecurityManager.cpp b/caps/nsScriptSecurityManager.cpp
new file mode 100644
index 0000000000..532412b198
--- /dev/null
+++ b/caps/nsScriptSecurityManager.cpp
@@ -0,0 +1,1852 @@
+/* -*- 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 "nsScriptSecurityManager.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/StaticPrefs_extensions.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/StoragePrincipalHelper.h"
+
+#include "xpcpublic.h"
+#include "XPCWrapper.h"
+#include "nsILoadContext.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptError.h"
+#include "nsINestedURI.h"
+#include "nspr.h"
+#include "nsJSPrincipals.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ContentPrincipal.h"
+#include "ExpandedPrincipal.h"
+#include "SystemPrincipal.h"
+#include "DomainPolicy.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "nsCRTGlue.h"
+#include "nsContentSecurityUtils.h"
+#include "nsDocShell.h"
+#include "nsError.h"
+#include "nsGlobalWindowInner.h"
+#include "nsDOMCID.h"
+#include "nsTextFormatter.h"
+#include "nsIStringBundle.h"
+#include "nsNetUtil.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIConsoleService.h"
+#include "nsIOService.h"
+#include "nsIContent.h"
+#include "nsDOMJSUtils.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsIClassInfo.h"
+#include "nsIURIFixup.h"
+#include "nsIURIMutator.h"
+#include "nsIChromeRegistry.h"
+#include "nsIResProtocolHandler.h"
+#include "nsIContentSecurityPolicy.h"
+#include "mozilla/Components.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/NullPrincipal.h"
+#include <stdint.h>
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ExtensionPolicyService.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "nsContentUtils.h"
+#include "nsJSUtils.h"
+#include "nsILoadInfo.h"
+
+// This should be probably defined on some other place... but I couldn't find it
+#define WEBAPPS_PERM_NAME "webapps-manage"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+StaticRefPtr<nsIIOService> nsScriptSecurityManager::sIOService;
+std::atomic<bool> nsScriptSecurityManager::sStrictFileOriginPolicy = true;
+
+namespace {
+
+class BundleHelper {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(BundleHelper)
+
+ static nsIStringBundle* GetOrCreate() {
+ MOZ_ASSERT(!sShutdown);
+
+ // Already shutting down. Nothing should require the use of the string
+ // bundle when shutting down.
+ if (sShutdown) {
+ return nullptr;
+ }
+
+ if (!sSelf) {
+ sSelf = new BundleHelper();
+ }
+
+ return sSelf->GetOrCreateInternal();
+ }
+
+ static void Shutdown() {
+ sSelf = nullptr;
+ sShutdown = true;
+ }
+
+ private:
+ ~BundleHelper() = default;
+
+ nsIStringBundle* GetOrCreateInternal() {
+ if (!mBundle) {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ if (NS_WARN_IF(!bundleService)) {
+ return nullptr;
+ }
+
+ nsresult rv = bundleService->CreateBundle(
+ "chrome://global/locale/security/caps.properties",
+ getter_AddRefs(mBundle));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ }
+
+ return mBundle;
+ }
+
+ nsCOMPtr<nsIStringBundle> mBundle;
+
+ static StaticRefPtr<BundleHelper> sSelf;
+ static bool sShutdown;
+};
+
+StaticRefPtr<BundleHelper> BundleHelper::sSelf;
+bool BundleHelper::sShutdown = false;
+
+} // namespace
+
+///////////////////////////
+// Convenience Functions //
+///////////////////////////
+
+class nsAutoInPrincipalDomainOriginSetter {
+ public:
+ nsAutoInPrincipalDomainOriginSetter() { ++sInPrincipalDomainOrigin; }
+ ~nsAutoInPrincipalDomainOriginSetter() { --sInPrincipalDomainOrigin; }
+ static uint32_t sInPrincipalDomainOrigin;
+};
+uint32_t nsAutoInPrincipalDomainOriginSetter::sInPrincipalDomainOrigin;
+
+static nsresult GetOriginFromURI(nsIURI* aURI, nsACString& aOrigin) {
+ if (!aURI) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ if (nsAutoInPrincipalDomainOriginSetter::sInPrincipalDomainOrigin > 1) {
+ // Allow a single recursive call to GetPrincipalDomainOrigin, since that
+ // might be happening on a different principal from the first call. But
+ // after that, cut off the recursion; it just indicates that something
+ // we're doing in this method causes us to reenter a security check here.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsAutoInPrincipalDomainOriginSetter autoSetter;
+
+ nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI);
+ NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
+
+ nsAutoCString hostPort;
+
+ nsresult rv = uri->GetHostPort(hostPort);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString scheme;
+ rv = uri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aOrigin = scheme + "://"_ns + hostPort;
+ } else {
+ // Some URIs (e.g., nsSimpleURI) don't support host. Just
+ // get the full spec.
+ rv = uri->GetSpec(aOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+static nsresult GetPrincipalDomainOrigin(nsIPrincipal* aPrincipal,
+ nsACString& aOrigin) {
+ aOrigin.Truncate();
+ nsCOMPtr<nsIURI> uri;
+ aPrincipal->GetDomain(getter_AddRefs(uri));
+ nsresult rv = GetOriginFromURI(uri, aOrigin);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+ // If there is no Domain fallback to the Principals Origin
+ return aPrincipal->GetOriginNoSuffix(aOrigin);
+}
+
+inline void SetPendingExceptionASCII(JSContext* cx, const char* aMsg) {
+ JS_ReportErrorASCII(cx, "%s", aMsg);
+}
+
+inline void SetPendingException(JSContext* cx, const char16_t* aMsg) {
+ NS_ConvertUTF16toUTF8 msg(aMsg);
+ JS_ReportErrorUTF8(cx, "%s", msg.get());
+}
+
+/* static */
+bool nsScriptSecurityManager::SecurityCompareURIs(nsIURI* aSourceURI,
+ nsIURI* aTargetURI) {
+ return NS_SecurityCompareURIs(aSourceURI, aTargetURI,
+ sStrictFileOriginPolicy);
+}
+
+// SecurityHashURI is consistent with SecurityCompareURIs because
+// NS_SecurityHashURI is consistent with NS_SecurityCompareURIs. See
+// nsNetUtil.h.
+uint32_t nsScriptSecurityManager::SecurityHashURI(nsIURI* aURI) {
+ return NS_SecurityHashURI(aURI);
+}
+
+/*
+ * GetChannelResultPrincipal will return the principal that the resource
+ * returned by this channel will use. For example, if the resource is in
+ * a sandbox, it will return the nullprincipal. If the resource is forced
+ * to inherit principal, it will return the principal of its parent. If
+ * the load doesn't require sandboxing or inheriting, it will return the same
+ * principal as GetChannelURIPrincipal. Namely the principal of the URI
+ * that is being loaded.
+ */
+NS_IMETHODIMP
+nsScriptSecurityManager::GetChannelResultPrincipal(nsIChannel* aChannel,
+ nsIPrincipal** aPrincipal) {
+ return GetChannelResultPrincipal(aChannel, aPrincipal,
+ /*aIgnoreSandboxing*/ false);
+}
+
+nsresult nsScriptSecurityManager::GetChannelResultPrincipalIfNotSandboxed(
+ nsIChannel* aChannel, nsIPrincipal** aPrincipal) {
+ return GetChannelResultPrincipal(aChannel, aPrincipal,
+ /*aIgnoreSandboxing*/ true);
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::GetChannelResultStoragePrincipal(
+ nsIChannel* aChannel, nsIPrincipal** aPrincipal) {
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetChannelResultPrincipal(aChannel, getter_AddRefs(principal),
+ /*aIgnoreSandboxing*/ false);
+ if (NS_WARN_IF(NS_FAILED(rv) || !principal)) {
+ return rv;
+ }
+
+ if (!(principal->GetIsContentPrincipal())) {
+ // If for some reason we don't have a content principal here, just reuse our
+ // principal for the storage principal too, since attempting to create a
+ // storage principal would fail anyway.
+ principal.forget(aPrincipal);
+ return NS_OK;
+ }
+
+ return StoragePrincipalHelper::Create(
+ aChannel, principal, /* aForceIsolation */ false, aPrincipal);
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::GetChannelResultPrincipals(
+ nsIChannel* aChannel, nsIPrincipal** aPrincipal,
+ nsIPrincipal** aPartitionedPrincipal) {
+ nsresult rv = GetChannelResultPrincipal(aChannel, aPrincipal,
+ /*aIgnoreSandboxing*/ false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!(*aPrincipal)->GetIsContentPrincipal()) {
+ // If for some reason we don't have a content principal here, just reuse our
+ // principal for the storage principal too, since attempting to create a
+ // storage principal would fail anyway.
+ nsCOMPtr<nsIPrincipal> copy = *aPrincipal;
+ copy.forget(aPartitionedPrincipal);
+ return NS_OK;
+ }
+
+ return StoragePrincipalHelper::Create(
+ aChannel, *aPrincipal, /* aForceIsolation */ true, aPartitionedPrincipal);
+}
+
+nsresult nsScriptSecurityManager::GetChannelResultPrincipal(
+ nsIChannel* aChannel, nsIPrincipal** aPrincipal, bool aIgnoreSandboxing) {
+ MOZ_ASSERT(aChannel, "Must have channel!");
+
+ // Check whether we have an nsILoadInfo that says what we should do.
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ if (loadInfo->GetForceInheritPrincipalOverruleOwner()) {
+ nsCOMPtr<nsIPrincipal> principalToInherit =
+ loadInfo->FindPrincipalToInherit(aChannel);
+ principalToInherit.forget(aPrincipal);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISupports> owner;
+ aChannel->GetOwner(getter_AddRefs(owner));
+ if (owner) {
+ CallQueryInterface(owner, aPrincipal);
+ if (*aPrincipal) {
+ return NS_OK;
+ }
+ }
+
+ if (!aIgnoreSandboxing && loadInfo->GetLoadingSandboxed()) {
+ // Determine the unsandboxed result principal to use as this null
+ // principal's precursor. Ignore errors here, as the precursor isn't
+ // required.
+ nsCOMPtr<nsIPrincipal> precursor;
+ GetChannelResultPrincipal(aChannel, getter_AddRefs(precursor),
+ /*aIgnoreSandboxing*/ true);
+
+ // Construct a deterministic null principal URI from the precursor and the
+ // loadinfo's nullPrincipalID.
+ nsCOMPtr<nsIURI> nullPrincipalURI = NullPrincipal::CreateURI(
+ precursor, &loadInfo->GetSandboxedNullPrincipalID());
+
+ // Use the URI to construct the sandboxed result principal.
+ OriginAttributes attrs;
+ loadInfo->GetOriginAttributes(&attrs);
+ nsCOMPtr<nsIPrincipal> sandboxedPrincipal =
+ NullPrincipal::Create(attrs, nullPrincipalURI);
+ sandboxedPrincipal.forget(aPrincipal);
+ return NS_OK;
+ }
+
+ bool forceInherit = loadInfo->GetForceInheritPrincipal();
+ if (aIgnoreSandboxing && !forceInherit) {
+ // Check if SEC_FORCE_INHERIT_PRINCIPAL was dropped because of
+ // sandboxing:
+ if (loadInfo->GetLoadingSandboxed() &&
+ loadInfo->GetForceInheritPrincipalDropped()) {
+ forceInherit = true;
+ }
+ }
+ if (forceInherit) {
+ nsCOMPtr<nsIPrincipal> principalToInherit =
+ loadInfo->FindPrincipalToInherit(aChannel);
+ principalToInherit.forget(aPrincipal);
+ return NS_OK;
+ }
+
+ auto securityMode = loadInfo->GetSecurityMode();
+ // The data: inheritance flags should only apply to the initial load,
+ // not to loads that it might have redirected to.
+ if (loadInfo->RedirectChain().IsEmpty() &&
+ (securityMode ==
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT ||
+ securityMode ==
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT ||
+ securityMode == nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT)) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> principalToInherit =
+ loadInfo->FindPrincipalToInherit(aChannel);
+ bool inheritForAboutBlank = loadInfo->GetAboutBlankInherits();
+
+ if (nsContentUtils::ChannelShouldInheritPrincipal(
+ principalToInherit, uri, inheritForAboutBlank, false)) {
+ principalToInherit.forget(aPrincipal);
+ return NS_OK;
+ }
+ }
+ return GetChannelURIPrincipal(aChannel, aPrincipal);
+}
+
+/* The principal of the URI that this channel is loading. This is never
+ * affected by things like sandboxed loads, or loads where we forcefully
+ * inherit the principal. Think of this as the principal of the server
+ * which this channel is loading from. Most callers should use
+ * GetChannelResultPrincipal instead of GetChannelURIPrincipal. Only
+ * call GetChannelURIPrincipal if you are sure that you want the
+ * principal that matches the uri, even in cases when the load is
+ * sandboxed or when the load could be a blob or data uri (i.e even when
+ * you encounter loads that may or may not be sandboxed and loads
+ * that may or may not inherit)."
+ */
+NS_IMETHODIMP
+nsScriptSecurityManager::GetChannelURIPrincipal(nsIChannel* aChannel,
+ nsIPrincipal** aPrincipal) {
+ MOZ_ASSERT(aChannel, "Must have channel!");
+
+ // Get the principal from the URI. Make sure this does the same thing
+ // as Document::Reset and PrototypeDocumentContentSink::Init.
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ // Inherit the origin attributes from loadInfo.
+ // If this is a top-level document load, the origin attributes of the
+ // loadInfo will be set from nsDocShell::DoURILoad.
+ // For subresource loading, the origin attributes of the loadInfo is from
+ // its loadingPrincipal.
+ OriginAttributes attrs = loadInfo->GetOriginAttributes();
+
+ // If the URI is supposed to inherit the security context of whoever loads it,
+ // we shouldn't make a content principal for it, so instead return a null
+ // principal.
+ bool inheritsPrincipal = false;
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
+ &inheritsPrincipal);
+ if (NS_FAILED(rv) || inheritsPrincipal) {
+ // Find a precursor principal to credit for the load. This won't impact
+ // security checks, but makes tracking the source of related loads easier.
+ nsCOMPtr<nsIPrincipal> precursorPrincipal =
+ loadInfo->FindPrincipalToInherit(aChannel);
+ nsCOMPtr<nsIURI> nullPrincipalURI =
+ NullPrincipal::CreateURI(precursorPrincipal);
+ *aPrincipal = NullPrincipal::Create(attrs, nullPrincipalURI).take();
+ return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIPrincipal> prin =
+ BasePrincipal::CreateContentPrincipal(uri, attrs);
+ prin.forget(aPrincipal);
+ return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
+}
+
+/////////////////////////////
+// nsScriptSecurityManager //
+/////////////////////////////
+
+////////////////////////////////////
+// Methods implementing ISupports //
+////////////////////////////////////
+NS_IMPL_ISUPPORTS(nsScriptSecurityManager, nsIScriptSecurityManager)
+
+///////////////////////////////////////////////////
+// Methods implementing nsIScriptSecurityManager //
+///////////////////////////////////////////////////
+
+///////////////// Security Checks /////////////////
+
+bool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(
+ JSContext* cx, JS::RuntimeCode aKind, JS::Handle<JSString*> aCode) {
+ MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext());
+
+ nsCOMPtr<nsIPrincipal> subjectPrincipal = nsContentUtils::SubjectPrincipal();
+
+ // Check if Eval is allowed per firefox hardening policy
+ bool contextForbidsEval =
+ (subjectPrincipal->IsSystemPrincipal() || XRE_IsE10sParentProcess());
+#if defined(ANDROID)
+ contextForbidsEval = false;
+#endif
+
+ if (contextForbidsEval) {
+ nsAutoJSString scriptSample;
+ if (aKind == JS::RuntimeCode::JS &&
+ NS_WARN_IF(!scriptSample.init(cx, aCode))) {
+ return false;
+ }
+
+ if (!nsContentSecurityUtils::IsEvalAllowed(
+ cx, subjectPrincipal->IsSystemPrincipal(), scriptSample)) {
+ return false;
+ }
+ }
+
+ // Get the window, if any, corresponding to the current global
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ if (nsGlobalWindowInner* win = xpc::CurrentWindowOrNull(cx)) {
+ csp = win->GetCsp();
+ }
+
+ if (!csp) {
+ // Get the CSP for addon sandboxes. If the principal is expanded and has a
+ // csp, we're probably in luck.
+ auto* basePrin = BasePrincipal::Cast(subjectPrincipal);
+ // ContentScriptAddonPolicy means it is also an expanded principal, thus
+ // this is in a sandbox used as a content script.
+ if (basePrin->ContentScriptAddonPolicy()) {
+ basePrin->As<ExpandedPrincipal>()->GetCsp(getter_AddRefs(csp));
+ }
+ // don't do anything unless there's a CSP
+ if (!csp) {
+ return true;
+ }
+ }
+
+ nsCOMPtr<nsICSPEventListener> cspEventListener;
+ if (!NS_IsMainThread()) {
+ WorkerPrivate* workerPrivate =
+ mozilla::dom::GetWorkerPrivateFromContext(cx);
+ if (workerPrivate) {
+ cspEventListener = workerPrivate->CSPEventListener();
+ }
+ }
+
+ bool evalOK = true;
+ bool reportViolation = false;
+ if (aKind == JS::RuntimeCode::JS) {
+ nsresult rv = csp->GetAllowsEval(&reportViolation, &evalOK);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("CSP: failed to get allowsEval");
+ return true; // fail open to not break sites.
+ }
+ } else {
+ if (NS_FAILED(csp->GetAllowsWasmEval(&reportViolation, &evalOK))) {
+ return false;
+ }
+ if (!evalOK) {
+ // Historically, CSP did not block WebAssembly in Firefox, and some
+ // add-ons use wasm and a stricter CSP. To avoid breaking them, ignore
+ // 'wasm-unsafe-eval' violations for MV2 extensions.
+ // TODO bug 1770909: remove this exception.
+ auto* addonPolicy = BasePrincipal::Cast(subjectPrincipal)->AddonPolicy();
+ if (addonPolicy && addonPolicy->ManifestVersion() == 2) {
+ reportViolation = true;
+ evalOK = true;
+ }
+ }
+ }
+
+ if (reportViolation) {
+ JS::AutoFilename scriptFilename;
+ nsAutoString fileName;
+ unsigned lineNum = 0;
+ unsigned columnNum = 0;
+ if (JS::DescribeScriptedCaller(cx, &scriptFilename, &lineNum, &columnNum)) {
+ if (const char* file = scriptFilename.get()) {
+ CopyUTF8toUTF16(nsDependentCString(file), fileName);
+ }
+ } else {
+ MOZ_ASSERT(!JS_IsExceptionPending(cx));
+ }
+
+ nsAutoJSString scriptSample;
+ if (aKind == JS::RuntimeCode::JS &&
+ NS_WARN_IF(!scriptSample.init(cx, aCode))) {
+ JS_ClearPendingException(cx);
+ return false;
+ }
+ uint16_t violationType =
+ aKind == JS::RuntimeCode::JS
+ ? nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL
+ : nsIContentSecurityPolicy::VIOLATION_TYPE_WASM_EVAL;
+ csp->LogViolationDetails(violationType,
+ nullptr, // triggering element
+ cspEventListener, fileName, scriptSample, lineNum,
+ columnNum, u""_ns, u""_ns);
+ }
+
+ return evalOK;
+}
+
+// static
+bool nsScriptSecurityManager::JSPrincipalsSubsume(JSPrincipals* first,
+ JSPrincipals* second) {
+ return nsJSPrincipals::get(first)->Subsumes(nsJSPrincipals::get(second));
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::CheckSameOriginURI(nsIURI* aSourceURI,
+ nsIURI* aTargetURI,
+ bool reportError,
+ bool aFromPrivateWindow) {
+ // Please note that aFromPrivateWindow is only 100% accurate if
+ // reportError is true.
+ if (!SecurityCompareURIs(aSourceURI, aTargetURI)) {
+ if (reportError) {
+ ReportError("CheckSameOriginError", aSourceURI, aTargetURI,
+ aFromPrivateWindow);
+ }
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::CheckLoadURIFromScript(JSContext* cx, nsIURI* aURI) {
+ // Get principal of currently executing script.
+ MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext());
+ nsIPrincipal* principal = nsContentUtils::SubjectPrincipal();
+ nsresult rv = CheckLoadURIWithPrincipal(
+ // Passing 0 for the window ID here is OK, because we will report a
+ // script-visible exception anyway.
+ principal, aURI, nsIScriptSecurityManager::STANDARD, 0);
+ if (NS_SUCCEEDED(rv)) {
+ // OK to load
+ return NS_OK;
+ }
+
+ // Report error.
+ nsAutoCString spec;
+ if (NS_FAILED(aURI->GetAsciiSpec(spec))) return NS_ERROR_FAILURE;
+ nsAutoCString msg("Access to '");
+ msg.Append(spec);
+ msg.AppendLiteral("' from script denied");
+ SetPendingExceptionASCII(cx, msg.get());
+ return NS_ERROR_DOM_BAD_URI;
+}
+
+/**
+ * Helper method to handle cases where a flag passed to
+ * CheckLoadURIWithPrincipal means denying loading if the given URI has certain
+ * nsIProtocolHandler flags set.
+ * @return if success, access is allowed. Otherwise, deny access
+ */
+static nsresult DenyAccessIfURIHasFlags(nsIURI* aURI, uint32_t aURIFlags) {
+ MOZ_ASSERT(aURI, "Must have URI!");
+
+ bool uriHasFlags;
+ nsresult rv = NS_URIChainHasFlags(aURI, aURIFlags, &uriHasFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (uriHasFlags) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ return NS_OK;
+}
+
+static bool EqualOrSubdomain(nsIURI* aProbeArg, nsIURI* aBase) {
+ nsresult rv;
+ nsCOMPtr<nsIURI> probe = aProbeArg;
+
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(tldService, false);
+ while (true) {
+ if (nsScriptSecurityManager::SecurityCompareURIs(probe, aBase)) {
+ return true;
+ }
+
+ nsAutoCString host, newHost;
+ rv = probe->GetHost(host);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = tldService->GetNextSubDomain(host, newHost);
+ if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+ return false;
+ }
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = NS_MutateURI(probe).SetHost(newHost).Finalize(probe);
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal,
+ nsIURI* aTargetURI,
+ uint32_t aFlags,
+ uint64_t aInnerWindowID) {
+ MOZ_ASSERT(aPrincipal, "CheckLoadURIWithPrincipal must have a principal");
+
+ // If someone passes a flag that we don't understand, we should
+ // fail, because they may need a security check that we don't
+ // provide.
+ NS_ENSURE_FALSE(
+ aFlags &
+ ~(nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT |
+ nsIScriptSecurityManager::ALLOW_CHROME |
+ nsIScriptSecurityManager::DISALLOW_SCRIPT |
+ nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL |
+ nsIScriptSecurityManager::DONT_REPORT_ERRORS),
+ NS_ERROR_UNEXPECTED);
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aTargetURI);
+
+ // If DISALLOW_INHERIT_PRINCIPAL is set, we prevent loading of URIs which
+ // would do such inheriting. That would be URIs that do not have their own
+ // security context. We do this even for the system principal.
+ if (aFlags & nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL) {
+ nsresult rv = DenyAccessIfURIHasFlags(
+ aTargetURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aPrincipal == mSystemPrincipal) {
+ // Allow access
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> sourceURI;
+ auto* basePrin = BasePrincipal::Cast(aPrincipal);
+ basePrin->GetURI(getter_AddRefs(sourceURI));
+ if (!sourceURI) {
+ if (basePrin->Is<ExpandedPrincipal>()) {
+ // If the target addon is MV3 or the pref is on we require extension
+ // resources loaded from content to be listed in web_accessible_resources.
+ auto* targetPolicy =
+ ExtensionPolicyService::GetSingleton().GetByURL(aTargetURI);
+ bool contentAccessRequired =
+ targetPolicy &&
+ (targetPolicy->ManifestVersion() > 2 ||
+ StaticPrefs::extensions_content_web_accessible_enabled());
+ auto expanded = basePrin->As<ExpandedPrincipal>();
+ const auto& allowList = expanded->AllowList();
+ // Only report errors when all principals fail.
+ // With expanded principals, which are used by extension content scripts,
+ // we check only against non-extension principals for access to extension
+ // resource to enforce making those resources explicitly web accessible.
+ uint32_t flags = aFlags | nsIScriptSecurityManager::DONT_REPORT_ERRORS;
+ for (size_t i = 0; i < allowList.Length() - 1; i++) {
+ if (contentAccessRequired &&
+ BasePrincipal::Cast(allowList[i])->AddonPolicy()) {
+ continue;
+ }
+ nsresult rv = CheckLoadURIWithPrincipal(allowList[i], aTargetURI, flags,
+ aInnerWindowID);
+ if (NS_SUCCEEDED(rv)) {
+ // Allow access if it succeeded with one of the allowlisted principals
+ return NS_OK;
+ }
+ }
+
+ if (contentAccessRequired &&
+ BasePrincipal::Cast(allowList.LastElement())->AddonPolicy()) {
+ bool reportErrors =
+ !(aFlags & nsIScriptSecurityManager::DONT_REPORT_ERRORS);
+ if (reportErrors) {
+ ReportError("CheckLoadURI", sourceURI, aTargetURI,
+ allowList.LastElement()
+ ->OriginAttributesRef()
+ .mPrivateBrowsingId > 0,
+ aInnerWindowID);
+ }
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ // Report errors (if requested) for the last principal.
+ return CheckLoadURIWithPrincipal(allowList.LastElement(), aTargetURI,
+ aFlags, aInnerWindowID);
+ }
+ NS_ERROR(
+ "Non-system principals or expanded principal passed to "
+ "CheckLoadURIWithPrincipal "
+ "must have a URI!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Automatic loads are not allowed from certain protocols.
+ if (aFlags &
+ nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT) {
+ nsresult rv = DenyAccessIfURIHasFlags(
+ sourceURI,
+ nsIProtocolHandler::URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If either URI is a nested URI, get the base URI
+ nsCOMPtr<nsIURI> sourceBaseURI = NS_GetInnermostURI(sourceURI);
+ nsCOMPtr<nsIURI> targetBaseURI = NS_GetInnermostURI(aTargetURI);
+
+ //-- get the target scheme
+ nsAutoCString targetScheme;
+ nsresult rv = targetBaseURI->GetScheme(targetScheme);
+ if (NS_FAILED(rv)) return rv;
+
+ //-- Some callers do not allow loading javascript:
+ if ((aFlags & nsIScriptSecurityManager::DISALLOW_SCRIPT) &&
+ targetScheme.EqualsLiteral("javascript")) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // Check for uris that are only loadable by principals that subsume them
+ bool targetURIIsLoadableBySubsumers = false;
+ rv = NS_URIChainHasFlags(targetBaseURI,
+ nsIProtocolHandler::URI_LOADABLE_BY_SUBSUMERS,
+ &targetURIIsLoadableBySubsumers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (targetURIIsLoadableBySubsumers) {
+ // check nothing else in the URI chain has flags that prevent
+ // access:
+ rv = CheckLoadURIFlags(
+ sourceURI, aTargetURI, sourceBaseURI, targetBaseURI, aFlags,
+ aPrincipal->OriginAttributesRef().mPrivateBrowsingId > 0,
+ aInnerWindowID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Check the principal is allowed to load the target.
+ if (aFlags & nsIScriptSecurityManager::DONT_REPORT_ERRORS) {
+ return aPrincipal->CheckMayLoad(targetBaseURI, false);
+ }
+ return aPrincipal->CheckMayLoadWithReporting(targetBaseURI, false,
+ aInnerWindowID);
+ }
+
+ //-- get the source scheme
+ nsAutoCString sourceScheme;
+ rv = sourceBaseURI->GetScheme(sourceScheme);
+ if (NS_FAILED(rv)) return rv;
+
+ if (sourceScheme.LowerCaseEqualsLiteral(NS_NULLPRINCIPAL_SCHEME)) {
+ // A null principal can target its own URI.
+ if (sourceURI == aTargetURI) {
+ return NS_OK;
+ }
+ } else if (sourceScheme.EqualsIgnoreCase("file") &&
+ targetScheme.EqualsIgnoreCase("moz-icon")) {
+ // exception for file: linking to moz-icon://.ext?size=...
+ // Note that because targetScheme is the base (innermost) URI scheme,
+ // this does NOT allow file -> moz-icon:file:///... links.
+ // This is intentional.
+ return NS_OK;
+ }
+
+ // Check for webextension
+ bool targetURIIsLoadableByExtensions = false;
+ rv = NS_URIChainHasFlags(aTargetURI,
+ nsIProtocolHandler::URI_LOADABLE_BY_EXTENSIONS,
+ &targetURIIsLoadableByExtensions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (targetURIIsLoadableByExtensions &&
+ BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
+ return NS_OK;
+ }
+
+ // If we get here, check all the schemes can link to each other, from the top
+ // down:
+ nsCOMPtr<nsIURI> currentURI = sourceURI;
+ nsCOMPtr<nsIURI> currentOtherURI = aTargetURI;
+
+ bool denySameSchemeLinks = false;
+ rv = NS_URIChainHasFlags(aTargetURI,
+ nsIProtocolHandler::URI_SCHEME_NOT_SELF_LINKABLE,
+ &denySameSchemeLinks);
+ if (NS_FAILED(rv)) return rv;
+
+ while (currentURI && currentOtherURI) {
+ nsAutoCString scheme, otherScheme;
+ currentURI->GetScheme(scheme);
+ currentOtherURI->GetScheme(otherScheme);
+
+ bool schemesMatch =
+ scheme.Equals(otherScheme, nsCaseInsensitiveCStringComparator);
+ bool isSamePage = false;
+ bool isExtensionMismatch = false;
+ // about: URIs are special snowflakes.
+ if (scheme.EqualsLiteral("about") && schemesMatch) {
+ nsAutoCString moduleName, otherModuleName;
+ // about: pages can always link to themselves:
+ isSamePage =
+ NS_SUCCEEDED(NS_GetAboutModuleName(currentURI, moduleName)) &&
+ NS_SUCCEEDED(
+ NS_GetAboutModuleName(currentOtherURI, otherModuleName)) &&
+ moduleName.Equals(otherModuleName);
+ if (!isSamePage) {
+ // We will have allowed the load earlier if the source page has
+ // system principal. So we know the source has a content
+ // principal, and it's trying to link to something else.
+ // Linkable about: pages are always reachable, even if we hit
+ // the CheckLoadURIFlags call below.
+ // We punch only 1 other hole: iff the source is unlinkable,
+ // we let them link to other pages explicitly marked SAFE
+ // for content. This avoids world-linkable about: pages linking
+ // to non-world-linkable about: pages.
+ nsCOMPtr<nsIAboutModule> module, otherModule;
+ bool knowBothModules =
+ NS_SUCCEEDED(
+ NS_GetAboutModule(currentURI, getter_AddRefs(module))) &&
+ NS_SUCCEEDED(NS_GetAboutModule(currentOtherURI,
+ getter_AddRefs(otherModule)));
+ uint32_t aboutModuleFlags = 0;
+ uint32_t otherAboutModuleFlags = 0;
+ knowBothModules =
+ knowBothModules &&
+ NS_SUCCEEDED(module->GetURIFlags(currentURI, &aboutModuleFlags)) &&
+ NS_SUCCEEDED(otherModule->GetURIFlags(currentOtherURI,
+ &otherAboutModuleFlags));
+ if (knowBothModules) {
+ isSamePage = !(aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) &&
+ (otherAboutModuleFlags &
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT);
+ if (isSamePage &&
+ otherAboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) {
+ // XXXgijs: this is a hack. The target will be nested
+ // (with innerURI of moz-safe-about:whatever), and
+ // the source isn't, so we won't pass if we finish
+ // the loop. We *should* pass, though, so return here.
+ // This hack can go away when bug 1228118 is fixed.
+ return NS_OK;
+ }
+ }
+ }
+ } else if (schemesMatch && scheme.EqualsLiteral("moz-extension")) {
+ // If it is not the same exension, we want to ensure we end up
+ // calling CheckLoadURIFlags
+ nsAutoCString host, otherHost;
+ currentURI->GetHost(host);
+ currentOtherURI->GetHost(otherHost);
+ isExtensionMismatch = !host.Equals(otherHost);
+ } else {
+ bool equalExceptRef = false;
+ rv = currentURI->EqualsExceptRef(currentOtherURI, &equalExceptRef);
+ isSamePage = NS_SUCCEEDED(rv) && equalExceptRef;
+ }
+
+ // If schemes are not equal, or they're equal but the target URI
+ // is different from the source URI and doesn't always allow linking
+ // from the same scheme, or this is two different extensions, check
+ // if the URI flags of the current target URI allow the current
+ // source URI to link to it.
+ // The policy is specified by the protocol flags on both URIs.
+ if (!schemesMatch || (denySameSchemeLinks && !isSamePage) ||
+ isExtensionMismatch) {
+ return CheckLoadURIFlags(
+ currentURI, currentOtherURI, sourceBaseURI, targetBaseURI, aFlags,
+ aPrincipal->OriginAttributesRef().mPrivateBrowsingId > 0,
+ aInnerWindowID);
+ }
+ // Otherwise... check if we can nest another level:
+ nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(currentURI);
+ nsCOMPtr<nsINestedURI> nestedOtherURI = do_QueryInterface(currentOtherURI);
+
+ // If schemes match and neither URI is nested further, we're OK.
+ if (!nestedURI && !nestedOtherURI) {
+ return NS_OK;
+ }
+ // If one is nested and the other isn't, something is wrong.
+ if (!nestedURI != !nestedOtherURI) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ // Otherwise, both should be nested and we'll go through the loop again.
+ nestedURI->GetInnerURI(getter_AddRefs(currentURI));
+ nestedOtherURI->GetInnerURI(getter_AddRefs(currentOtherURI));
+ }
+
+ // We should never get here. We should always return from inside the loop.
+ return NS_ERROR_DOM_BAD_URI;
+}
+
+/**
+ * Helper method to check whether the target URI and its innermost ("base") URI
+ * has protocol flags that should stop it from being loaded by the source URI
+ * (and/or the source URI's innermost ("base") URI), taking into account any
+ * nsIScriptSecurityManager flags originally passed to
+ * CheckLoadURIWithPrincipal and friends.
+ *
+ * @return if success, access is allowed. Otherwise, deny access
+ */
+nsresult nsScriptSecurityManager::CheckLoadURIFlags(
+ nsIURI* aSourceURI, nsIURI* aTargetURI, nsIURI* aSourceBaseURI,
+ nsIURI* aTargetBaseURI, uint32_t aFlags, bool aFromPrivateWindow,
+ uint64_t aInnerWindowID) {
+ // Note that the order of policy checks here is very important!
+ // We start from most restrictive and work our way down.
+ bool reportErrors = !(aFlags & nsIScriptSecurityManager::DONT_REPORT_ERRORS);
+ const char* errorTag = "CheckLoadURIError";
+
+ nsAutoCString targetScheme;
+ nsresult rv = aTargetBaseURI->GetScheme(targetScheme);
+ if (NS_FAILED(rv)) return rv;
+
+ // Check for system target URI. Regular (non web accessible) extension
+ // URIs will also have URI_DANGEROUS_TO_LOAD.
+ rv = DenyAccessIfURIHasFlags(aTargetURI,
+ nsIProtocolHandler::URI_DANGEROUS_TO_LOAD);
+ if (NS_FAILED(rv)) {
+ // Deny access, since the origin principal is not system
+ if (reportErrors) {
+ ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow,
+ aInnerWindowID);
+ }
+ return rv;
+ }
+
+ // Used by ExtensionProtocolHandler to prevent loading extension resources
+ // in private contexts if the extension does not have permission.
+ if (aFromPrivateWindow) {
+ rv = DenyAccessIfURIHasFlags(
+ aTargetURI, nsIProtocolHandler::URI_DISALLOW_IN_PRIVATE_CONTEXT);
+ if (NS_FAILED(rv)) {
+ if (reportErrors) {
+ ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow,
+ aInnerWindowID);
+ }
+ return rv;
+ }
+ }
+
+ // If MV3 Extension uris are web accessible they have
+ // WEBEXT_URI_WEB_ACCESSIBLE.
+ bool maybeWebAccessible = false;
+ NS_URIChainHasFlags(aTargetURI, nsIProtocolHandler::WEBEXT_URI_WEB_ACCESSIBLE,
+ &maybeWebAccessible);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (maybeWebAccessible) {
+ bool isWebAccessible = false;
+ rv = ExtensionPolicyService::GetSingleton().SourceMayLoadExtensionURI(
+ aSourceURI, aTargetURI, &isWebAccessible);
+ if (NS_SUCCEEDED(rv) && isWebAccessible) {
+ return NS_OK;
+ }
+ if (reportErrors) {
+ ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow,
+ aInnerWindowID);
+ }
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // Check for chrome target URI
+ bool targetURIIsUIResource = false;
+ rv = NS_URIChainHasFlags(aTargetURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
+ &targetURIIsUIResource);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (targetURIIsUIResource) {
+ // ALLOW_CHROME is a flag that we pass on all loads _except_ docshell
+ // loads (since docshell loads run the loaded content with its origin
+ // principal). We are effectively allowing resource:// and chrome://
+ // URIs to load as long as they are content accessible and as long
+ // they're not loading it as a document.
+ if (aFlags & nsIScriptSecurityManager::ALLOW_CHROME) {
+ bool sourceIsUIResource = false;
+ rv = NS_URIChainHasFlags(aSourceBaseURI,
+ nsIProtocolHandler::URI_IS_UI_RESOURCE,
+ &sourceIsUIResource);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (sourceIsUIResource) {
+ // Special case for moz-icon URIs loaded by a local resources like
+ // e.g. chrome: or resource:
+ if (targetScheme.EqualsLiteral("moz-icon")) {
+ return NS_OK;
+ }
+ }
+
+ if (targetScheme.EqualsLiteral("resource")) {
+ if (StaticPrefs::security_all_resource_uri_content_accessible()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIProtocolHandler> ph;
+ rv = sIOService->GetProtocolHandler("resource", getter_AddRefs(ph));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!ph) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsCOMPtr<nsIResProtocolHandler> rph = do_QueryInterface(ph);
+ if (!rph) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ bool accessAllowed = false;
+ rph->AllowContentToAccess(aTargetBaseURI, &accessAllowed);
+ if (accessAllowed) {
+ return NS_OK;
+ }
+ } else if (targetScheme.EqualsLiteral("chrome")) {
+ // Allow the load only if the chrome package is allowlisted.
+ nsCOMPtr<nsIXULChromeRegistry> reg(
+ do_GetService(NS_CHROMEREGISTRY_CONTRACTID));
+ if (reg) {
+ bool accessAllowed = false;
+ reg->AllowContentToAccess(aTargetBaseURI, &accessAllowed);
+ if (accessAllowed) {
+ return NS_OK;
+ }
+ }
+ } else if (targetScheme.EqualsLiteral("moz-page-thumb") ||
+ targetScheme.EqualsLiteral("page-icon")) {
+ if (XRE_IsParentProcess()) {
+ return NS_OK;
+ }
+
+ auto& remoteType = dom::ContentChild::GetSingleton()->GetRemoteType();
+ if (remoteType == PRIVILEGEDABOUT_REMOTE_TYPE) {
+ return NS_OK;
+ }
+ }
+ }
+
+ if (reportErrors) {
+ ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow,
+ aInnerWindowID);
+ }
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // Check for target URI pointing to a file
+ bool targetURIIsLocalFile = false;
+ rv = NS_URIChainHasFlags(aTargetURI, nsIProtocolHandler::URI_IS_LOCAL_FILE,
+ &targetURIIsLocalFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (targetURIIsLocalFile) {
+ // Allow domains that were allowlisted in the prefs. In 99.9% of cases,
+ // this array is empty.
+ bool isAllowlisted;
+ MOZ_ALWAYS_SUCCEEDS(InFileURIAllowlist(aSourceURI, &isAllowlisted));
+ if (isAllowlisted) {
+ return NS_OK;
+ }
+
+ // Allow chrome://
+ if (aSourceBaseURI->SchemeIs("chrome")) {
+ return NS_OK;
+ }
+
+ // Nothing else.
+ if (reportErrors) {
+ ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow,
+ aInnerWindowID);
+ }
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+#ifdef DEBUG
+ {
+ // Everyone is allowed to load this. The case URI_LOADABLE_BY_SUBSUMERS
+ // is handled by the caller which is just delegating to us as a helper.
+ bool hasSubsumersFlag = false;
+ NS_URIChainHasFlags(aTargetBaseURI,
+ nsIProtocolHandler::URI_LOADABLE_BY_SUBSUMERS,
+ &hasSubsumersFlag);
+ bool hasLoadableByAnyone = false;
+ NS_URIChainHasFlags(aTargetBaseURI,
+ nsIProtocolHandler::URI_LOADABLE_BY_ANYONE,
+ &hasLoadableByAnyone);
+ MOZ_ASSERT(hasLoadableByAnyone || hasSubsumersFlag,
+ "why do we get here and do not have any of the two flags set?");
+ }
+#endif
+
+ return NS_OK;
+}
+
+nsresult nsScriptSecurityManager::ReportError(const char* aMessageTag,
+ const nsACString& aSourceSpec,
+ const nsACString& aTargetSpec,
+ bool aFromPrivateWindow,
+ uint64_t aInnerWindowID) {
+ if (aSourceSpec.IsEmpty() || aTargetSpec.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIStringBundle> bundle = BundleHelper::GetOrCreate();
+ if (NS_WARN_IF(!bundle)) {
+ return NS_OK;
+ }
+
+ // Localize the error message
+ nsAutoString message;
+ AutoTArray<nsString, 2> formatStrings;
+ CopyASCIItoUTF16(aSourceSpec, *formatStrings.AppendElement());
+ CopyASCIItoUTF16(aTargetSpec, *formatStrings.AppendElement());
+ nsresult rv =
+ bundle->FormatStringFromName(aMessageTag, formatStrings, message);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(console, NS_ERROR_FAILURE);
+ nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ NS_ENSURE_TRUE(error, NS_ERROR_FAILURE);
+
+ // using category of "SOP" so we can link to MDN
+ if (aInnerWindowID != 0) {
+ rv = error->InitWithWindowID(
+ message, u""_ns, u""_ns, 0, 0, nsIScriptError::errorFlag, "SOP"_ns,
+ aInnerWindowID, true /* From chrome context */);
+ } else {
+ rv = error->Init(message, u""_ns, u""_ns, 0, 0, nsIScriptError::errorFlag,
+ "SOP"_ns, aFromPrivateWindow,
+ true /* From chrome context */);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ console->LogMessage(error);
+ return NS_OK;
+}
+
+nsresult nsScriptSecurityManager::ReportError(const char* aMessageTag,
+ nsIURI* aSource, nsIURI* aTarget,
+ bool aFromPrivateWindow,
+ uint64_t aInnerWindowID) {
+ NS_ENSURE_TRUE(aSource && aTarget, NS_ERROR_NULL_POINTER);
+
+ // Get the source URL spec
+ nsAutoCString sourceSpec;
+ nsresult rv = aSource->GetAsciiSpec(sourceSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the target URL spec
+ nsAutoCString targetSpec;
+ rv = aTarget->GetAsciiSpec(targetSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return ReportError(aMessageTag, sourceSpec, targetSpec, aFromPrivateWindow,
+ aInnerWindowID);
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::CheckLoadURIStrWithPrincipal(
+ nsIPrincipal* aPrincipal, const nsACString& aTargetURIStr,
+ uint32_t aFlags) {
+ nsresult rv;
+ nsCOMPtr<nsIURI> target;
+ rv = NS_NewURI(getter_AddRefs(target), aTargetURIStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CheckLoadURIWithPrincipal(aPrincipal, target, aFlags, 0);
+ if (rv == NS_ERROR_DOM_BAD_URI) {
+ // Don't warn because NS_ERROR_DOM_BAD_URI is one of the expected
+ // return values.
+ return rv;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now start testing fixup -- since aTargetURIStr is a string, not
+ // an nsIURI, we may well end up fixing it up before loading.
+ // Note: This needs to stay in sync with the nsIURIFixup api.
+ nsCOMPtr<nsIURIFixup> fixup = components::URIFixup::Service();
+ if (!fixup) {
+ return rv;
+ }
+
+ // URIFixup's keyword and alternate flags can only fixup to http/https, so we
+ // can skip testing them. This simplifies our life because this code can be
+ // invoked from the content process where the search service would not be
+ // available.
+ uint32_t flags[] = {nsIURIFixup::FIXUP_FLAG_NONE,
+ nsIURIFixup::FIXUP_FLAG_FIX_SCHEME_TYPOS};
+ for (uint32_t i = 0; i < ArrayLength(flags); ++i) {
+ uint32_t fixupFlags = flags[i];
+ if (aPrincipal->OriginAttributesRef().mPrivateBrowsingId > 0) {
+ fixupFlags |= nsIURIFixup::FIXUP_FLAG_PRIVATE_CONTEXT;
+ }
+ nsCOMPtr<nsIURIFixupInfo> fixupInfo;
+ rv = fixup->GetFixupURIInfo(aTargetURIStr, fixupFlags,
+ getter_AddRefs(fixupInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = fixupInfo->GetPreferredURI(getter_AddRefs(target));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CheckLoadURIWithPrincipal(aPrincipal, target, aFlags, 0);
+ if (rv == NS_ERROR_DOM_BAD_URI) {
+ // Don't warn because NS_ERROR_DOM_BAD_URI is one of the expected
+ // return values.
+ return rv;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::CheckLoadURIWithPrincipalFromJS(
+ nsIPrincipal* aPrincipal, nsIURI* aTargetURI, uint32_t aFlags,
+ uint64_t aInnerWindowID, JSContext* aCx) {
+ MOZ_ASSERT(aPrincipal,
+ "CheckLoadURIWithPrincipalFromJS must have a principal");
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aTargetURI);
+
+ nsresult rv =
+ CheckLoadURIWithPrincipal(aPrincipal, aTargetURI, aFlags, aInnerWindowID);
+ if (NS_FAILED(rv)) {
+ nsAutoCString uriStr;
+ Unused << aTargetURI->GetSpec(uriStr);
+
+ nsAutoCString message("Load of ");
+ message.Append(uriStr);
+
+ nsAutoCString principalStr;
+ Unused << aPrincipal->GetSpec(principalStr);
+ if (!principalStr.IsEmpty()) {
+ message.AppendPrintf(" from %s", principalStr.get());
+ }
+
+ message.Append(" denied");
+
+ dom::Throw(aCx, rv, message);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::CheckLoadURIStrWithPrincipalFromJS(
+ nsIPrincipal* aPrincipal, const nsACString& aTargetURIStr, uint32_t aFlags,
+ JSContext* aCx) {
+ nsCOMPtr<nsIURI> targetURI;
+ MOZ_TRY(NS_NewURI(getter_AddRefs(targetURI), aTargetURIStr));
+
+ return CheckLoadURIWithPrincipalFromJS(aPrincipal, targetURI, aFlags, 0, aCx);
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::InFileURIAllowlist(nsIURI* aUri, bool* aResult) {
+ MOZ_ASSERT(aUri);
+ MOZ_ASSERT(aResult);
+
+ *aResult = false;
+ for (nsIURI* uri : EnsureFileURIAllowlist()) {
+ if (EqualOrSubdomain(aUri, uri)) {
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+///////////////// Principals ///////////////////////
+
+NS_IMETHODIMP
+nsScriptSecurityManager::GetSystemPrincipal(nsIPrincipal** result) {
+ NS_ADDREF(*result = mSystemPrincipal);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::CreateContentPrincipal(
+ nsIURI* aURI, JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx,
+ nsIPrincipal** aPrincipal) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<nsIPrincipal> prin =
+ BasePrincipal::CreateContentPrincipal(aURI, attrs);
+ prin.forget(aPrincipal);
+ return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::CreateContentPrincipalFromOrigin(
+ const nsACString& aOrigin, nsIPrincipal** aPrincipal) {
+ if (StringBeginsWith(aOrigin, "["_ns)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (StringBeginsWith(aOrigin,
+ nsLiteralCString(NS_NULLPRINCIPAL_SCHEME ":"))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateContentPrincipal(aOrigin);
+ prin.forget(aPrincipal);
+ return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::PrincipalToJSON(nsIPrincipal* aPrincipal,
+ nsACString& aJSON) {
+ aJSON.Truncate();
+ if (!aPrincipal) {
+ return NS_ERROR_FAILURE;
+ }
+
+ BasePrincipal::Cast(aPrincipal)->ToJSON(aJSON);
+
+ if (aJSON.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::JSONToPrincipal(const nsACString& aJSON,
+ nsIPrincipal** aPrincipal) {
+ if (aJSON.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = BasePrincipal::FromJSON(aJSON);
+
+ if (!principal) {
+ return NS_ERROR_FAILURE;
+ }
+
+ principal.forget(aPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::CreateNullPrincipal(
+ JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx,
+ nsIPrincipal** aPrincipal) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<nsIPrincipal> prin = NullPrincipal::Create(attrs);
+ prin.forget(aPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::GetLoadContextContentPrincipal(
+ nsIURI* aURI, nsILoadContext* aLoadContext, nsIPrincipal** aPrincipal) {
+ NS_ENSURE_STATE(aLoadContext);
+ OriginAttributes docShellAttrs;
+ aLoadContext->GetOriginAttributes(docShellAttrs);
+
+ nsCOMPtr<nsIPrincipal> prin =
+ BasePrincipal::CreateContentPrincipal(aURI, docShellAttrs);
+ prin.forget(aPrincipal);
+ return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::GetDocShellContentPrincipal(
+ nsIURI* aURI, nsIDocShell* aDocShell, nsIPrincipal** aPrincipal) {
+ nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateContentPrincipal(
+ aURI, nsDocShell::Cast(aDocShell)->GetOriginAttributes());
+ prin.forget(aPrincipal);
+ return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::PrincipalWithOA(
+ nsIPrincipal* aPrincipal, JS::Handle<JS::Value> aOriginAttributes,
+ JSContext* aCx, nsIPrincipal** aReturnPrincipal) {
+ if (!aPrincipal) {
+ return NS_OK;
+ }
+ if (aPrincipal->GetIsContentPrincipal()) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ auto* contentPrincipal = static_cast<ContentPrincipal*>(aPrincipal);
+ RefPtr<ContentPrincipal> copy =
+ new ContentPrincipal(contentPrincipal, attrs);
+ NS_ENSURE_TRUE(copy, NS_ERROR_FAILURE);
+ copy.forget(aReturnPrincipal);
+ } else {
+ // We do this for null principals, system principals (both fine)
+ // ... and expanded principals, where we should probably do something
+ // cleverer, but I also don't think we care too much.
+ nsCOMPtr<nsIPrincipal> prin = aPrincipal;
+ prin.forget(aReturnPrincipal);
+ }
+
+ return *aReturnPrincipal ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::CanCreateWrapper(JSContext* cx, const nsIID& aIID,
+ nsISupports* aObj,
+ nsIClassInfo* aClassInfo) {
+ // XXX Special case for Exception ?
+
+ // We give remote-XUL allowlisted domains a free pass here. See bug 932906.
+ JS::Rooted<JS::Realm*> contextRealm(cx, JS::GetCurrentRealmOrNull(cx));
+ MOZ_RELEASE_ASSERT(contextRealm);
+ if (!xpc::AllowContentXBLScope(contextRealm)) {
+ return NS_OK;
+ }
+
+ if (nsContentUtils::IsCallerChrome()) {
+ return NS_OK;
+ }
+
+ //-- Access denied, report an error
+ nsAutoCString originUTF8;
+ nsIPrincipal* subjectPrincipal = nsContentUtils::SubjectPrincipal();
+ GetPrincipalDomainOrigin(subjectPrincipal, originUTF8);
+ NS_ConvertUTF8toUTF16 originUTF16(originUTF8);
+ nsAutoCString classInfoNameUTF8;
+ if (aClassInfo) {
+ aClassInfo->GetClassDescription(classInfoNameUTF8);
+ }
+ if (classInfoNameUTF8.IsEmpty()) {
+ classInfoNameUTF8.AssignLiteral("UnnamedClass");
+ }
+
+ nsCOMPtr<nsIStringBundle> bundle = BundleHelper::GetOrCreate();
+ if (NS_WARN_IF(!bundle)) {
+ return NS_OK;
+ }
+
+ NS_ConvertUTF8toUTF16 classInfoUTF16(classInfoNameUTF8);
+ nsresult rv;
+ nsAutoString errorMsg;
+ if (originUTF16.IsEmpty()) {
+ AutoTArray<nsString, 1> formatStrings = {classInfoUTF16};
+ rv = bundle->FormatStringFromName("CreateWrapperDenied", formatStrings,
+ errorMsg);
+ } else {
+ AutoTArray<nsString, 2> formatStrings = {classInfoUTF16, originUTF16};
+ rv = bundle->FormatStringFromName("CreateWrapperDeniedForOrigin",
+ formatStrings, errorMsg);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetPendingException(cx, errorMsg.get());
+ return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::CanCreateInstance(JSContext* cx, const nsCID& aCID) {
+ if (nsContentUtils::IsCallerChrome()) {
+ return NS_OK;
+ }
+
+ //-- Access denied, report an error
+ nsAutoCString errorMsg("Permission denied to create instance of class. CID=");
+ char cidStr[NSID_LENGTH];
+ aCID.ToProvidedString(cidStr);
+ errorMsg.Append(cidStr);
+ SetPendingExceptionASCII(cx, errorMsg.get());
+ return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::CanGetService(JSContext* cx, const nsCID& aCID) {
+ if (nsContentUtils::IsCallerChrome()) {
+ return NS_OK;
+ }
+
+ //-- Access denied, report an error
+ nsAutoCString errorMsg("Permission denied to get service. CID=");
+ char cidStr[NSID_LENGTH];
+ aCID.ToProvidedString(cidStr);
+ errorMsg.Append(cidStr);
+ SetPendingExceptionASCII(cx, errorMsg.get());
+ return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED;
+}
+
+const char sJSEnabledPrefName[] = "javascript.enabled";
+const char sFileOriginPolicyPrefName[] =
+ "security.fileuri.strict_origin_policy";
+
+static const char* kObservedPrefs[] = {sJSEnabledPrefName,
+ sFileOriginPolicyPrefName,
+ "capability.policy.", nullptr};
+
+/////////////////////////////////////////////
+// Constructor, Destructor, Initialization //
+/////////////////////////////////////////////
+nsScriptSecurityManager::nsScriptSecurityManager(void)
+ : mPrefInitialized(false), mIsJavaScriptEnabled(false) {
+ static_assert(
+ sizeof(intptr_t) == sizeof(void*),
+ "intptr_t and void* have different lengths on this platform. "
+ "This may cause a security failure with the SecurityLevel union.");
+}
+
+nsresult nsScriptSecurityManager::Init() {
+ nsresult rv;
+ RefPtr<nsIIOService> io = mozilla::components::IO::Service(&rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ sIOService = std::move(io);
+ InitPrefs();
+
+ // Create our system principal singleton
+ mSystemPrincipal = SystemPrincipal::Init();
+
+ return NS_OK;
+}
+
+void nsScriptSecurityManager::InitJSCallbacks(JSContext* aCx) {
+ //-- Register security check callback in the JS engine
+ // Currently this is used to control access to function.caller
+
+ static const JSSecurityCallbacks securityCallbacks = {
+ ContentSecurityPolicyPermitsJSAction,
+ JSPrincipalsSubsume,
+ };
+
+ MOZ_ASSERT(!JS_GetSecurityCallbacks(aCx));
+ JS_SetSecurityCallbacks(aCx, &securityCallbacks);
+ JS_InitDestroyPrincipalsCallback(aCx, nsJSPrincipals::Destroy);
+
+ JS_SetTrustedPrincipals(aCx, BasePrincipal::Cast(mSystemPrincipal));
+}
+
+/* static */
+void nsScriptSecurityManager::ClearJSCallbacks(JSContext* aCx) {
+ JS_SetSecurityCallbacks(aCx, nullptr);
+ JS_SetTrustedPrincipals(aCx, nullptr);
+}
+
+static StaticRefPtr<nsScriptSecurityManager> gScriptSecMan;
+
+nsScriptSecurityManager::~nsScriptSecurityManager(void) {
+ Preferences::UnregisterPrefixCallbacks(
+ nsScriptSecurityManager::ScriptSecurityPrefChanged, kObservedPrefs, this);
+ if (mDomainPolicy) {
+ mDomainPolicy->Deactivate();
+ }
+ // ContentChild might hold a reference to the domain policy,
+ // and it might release it only after the security manager is
+ // gone. But we can still assert this for the main process.
+ MOZ_ASSERT_IF(XRE_IsParentProcess(), !mDomainPolicy);
+}
+
+void nsScriptSecurityManager::Shutdown() {
+ sIOService = nullptr;
+ BundleHelper::Shutdown();
+ SystemPrincipal::Shutdown();
+}
+
+nsScriptSecurityManager* nsScriptSecurityManager::GetScriptSecurityManager() {
+ return gScriptSecMan;
+}
+
+/* static */
+void nsScriptSecurityManager::InitStatics() {
+ RefPtr<nsScriptSecurityManager> ssManager = new nsScriptSecurityManager();
+ nsresult rv = ssManager->Init();
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("ssManager->Init() failed");
+ }
+
+ ClearOnShutdown(&gScriptSecMan);
+ gScriptSecMan = ssManager;
+}
+
+// Currently this nsGenericFactory constructor is used only from FastLoad
+// (XPCOM object deserialization) code, when "creating" the system principal
+// singleton.
+already_AddRefed<SystemPrincipal>
+nsScriptSecurityManager::SystemPrincipalSingletonConstructor() {
+ if (gScriptSecMan)
+ return do_AddRef(gScriptSecMan->mSystemPrincipal)
+ .downcast<SystemPrincipal>();
+ return nullptr;
+}
+
+struct IsWhitespace {
+ static bool Test(char aChar) { return NS_IsAsciiWhitespace(aChar); };
+};
+struct IsWhitespaceOrComma {
+ static bool Test(char aChar) {
+ return aChar == ',' || NS_IsAsciiWhitespace(aChar);
+ };
+};
+
+template <typename Predicate>
+uint32_t SkipPast(const nsCString& str, uint32_t base) {
+ while (base < str.Length() && Predicate::Test(str[base])) {
+ ++base;
+ }
+ return base;
+}
+
+template <typename Predicate>
+uint32_t SkipUntil(const nsCString& str, uint32_t base) {
+ while (base < str.Length() && !Predicate::Test(str[base])) {
+ ++base;
+ }
+ return base;
+}
+
+// static
+void nsScriptSecurityManager::ScriptSecurityPrefChanged(const char* aPref,
+ void* aSelf) {
+ static_cast<nsScriptSecurityManager*>(aSelf)->ScriptSecurityPrefChanged(
+ aPref);
+}
+
+inline void nsScriptSecurityManager::ScriptSecurityPrefChanged(
+ const char* aPref) {
+ MOZ_ASSERT(mPrefInitialized);
+ mIsJavaScriptEnabled =
+ Preferences::GetBool(sJSEnabledPrefName, mIsJavaScriptEnabled);
+ sStrictFileOriginPolicy =
+ Preferences::GetBool(sFileOriginPolicyPrefName, false);
+ mFileURIAllowlist.reset();
+}
+
+void nsScriptSecurityManager::AddSitesToFileURIAllowlist(
+ const nsCString& aSiteList) {
+ for (uint32_t base = SkipPast<IsWhitespace>(aSiteList, 0), bound = 0;
+ base < aSiteList.Length();
+ base = SkipPast<IsWhitespace>(aSiteList, bound)) {
+ // Grab the current site.
+ bound = SkipUntil<IsWhitespace>(aSiteList, base);
+ nsAutoCString site(Substring(aSiteList, base, bound - base));
+
+ // Check if the URI is schemeless. If so, add both http and https.
+ nsAutoCString unused;
+ if (NS_FAILED(sIOService->ExtractScheme(site, unused))) {
+ AddSitesToFileURIAllowlist("http://"_ns + site);
+ AddSitesToFileURIAllowlist("https://"_ns + site);
+ continue;
+ }
+
+ // Convert it to a URI and add it to our list.
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), site);
+ if (NS_SUCCEEDED(rv)) {
+ mFileURIAllowlist.ref().AppendElement(uri);
+ } else {
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService("@mozilla.org/consoleservice;1"));
+ if (console) {
+ nsAutoString msg =
+ u"Unable to to add site to file:// URI allowlist: "_ns +
+ NS_ConvertASCIItoUTF16(site);
+ console->LogStringMessage(msg.get());
+ }
+ }
+ }
+}
+
+nsresult nsScriptSecurityManager::InitPrefs() {
+ nsIPrefBranch* branch = Preferences::GetRootBranch();
+ NS_ENSURE_TRUE(branch, NS_ERROR_FAILURE);
+
+ mPrefInitialized = true;
+
+ // Set the initial value of the "javascript.enabled" prefs
+ ScriptSecurityPrefChanged();
+
+ // set observer callbacks in case the value of the prefs change
+ Preferences::RegisterPrefixCallbacks(
+ nsScriptSecurityManager::ScriptSecurityPrefChanged, kObservedPrefs, this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::GetDomainPolicyActive(bool* aRv) {
+ *aRv = !!mDomainPolicy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::ActivateDomainPolicy(nsIDomainPolicy** aRv) {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_SERVICE_NOT_AVAILABLE;
+ }
+
+ return ActivateDomainPolicyInternal(aRv);
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::ActivateDomainPolicyInternal(nsIDomainPolicy** aRv) {
+ // We only allow one domain policy at a time. The holder of the previous
+ // policy must explicitly deactivate it first.
+ if (mDomainPolicy) {
+ return NS_ERROR_SERVICE_NOT_AVAILABLE;
+ }
+
+ mDomainPolicy = new DomainPolicy();
+ nsCOMPtr<nsIDomainPolicy> ptr = mDomainPolicy;
+ ptr.forget(aRv);
+ return NS_OK;
+}
+
+// Intentionally non-scriptable. Script must have a reference to the
+// nsIDomainPolicy to deactivate it.
+void nsScriptSecurityManager::DeactivateDomainPolicy() {
+ mDomainPolicy = nullptr;
+}
+
+void nsScriptSecurityManager::CloneDomainPolicy(DomainPolicyClone* aClone) {
+ MOZ_ASSERT(aClone);
+ if (mDomainPolicy) {
+ mDomainPolicy->CloneDomainPolicy(aClone);
+ } else {
+ aClone->active() = false;
+ }
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::PolicyAllowsScript(nsIURI* aURI, bool* aRv) {
+ nsresult rv;
+
+ // Compute our rule. If we don't have any domain policy set up that might
+ // provide exceptions to this rule, we're done.
+ *aRv = mIsJavaScriptEnabled;
+ if (!mDomainPolicy) {
+ return NS_OK;
+ }
+
+ // We have a domain policy. Grab the appropriate set of exceptions to the
+ // rule (either the blocklist or the allowlist, depending on whether script
+ // is enabled or disabled by default).
+ nsCOMPtr<nsIDomainSet> exceptions;
+ nsCOMPtr<nsIDomainSet> superExceptions;
+ if (*aRv) {
+ mDomainPolicy->GetBlocklist(getter_AddRefs(exceptions));
+ mDomainPolicy->GetSuperBlocklist(getter_AddRefs(superExceptions));
+ } else {
+ mDomainPolicy->GetAllowlist(getter_AddRefs(exceptions));
+ mDomainPolicy->GetSuperAllowlist(getter_AddRefs(superExceptions));
+ }
+
+ bool contains;
+ rv = exceptions->Contains(aURI, &contains);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (contains) {
+ *aRv = !*aRv;
+ return NS_OK;
+ }
+ rv = superExceptions->ContainsSuperDomain(aURI, &contains);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (contains) {
+ *aRv = !*aRv;
+ }
+
+ return NS_OK;
+}
+
+const nsTArray<nsCOMPtr<nsIURI>>&
+nsScriptSecurityManager::EnsureFileURIAllowlist() {
+ if (mFileURIAllowlist.isSome()) {
+ return mFileURIAllowlist.ref();
+ }
+
+ //
+ // Rebuild the set of principals for which we allow file:// URI loads. This
+ // implements a small subset of an old pref-based CAPS people that people
+ // have come to depend on. See bug 995943.
+ //
+
+ mFileURIAllowlist.emplace();
+ nsAutoCString policies;
+ mozilla::Preferences::GetCString("capability.policy.policynames", policies);
+ for (uint32_t base = SkipPast<IsWhitespaceOrComma>(policies, 0), bound = 0;
+ base < policies.Length();
+ base = SkipPast<IsWhitespaceOrComma>(policies, bound)) {
+ // Grab the current policy name.
+ bound = SkipUntil<IsWhitespaceOrComma>(policies, base);
+ auto policyName = Substring(policies, base, bound - base);
+
+ // Figure out if this policy allows loading file:// URIs. If not, we can
+ // skip it.
+ nsCString checkLoadURIPrefName =
+ "capability.policy."_ns + policyName + ".checkloaduri.enabled"_ns;
+ nsAutoString value;
+ nsresult rv = Preferences::GetString(checkLoadURIPrefName.get(), value);
+ if (NS_FAILED(rv) || !value.LowerCaseEqualsLiteral("allaccess")) {
+ continue;
+ }
+
+ // Grab the list of domains associated with this policy.
+ nsCString domainPrefName =
+ "capability.policy."_ns + policyName + ".sites"_ns;
+ nsAutoCString siteList;
+ Preferences::GetCString(domainPrefName.get(), siteList);
+ AddSitesToFileURIAllowlist(siteList);
+ }
+
+ return mFileURIAllowlist.ref();
+}
diff --git a/caps/nsScriptSecurityManager.h b/caps/nsScriptSecurityManager.h
new file mode 100644
index 0000000000..bc55a70ad6
--- /dev/null
+++ b/caps/nsScriptSecurityManager.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 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/. */
+
+#ifndef nsScriptSecurityManager_h__
+#define nsScriptSecurityManager_h__
+
+#include "nsIScriptSecurityManager.h"
+
+#include "mozilla/Maybe.h"
+#include "nsIPrincipal.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStringFwd.h"
+#include "js/TypeDecls.h"
+
+#include <stdint.h>
+
+class nsIIOService;
+class nsIStringBundle;
+
+namespace mozilla {
+class OriginAttributes;
+class SystemPrincipal;
+} // namespace mozilla
+
+namespace JS {
+enum class RuntimeCode;
+} // namespace JS
+
+/////////////////////////////
+// nsScriptSecurityManager //
+/////////////////////////////
+#define NS_SCRIPTSECURITYMANAGER_CID \
+ { \
+ 0x7ee2a4c0, 0x4b93, 0x17d3, { \
+ 0xba, 0x18, 0x00, 0x60, 0xb0, 0xf1, 0x99, 0xa2 \
+ } \
+ }
+
+class nsScriptSecurityManager final : public nsIScriptSecurityManager {
+ public:
+ static void Shutdown();
+
+ NS_DEFINE_STATIC_CID_ACCESSOR(NS_SCRIPTSECURITYMANAGER_CID)
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCRIPTSECURITYMANAGER
+
+ static nsScriptSecurityManager* GetScriptSecurityManager();
+
+ // Invoked exactly once, by XPConnect.
+ static void InitStatics();
+
+ void InitJSCallbacks(JSContext* aCx);
+
+ // This has to be static because it is called after gScriptSecMan is cleared.
+ static void ClearJSCallbacks(JSContext* aCx);
+
+ static already_AddRefed<mozilla::SystemPrincipal>
+ SystemPrincipalSingletonConstructor();
+
+ /**
+ * Utility method for comparing two URIs. For security purposes, two URIs
+ * are equivalent if their schemes, hosts, and ports (if any) match. This
+ * method returns true if aSubjectURI and aObjectURI have the same origin,
+ * false otherwise.
+ */
+ static bool SecurityCompareURIs(nsIURI* aSourceURI, nsIURI* aTargetURI);
+ static uint32_t SecurityHashURI(nsIURI* aURI);
+
+ static nsresult ReportError(const char* aMessageTag, nsIURI* aSource,
+ nsIURI* aTarget, bool aFromPrivateWindow,
+ uint64_t aInnerWindowID = 0);
+ static nsresult ReportError(const char* aMessageTag,
+ const nsACString& sourceSpec,
+ const nsACString& targetSpec,
+ bool aFromPrivateWindow,
+ uint64_t aInnerWindowID = 0);
+
+ static uint32_t HashPrincipalByOrigin(nsIPrincipal* aPrincipal);
+
+ static bool GetStrictFileOriginPolicy() { return sStrictFileOriginPolicy; }
+
+ void DeactivateDomainPolicy();
+
+ private:
+ // GetScriptSecurityManager is the only call that can make one
+ nsScriptSecurityManager();
+ virtual ~nsScriptSecurityManager();
+
+ // Decides, based on CSP, whether or not eval() and stuff can be executed.
+ static bool ContentSecurityPolicyPermitsJSAction(JSContext* cx,
+ JS::RuntimeCode kind,
+ JS::Handle<JSString*> aCode);
+
+ static bool JSPrincipalsSubsume(JSPrincipals* first, JSPrincipals* second);
+
+ nsresult Init();
+
+ nsresult InitPrefs();
+
+ static void ScriptSecurityPrefChanged(const char* aPref, void* aSelf);
+ void ScriptSecurityPrefChanged(const char* aPref = nullptr);
+
+ inline void AddSitesToFileURIAllowlist(const nsCString& aSiteList);
+
+ nsresult GetChannelResultPrincipal(nsIChannel* aChannel,
+ nsIPrincipal** aPrincipal,
+ bool aIgnoreSandboxing);
+
+ nsresult CheckLoadURIFlags(nsIURI* aSourceURI, nsIURI* aTargetURI,
+ nsIURI* aSourceBaseURI, nsIURI* aTargetBaseURI,
+ uint32_t aFlags, bool aFromPrivateWindow,
+ uint64_t aInnerWindowID);
+
+ // Returns the file URI allowlist, initializing it if it has not been
+ // initialized.
+ const nsTArray<nsCOMPtr<nsIURI>>& EnsureFileURIAllowlist();
+
+ nsCOMPtr<nsIPrincipal> mSystemPrincipal;
+ bool mPrefInitialized;
+ bool mIsJavaScriptEnabled;
+
+ // List of URIs whose domains and sub-domains are allowlisted to allow
+ // access to file: URIs. Lazily initialized; isNothing() when not yet
+ // initialized.
+ mozilla::Maybe<nsTArray<nsCOMPtr<nsIURI>>> mFileURIAllowlist;
+
+ // This machinery controls new-style domain policies. The old-style
+ // policy machinery will be removed soon.
+ nsCOMPtr<nsIDomainPolicy> mDomainPolicy;
+
+ static std::atomic<bool> sStrictFileOriginPolicy;
+
+ static mozilla::StaticRefPtr<nsIIOService> sIOService;
+ static nsIStringBundle* sStrBundle;
+};
+
+#endif // nsScriptSecurityManager_h__
diff --git a/caps/tests/gtest/TestBackgroundThreadPrincipal.cpp b/caps/tests/gtest/TestBackgroundThreadPrincipal.cpp
new file mode 100644
index 0000000000..82c7b15757
--- /dev/null
+++ b/caps/tests/gtest/TestBackgroundThreadPrincipal.cpp
@@ -0,0 +1,94 @@
+/* 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 "gtest/gtest.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ContentPrincipal.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsIEventTarget.h"
+#include "nsISerialEventTarget.h"
+#include "nsIURIMutator.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+template <typename F>
+void RunOnBackgroundThread(F&& aFunction) {
+ nsCOMPtr<nsISerialEventTarget> backgroundQueue;
+ ASSERT_NS_SUCCEEDED(NS_CreateBackgroundTaskQueue(
+ "RunOnBackgroundThread", getter_AddRefs(backgroundQueue)));
+ ASSERT_NS_SUCCEEDED(NS_DispatchAndSpinEventLoopUntilComplete(
+ "RunOnBackgroundThread"_ns, backgroundQueue,
+ NS_NewRunnableFunction("RunOnBackgroundThread",
+ std::forward<F>(aFunction))));
+}
+
+TEST(BackgroundThreadPrincipal, CreateContent)
+{
+ RunOnBackgroundThread([] {
+ nsCOMPtr<nsIURI> contentURI;
+ ASSERT_NS_SUCCEEDED(NS_NewURI(getter_AddRefs(contentURI),
+ "http://subdomain.example.com:8000"_ns));
+ RefPtr<BasePrincipal> contentPrincipal =
+ BasePrincipal::CreateContentPrincipal(contentURI, OriginAttributes());
+ EXPECT_TRUE(contentPrincipal->Is<ContentPrincipal>());
+
+ nsCString origin;
+ ASSERT_NS_SUCCEEDED(contentPrincipal->GetOrigin(origin));
+ EXPECT_EQ(origin, "http://subdomain.example.com:8000"_ns);
+
+ nsCString siteOrigin;
+ ASSERT_NS_SUCCEEDED(contentPrincipal->GetSiteOrigin(siteOrigin));
+ EXPECT_EQ(siteOrigin, "http://example.com"_ns);
+ });
+}
+
+TEST(BackgroundThreadPrincipal, CreateNull)
+{
+ RunOnBackgroundThread([] {
+ nsCOMPtr<nsIURI> contentURI;
+ ASSERT_NS_SUCCEEDED(NS_NewURI(getter_AddRefs(contentURI),
+ "data:text/plain,hello world"_ns));
+ RefPtr<BasePrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(contentURI, OriginAttributes());
+ EXPECT_TRUE(principal->Is<NullPrincipal>());
+
+ nsCString origin;
+ ASSERT_NS_SUCCEEDED(principal->GetOrigin(origin));
+ EXPECT_TRUE(StringBeginsWith(origin, "moz-nullprincipal:"_ns));
+ });
+}
+
+TEST(BackgroundThreadPrincipal, PrincipalInfoConversions)
+{
+ RunOnBackgroundThread([] {
+ nsCOMPtr<nsIURI> contentURI;
+ ASSERT_NS_SUCCEEDED(NS_NewURI(getter_AddRefs(contentURI),
+ "http://subdomain.example.com:8000"_ns));
+ RefPtr<BasePrincipal> contentPrincipal =
+ BasePrincipal::CreateContentPrincipal(contentURI, OriginAttributes());
+ EXPECT_TRUE(contentPrincipal->Is<ContentPrincipal>());
+
+ ipc::PrincipalInfo info;
+ ASSERT_NS_SUCCEEDED(ipc::PrincipalToPrincipalInfo(contentPrincipal, &info));
+
+ EXPECT_TRUE(info.type() == ipc::PrincipalInfo::TContentPrincipalInfo);
+ EXPECT_EQ(info.get_ContentPrincipalInfo().spec(),
+ "http://subdomain.example.com:8000/"_ns);
+ EXPECT_EQ(info.get_ContentPrincipalInfo().baseDomain(), "example.com"_ns);
+
+ auto result = PrincipalInfoToPrincipal(info);
+ ASSERT_TRUE(result.isOk());
+ nsCOMPtr<nsIPrincipal> deserialized = result.unwrap();
+ EXPECT_TRUE(deserialized->GetIsContentPrincipal());
+
+ EXPECT_TRUE(deserialized->Equals(contentPrincipal));
+ });
+}
+
+} // namespace mozilla
diff --git a/caps/tests/gtest/TestNullPrincipalPrecursor.cpp b/caps/tests/gtest/TestNullPrincipalPrecursor.cpp
new file mode 100644
index 0000000000..96db5aeffd
--- /dev/null
+++ b/caps/tests/gtest/TestNullPrincipalPrecursor.cpp
@@ -0,0 +1,56 @@
+/* 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 "gtest/gtest.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/NullPrincipal.h"
+#include "nsIURIMutator.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+
+TEST(NullPrincipalPrecursor, EscapingRoundTrips)
+{
+ nsTArray<nsCString> inputs;
+
+ inputs.AppendElements(mozilla::Span(std::array{
+ "mailbox:///dev/shm/tmp5wkt9ff_.mozrunner/Mail/Local%20Folders/secure-mail?number=5"_ns,
+ }));
+
+ // Add a string for every ASCII byte both escaped and unescaped.
+ for (uint8_t c = 0; c < 128; ++c) {
+ inputs.AppendElement(nsPrintfCString("%02X: %c", c, (char)c));
+ inputs.AppendElement(nsPrintfCString("%02X: %%%02X", c, c));
+ }
+
+ nsID dummyID{0xddf15eaf,
+ 0x3837,
+ 0x4678,
+ {0x80, 0x3b, 0x86, 0x86, 0xe8, 0x17, 0x66, 0x71}};
+ nsCOMPtr<nsIURI> baseURI = NullPrincipal::CreateURI(nullptr, &dummyID);
+ ASSERT_TRUE(baseURI);
+
+ for (auto& input : inputs) {
+ // First build an escaped version of the input string using
+ // `EscapePrecursorQuery`.
+ nsCString escaped(input);
+ NullPrincipal::EscapePrecursorQuery(escaped);
+
+ // Make sure that this escaped URI round-trips through a `moz-nullprincipal`
+ // URI's query without any additional escapes.
+ nsCOMPtr<nsIURI> clone;
+ EXPECT_NS_SUCCEEDED(
+ NS_MutateURI(baseURI).SetQuery(escaped).Finalize(clone));
+ nsCString query;
+ EXPECT_NS_SUCCEEDED(clone->GetQuery(query));
+ EXPECT_EQ(escaped, query);
+
+ // Try to unescape our escaped URI and make sure we recover the input
+ // string.
+ nsCString unescaped(escaped);
+ NullPrincipal::UnescapePrecursorQuery(unescaped);
+ EXPECT_EQ(input, unescaped);
+ }
+}
+
+} // namespace mozilla
diff --git a/caps/tests/gtest/TestOriginAttributes.cpp b/caps/tests/gtest/TestOriginAttributes.cpp
new file mode 100644
index 0000000000..fa759f80d5
--- /dev/null
+++ b/caps/tests/gtest/TestOriginAttributes.cpp
@@ -0,0 +1,118 @@
+/* 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 "gtest/gtest.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/Preferences.h"
+#include "nsNetUtil.h"
+
+using mozilla::OriginAttributes;
+using mozilla::Preferences;
+
+#define FPI_PREF "privacy.firstparty.isolate"
+#define SITE_PREF "privacy.firstparty.isolate.use_site"
+
+#define TEST_FPD(_spec, _expected) \
+ TestFPD(nsLiteralString(_spec), nsLiteralString(_expected))
+
+namespace mozilla {
+
+static void TestSuffix(const OriginAttributes& attrs) {
+ nsAutoCString suffix;
+ attrs.CreateSuffix(suffix);
+
+ OriginAttributes attrsFromSuffix;
+ bool success = attrsFromSuffix.PopulateFromSuffix(suffix);
+ EXPECT_TRUE(success);
+
+ EXPECT_EQ(attrs, attrsFromSuffix);
+}
+
+static void TestFPD(const nsAString& spec, const nsAString& expected) {
+ OriginAttributes attrs;
+ nsCOMPtr<nsIURI> url;
+ ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK);
+ attrs.SetFirstPartyDomain(true, url);
+ EXPECT_TRUE(attrs.mFirstPartyDomain.Equals(expected));
+
+ TestSuffix(attrs);
+}
+
+TEST(OriginAttributes, Suffix_default)
+{
+ OriginAttributes attrs;
+ TestSuffix(attrs);
+}
+
+TEST(OriginAttributes, Suffix_inIsolatedMozBrowser)
+{
+ OriginAttributes attrs(true);
+ TestSuffix(attrs);
+}
+
+TEST(OriginAttributes, FirstPartyDomain_default)
+{
+ bool oldFpiPref = Preferences::GetBool(FPI_PREF);
+ Preferences::SetBool(FPI_PREF, true);
+ bool oldSitePref = Preferences::GetBool(SITE_PREF);
+ Preferences::SetBool(SITE_PREF, false);
+
+ TEST_FPD(u"http://www.example.com", u"example.com");
+ TEST_FPD(u"http://www.example.com:80", u"example.com");
+ TEST_FPD(u"http://www.example.com:8080", u"example.com");
+ TEST_FPD(u"http://s3.amazonaws.com", u"s3.amazonaws.com");
+ TEST_FPD(u"http://com", u"com");
+ TEST_FPD(u"http://com.", u"com.");
+ TEST_FPD(u"http://com:8080", u"com");
+ TEST_FPD(u"http://.com", u"");
+ TEST_FPD(u"http://..com", u"");
+ TEST_FPD(u"http://127.0.0.1", u"127.0.0.1");
+ TEST_FPD(u"http://[::1]", u"[::1]");
+ TEST_FPD(u"about:config",
+ u"about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla");
+ TEST_FPD(u"moz-extension://f5b6ca10-5bd4-4ed6-9baf-820dc5152bc1", u"");
+ TEST_FPD(u"moz-nullprincipal:{9bebdabb-828a-4284-8b00-432a968c6e42}",
+ u"9bebdabb-828a-4284-8b00-432a968c6e42.mozilla");
+ TEST_FPD(
+ u"moz-nullprincipal:{9bebdabb-828a-4284-8b00-432a968c6e42}"
+ u"?https://www.example.com",
+ u"9bebdabb-828a-4284-8b00-432a968c6e42.mozilla");
+
+ Preferences::SetBool(FPI_PREF, oldFpiPref);
+ Preferences::SetBool(SITE_PREF, oldSitePref);
+}
+
+TEST(OriginAttributes, FirstPartyDomain_site)
+{
+ bool oldFpiPref = Preferences::GetBool(FPI_PREF);
+ Preferences::SetBool(FPI_PREF, true);
+ bool oldSitePref = Preferences::GetBool(SITE_PREF);
+ Preferences::SetBool(SITE_PREF, true);
+
+ TEST_FPD(u"http://www.example.com", u"(http,example.com)");
+ TEST_FPD(u"http://www.example.com:80", u"(http,example.com)");
+ TEST_FPD(u"http://www.example.com:8080", u"(http,example.com)");
+ TEST_FPD(u"http://s3.amazonaws.com", u"(http,s3.amazonaws.com)");
+ TEST_FPD(u"http://com", u"(http,com)");
+ TEST_FPD(u"http://com.", u"(http,com.)");
+ TEST_FPD(u"http://com:8080", u"(http,com,8080)");
+ TEST_FPD(u"http://.com", u"(http,.com)");
+ TEST_FPD(u"http://..com", u"(http,..com)");
+ TEST_FPD(u"http://127.0.0.1", u"(http,127.0.0.1)");
+ TEST_FPD(u"http://[::1]", u"(http,[::1])");
+ TEST_FPD(u"about:config",
+ u"(about,about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla)");
+ TEST_FPD(u"moz-extension://f5b6ca10-5bd4-4ed6-9baf-820dc5152bc1", u"");
+ TEST_FPD(u"moz-nullprincipal:{9bebdabb-828a-4284-8b00-432a968c6e42}",
+ u"9bebdabb-828a-4284-8b00-432a968c6e42.mozilla");
+ TEST_FPD(
+ u"moz-nullprincipal:{9bebdabb-828a-4284-8b00-432a968c6e42}"
+ u"?https://www.example.com",
+ u"9bebdabb-828a-4284-8b00-432a968c6e42.mozilla");
+
+ Preferences::SetBool(FPI_PREF, oldFpiPref);
+ Preferences::SetBool(SITE_PREF, oldSitePref);
+}
+
+} // namespace mozilla
diff --git a/caps/tests/gtest/TestPrincipalAttributes.cpp b/caps/tests/gtest/TestPrincipalAttributes.cpp
new file mode 100644
index 0000000000..bc0bff90f6
--- /dev/null
+++ b/caps/tests/gtest/TestPrincipalAttributes.cpp
@@ -0,0 +1,39 @@
+/* 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 "gtest/gtest.h"
+#include "mozilla/BasePrincipal.h"
+#include "nsScriptSecurityManager.h"
+
+using namespace mozilla;
+
+class PrincipalAttributesParam {
+ public:
+ nsAutoCString spec;
+ bool expectIsIpAddress;
+};
+
+class PrincipalAttributesTest
+ : public ::testing::TestWithParam<PrincipalAttributesParam> {};
+
+TEST_P(PrincipalAttributesTest, PrincipalAttributesTest) {
+ nsCOMPtr<nsIScriptSecurityManager> ssm =
+ nsScriptSecurityManager::GetScriptSecurityManager();
+
+ nsAutoCString spec(GetParam().spec);
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv =
+ ssm->CreateContentPrincipalFromOrigin(spec, getter_AddRefs(principal));
+ ASSERT_EQ(rv, NS_OK);
+
+ ASSERT_EQ(principal->GetIsIpAddress(), GetParam().expectIsIpAddress);
+}
+
+static const PrincipalAttributesParam kAttributes[] = {
+ {nsAutoCString("https://mozilla.com"), false},
+ {nsAutoCString("https://127.0.0.1"), true},
+ {nsAutoCString("https://[::1]"), true},
+};
+
+INSTANTIATE_TEST_SUITE_P(TestPrincipalAttributes, PrincipalAttributesTest,
+ ::testing::ValuesIn(kAttributes));
diff --git a/caps/tests/gtest/TestPrincipalSerialization.cpp b/caps/tests/gtest/TestPrincipalSerialization.cpp
new file mode 100644
index 0000000000..159cf1c269
--- /dev/null
+++ b/caps/tests/gtest/TestPrincipalSerialization.cpp
@@ -0,0 +1,214 @@
+/* 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 "gtest/gtest.h"
+#include "mozilla/Base64.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ContentPrincipal.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/SystemPrincipal.h"
+#include "mozilla/ExpandedPrincipal.h"
+
+using mozilla::BasePrincipal;
+using mozilla::ContentPrincipal;
+using mozilla::NullPrincipal;
+using mozilla::SystemPrincipal;
+
+// None of these tests work in debug due to assert guards
+#ifndef MOZ_DEBUG
+
+// calling toJson() twice with the same string arg
+// (ensure that we truncate correctly where needed)
+TEST(PrincipalSerialization, ReusedJSONArgument)
+{
+ nsCOMPtr<nsIScriptSecurityManager> ssm =
+ nsScriptSecurityManager::GetScriptSecurityManager();
+
+ nsAutoCString spec("https://mozilla.com");
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv =
+ ssm->CreateContentPrincipalFromOrigin(spec, getter_AddRefs(principal));
+ ASSERT_EQ(rv, NS_OK);
+
+ nsAutoCString JSON;
+ rv = BasePrincipal::Cast(principal)->ToJSON(JSON);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_TRUE(JSON.EqualsLiteral("{\"1\":{\"0\":\"https://mozilla.com/\"}}"));
+
+ nsAutoCString spec2("https://example.com");
+ nsCOMPtr<nsIPrincipal> principal2;
+ rv = ssm->CreateContentPrincipalFromOrigin(spec2, getter_AddRefs(principal2));
+ ASSERT_EQ(rv, NS_OK);
+
+ // Reuse JSON without truncation to check the code is doing this
+ rv = BasePrincipal::Cast(principal2)->ToJSON(JSON);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_TRUE(JSON.EqualsLiteral("{\"1\":{\"0\":\"https://example.com/\"}}"));
+}
+
+// Assure that calling FromProperties() with an empty array list always returns
+// a nullptr The exception here is SystemPrincipal which doesn't have fields but
+// it also doesn't implement FromProperties These are overly cautious checks
+// that we don't try to create a principal in reality FromProperties is only
+// called with a populated array.
+TEST(PrincipalSerialization, FromPropertiesEmpty)
+{
+ nsTArray<ContentPrincipal::KeyVal> resContent;
+ nsCOMPtr<nsIPrincipal> contentPrincipal =
+ ContentPrincipal::FromProperties(resContent);
+ ASSERT_EQ(nullptr, contentPrincipal);
+
+ nsTArray<ExpandedPrincipal::KeyVal> resExpanded;
+ nsCOMPtr<nsIPrincipal> expandedPrincipal =
+ ExpandedPrincipal::FromProperties(resExpanded);
+ ASSERT_EQ(nullptr, expandedPrincipal);
+
+ nsTArray<NullPrincipal::KeyVal> resNull;
+ nsCOMPtr<nsIPrincipal> nullprincipal = NullPrincipal::FromProperties(resNull);
+ ASSERT_EQ(nullptr, nullprincipal);
+}
+
+// Double check that if we have two valid principals in a serialized JSON that
+// nullptr is returned
+TEST(PrincipalSerialization, TwoKeys)
+{
+ // Sanity check that this returns a system principal
+ nsCOMPtr<nsIPrincipal> systemPrincipal =
+ BasePrincipal::FromJSON("{\"3\":{}}"_ns);
+ ASSERT_EQ(BasePrincipal::Cast(systemPrincipal)->Kind(),
+ BasePrincipal::eSystemPrincipal);
+
+ // Sanity check that this returns a content principal
+ nsCOMPtr<nsIPrincipal> contentPrincipal =
+ BasePrincipal::FromJSON("{\"1\":{\"0\":\"https://mozilla.com\"}}"_ns);
+ ASSERT_EQ(BasePrincipal::Cast(contentPrincipal)->Kind(),
+ BasePrincipal::eContentPrincipal);
+
+ // Check both combined don't return a principal
+ nsCOMPtr<nsIPrincipal> combinedPrincipal = BasePrincipal::FromJSON(
+ "{\"1\":{\"0\":\"https://mozilla.com\"},\"3\":{}}"_ns);
+ ASSERT_EQ(nullptr, combinedPrincipal);
+}
+
+#endif // ifndef MOZ_DEBUG
+
+TEST(PrincipalSerialization, ExpandedPrincipal)
+{
+ // Check basic Expandedprincipal works without OA
+ nsCOMPtr<nsIScriptSecurityManager> ssm =
+ nsScriptSecurityManager::GetScriptSecurityManager();
+
+ uint32_t length = 2;
+ nsTArray<nsCOMPtr<nsIPrincipal> > allowedDomains(length);
+ allowedDomains.SetLength(length);
+
+ nsAutoCString spec("https://mozilla.com");
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv =
+ ssm->CreateContentPrincipalFromOrigin(spec, getter_AddRefs(principal));
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(BasePrincipal::Cast(principal)->Kind(),
+ BasePrincipal::eContentPrincipal);
+ allowedDomains[0] = principal;
+
+ nsAutoCString spec2("https://mozilla.org");
+ nsCOMPtr<nsIPrincipal> principal2;
+ rv = ssm->CreateContentPrincipalFromOrigin(spec2, getter_AddRefs(principal2));
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(BasePrincipal::Cast(principal2)->Kind(),
+ BasePrincipal::eContentPrincipal);
+ allowedDomains[1] = principal2;
+
+ mozilla::OriginAttributes attrs;
+ RefPtr<ExpandedPrincipal> result =
+ ExpandedPrincipal::Create(allowedDomains, attrs);
+ ASSERT_EQ(BasePrincipal::Cast(result)->Kind(),
+ BasePrincipal::eExpandedPrincipal);
+
+ nsAutoCString JSON;
+ rv = BasePrincipal::Cast(result)->ToJSON(JSON);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_STREQ(JSON.get(),
+ "{\"2\":{\"0\":[{\"1\":{\"0\":\"https://mozilla.com/"
+ "\"}},{\"1\":{\"0\":\"https://mozilla.org/\"}}]}}");
+
+ nsCOMPtr<nsIPrincipal> returnedPrincipal = BasePrincipal::FromJSON(JSON);
+ auto outPrincipal = BasePrincipal::Cast(returnedPrincipal);
+ ASSERT_EQ(outPrincipal->Kind(), BasePrincipal::eExpandedPrincipal);
+
+ ASSERT_TRUE(outPrincipal->FastSubsumesIgnoringFPD(principal));
+ ASSERT_TRUE(outPrincipal->FastSubsumesIgnoringFPD(principal2));
+
+ nsAutoCString specDev("https://mozilla.dev");
+ nsCOMPtr<nsIPrincipal> principalDev;
+ rv = ssm->CreateContentPrincipalFromOrigin(specDev,
+ getter_AddRefs(principalDev));
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(BasePrincipal::Cast(principalDev)->Kind(),
+ BasePrincipal::eContentPrincipal);
+
+ ASSERT_FALSE(outPrincipal->FastSubsumesIgnoringFPD(principalDev));
+}
+
+TEST(PrincipalSerialization, ExpandedPrincipalOA)
+{
+ // Check Expandedprincipal works with top level OA
+ nsCOMPtr<nsIScriptSecurityManager> ssm =
+ nsScriptSecurityManager::GetScriptSecurityManager();
+
+ uint32_t length = 2;
+ nsTArray<nsCOMPtr<nsIPrincipal> > allowedDomains(length);
+ allowedDomains.SetLength(length);
+
+ nsAutoCString spec("https://mozilla.com");
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv =
+ ssm->CreateContentPrincipalFromOrigin(spec, getter_AddRefs(principal));
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(BasePrincipal::Cast(principal)->Kind(),
+ BasePrincipal::eContentPrincipal);
+ allowedDomains[0] = principal;
+
+ nsAutoCString spec2("https://mozilla.org");
+ nsCOMPtr<nsIPrincipal> principal2;
+ rv = ssm->CreateContentPrincipalFromOrigin(spec2, getter_AddRefs(principal2));
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(BasePrincipal::Cast(principal2)->Kind(),
+ BasePrincipal::eContentPrincipal);
+ allowedDomains[1] = principal2;
+
+ mozilla::OriginAttributes attrs;
+ nsAutoCString suffix("^userContextId=1");
+ bool ok = attrs.PopulateFromSuffix(suffix);
+ ASSERT_TRUE(ok);
+
+ RefPtr<ExpandedPrincipal> result =
+ ExpandedPrincipal::Create(allowedDomains, attrs);
+ ASSERT_EQ(BasePrincipal::Cast(result)->Kind(),
+ BasePrincipal::eExpandedPrincipal);
+
+ nsAutoCString JSON;
+ rv = BasePrincipal::Cast(result)->ToJSON(JSON);
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_STREQ(JSON.get(),
+ "{\"2\":{\"0\":[{\"1\":{\"0\":\"https://mozilla.com/"
+ "\"}},{\"1\":{\"0\":\"https://mozilla.org/"
+ "\"}}],\"1\":\"^userContextId=1\"}}");
+
+ nsCOMPtr<nsIPrincipal> returnedPrincipal = BasePrincipal::FromJSON(JSON);
+ auto outPrincipal = BasePrincipal::Cast(returnedPrincipal);
+ ASSERT_EQ(outPrincipal->Kind(), BasePrincipal::eExpandedPrincipal);
+
+ ASSERT_TRUE(outPrincipal->FastSubsumesIgnoringFPD(principal));
+ ASSERT_TRUE(outPrincipal->FastSubsumesIgnoringFPD(principal2));
+
+ nsAutoCString specDev("https://mozilla.dev");
+ nsCOMPtr<nsIPrincipal> principalDev;
+ rv = ssm->CreateContentPrincipalFromOrigin(specDev,
+ getter_AddRefs(principalDev));
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_EQ(BasePrincipal::Cast(principalDev)->Kind(),
+ BasePrincipal::eContentPrincipal);
+
+ ASSERT_FALSE(outPrincipal->FastSubsumesIgnoringFPD(principalDev));
+}
diff --git a/caps/tests/gtest/TestRedirectChainURITruncation.cpp b/caps/tests/gtest/TestRedirectChainURITruncation.cpp
new file mode 100644
index 0000000000..34c633499c
--- /dev/null
+++ b/caps/tests/gtest/TestRedirectChainURITruncation.cpp
@@ -0,0 +1,231 @@
+/* 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 "gtest/gtest.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ContentPrincipal.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/SystemPrincipal.h"
+#include "mozilla/ExpandedPrincipal.h"
+#include "nsContentUtils.h"
+#include "mozilla/LoadInfo.h"
+
+namespace mozilla {
+
+void checkPrincipalTruncation(nsIPrincipal* aPrincipal,
+ const nsACString& aExpectedSpec = ""_ns,
+ const nsTArray<nsCString>& aExpectedSpecs = {}) {
+ nsCOMPtr<nsIPrincipal> truncatedPrincipal =
+ net::CreateTruncatedPrincipal(aPrincipal);
+ ASSERT_TRUE(truncatedPrincipal);
+
+ if (aPrincipal->IsSystemPrincipal()) {
+ ASSERT_TRUE(truncatedPrincipal->IsSystemPrincipal());
+ return;
+ }
+
+ if (aPrincipal->GetIsNullPrincipal()) {
+ nsCOMPtr<nsIPrincipal> precursorPrincipal =
+ aPrincipal->GetPrecursorPrincipal();
+
+ nsAutoCString principalSpecEnding("}");
+ nsAutoCString expectedTestSpec(aExpectedSpec);
+ if (!aExpectedSpec.IsEmpty()) {
+ principalSpecEnding += "?"_ns;
+ expectedTestSpec += "/"_ns;
+ }
+
+ if (precursorPrincipal) {
+ nsAutoCString precursorSpec;
+ precursorPrincipal->GetAsciiSpec(precursorSpec);
+ ASSERT_TRUE(precursorSpec.Equals(expectedTestSpec));
+ }
+
+ // NullPrincipals have UUIDs as part of their scheme i.e.
+ // moz-nullprincipal:{9bebdabb-828a-4284-8b00-432a968c6e42}
+ // To avoid having to know the UUID beforehand we check the principal's spec
+ // before and after the UUID
+ nsAutoCString principalSpec;
+ truncatedPrincipal->GetAsciiSpec(principalSpec);
+ ASSERT_TRUE(StringBeginsWith(principalSpec, "moz-nullprincipal:{"_ns));
+ ASSERT_TRUE(
+ StringEndsWith(principalSpec, principalSpecEnding + aExpectedSpec));
+ return;
+ }
+
+ if (aPrincipal->GetIsExpandedPrincipal()) {
+ const nsTArray<nsCOMPtr<nsIPrincipal>>& truncatedAllowList =
+ BasePrincipal::Cast(truncatedPrincipal)
+ ->As<ExpandedPrincipal>()
+ ->AllowList();
+
+ for (size_t i = 0; i < aExpectedSpecs.Length(); ++i) {
+ nsAutoCString principalSpec;
+ truncatedAllowList[i]->GetAsciiSpec(principalSpec);
+ ASSERT_TRUE(principalSpec.Equals(aExpectedSpecs[i]));
+ }
+ return;
+ }
+
+ if (aPrincipal->GetIsContentPrincipal()) {
+ nsAutoCString principalSpec;
+ truncatedPrincipal->GetAsciiSpec(principalSpec);
+ ASSERT_TRUE(principalSpec.Equals(aExpectedSpec));
+ return;
+ }
+
+ // Tests should not reach this point
+ ADD_FAILURE();
+}
+
+void checkPrincipalTruncation(nsIPrincipal* aPrincipal,
+ const nsTArray<nsCString>& aExpectedSpecs = {}) {
+ checkPrincipalTruncation(aPrincipal, ""_ns, aExpectedSpecs);
+}
+
+TEST(RedirectChainURITruncation, ContentPrincipal)
+{
+ // ======================= HTTP Scheme =======================
+ nsAutoCString httpSpec(
+ "http://root:toor@www.example.com:200/foo/bar/baz.html?qux#thud");
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), httpSpec);
+ ASSERT_EQ(rv, NS_OK);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ OriginAttributes attrs;
+ principal = BasePrincipal::CreateContentPrincipal(uri, attrs);
+ ASSERT_TRUE(principal);
+
+ checkPrincipalTruncation(principal,
+ "http://www.example.com:200/foo/bar/baz.html"_ns);
+
+ // ======================= HTTPS Scheme =======================
+ nsAutoCString httpsSpec(
+ "https://root:toor@www.example.com:200/foo/bar/baz.html?qux#thud");
+ rv = NS_NewURI(getter_AddRefs(uri), httpsSpec);
+ ASSERT_EQ(rv, NS_OK);
+
+ principal = BasePrincipal::CreateContentPrincipal(uri, attrs);
+ ASSERT_TRUE(principal);
+
+ checkPrincipalTruncation(principal,
+ "https://www.example.com:200/foo/bar/baz.html"_ns);
+
+ // ======================= View Source Scheme =======================
+ nsAutoCString viewSourceSpec(
+ "view-source:https://root:toor@www.example.com:200/foo/bar/"
+ "baz.html?qux#thud");
+ rv = NS_NewURI(getter_AddRefs(uri), viewSourceSpec);
+ ASSERT_EQ(rv, NS_OK);
+
+ principal = BasePrincipal::CreateContentPrincipal(uri, attrs);
+ ASSERT_TRUE(principal);
+
+ checkPrincipalTruncation(
+ principal, "view-source:https://www.example.com:200/foo/bar/baz.html"_ns);
+
+ // ======================= About Scheme =======================
+ nsAutoCString aboutSpec("about:config");
+ rv = NS_NewURI(getter_AddRefs(uri), aboutSpec);
+ ASSERT_EQ(rv, NS_OK);
+
+ principal = BasePrincipal::CreateContentPrincipal(uri, attrs);
+ ASSERT_TRUE(principal);
+
+ checkPrincipalTruncation(principal, "about:config"_ns);
+
+ // ======================= Resource Scheme =======================
+ nsAutoCString resourceSpec("resource://testing/");
+ rv = NS_NewURI(getter_AddRefs(uri), resourceSpec);
+ ASSERT_EQ(rv, NS_OK);
+
+ principal = BasePrincipal::CreateContentPrincipal(uri, attrs);
+ ASSERT_TRUE(principal);
+
+ checkPrincipalTruncation(principal, "resource://testing/"_ns);
+
+ // ======================= Chrome Scheme =======================
+ nsAutoCString chromeSpec("chrome://foo/content/bar.xul");
+ rv = NS_NewURI(getter_AddRefs(uri), chromeSpec);
+ ASSERT_EQ(rv, NS_OK);
+
+ principal = BasePrincipal::CreateContentPrincipal(uri, attrs);
+ ASSERT_TRUE(principal);
+
+ checkPrincipalTruncation(principal, "chrome://foo/content/bar.xul"_ns);
+}
+
+TEST(RedirectChainURITruncation, NullPrincipal)
+{
+ // ======================= NullPrincipal =======================
+ nsCOMPtr<nsIPrincipal> principal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+ ASSERT_TRUE(principal);
+
+ checkPrincipalTruncation(principal, ""_ns);
+
+ // ======================= NullPrincipal & Precursor =======================
+ nsAutoCString precursorSpec(
+ "https://root:toor@www.example.com:200/foo/bar/baz.html?qux#thud");
+
+ nsCOMPtr<nsIURI> precursorURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(precursorURI), precursorSpec);
+ ASSERT_EQ(rv, NS_OK);
+
+ OriginAttributes attrs;
+ nsCOMPtr<nsIPrincipal> precursorPrincipal =
+ BasePrincipal::CreateContentPrincipal(precursorURI, attrs);
+ principal = NullPrincipal::CreateWithInheritedAttributes(precursorPrincipal);
+ ASSERT_TRUE(principal);
+
+ checkPrincipalTruncation(principal, "https://www.example.com:200"_ns);
+}
+
+TEST(RedirectChainURITruncation, SystemPrincipal)
+{
+ nsCOMPtr<nsIPrincipal> principal = nsContentUtils::GetSystemPrincipal();
+ ASSERT_TRUE(principal);
+
+ checkPrincipalTruncation(principal, ""_ns);
+}
+
+TEST(RedirectChainURITruncation, ExtendedPrincipal)
+{
+ // ======================= HTTP Scheme =======================
+ nsAutoCString httpSpec(
+ "http://root:toor@www.example.com:200/foo/bar/baz.html?qux#thud");
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), httpSpec);
+ ASSERT_EQ(rv, NS_OK);
+
+ nsCOMPtr<nsIPrincipal> firstContentPrincipal;
+ OriginAttributes attrs;
+ firstContentPrincipal = BasePrincipal::CreateContentPrincipal(uri, attrs);
+ ASSERT_TRUE(firstContentPrincipal);
+
+ // ======================= HTTPS Scheme =======================
+ nsCOMPtr<nsIPrincipal> secondContentPrincipal;
+ nsAutoCString httpsSpec(
+ "https://root:toor@www.example.com:200/foo/bar/baz.html?qux#thud");
+ rv = NS_NewURI(getter_AddRefs(uri), httpsSpec);
+ ASSERT_EQ(rv, NS_OK);
+
+ secondContentPrincipal = BasePrincipal::CreateContentPrincipal(uri, attrs);
+ ASSERT_TRUE(secondContentPrincipal);
+
+ // ======================= ExpandedPrincipal =======================
+ const nsTArray<nsCString>& expectedSpecs = {
+ "http://www.example.com:200/foo/bar/baz.html"_ns,
+ "https://www.example.com:200/foo/bar/baz.html"_ns,
+ };
+ nsTArray<nsCOMPtr<nsIPrincipal>> allowList = {firstContentPrincipal,
+ secondContentPrincipal};
+ nsCOMPtr<nsIPrincipal> principal =
+ ExpandedPrincipal::Create(allowList, attrs);
+ ASSERT_TRUE(principal);
+
+ checkPrincipalTruncation(principal, expectedSpecs);
+}
+
+} // namespace mozilla
diff --git a/caps/tests/gtest/moz.build b/caps/tests/gtest/moz.build
new file mode 100644
index 0000000000..10b1a66f3b
--- /dev/null
+++ b/caps/tests/gtest/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+UNIFIED_SOURCES += [
+ "TestBackgroundThreadPrincipal.cpp",
+ "TestNullPrincipalPrecursor.cpp",
+ "TestOriginAttributes.cpp",
+ "TestPrincipalAttributes.cpp",
+ "TestPrincipalSerialization.cpp",
+ "TestRedirectChainURITruncation.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/caps/tests/mochitest/browser.ini b/caps/tests/mochitest/browser.ini
new file mode 100644
index 0000000000..a1c76eb57b
--- /dev/null
+++ b/caps/tests/mochitest/browser.ini
@@ -0,0 +1,2 @@
+[browser_checkloaduri.js]
+[browser_aboutOrigin.js]
diff --git a/caps/tests/mochitest/browser_aboutOrigin.js b/caps/tests/mochitest/browser_aboutOrigin.js
new file mode 100644
index 0000000000..fc2e2d8f53
--- /dev/null
+++ b/caps/tests/mochitest/browser_aboutOrigin.js
@@ -0,0 +1,12 @@
+"use strict";
+
+let tests = ["about:robots?foo", "about:robots#foo", "about:robots?foo#bar"];
+tests.forEach(async test => {
+ add_task(async () => {
+ await BrowserTestUtils.withNewTab(test, async browser => {
+ await SpecialPowers.spawn(browser, [], () => {
+ is(content.document.nodePrincipal.origin, "about:robots");
+ });
+ });
+ });
+});
diff --git a/caps/tests/mochitest/browser_checkloaduri.js b/caps/tests/mochitest/browser_checkloaduri.js
new file mode 100644
index 0000000000..11ff2d1a08
--- /dev/null
+++ b/caps/tests/mochitest/browser_checkloaduri.js
@@ -0,0 +1,393 @@
+"use strict";
+
+let ssm = Services.scriptSecurityManager;
+// This will show a directory listing, but we never actually load these so that's OK.
+const kDummyPage = getRootDirectory(gTestPath);
+
+const kAboutPagesRegistered = Promise.all([
+ BrowserTestUtils.registerAboutPage(
+ registerCleanupFunction,
+ "test-chrome-privs",
+ kDummyPage,
+ Ci.nsIAboutModule.ALLOW_SCRIPT
+ ),
+ BrowserTestUtils.registerAboutPage(
+ registerCleanupFunction,
+ "test-chrome-privs2",
+ kDummyPage,
+ Ci.nsIAboutModule.ALLOW_SCRIPT
+ ),
+ BrowserTestUtils.registerAboutPage(
+ registerCleanupFunction,
+ "test-unknown-linkable",
+ kDummyPage,
+ Ci.nsIAboutModule.MAKE_LINKABLE | Ci.nsIAboutModule.ALLOW_SCRIPT
+ ),
+ BrowserTestUtils.registerAboutPage(
+ registerCleanupFunction,
+ "test-unknown-linkable2",
+ kDummyPage,
+ Ci.nsIAboutModule.MAKE_LINKABLE | Ci.nsIAboutModule.ALLOW_SCRIPT
+ ),
+ BrowserTestUtils.registerAboutPage(
+ registerCleanupFunction,
+ "test-unknown-unlinkable",
+ kDummyPage,
+ Ci.nsIAboutModule.ALLOW_SCRIPT
+ ),
+ BrowserTestUtils.registerAboutPage(
+ registerCleanupFunction,
+ "test-unknown-unlinkable2",
+ kDummyPage,
+ Ci.nsIAboutModule.ALLOW_SCRIPT
+ ),
+ BrowserTestUtils.registerAboutPage(
+ registerCleanupFunction,
+ "test-content-unlinkable",
+ kDummyPage,
+ Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ Ci.nsIAboutModule.ALLOW_SCRIPT
+ ),
+ BrowserTestUtils.registerAboutPage(
+ registerCleanupFunction,
+ "test-content-unlinkable2",
+ kDummyPage,
+ Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ Ci.nsIAboutModule.ALLOW_SCRIPT
+ ),
+ BrowserTestUtils.registerAboutPage(
+ registerCleanupFunction,
+ "test-content-linkable",
+ kDummyPage,
+ Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ Ci.nsIAboutModule.MAKE_LINKABLE |
+ Ci.nsIAboutModule.ALLOW_SCRIPT
+ ),
+ BrowserTestUtils.registerAboutPage(
+ registerCleanupFunction,
+ "test-content-linkable2",
+ kDummyPage,
+ Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ Ci.nsIAboutModule.MAKE_LINKABLE |
+ Ci.nsIAboutModule.ALLOW_SCRIPT
+ ),
+]);
+
+const URLs = new Map([
+ [
+ "http://www.example.com",
+ [
+ // For each of these entries, the booleans represent whether the parent URI can:
+ // - load them
+ // - load them without principal inheritance
+ // - whether the URI can be created at all (some protocol handlers will
+ // refuse to create certain variants)
+ ["http://www.example2.com", true, true, true],
+ ["https://www.example2.com", true, true, true],
+ ["moz-icon:file:///foo/bar/baz.exe", false, false, true],
+ ["moz-icon://.exe", false, false, true],
+ ["chrome://foo/content/bar.xul", false, false, true],
+ ["view-source:http://www.example2.com", false, false, true],
+ ["view-source:https://www.example2.com", false, false, true],
+ ["data:text/html,Hi", true, false, true],
+ ["view-source:data:text/html,Hi", false, false, true],
+ ["javascript:alert('hi')", true, false, true],
+ ["moz://a", false, false, true],
+ ["about:test-chrome-privs", false, false, true],
+ ["about:test-unknown-unlinkable", false, false, true],
+ ["about:test-content-unlinkable", false, false, true],
+ ["about:test-content-linkable", true, true, true],
+ // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it:
+ ["about:test-unknown-linkable", false, false, true],
+ ],
+ ],
+ [
+ "view-source:http://www.example.com",
+ [
+ ["http://www.example2.com", true, true, true],
+ ["https://www.example2.com", true, true, true],
+ ["moz-icon:file:///foo/bar/baz.exe", false, false, true],
+ ["moz-icon://.exe", false, false, true],
+ ["chrome://foo/content/bar.xul", false, false, true],
+ ["view-source:http://www.example2.com", true, true, true],
+ ["view-source:https://www.example2.com", true, true, true],
+ ["data:text/html,Hi", true, false, true],
+ ["view-source:data:text/html,Hi", true, false, true],
+ ["javascript:alert('hi')", true, false, true],
+ ["moz://a", false, false, true],
+ ["about:test-chrome-privs", false, false, true],
+ ["about:test-unknown-unlinkable", false, false, true],
+ ["about:test-content-unlinkable", false, false, true],
+ ["about:test-content-linkable", true, true, true],
+ // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it:
+ ["about:test-unknown-linkable", false, false, true],
+ ],
+ ],
+ // about: related tests.
+ [
+ "about:test-chrome-privs",
+ [
+ ["about:test-chrome-privs", true, true, true],
+ ["about:test-chrome-privs2", true, true, true],
+ ["about:test-chrome-privs2?foo#bar", true, true, true],
+ ["about:test-chrome-privs2?foo", true, true, true],
+ ["about:test-chrome-privs2#bar", true, true, true],
+
+ ["about:test-unknown-unlinkable", true, true, true],
+
+ ["about:test-content-unlinkable", true, true, true],
+ ["about:test-content-unlinkable?foo", true, true, true],
+ ["about:test-content-unlinkable?foo#bar", true, true, true],
+ ["about:test-content-unlinkable#bar", true, true, true],
+
+ ["about:test-content-linkable", true, true, true],
+
+ ["about:test-unknown-linkable", true, true, true],
+ ["moz-icon:file:///foo/bar/baz.exe", true, true, true],
+ ["moz-icon://.exe", true, true, true],
+ ],
+ ],
+ [
+ "about:test-unknown-unlinkable",
+ [
+ ["about:test-chrome-privs", false, false, true],
+
+ // Can link to ourselves:
+ ["about:test-unknown-unlinkable", true, true, true],
+ // Can't link to unlinkable content if we're not sure it's privileged:
+ ["about:test-unknown-unlinkable2", false, false, true],
+
+ ["about:test-content-unlinkable", true, true, true],
+ ["about:test-content-unlinkable2", true, true, true],
+ ["about:test-content-unlinkable2?foo", true, true, true],
+ ["about:test-content-unlinkable2?foo#bar", true, true, true],
+ ["about:test-content-unlinkable2#bar", true, true, true],
+
+ ["about:test-content-linkable", true, true, true],
+
+ // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it:
+ ["about:test-unknown-linkable", false, false, true],
+ ],
+ ],
+ [
+ "about:test-content-unlinkable",
+ [
+ ["about:test-chrome-privs", false, false, true],
+
+ // Can't link to unlinkable content if we're not sure it's privileged:
+ ["about:test-unknown-unlinkable", false, false, true],
+
+ ["about:test-content-unlinkable", true, true, true],
+ ["about:test-content-unlinkable2", true, true, true],
+ ["about:test-content-unlinkable2?foo", true, true, true],
+ ["about:test-content-unlinkable2?foo#bar", true, true, true],
+ ["about:test-content-unlinkable2#bar", true, true, true],
+
+ ["about:test-content-linkable", true, true, true],
+ ["about:test-unknown-linkable", false, false, true],
+ ],
+ ],
+ [
+ "about:test-unknown-linkable",
+ [
+ ["about:test-chrome-privs", false, false, true],
+
+ // Linkable content can't link to unlinkable content.
+ ["about:test-unknown-unlinkable", false, false, true],
+
+ ["about:test-content-unlinkable", false, false, true],
+ ["about:test-content-unlinkable2", false, false, true],
+ ["about:test-content-unlinkable2?foo", false, false, true],
+ ["about:test-content-unlinkable2?foo#bar", false, false, true],
+ ["about:test-content-unlinkable2#bar", false, false, true],
+
+ // ... but it can link to other linkable content.
+ ["about:test-content-linkable", true, true, true],
+
+ // Can link to ourselves:
+ ["about:test-unknown-linkable", true, true, true],
+
+ // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it:
+ ["about:test-unknown-linkable2", false, false, true],
+ ],
+ ],
+ [
+ "about:test-content-linkable",
+ [
+ ["about:test-chrome-privs", false, false, true],
+
+ // Linkable content can't link to unlinkable content.
+ ["about:test-unknown-unlinkable", false, false, true],
+
+ ["about:test-content-unlinkable", false, false, true],
+
+ // ... but it can link to itself and other linkable content.
+ ["about:test-content-linkable", true, true, true],
+ ["about:test-content-linkable2", true, true, true],
+
+ // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it:
+ ["about:test-unknown-linkable", false, false, true],
+ ],
+ ],
+]);
+
+function testURL(
+ source,
+ target,
+ canLoad,
+ canLoadWithoutInherit,
+ canCreate,
+ flags
+) {
+ function getPrincipalDesc(principal) {
+ if (principal.spec != "") {
+ return principal.spec;
+ }
+ if (principal.isSystemPrincipal) {
+ return "system principal";
+ }
+ if (principal.isNullPrincipal) {
+ return "null principal";
+ }
+ return "unknown principal";
+ }
+ let threw = false;
+ let targetURI;
+ try {
+ targetURI = Services.io.newURI(target);
+ } catch (ex) {
+ ok(
+ !canCreate,
+ "Shouldn't be passing URIs that we can't create. Failed to create: " +
+ target
+ );
+ return;
+ }
+ ok(
+ canCreate,
+ "Created a URI for " +
+ target +
+ " which should " +
+ (canCreate ? "" : "not ") +
+ "be possible."
+ );
+ try {
+ ssm.checkLoadURIWithPrincipal(source, targetURI, flags);
+ } catch (ex) {
+ info(ex.message);
+ threw = true;
+ }
+ let inheritDisallowed = flags & ssm.DISALLOW_INHERIT_PRINCIPAL;
+ let shouldThrow = inheritDisallowed ? !canLoadWithoutInherit : !canLoad;
+ ok(
+ threw == shouldThrow,
+ "Should " +
+ (shouldThrow ? "" : "not ") +
+ "throw an error when loading " +
+ target +
+ " from " +
+ getPrincipalDesc(source) +
+ (inheritDisallowed ? " without" : " with") +
+ " principal inheritance."
+ );
+}
+
+add_task(async function () {
+ // In this test we want to verify both http and https load
+ // restrictions, hence we explicitly switch off the https-first
+ // upgrading mechanism.
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first", false]],
+ });
+
+ await kAboutPagesRegistered;
+ let baseFlags = ssm.STANDARD | ssm.DONT_REPORT_ERRORS;
+ for (let [sourceString, targetsAndExpectations] of URLs) {
+ let source;
+ if (sourceString.startsWith("about:test-chrome-privs")) {
+ source = ssm.getSystemPrincipal();
+ } else {
+ source = ssm.createContentPrincipal(Services.io.newURI(sourceString), {});
+ }
+ for (let [
+ target,
+ canLoad,
+ canLoadWithoutInherit,
+ canCreate,
+ ] of targetsAndExpectations) {
+ testURL(
+ source,
+ target,
+ canLoad,
+ canLoadWithoutInherit,
+ canCreate,
+ baseFlags
+ );
+ testURL(
+ source,
+ target,
+ canLoad,
+ canLoadWithoutInherit,
+ canCreate,
+ baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL
+ );
+ }
+ }
+
+ // Now test blob URIs, which we need to do in-content.
+ await BrowserTestUtils.withNewTab(
+ "http://www.example.com/",
+ async function (browser) {
+ await SpecialPowers.spawn(
+ browser,
+ [testURL.toString()],
+ async function (testURLFn) {
+ // eslint-disable-next-line no-shadow , no-eval
+ let testURL = eval("(" + testURLFn + ")");
+ // eslint-disable-next-line no-shadow
+ let ssm = Services.scriptSecurityManager;
+ // eslint-disable-next-line no-shadow
+ let baseFlags = ssm.STANDARD | ssm.DONT_REPORT_ERRORS;
+ // eslint-disable-next-line no-unused-vars
+ let b = new content.Blob(["I am a blob"]);
+ let contentBlobURI = content.URL.createObjectURL(b);
+ let contentPrincipal = content.document.nodePrincipal;
+ // Loading this blob URI from the content page should work:
+ testURL(
+ contentPrincipal,
+ contentBlobURI,
+ true,
+ true,
+ true,
+ baseFlags
+ );
+ testURL(
+ contentPrincipal,
+ contentBlobURI,
+ true,
+ true,
+ true,
+ baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL
+ );
+
+ testURL(
+ contentPrincipal,
+ "view-source:" + contentBlobURI,
+ false,
+ false,
+ true,
+ baseFlags
+ );
+ testURL(
+ contentPrincipal,
+ "view-source:" + contentBlobURI,
+ false,
+ false,
+ true,
+ baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL
+ );
+ }
+ );
+ }
+ );
+});
diff --git a/caps/tests/mochitest/chrome.ini b/caps/tests/mochitest/chrome.ini
new file mode 100644
index 0000000000..776afa34e4
--- /dev/null
+++ b/caps/tests/mochitest/chrome.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ file_data.txt
+ file_disableScript.html
+ !/caps/tests/mochitest/file_data.txt
+ !/caps/tests/mochitest/file_disableScript.html
+
+[test_bug995943.xhtml]
+skip-if = (verify && debug && (os == 'mac'))
+[test_addonMayLoad.html]
+[test_disableScript.xhtml]
diff --git a/caps/tests/mochitest/file_bug1367586-followon.html b/caps/tests/mochitest/file_bug1367586-followon.html
new file mode 100644
index 0000000000..3b648ce746
--- /dev/null
+++ b/caps/tests/mochitest/file_bug1367586-followon.html
@@ -0,0 +1 @@
+<body>Follow-on navigation content</body>
diff --git a/caps/tests/mochitest/file_bug1367586-redirect.sjs b/caps/tests/mochitest/file_bug1367586-redirect.sjs
new file mode 100644
index 0000000000..12a980155f
--- /dev/null
+++ b/caps/tests/mochitest/file_bug1367586-redirect.sjs
@@ -0,0 +1,8 @@
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 302, "Moved");
+ aResponse.setHeader(
+ "Location",
+ "http://mochi.test:8888/tests/caps/tests/mochitest/file_bug1367586-target.html"
+ );
+ aResponse.write("To be redirected to target");
+}
diff --git a/caps/tests/mochitest/file_bug1367586-target.html b/caps/tests/mochitest/file_bug1367586-target.html
new file mode 100644
index 0000000000..e2a2fde20d
--- /dev/null
+++ b/caps/tests/mochitest/file_bug1367586-target.html
@@ -0,0 +1,6 @@
+<head><script>
+window.addEventListener("pageshow", function(event) {
+ parent.ok(!event.persisted, "Should not load from bfcache");
+});
+</script></head>
+<body>Redirect target content</body>
diff --git a/caps/tests/mochitest/file_data.txt b/caps/tests/mochitest/file_data.txt
new file mode 100644
index 0000000000..26d7bd8488
--- /dev/null
+++ b/caps/tests/mochitest/file_data.txt
@@ -0,0 +1 @@
+server data fetched over XHR
diff --git a/caps/tests/mochitest/file_disableScript.html b/caps/tests/mochitest/file_disableScript.html
new file mode 100644
index 0000000000..f4888cd586
--- /dev/null
+++ b/caps/tests/mochitest/file_disableScript.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+var gFiredOnload = false;
+var gFiredOnclick = false;
+</script>
+</head>
+<body onload="gFiredOnload = true;" onclick="gFiredOnclick = true;">
+</body>
+</html>
diff --git a/caps/tests/mochitest/mochitest.ini b/caps/tests/mochitest/mochitest.ini
new file mode 100644
index 0000000000..9036a3ac23
--- /dev/null
+++ b/caps/tests/mochitest/mochitest.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+support-files =
+ file_bug1367586-followon.html
+ file_bug1367586-redirect.sjs
+ file_bug1367586-target.html
+ file_data.txt
+ file_disableScript.html
+ !/js/xpconnect/tests/mochitest/file_empty.html
+
+[test_bug246699.html]
+[test_bug292789.html]
+skip-if = os == 'android'
+[test_bug423375.html]
+[test_bug470804.html]
+[test_bug1367586.html]
+skip-if =
+ http3
+[test_disallowInheritPrincipal.html]
diff --git a/caps/tests/mochitest/resource_test_file.html b/caps/tests/mochitest/resource_test_file.html
new file mode 100644
index 0000000000..8201bd70e0
--- /dev/null
+++ b/caps/tests/mochitest/resource_test_file.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><head><title>resource test file</title></head><body></body></html>
diff --git a/caps/tests/mochitest/test_addonMayLoad.html b/caps/tests/mochitest/test_addonMayLoad.html
new file mode 100644
index 0000000000..2d5e5e59f1
--- /dev/null
+++ b/caps/tests/mochitest/test_addonMayLoad.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1180921
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1180921</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1180921 */
+ let module = Cu.getGlobalForObject(Services);
+ let ssm = Services.scriptSecurityManager;
+
+ function StubPolicy(id, subdomain) {
+ /* globals MatchPatternSet */
+ return new module.WebExtensionPolicy({
+ id,
+ mozExtensionHostname: id,
+ baseURL: `file:///{id}`,
+
+ allowedOrigins: new MatchPatternSet([`*://${subdomain}.example.org/*`]),
+ localizeCallback(string) {},
+ });
+ }
+
+ /* globals WebExtensionPolicy */
+ let policyA = StubPolicy("addona", "test1");
+ let policyB = StubPolicy("addonb", "test2");
+ policyA.active = true;
+ policyB.active = true;
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.registerCleanupFunction(function() {
+ policyA.active = false;
+ policyB.active = false;
+ });
+
+ function tryLoad(sb, uri) {
+ let p = new Promise(function(resolve, reject) {
+ Cu.exportFunction(resolve, sb, { defineAs: "finish" });
+ Cu.exportFunction(reject, sb, { defineAs: "error" });
+ sb.eval("try { (function () { " +
+ " var xhr = new XMLHttpRequest();" +
+ " xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { finish(xhr.status == 200); } };" +
+ " xhr.open('GET', '" + uri + "', true);" +
+ " xhr.send();" +
+ "})() } catch (e) { error(e); }");
+ });
+ return p;
+ }
+
+ let addonA = new Cu.Sandbox(ssm.createContentPrincipal(Services.io.newURI("moz-extension://addonA/"), {}),
+ {wantGlobalProperties: ["XMLHttpRequest"]});
+ let addonB = new Cu.Sandbox(ssm.createContentPrincipal(Services.io.newURI("moz-extension://addonB/"), {}),
+ {wantGlobalProperties: ["XMLHttpRequest"]});
+
+ function uriForDomain(d) { return d + "/tests/caps/tests/mochitest/file_data.txt"; }
+
+ tryLoad(addonA, uriForDomain("http://test4.example.org"))
+ .then(function(success) {
+ ok(!success, "cross-origin load should fail for addon A");
+ return tryLoad(addonA, uriForDomain("http://test1.example.org"));
+ }).then(function(success) {
+ ok(success, "allowlisted cross-origin load of test1 should succeed for addon A");
+ return tryLoad(addonB, uriForDomain("http://test1.example.org"));
+ }).then(function(success) {
+ ok(!success, "non-allowlisted cross-origin load of test1 should fail for addon B");
+ return tryLoad(addonB, uriForDomain("http://test2.example.org"));
+ }).then(function(success) {
+ ok(success, "allowlisted cross-origin load of test2 should succeed for addon B");
+ return tryLoad(addonA, uriForDomain("http://test2.example.org"));
+ }).then(function(success) {
+ ok(!success, "non-allowlisted cross-origin load of test2 should fail for addon A");
+ SimpleTest.finish();
+ }, function(e) {
+ ok(false, "Rejected promise chain: " + e);
+ SimpleTest.finish();
+ });
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1180921">Mozilla Bug 1180921</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/caps/tests/mochitest/test_bug1367586.html b/caps/tests/mochitest/test_bug1367586.html
new file mode 100644
index 0000000000..d95693c94d
--- /dev/null
+++ b/caps/tests/mochitest/test_bug1367586.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1367586
+-->
+<head>
+ <title>Test for Bug 1367586</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe id="load-frame"></iframe>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var frm = document.getElementById("load-frame");
+var step = 0;
+
+window.addEventListener("load", () => {
+ frm.contentWindow.location = "http://mochi.test:8888/tests/caps/tests/mochitest/file_bug1367586-redirect.sjs";
+ frm.addEventListener("load", function() {
+ ++step;
+ SimpleTest.executeSoon((function(_step, _frm) {
+ switch (_step) {
+ case 1:
+ is(_frm.contentWindow.location.href, "http://mochi.test:8888/tests/caps/tests/mochitest/file_bug1367586-target.html",
+ "Redirected to the expected target in step 1");
+ _frm.contentWindow.location = "http://mochi.test:8888/tests/caps/tests/mochitest/file_bug1367586-followon.html";
+ break;
+ case 2:
+ is(_frm.contentWindow.location.href, "http://mochi.test:8888/tests/caps/tests/mochitest/file_bug1367586-followon.html",
+ "Navigated to the expected URL in step 2");
+ _frm.contentWindow.history.back();
+ break;
+ case 3:
+ is(_frm.contentWindow.location.href, "http://mochi.test:8888/tests/caps/tests/mochitest/file_bug1367586-target.html",
+ "Seeing the correct URL when navigating back in step 3");
+ SimpleTest.finish();
+ break;
+ }
+ }).bind(window, step, frm));
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/caps/tests/mochitest/test_bug246699.html b/caps/tests/mochitest/test_bug246699.html
new file mode 100644
index 0000000000..9ff25c110a
--- /dev/null
+++ b/caps/tests/mochitest/test_bug246699.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=246699
+-->
+<head>
+ <title>Test for Bug 246699</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=246699">Mozilla Bug 246699</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="load-frame"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/**
+ * Test for Bug 246699
+ * (should produce stack information for caps errors)
+ */
+function isError(e) {
+ return e.constructor.name === "Error" || e.constructor.name === "TypeError";
+}
+
+function hasStack(e) {
+ return isError(e) && /inciteCaps/.test(e.stack);
+}
+
+function inciteCaps(f) {
+ try {
+ f();
+ return "operation succeeded";
+ } catch (e) {
+ if (hasStack(e)) {
+ return "denied-stack";
+ }
+ return "unexpected: " + e;
+ }
+}
+
+function tryChromeLoad() {
+ window.frames[0].location = "chrome://global/content/mozilla.html";
+}
+
+function tryComponentsClasses() {
+ return SpecialPowers.unwrap(SpecialPowers.Cc)["@mozilla.org/dummy;1"];
+}
+
+
+is(inciteCaps(tryChromeLoad), "denied-stack",
+ "should get stack for content-loading-chrome rejection");
+is(inciteCaps(tryComponentsClasses), "denied-stack",
+ "should get stack for SpecialPowers.Components.classes rejection");
+</script>
+</pre>
+</body>
+</html>
diff --git a/caps/tests/mochitest/test_bug292789.html b/caps/tests/mochitest/test_bug292789.html
new file mode 100644
index 0000000000..cd177dfd5b
--- /dev/null
+++ b/caps/tests/mochitest/test_bug292789.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=292789
+-->
+<head>
+ <title>Test for Bug 292789</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=292789">Mozilla Bug 292789</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <script src="chrome://global/content/treeUtils.js"></script>
+ <script type="application/javascript" src="chrome://mozapps/content/update/history.js"></script>
+ <script id="resjs" type="application/javascript"></script>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 292789
+ *
+ * Selectively allow access to allowlisted chrome packages
+ * even for ALLOW_CHROME mechanisms (<script>, <img> etc)
+ */
+
+/* import-globals-from ../../../toolkit/content/treeUtils.js */
+/* import-globals-from ../../../toolkit/mozapps/update/content/history.js */
+
+SimpleTest.waitForExplicitFinish();
+
+let ChromeUtils = {
+ import() { return {}; },
+};
+
+/** <script src=""> test */
+function testScriptSrc(aCallback) {
+ is(typeof gTreeUtils.sort, "function",
+ "content can still load <script> from chrome://global");
+
+ /** Try to find an export from history.js. We will find it if it is
+ * improperly not blocked, otherwise it will be "undefined".
+ */
+ is(typeof gUpdateHistory, "undefined",
+ "content should not be able to load <script> from chrome://mozapps");
+
+ /** make sure the last one didn't pass because someone
+ * moved history.js
+ */
+ var resjs = document.getElementById("resjs");
+ resjs.onload = scriptOnload;
+ resjs.src = "resource://gre/chrome/toolkit/content/mozapps/update/history.js";
+ document.getElementById("content").appendChild(resjs);
+
+ function scriptOnload() {
+ is(typeof gUpdateHistory.onLoad, "function",
+ "history.js has not moved unexpectedly");
+
+ // trigger the callback
+ if (aCallback)
+ aCallback();
+ }
+}
+
+/** <img src=""> tests */
+var img_global = "chrome://global/skin/media/error.png";
+var img_mozapps = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
+var res_mozapps = "resource://gre/chrome/toolkit/skin/classic/mozapps/extensions/extensionGeneric.svg";
+
+var imgTests = [[img_global, "success"],
+ [img_mozapps, "fail"],
+ [res_mozapps, "success"]];
+
+var curImgTest = 0;
+
+function runImgTest() {
+ var test = imgTests[curImgTest++];
+ var callback = curImgTest == imgTests.length ? finishTest : runImgTest;
+ loadImage(test[0], test[1], callback);
+}
+
+function finishTest() {
+ SimpleTest.finish();
+}
+
+function fail(event) {
+ is("fail", event.target.expected,
+ "content should not be allowed to load " + event.target.src);
+ if (event.target.callback)
+ event.target.callback();
+}
+
+function success(event) {
+ is("success", event.target.expected,
+ "content should be able to load " + event.target.src);
+ if (event.target.callback)
+ event.target.callback();
+}
+
+function loadImage(uri, expect, callback) {
+ var img = document.createElement("img");
+ img.onerror = fail;
+ img.onload = success;
+ img.expected = expect;
+ img.callback = callback;
+ img.src = uri;
+ // document.getElementById("content").appendChild(img);
+}
+
+// Start off the script src test, and have it start the img tests when complete.
+// Temporarily allow content to access all resource:// URIs.
+SpecialPowers.pushPrefEnv({
+ set: [
+ ["security.all_resource_uri_content_accessible", true],
+ ],
+}, () => testScriptSrc(runImgTest));
+</script>
+</pre>
+</body>
+</html>
diff --git a/caps/tests/mochitest/test_bug423375.html b/caps/tests/mochitest/test_bug423375.html
new file mode 100644
index 0000000000..2ae1dfb923
--- /dev/null
+++ b/caps/tests/mochitest/test_bug423375.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=423375
+-->
+<head>
+ <title>Test for Bug 423375</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=423375">Mozilla Bug 423375</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="load-frame"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/**
+ *Test for Bug 423375
+ *(content shouldn't be able to load chrome: or resource:)
+ */
+function tryLoad(url) {
+ try {
+ window.frames[0].location = url;
+ return "loaded";
+ } catch (e) {
+ if (/Access.*denied/.test(String(e))) {
+ return "denied";
+ }
+ return "unexpected: " + e;
+ }
+}
+
+is(tryLoad("chrome://global/content/mozilla.html"), "denied",
+ "content should have been prevented from loading chrome: URL");
+is(tryLoad("resource://gre-resources/html.css"), "denied",
+ "content should have been prevented from loading resource: URL");
+</script>
+</pre>
+</body>
+</html>
diff --git a/caps/tests/mochitest/test_bug470804.html b/caps/tests/mochitest/test_bug470804.html
new file mode 100644
index 0000000000..26da479af6
--- /dev/null
+++ b/caps/tests/mochitest/test_bug470804.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=470804
+-->
+<head>
+ <title>Test for Bug 470804</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=470804">Mozilla Bug 470804</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 470804
+ Passing a null targetURL to checkLoadURIWithPrincipal shouldn't crash
+ */
+
+const nsIScriptSecurityManager = SpecialPowers.Ci.nsIScriptSecurityManager;
+var secMan = SpecialPowers.Services.scriptSecurityManager;
+var principal = SpecialPowers.wrap(document).nodePrincipal;
+isnot(principal, undefined, "Should have a principal");
+isnot(principal, null, "Should have a non-null principal");
+is(principal.isSystemPrincipal, false,
+ "Shouldn't have system principal here");
+try {
+ secMan.checkLoadURIWithPrincipal(principal, null,
+ nsIScriptSecurityManager.STANDARD);
+} catch (e) {
+ // throwing is fine, it's just crashing that's bad
+}
+ok(true, "Survival: we should get here without crashing");
+</script>
+</pre>
+</body>
+</html>
diff --git a/caps/tests/mochitest/test_bug995943.xhtml b/caps/tests/mochitest/test_bug995943.xhtml
new file mode 100644
index 0000000000..0ba95f548d
--- /dev/null
+++ b/caps/tests/mochitest/test_bug995943.xhtml
@@ -0,0 +1,111 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=995943
+-->
+<window title="Mozilla Bug 995943"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=995943"
+ target="_blank">Mozilla Bug 995943</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ function debug(msg) { info(msg); }
+
+ /** Test for CAPS file:// URI prefs. */
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestCompleteLog();
+ if (navigator.userAgent.includes("Mac OS X 10.10"))
+ SimpleTest.expectAssertions(5, 11); // See bug 1067022, 1307988
+ else if (Services.appinfo.OS == "WINNT")
+ SimpleTest.expectAssertions(0, 1); // See bug 1067022
+ else
+ SimpleTest.expectAssertions(0, 9); // See bug 1305241, 1145314
+
+ var rootdir = Services.appinfo.OS == "WINNT" ? "file:///C:" : "file:///";
+
+ function checkLoadFileURI(domain, shouldLoad) {
+ debug("Invoking checkLoadFileURI with domain: " + domain + ", shouldLoad: " + shouldLoad);
+ return new Promise(function(resolve, reject) {
+ $('ifr').addEventListener('load', function l1() {
+ debug("Invoked l1 for " + domain);
+ $('ifr').removeEventListener('load', l1);
+ function l2() {
+ debug("Invoked l2 for " + domain);
+ $('ifr').removeEventListener('load', l2);
+ ok(shouldLoad, "Successfully loaded file:// URI for domain: " + domain);
+ resolve();
+ }
+ $('ifr').addEventListener('load', l2);
+ try {
+ window[0].wrappedJSObject.location = rootdir;
+ debug("Successfully navigated for " + domain);
+ } catch (e) {
+ ok(!shouldLoad && /denied|insecure/.test(e),
+ "Prevented loading of file:// URI for domain: " + domain + " - " + e);
+ $('ifr').removeEventListener('load', l2);
+ resolve();
+ }
+ });
+ let targetURI = domain + '/tests/js/xpconnect/tests/mochitest/file_empty.html';
+ debug("Navigating iframe to " + targetURI);
+ $('ifr').contentWindow.location = targetURI;
+ });
+ }
+
+ function pushPrefs(prefs) {
+ return SpecialPowers.pushPrefEnv({ set: prefs });
+ }
+
+ function popPrefs() {
+ return new Promise(function(resolve) { SpecialPowers.popPrefEnv(resolve); });
+ }
+
+ var gGoCount = 0;
+ function go() {
+ debug("Invoking go for window with id: " + window.windowGlobalChild.innerWindowId);
+ is(++gGoCount, 1, "Should only call go once!");
+ checkLoadFileURI('http://example.com', false).then(
+ pushPrefs.bind(null, [['capability.policy.policynames', ' somepolicy '],
+ ['capability.policy.somepolicy.checkloaduri.enabled', 'AlLAcCeSs'],
+ ['capability.policy.somepolicy.sites', 'http://example.com']]))
+ .then(checkLoadFileURI.bind(null, 'http://example.com', true))
+ .then(popPrefs)
+ .then(checkLoadFileURI.bind(null, 'http://example.com', false))
+ .then(
+ pushPrefs.bind(null, [['capability.policy.policynames', ',somepolicy, someotherpolicy, '],
+ ['capability.policy.somepolicy.checkloaduri.enabled', 'allaccess'],
+ ['capability.policy.someotherpolicy.checkloaduri.enabled', 'nope'],
+ ['capability.policy.somepolicy.sites', ' http://example.org test1.example.com https://test2.example.com '],
+ ['capability.policy.someotherpolicy.sites', 'http://example.net ']]))
+ .then(checkLoadFileURI.bind(null, 'http://example.org', true))
+ .then(checkLoadFileURI.bind(null, 'http://test2.example.com', false))
+ .then(checkLoadFileURI.bind(null, 'https://test2.example.com', true))
+ .then(checkLoadFileURI.bind(null, 'http://sub1.test2.example.com', false))
+ .then(checkLoadFileURI.bind(null, 'https://sub1.test2.example.com', true))
+ .then(checkLoadFileURI.bind(null, 'http://example.net', false))
+ .then(checkLoadFileURI.bind(null, 'http://test1.example.com', true))
+ .then(checkLoadFileURI.bind(null, 'https://test1.example.com', true))
+ .then(checkLoadFileURI.bind(null, 'http://sub1.test1.example.com', true))
+ .then(checkLoadFileURI.bind(null, 'https://sub1.test1.example.com', true))
+ .then(pushPrefs.bind(null, [['capability.policy.someotherpolicy.checkloaduri.enabled', 'allAccess']]))
+ .then(checkLoadFileURI.bind(null, 'http://example.net', true))
+ .then(popPrefs)
+ .then(popPrefs)
+ .then(checkLoadFileURI.bind(null, 'http://example.net', false))
+ .then(SimpleTest.finish.bind(SimpleTest));
+
+ }
+ addLoadEvent(go);
+
+ ]]>
+ </script>
+ <iframe id="ifr" type="content" />
+</window>
diff --git a/caps/tests/mochitest/test_disableScript.xhtml b/caps/tests/mochitest/test_disableScript.xhtml
new file mode 100644
index 0000000000..8c16c13bc3
--- /dev/null
+++ b/caps/tests/mochitest/test_disableScript.xhtml
@@ -0,0 +1,330 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=840488
+-->
+<window title="Mozilla Bug 840488"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=840488"
+ target="_blank">Mozilla Bug 840488</a>
+ </body>
+
+ <iframe id="root" name="root" type="content"/>
+ <iframe id="chromeFrame" name="chromeFrame" type="content"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ /* eslint-disable mozilla/no-useless-parameters, no-redeclare, no-undef */
+ <![CDATA[
+
+ /** Test for all the different ways that script can be disabled for a given global. */
+
+ SimpleTest.waitForExplicitFinish();
+ const ssm = Services.scriptSecurityManager;
+ function makeURI(uri) { return Services.io.newURI(uri); }
+ const path = "/tests/caps/tests/mochitest/file_disableScript.html";
+ const uri = "http://www.example.com" + path;
+ var rootFrame = document.getElementById('root');
+ var chromeFrame = document.getElementById('chromeFrame');
+ navigateFrame(rootFrame, uri + "?name=rootframe").then(function() {
+ navigateFrame(chromeFrame, "file_disableScript.html").then(go);
+ });
+
+ function navigateFrame(ifr, src) {
+ return new Promise(resolve => {
+ function onload() {
+ ifr.removeEventListener('load', onload);
+ resolve();
+ }
+ ifr.addEventListener('load', onload, false);
+ ifr.setAttribute('src', src);
+ });
+ }
+
+ function navigateBack(ifr) {
+ return new Promise(resolve => {
+
+ // pageshow events don't fire on the iframe element, so we need to use the
+ // chrome event handler for the docshell.
+ var browser = ifr.contentWindow.docShell.chromeEventHandler;
+ function onpageshow(evt) {
+ info("Navigated back. Persisted: " + evt.persisted);
+ browser.removeEventListener('pageshow', onpageshow);
+ resolve();
+ }
+ browser.addEventListener('pageshow', onpageshow, false);
+ ifr.contentWindow.history.back();
+ });
+ }
+
+ function addFrame(parentWin, name, expectOnload) {
+ let ifr = parentWin.document.createElement('iframe');
+ parentWin.document.body.appendChild(ifr);
+ ifr.setAttribute('name', name);
+ return new Promise(resolve => {
+ // We need to append 'name' to avoid running afoul of recursive frame detection.
+ let frameURI = uri + "?name=" + name;
+ navigateFrame(ifr, frameURI).then(function() {
+ is(String(ifr.contentWindow.location), frameURI, "Successful load");
+ is(!!ifr.contentWindow.wrappedJSObject.gFiredOnload, expectOnload,
+ "onload should only fire when scripts are enabled");
+ resolve();
+ });
+ });
+ }
+
+ function checkScriptEnabled(win, expectEnabled) {
+ win.wrappedJSObject.gFiredOnclick = false;
+ win.document.body.dispatchEvent(new win.Event('click'));
+ is(win.wrappedJSObject.gFiredOnclick, expectEnabled, "Checking script-enabled for " + win.name + " (" + win.location + ")");
+ }
+
+ function setScriptEnabled(win, enabled) {
+ win.browsingContext.allowJavascript = enabled;
+ }
+
+ function testList(expectEnabled, win, list, idx) {
+ idx = idx || 0;
+ return new Promise(resolve => {
+ let target = list[idx] + path;
+ info("Testing scriptability for: " + target + ". expecting " + expectEnabled);
+ navigateFrame(win.frameElement, target).then(function() {
+ checkScriptEnabled(win, expectEnabled);
+ if (idx == list.length - 1)
+ resolve();
+ else
+ testList(expectEnabled, win, list, idx + 1).then(function() { resolve(); });
+ });
+ });
+ }
+
+ function testDomainPolicy(defaultScriptability, exceptions, superExceptions,
+ exempt, notExempt, set, superSet, win) {
+ // Populate our sets.
+ for (var e of exceptions)
+ set.add(makeURI(e));
+ for (var e of superExceptions)
+ superSet.add(makeURI(e));
+
+ return testList(defaultScriptability, win, notExempt).then(function() {
+ return testList(!defaultScriptability, win, exempt);
+ });
+ }
+
+ function setScriptEnabledForBrowser(enabled) {
+ var prefname = "javascript.enabled";
+ Services.prefs.setBoolPref(prefname, enabled);
+ }
+
+ function reloadFrame(frame) {
+ return new Promise(resolve => {
+ frame.addEventListener('load', function onload() {
+ resolve();
+ frame.removeEventListener('load', onload);
+ }, false);
+ frame.contentWindow.location.reload(true);
+ });
+ }
+
+ function go() {
+ var rootWin = rootFrame.contentWindow;
+ var chromeWin = chromeFrame.contentWindow;
+
+ // Test simple docshell enable/disable.
+ checkScriptEnabled(rootWin, true);
+ setScriptEnabled(rootWin, false);
+ checkScriptEnabled(rootWin, false);
+ setScriptEnabled(rootWin, true);
+ checkScriptEnabled(rootWin, true);
+
+ // Privileged frames are immune to docshell flags.
+ ok(chromeWin.document.nodePrincipal.isSystemPrincipal, "Sanity check for System Principal");
+ setScriptEnabled(chromeWin, false);
+ checkScriptEnabled(chromeWin, true);
+ setScriptEnabled(chromeWin, true);
+
+ // Play around with the docshell tree and make sure everything works as
+ // we expect.
+ addFrame(rootWin, 'parent', true).then(function() {
+ checkScriptEnabled(rootWin[0], true);
+ return addFrame(rootWin[0], 'childA', true);
+ }).then(function() {
+ checkScriptEnabled(rootWin[0][0], true);
+ setScriptEnabled(rootWin[0], false);
+ checkScriptEnabled(rootWin, true);
+ checkScriptEnabled(rootWin[0], false);
+ checkScriptEnabled(rootWin[0][0], false);
+ return addFrame(rootWin[0], 'childB', false);
+ }).then(function() {
+ checkScriptEnabled(rootWin[0][1], false);
+ setScriptEnabled(rootWin[0][0], false);
+ setScriptEnabled(rootWin[0], true);
+ checkScriptEnabled(rootWin[0], true);
+ checkScriptEnabled(rootWin[0][0], false);
+ setScriptEnabled(rootWin[0][0], true);
+
+ // Flags are inherited from the parent docshell at attach time. Note that
+ // the flag itself is inherited, regardless of whether or not scripts are
+ // currently allowed on the parent (which could depend on the parent's
+ // parent). Check that.
+ checkScriptEnabled(rootWin[0][1], false);
+ setScriptEnabled(rootWin[0], false);
+ setScriptEnabled(rootWin[0][1], true);
+ return addFrame(rootWin[0][1], 'grandchild', false);
+ }).then(function() {
+ checkScriptEnabled(rootWin[0], false);
+ checkScriptEnabled(rootWin[0][1], false);
+ checkScriptEnabled(rootWin[0][1][0], false);
+ setScriptEnabled(rootWin[0], true);
+ checkScriptEnabled(rootWin[0], true);
+ checkScriptEnabled(rootWin[0][1], true);
+ checkScriptEnabled(rootWin[0][1][0], true);
+
+ // Try navigating two frames, then munging docshell scriptability, then
+ // pulling the frames out of the bfcache to make sure that flags are
+ // properly propagated to inactive inner windows. We do this both for an
+ // 'own' docshell, as well as for an ancestor docshell.
+ return navigateFrame(rootWin[0][0].frameElement, rootWin[0][0].location + '-navigated');
+ }).then(function() { return navigateFrame(rootWin[0][1][0].frameElement, rootWin[0][1][0].location + '-navigated'); })
+ .then(function() {
+ checkScriptEnabled(rootWin[0][0], true);
+ checkScriptEnabled(rootWin[0][1][0], true);
+ setScriptEnabled(rootWin[0][0], false);
+ setScriptEnabled(rootWin[0][1], false);
+ checkScriptEnabled(rootWin[0][0], false);
+ checkScriptEnabled(rootWin[0][1][0], false);
+ return navigateBack(rootWin[0][0].frameElement);
+ }).then(function() { return navigateBack(rootWin[0][1][0].frameElement); })
+ .then(function() {
+ checkScriptEnabled(rootWin[0][0], false);
+ checkScriptEnabled(rootWin[0][1][0], false);
+
+ // Disable JS via the global pref pref. This is only guaranteed to have an effect
+ // for subsequent loads.
+ setScriptEnabledForBrowser(false);
+ return reloadFrame(rootFrame);
+ }).then(function() {
+ checkScriptEnabled(rootWin, false);
+ checkScriptEnabled(chromeWin, true);
+ setScriptEnabledForBrowser(true);
+ return reloadFrame(rootFrame);
+ }).then(function() {
+ checkScriptEnabled(rootWin, true);
+
+ // Play around with dynamically blocking script for a given global.
+ // This takes effect immediately.
+ Cu.blockScriptForGlobal(rootWin);
+ Cu.blockScriptForGlobal(rootWin);
+ Cu.unblockScriptForGlobal(rootWin);
+ checkScriptEnabled(rootWin, false);
+ Cu.unblockScriptForGlobal(rootWin);
+ checkScriptEnabled(rootWin, true);
+ Cu.blockScriptForGlobal(rootWin);
+ try {
+ Cu.blockScriptForGlobal(chromeWin);
+ ok(false, "Should have thrown");
+ } catch (e) {
+ ok(/may not be disabled/.test(e),
+ "Shouldn't be able to programmatically block script for system globals");
+ }
+ return reloadFrame(rootFrame);
+ }).then(function() {
+ checkScriptEnabled(rootWin, true);
+
+ // Test system-wide domain policy. This only takes effect for subsequently-
+ // loaded globals.
+
+ // Check the basic semantics of the sets.
+ is(ssm.domainPolicyActive, false, "not enabled");
+ window.policy = ssm.activateDomainPolicy();
+ ok(policy instanceof Ci.nsIDomainPolicy, "Got a policy");
+ try {
+ ssm.activateDomainPolicy();
+ ok(false, "Should have thrown");
+ } catch (e) {
+ ok(true, "can't have two live domain policies");
+ }
+ var sbRef = policy.superBlocklist;
+ isnot(sbRef, null, "superBlocklist non-null");
+ ok(!sbRef.contains(makeURI('http://www.example.com')));
+ sbRef.add(makeURI('http://www.example.com/foopy'));
+ ok(sbRef.contains(makeURI('http://www.example.com')));
+ sbRef.remove(makeURI('http://www.example.com'));
+ ok(!sbRef.contains(makeURI('http://www.example.com')));
+ sbRef.add(makeURI('http://www.example.com/foopy/this.that/'));
+ ok(sbRef.contains(makeURI('http://www.example.com/baz')));
+ ok(!sbRef.contains(makeURI('https://www.example.com')));
+ ok(!sbRef.contains(makeURI('https://www.example.com:88')));
+ ok(!sbRef.contains(makeURI('http://foo.www.example.com')));
+ ok(sbRef.containsSuperDomain(makeURI('http://foo.www.example.com')));
+ ok(sbRef.containsSuperDomain(makeURI('http://foo.bar.www.example.com')));
+ ok(!sbRef.containsSuperDomain(makeURI('http://foo.bar.www.exxample.com')));
+ ok(!sbRef.containsSuperDomain(makeURI('http://example.com')));
+ ok(!sbRef.containsSuperDomain(makeURI('http://com/this.that/')));
+ ok(!sbRef.containsSuperDomain(makeURI('https://foo.www.example.com')));
+ ok(sbRef.contains(makeURI('http://www.example.com')));
+ policy.deactivate();
+ is(ssm.domainPolicyActive, false, "back to inactive");
+ ok(!sbRef.contains(makeURI('http://www.example.com')),
+ "Disabling domain policy clears the set");
+ policy = ssm.activateDomainPolicy();
+ ok(policy.superBlocklist);
+ isnot(sbRef, policy.superBlocklist, "Mint new sets each time!");
+ policy.deactivate();
+ is(policy.blocklist, null, "blocklist nulled out");
+ policy = ssm.activateDomainPolicy();
+ isnot(policy.blocklist, null, "non-null again");
+ isnot(policy.blocklist, sbRef, "freshly minted");
+ policy.deactivate();
+
+ //
+ // Now, create and apply a mock-policy. We check the same policy both as
+ // a blocklist and as a allowlist.
+ //
+
+ window.testPolicy = {
+ // The policy.
+ exceptions: ['http://test1.example.com', 'http://example.com'],
+ superExceptions: ['http://test2.example.org', 'https://test1.example.com'],
+
+ // The testcases.
+ exempt: ['http://test1.example.com', 'http://example.com',
+ 'http://test2.example.org', 'http://sub1.test2.example.org',
+ 'https://sub1.test1.example.com'],
+
+ notExempt: ['http://test2.example.com', 'http://sub1.test1.example.com',
+ 'http://www.example.com', 'https://test2.example.com',
+ 'https://example.com', 'http://test1.example.org'],
+ };
+
+ policy = ssm.activateDomainPolicy();
+ info("Testing Blocklist-style Domain Policy");
+ return testDomainPolicy(true, testPolicy.exceptions,
+ testPolicy.superExceptions, testPolicy.exempt,
+ testPolicy.notExempt, policy.blocklist,
+ policy.superBlocklist, rootWin);
+ }).then(function() {
+ policy.deactivate();
+ policy = ssm.activateDomainPolicy();
+ info("Testing Allowlist-style Domain Policy");
+ setScriptEnabledForBrowser(false);
+ return testDomainPolicy(false, testPolicy.exceptions,
+ testPolicy.superExceptions, testPolicy.exempt,
+ testPolicy.notExempt, policy.allowlist,
+ policy.superAllowlist, rootWin);
+ }).then(function() {
+ setScriptEnabledForBrowser(true);
+ policy.deactivate();
+
+ SimpleTest.finish();
+ });
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/caps/tests/mochitest/test_disallowInheritPrincipal.html b/caps/tests/mochitest/test_disallowInheritPrincipal.html
new file mode 100644
index 0000000000..308ee61320
--- /dev/null
+++ b/caps/tests/mochitest/test_disallowInheritPrincipal.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=732413
+-->
+<head>
+ <title>Test for Bug 732413</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=732413">Mozilla Bug 732413</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 732413
+ Passing DISALLOW_INHERIT_PRINCIPAL flag should be effective even if
+ aPrincipal is the system principal.
+ */
+
+const nsIScriptSecurityManager = SpecialPowers.Ci.nsIScriptSecurityManager;
+var secMan = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(nsIScriptSecurityManager);
+var sysPrincipal = secMan.getSystemPrincipal();
+isnot(sysPrincipal, undefined, "Should have a principal");
+isnot(sysPrincipal, null, "Should have a non-null principal");
+is(sysPrincipal.isSystemPrincipal, true,
+ "Should have system principal here");
+
+
+var inheritingURI = SpecialPowers.Services.io.newURI("javascript:1+1");
+
+// First try a normal call to checkLoadURIWithPrincipal
+try {
+ secMan.checkLoadURIWithPrincipal(sysPrincipal, inheritingURI,
+ nsIScriptSecurityManager.STANDARD);
+ ok(true, "checkLoadURI allowed the load");
+} catch (e) {
+ ok(false, "checkLoadURI failed unexpectedly: " + e);
+}
+
+// Now call checkLoadURIWithPrincipal with DISALLOW_INHERIT_PRINCIPAL
+try {
+ secMan.checkLoadURIWithPrincipal(sysPrincipal, inheritingURI,
+ nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+ ok(false, "checkLoadURI allowed the load unexpectedly");
+} catch (e) {
+ ok(true, "checkLoadURI prevented load of principal-inheriting URI");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/caps/tests/unit/test_ipv6_host_literal.js b/caps/tests/unit/test_ipv6_host_literal.js
new file mode 100644
index 0000000000..cde7d82d7e
--- /dev/null
+++ b/caps/tests/unit/test_ipv6_host_literal.js
@@ -0,0 +1,38 @@
+var ssm = Services.scriptSecurityManager;
+function makeURI(uri) {
+ return Services.io.newURI(uri);
+}
+
+function testIPV6Host(aHost, aExpected) {
+ var ipv6Host = ssm.createContentPrincipal(makeURI(aHost), {});
+ Assert.equal(ipv6Host.origin, aExpected);
+}
+
+function run_test() {
+ testIPV6Host("http://[::1]/", "http://[::1]");
+
+ testIPV6Host(
+ "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]/",
+ "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]"
+ );
+
+ testIPV6Host(
+ "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443/",
+ "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443"
+ );
+
+ testIPV6Host(
+ "http://[2001:db8:85a3::1319:8a2e:370:7348]/",
+ "http://[2001:db8:85a3:0:1319:8a2e:370:7348]"
+ );
+
+ testIPV6Host(
+ "http://[20D1:0000:3238:DFE1:63:0000:0000:FEFB]/",
+ "http://[20d1:0:3238:dfe1:63::fefb]"
+ );
+
+ testIPV6Host(
+ "http://[20D1:0:3238:DFE1:63::FEFB]/",
+ "http://[20d1:0:3238:dfe1:63::fefb]"
+ );
+}
diff --git a/caps/tests/unit/test_oa_partitionKey_pattern.js b/caps/tests/unit/test_oa_partitionKey_pattern.js
new file mode 100644
index 0000000000..5d0625018f
--- /dev/null
+++ b/caps/tests/unit/test_oa_partitionKey_pattern.js
@@ -0,0 +1,159 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests origin attributes partitionKey pattern matching.
+ */
+
+"use strict";
+
+function testMatch(oa, pattern, shouldMatch = true) {
+ let msg = `Origin attributes should ${
+ shouldMatch ? "match" : "not match"
+ } pattern.`;
+ msg += ` oa: ${JSON.stringify(oa)} - pattern: ${JSON.stringify(pattern)}`;
+ Assert.equal(
+ ChromeUtils.originAttributesMatchPattern(oa, pattern),
+ shouldMatch,
+ msg
+ );
+}
+
+function getPartitionKey(scheme, baseDomain, port) {
+ if (!scheme || !baseDomain) {
+ return "";
+ }
+ return `(${scheme},${baseDomain}${port ? `,${port}` : ``})`;
+}
+
+function getOAWithPartitionKey(scheme, baseDomain, port, oa = {}) {
+ oa.partitionKey = getPartitionKey(scheme, baseDomain, port);
+ return oa;
+}
+
+/**
+ * Tests that an OriginAttributesPattern which is empty or only has an empty
+ * partitionKeyPattern matches any partitionKey.
+ */
+add_task(async function test_empty_pattern_matches_any() {
+ let list = [
+ getOAWithPartitionKey("https", "example.com"),
+ getOAWithPartitionKey("http", "example.net", 8080),
+ getOAWithPartitionKey(),
+ ];
+
+ for (let oa of list) {
+ testMatch(oa, {});
+ testMatch(oa, { partitionKeyPattern: {} });
+ }
+});
+
+/**
+ * Tests that if a partitionKeyPattern is passed, but the partitionKey is
+ * invalid, the pattern match will always fail.
+ */
+add_task(async function test_invalid_pk() {
+ let list = [
+ "()",
+ "(,,)",
+ "(https)",
+ "(https,,)",
+ "(example.com)",
+ "(http,example.com,invalid)",
+ "(http,example.com,8000,1000)",
+ ].map(partitionKey => ({ partitionKey }));
+
+ for (let oa of list) {
+ testMatch(oa, {});
+ testMatch(oa, { partitionKeyPattern: {} });
+ testMatch(
+ oa,
+ { partitionKeyPattern: { baseDomain: "example.com" } },
+ false
+ );
+ testMatch(oa, { partitionKeyPattern: { scheme: "https" } }, false);
+ }
+});
+
+/**
+ * Tests that if a pattern sets "partitionKey" it takes precedence over "partitionKeyPattern".
+ */
+add_task(async function test_string_overwrites_pattern() {
+ let oa = getOAWithPartitionKey("https", "example.com", 8080, {
+ userContextId: 2,
+ });
+
+ testMatch(oa, { partitionKey: oa.partitionKey });
+ testMatch(oa, {
+ partitionKey: oa.partitionKey,
+ partitionKeyPattern: { baseDomain: "example.com" },
+ });
+ testMatch(oa, {
+ partitionKey: oa.partitionKey,
+ partitionKeyPattern: { baseDomain: "example.net" },
+ });
+ testMatch(
+ oa,
+ {
+ partitionKey: getPartitionKey("https", "example.net"),
+ partitionKeyPattern: { scheme: "https", baseDomain: "example.com" },
+ },
+ false
+ );
+});
+
+/**
+ * Tests that we can match parts of a partitionKey by setting
+ * partitionKeyPattern.
+ */
+add_task(async function test_pattern() {
+ let a = getOAWithPartitionKey("https", "example.com", 8080, {
+ userContextId: 2,
+ });
+ let b = getOAWithPartitionKey("https", "example.com", undefined, {
+ privateBrowsingId: 1,
+ });
+
+ for (let oa of [a, b]) {
+ // Match
+ testMatch(oa, { partitionKeyPattern: { scheme: "https" } });
+ testMatch(oa, {
+ partitionKeyPattern: { scheme: "https", baseDomain: "example.com" },
+ });
+ testMatch(
+ oa,
+ {
+ partitionKeyPattern: {
+ scheme: "https",
+ baseDomain: "example.com",
+ port: 8080,
+ },
+ },
+ oa == a
+ );
+ testMatch(oa, {
+ partitionKeyPattern: { baseDomain: "example.com" },
+ });
+ testMatch(
+ oa,
+ {
+ partitionKeyPattern: { port: 8080 },
+ },
+ oa == a
+ );
+
+ // Mismatch
+ testMatch(oa, { partitionKeyPattern: { scheme: "http" } }, false);
+ testMatch(
+ oa,
+ { partitionKeyPattern: { baseDomain: "example.net" } },
+ false
+ );
+ testMatch(oa, { partitionKeyPattern: { port: 8443 } }, false);
+ testMatch(
+ oa,
+ { partitionKeyPattern: { scheme: "https", baseDomain: "example.net" } },
+ false
+ );
+ }
+});
diff --git a/caps/tests/unit/test_origin.js b/caps/tests/unit/test_origin.js
new file mode 100644
index 0000000000..c0cbc2996a
--- /dev/null
+++ b/caps/tests/unit/test_origin.js
@@ -0,0 +1,323 @@
+var ssm = Services.scriptSecurityManager;
+function makeURI(uri) {
+ return Services.io.newURI(uri);
+}
+
+function checkThrows(f) {
+ var threw = false;
+ try {
+ f();
+ } catch (e) {
+ threw = true;
+ }
+ Assert.ok(threw);
+}
+
+function checkCrossOrigin(a, b) {
+ Assert.ok(!a.equals(b));
+ Assert.ok(!a.equalsConsideringDomain(b));
+ Assert.ok(!a.subsumes(b));
+ Assert.ok(!a.subsumesConsideringDomain(b));
+ Assert.ok(!b.subsumes(a));
+ Assert.ok(!b.subsumesConsideringDomain(a));
+}
+
+function checkOriginAttributes(prin, attrs, suffix) {
+ attrs = attrs || {};
+ Assert.equal(
+ prin.originAttributes.inIsolatedMozBrowser,
+ attrs.inIsolatedMozBrowser || false
+ );
+ Assert.equal(prin.originSuffix, suffix || "");
+ Assert.equal(ChromeUtils.originAttributesToSuffix(attrs), suffix || "");
+ Assert.ok(
+ ChromeUtils.originAttributesMatchPattern(prin.originAttributes, attrs)
+ );
+ if (!prin.isNullPrincipal && !prin.origin.startsWith("[")) {
+ Assert.ok(ssm.createContentPrincipalFromOrigin(prin.origin).equals(prin));
+ } else {
+ checkThrows(() => ssm.createContentPrincipalFromOrigin(prin.origin));
+ }
+}
+
+function checkSandboxOriginAttributes(arr, attrs, options) {
+ options = options || {};
+ var sandbox = Cu.Sandbox(arr, options);
+ checkOriginAttributes(
+ Cu.getObjectPrincipal(sandbox),
+ attrs,
+ ChromeUtils.originAttributesToSuffix(attrs)
+ );
+}
+
+// utility function useful for debugging
+// eslint-disable-next-line no-unused-vars
+function printAttrs(name, attrs) {
+ info(
+ name +
+ " {\n" +
+ "\tuserContextId: " +
+ attrs.userContextId +
+ ",\n" +
+ "\tinIsolatedMozBrowser: " +
+ attrs.inIsolatedMozBrowser +
+ ",\n" +
+ "\tprivateBrowsingId: '" +
+ attrs.privateBrowsingId +
+ "',\n" +
+ "\tfirstPartyDomain: '" +
+ attrs.firstPartyDomain +
+ "'\n}"
+ );
+}
+
+function checkValues(attrs, values) {
+ values = values || {};
+ // printAttrs("attrs", attrs);
+ // printAttrs("values", values);
+ Assert.equal(attrs.userContextId, values.userContextId || 0);
+ Assert.equal(
+ attrs.inIsolatedMozBrowser,
+ values.inIsolatedMozBrowser || false
+ );
+ Assert.equal(attrs.privateBrowsingId, values.privateBrowsingId || "");
+ Assert.equal(attrs.firstPartyDomain, values.firstPartyDomain || "");
+}
+
+function run_test() {
+ // Attributeless origins.
+ Assert.equal(ssm.getSystemPrincipal().origin, "[System Principal]");
+ checkOriginAttributes(ssm.getSystemPrincipal());
+ var exampleOrg = ssm.createContentPrincipal(
+ makeURI("http://example.org"),
+ {}
+ );
+ Assert.equal(exampleOrg.origin, "http://example.org");
+ checkOriginAttributes(exampleOrg);
+ var exampleCom = ssm.createContentPrincipal(
+ makeURI("https://www.example.com:123"),
+ {}
+ );
+ Assert.equal(exampleCom.origin, "https://www.example.com:123");
+ checkOriginAttributes(exampleCom);
+ var nullPrin = Cu.getObjectPrincipal(new Cu.Sandbox(null));
+ Assert.ok(
+ /^moz-nullprincipal:\{([0-9]|[a-z]|\-){36}\}$/.test(nullPrin.origin)
+ );
+ checkOriginAttributes(nullPrin);
+ var ipv6Prin = ssm.createContentPrincipal(
+ makeURI("https://[2001:db8::ff00:42:8329]:123"),
+ {}
+ );
+ Assert.equal(ipv6Prin.origin, "https://[2001:db8::ff00:42:8329]:123");
+ checkOriginAttributes(ipv6Prin);
+ var ipv6NPPrin = ssm.createContentPrincipal(
+ makeURI("https://[2001:db8::ff00:42:8329]"),
+ {}
+ );
+ Assert.equal(ipv6NPPrin.origin, "https://[2001:db8::ff00:42:8329]");
+ checkOriginAttributes(ipv6NPPrin);
+ var ep = Cu.getObjectPrincipal(
+ Cu.Sandbox([exampleCom, nullPrin, exampleOrg])
+ );
+ checkOriginAttributes(ep);
+ checkCrossOrigin(exampleCom, exampleOrg);
+ checkCrossOrigin(exampleOrg, nullPrin);
+
+ // nsEP origins should be in lexical order.
+ Assert.equal(
+ ep.origin,
+ `[Expanded Principal [${exampleCom.origin}, ${nullPrin.origin}, ${exampleOrg.origin}]]`
+ );
+
+ // Make sure createContentPrincipal does what the rest of gecko does.
+ Assert.ok(
+ exampleOrg.equals(
+ Cu.getObjectPrincipal(new Cu.Sandbox("http://example.org"))
+ )
+ );
+
+ //
+ // Test origin attributes.
+ //
+
+ // Just browser.
+ var exampleOrg_browser = ssm.createContentPrincipal(
+ makeURI("http://example.org"),
+ { inIsolatedMozBrowser: true }
+ );
+ var nullPrin_browser = ssm.createNullPrincipal({
+ inIsolatedMozBrowser: true,
+ });
+ checkOriginAttributes(
+ exampleOrg_browser,
+ { inIsolatedMozBrowser: true },
+ "^inBrowser=1"
+ );
+ checkOriginAttributes(
+ nullPrin_browser,
+ { inIsolatedMozBrowser: true },
+ "^inBrowser=1"
+ );
+ Assert.equal(exampleOrg_browser.origin, "http://example.org^inBrowser=1");
+
+ // First party Uri
+ var exampleOrg_firstPartyDomain = ssm.createContentPrincipal(
+ makeURI("http://example.org"),
+ { firstPartyDomain: "example.org" }
+ );
+ checkOriginAttributes(
+ exampleOrg_firstPartyDomain,
+ { firstPartyDomain: "example.org" },
+ "^firstPartyDomain=example.org"
+ );
+ Assert.equal(
+ exampleOrg_firstPartyDomain.origin,
+ "http://example.org^firstPartyDomain=example.org"
+ );
+
+ // Just userContext.
+ var exampleOrg_userContext = ssm.createContentPrincipal(
+ makeURI("http://example.org"),
+ { userContextId: 42 }
+ );
+ checkOriginAttributes(
+ exampleOrg_userContext,
+ { userContextId: 42 },
+ "^userContextId=42"
+ );
+ Assert.equal(
+ exampleOrg_userContext.origin,
+ "http://example.org^userContextId=42"
+ );
+
+ checkSandboxOriginAttributes(null, {});
+ checkSandboxOriginAttributes("http://example.org", {});
+ checkSandboxOriginAttributes(
+ "http://example.org",
+ {},
+ { originAttributes: {} }
+ );
+ checkSandboxOriginAttributes(["http://example.org"], {});
+ checkSandboxOriginAttributes(
+ ["http://example.org"],
+ {},
+ { originAttributes: {} }
+ );
+
+ // Check that all of the above are cross-origin.
+ checkCrossOrigin(exampleOrg_browser, nullPrin_browser);
+ checkCrossOrigin(exampleOrg_firstPartyDomain, exampleOrg);
+ checkCrossOrigin(exampleOrg_userContext, exampleOrg);
+
+ // Check Principal kinds.
+ function checkKind(prin, kind) {
+ Assert.equal(prin.isNullPrincipal, kind == "nullPrincipal");
+ Assert.equal(prin.isContentPrincipal, kind == "contentPrincipal");
+ Assert.equal(prin.isExpandedPrincipal, kind == "expandedPrincipal");
+ Assert.equal(prin.isSystemPrincipal, kind == "systemPrincipal");
+ }
+ checkKind(ssm.createNullPrincipal({}), "nullPrincipal");
+ checkKind(
+ ssm.createContentPrincipal(makeURI("http://www.example.com"), {}),
+ "contentPrincipal"
+ );
+ checkKind(
+ Cu.getObjectPrincipal(
+ Cu.Sandbox([
+ ssm.createContentPrincipal(makeURI("http://www.example.com"), {}),
+ ])
+ ),
+ "expandedPrincipal"
+ );
+ checkKind(ssm.getSystemPrincipal(), "systemPrincipal");
+
+ //
+ // Test Origin Attribute Manipulation
+ //
+
+ // check that we can create an empty origin attributes dict with default
+ // members and values.
+ var emptyAttrs = ChromeUtils.fillNonDefaultOriginAttributes({});
+ checkValues(emptyAttrs);
+
+ var uri = "http://example.org";
+ var tests = [
+ ["", {}],
+ ["^userContextId=3", { userContextId: 3 }],
+ ["^inBrowser=1", { inIsolatedMozBrowser: true }],
+ ["^firstPartyDomain=example.org", { firstPartyDomain: "example.org" }],
+ ];
+
+ // check that we can create an origin attributes from an origin properly
+ tests.forEach(t => {
+ let attrs = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]);
+ checkValues(attrs, t[1]);
+ Assert.equal(ChromeUtils.originAttributesToSuffix(attrs), t[0]);
+ });
+
+ // check that we can create an origin attributes from a dict properly
+ tests.forEach(t => {
+ let attrs = ChromeUtils.fillNonDefaultOriginAttributes(t[1]);
+ checkValues(attrs, t[1]);
+ Assert.equal(ChromeUtils.originAttributesToSuffix(attrs), t[0]);
+ });
+
+ // each row in the dflt_tests array has these values:
+ // [0] - the suffix used to create an origin attribute from
+ // [1] - the expected result of creating an origin attributes from [0]
+ // [2] - the expected result after setting userContextId to the default
+ // [3] - the expected result of creating a suffix from [2]
+ var dflt_tests = [
+ ["", {}, {}, ""],
+ ["^userContextId=3", { userContextId: 3 }, {}, ""],
+ ];
+
+ // check that we can set the userContextId to default properly
+ dflt_tests.forEach(t => {
+ let orig = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]);
+ checkValues(orig, t[1]);
+ let mod = orig;
+ mod.userContextId = 0;
+ checkValues(mod, t[2]);
+ Assert.equal(ChromeUtils.originAttributesToSuffix(mod), t[3]);
+ });
+
+ // each row in the dflt2_tests array has these values:
+ // [0] - the suffix used to create an origin attribute from
+ // [1] - the expected result of creating an origin attributes from [0]
+ // [2] - the expected result after setting firstPartyUri to the default
+ // [3] - the expected result of creating a suffix from [2]
+ var dflt2_tests = [
+ ["", {}, {}, ""],
+ ["^firstPartyDomain=foo.com", { firstPartyDomain: "foo.com" }, {}, ""],
+ ];
+
+ // check that we can set the userContextId to default properly
+ dflt2_tests.forEach(t => {
+ let orig = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]);
+ checkValues(orig, t[1]);
+ let mod = orig;
+ mod.firstPartyDomain = "";
+ checkValues(mod, t[2]);
+ Assert.equal(ChromeUtils.originAttributesToSuffix(mod), t[3]);
+ });
+
+ var fileURI = makeURI("file:///foo/bar").QueryInterface(Ci.nsIFileURL);
+ var fileTests = [
+ [true, fileURI.spec],
+ [false, "file://UNIVERSAL_FILE_URI_ORIGIN"],
+ ];
+ fileTests.forEach(t => {
+ Services.prefs.setBoolPref("security.fileuri.strict_origin_policy", t[0]);
+ var filePrin = ssm.createContentPrincipal(fileURI, {});
+ Assert.equal(filePrin.origin, t[1]);
+ });
+ Services.prefs.clearUserPref("security.fileuri.strict_origin_policy");
+
+ var aboutBlankURI = makeURI("about:blank");
+ var aboutBlankPrin = ssm.createContentPrincipal(aboutBlankURI, {});
+ Assert.ok(
+ /^moz-nullprincipal:\{([0-9]|[a-z]|\-){36}\}$/.test(aboutBlankPrin.origin)
+ );
+}
diff --git a/caps/tests/unit/test_precursor_principal.js b/caps/tests/unit/test_precursor_principal.js
new file mode 100644
index 0000000000..37c1ae13bc
--- /dev/null
+++ b/caps/tests/unit/test_precursor_principal.js
@@ -0,0 +1,259 @@
+"use strict";
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+const { XPCShellContentUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/XPCShellContentUtils.sys.mjs"
+);
+const { ExtensionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ExtensionXPCShellUtils.sys.mjs"
+);
+
+XPCShellContentUtils.init(this);
+ExtensionTestUtils.init(this);
+
+const server = XPCShellContentUtils.createHttpServer({
+ hosts: ["example.com", "example.org"],
+});
+
+server.registerPathHandler("/static_frames", (request, response) => {
+ response.setHeader("Content-Type", "text/html");
+ response.write(`
+ <iframe name="same_origin" sandbox="allow-scripts allow-same-origin" src="http://example.com/frame"></iframe>
+ <iframe name="same_origin_sandbox" sandbox="allow-scripts" src="http://example.com/frame"></iframe>
+ <iframe name="cross_origin" sandbox="allow-scripts allow-same-origin" src="http://example.org/frame"></iframe>
+ <iframe name="cross_origin_sandbox" sandbox="allow-scripts" src="http://example.org/frame"></iframe>
+ <iframe name="data_uri" sandbox="allow-scripts allow-same-origin" src="data:text/html,<h1>Data Subframe</h1>"></iframe>
+ <iframe name="data_uri_sandbox" sandbox="allow-scripts" src="data:text/html,<h1>Data Subframe</h1>"></iframe>
+ <iframe name="srcdoc" sandbox="allow-scripts allow-same-origin" srcdoc="<h1>Srcdoc Subframe</h1>"></iframe>
+ <iframe name="srcdoc_sandbox" sandbox="allow-scripts" srcdoc="<h1>Srcdoc Subframe</h1>"></iframe>
+ <iframe name="blank" sandbox="allow-scripts allow-same-origin"></iframe>
+ <iframe name="blank_sandbox" sandbox="allow-scripts"></iframe>
+ <iframe name="redirect_com" sandbox="allow-scripts allow-same-origin" src="/redirect?com"></iframe>
+ <iframe name="redirect_com_sandbox" sandbox="allow-scripts" src="/redirect?com"></iframe>
+ <iframe name="redirect_org" sandbox="allow-scripts allow-same-origin" src="/redirect?org"></iframe>
+ <iframe name="redirect_org_sandbox" sandbox="allow-scripts" src="/redirect?org"></iframe>
+ <iframe name="ext_redirect_com_com" sandbox="allow-scripts allow-same-origin" src="/ext_redirect?com"></iframe>
+ <iframe name="ext_redirect_com_com_sandbox" sandbox="allow-scripts" src="/ext_redirect?com"></iframe>
+ <iframe name="ext_redirect_org_com" sandbox="allow-scripts allow-same-origin" src="http://example.org/ext_redirect?com"></iframe>
+ <iframe name="ext_redirect_org_com_sandbox" sandbox="allow-scripts" src="http://example.org/ext_redirect?com"></iframe>
+ <iframe name="ext_redirect_com_org" sandbox="allow-scripts allow-same-origin" src="/ext_redirect?org"></iframe>
+ <iframe name="ext_redirect_com_org_sandbox" sandbox="allow-scripts" src="/ext_redirect?org"></iframe>
+ <iframe name="ext_redirect_org_org" sandbox="allow-scripts allow-same-origin" src="http://example.org/ext_redirect?org"></iframe>
+ <iframe name="ext_redirect_org_org_sandbox" sandbox="allow-scripts" src="http://example.org/ext_redirect?org"></iframe>
+ <iframe name="ext_redirect_com_data" sandbox="allow-scripts allow-same-origin" src="/ext_redirect?data"></iframe>
+ <iframe name="ext_redirect_com_data_sandbox" sandbox="allow-scripts" src="/ext_redirect?data"></iframe>
+ <iframe name="ext_redirect_org_data" sandbox="allow-scripts allow-same-origin" src="http://example.org/ext_redirect?data"></iframe>
+ <iframe name="ext_redirect_org_data_sandbox" sandbox="allow-scripts" src="http://example.org/ext_redirect?data"></iframe>
+
+ <!-- XXX(nika): These aren't static as they perform loads dynamically - perhaps consider testing them separately? -->
+ <iframe name="client_replace_org_blank" sandbox="allow-scripts allow-same-origin" src="http://example.org/client_replace?blank"></iframe>
+ <iframe name="client_replace_org_blank_sandbox" sandbox="allow-scripts" src="http://example.org/client_replace?blank"></iframe>
+ <iframe name="client_replace_org_data" sandbox="allow-scripts allow-same-origin" src="http://example.org/client_replace?data"></iframe>
+ <iframe name="client_replace_org_data_sandbox" sandbox="allow-scripts" src="http://example.org/client_replace?data"></iframe>
+ `);
+});
+
+server.registerPathHandler("/frame", (request, response) => {
+ response.setHeader("Content-Type", "text/html");
+ response.write(`<h1>HTTP Subframe</h1>`);
+});
+
+server.registerPathHandler("/redirect", (request, response) => {
+ let redirect;
+ if (request.queryString == "com") {
+ redirect = "http://example.com/frame";
+ } else if (request.queryString == "org") {
+ redirect = "http://example.org/frame";
+ } else {
+ response.setStatusLine(request.httpVersion, 404, "Not found");
+ return;
+ }
+
+ response.setStatusLine(request.httpVersion, 302, "Found");
+ response.setHeader("Location", redirect);
+});
+
+server.registerPathHandler("/client_replace", (request, response) => {
+ let redirect;
+ if (request.queryString == "blank") {
+ redirect = "about:blank";
+ } else if (request.queryString == "data") {
+ redirect = "data:text/html,<h1>Data Subframe</h1>";
+ } else {
+ response.setStatusLine(request.httpVersion, 404, "Not found");
+ return;
+ }
+
+ response.setHeader("Content-Type", "text/html");
+ response.write(`
+ <script>
+ window.location.replace(${JSON.stringify(redirect)});
+ </script>
+ `);
+});
+
+add_task(async function sandboxed_precursor() {
+ // Bug 1725345: Make XPCShellContentUtils.createHttpServer support https
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+
+ let extension = await ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["webRequest", "webRequestBlocking", "<all_urls>"],
+ },
+ background() {
+ // eslint-disable-next-line no-undef
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ let url = new URL(details.url);
+ if (!url.pathname.includes("ext_redirect")) {
+ return {};
+ }
+
+ let redirectUrl;
+ if (url.search == "?com") {
+ redirectUrl = "http://example.com/frame";
+ } else if (url.search == "?org") {
+ redirectUrl = "http://example.org/frame";
+ } else if (url.search == "?data") {
+ redirectUrl = "data:text/html,<h1>Data Subframe</h1>";
+ }
+ return { redirectUrl };
+ },
+ { urls: ["<all_urls>"] },
+ ["blocking"]
+ );
+ },
+ });
+ await extension.startup();
+
+ registerCleanupFunction(async function () {
+ await extension.unload();
+ });
+
+ for (let userContextId of [undefined, 1]) {
+ let comURI = Services.io.newURI("http://example.com");
+ let comPrin = Services.scriptSecurityManager.createContentPrincipal(
+ comURI,
+ { userContextId }
+ );
+ let orgURI = Services.io.newURI("http://example.org");
+ let orgPrin = Services.scriptSecurityManager.createContentPrincipal(
+ orgURI,
+ { userContextId }
+ );
+
+ let page = await XPCShellContentUtils.loadContentPage(
+ "http://example.com/static_frames",
+ {
+ remote: true,
+ remoteSubframes: true,
+ userContextId,
+ }
+ );
+ let bc = page.browsingContext;
+
+ ok(
+ bc.currentWindowGlobal.documentPrincipal.equals(comPrin),
+ "toplevel principal matches"
+ );
+
+ // XXX: This is sketchy as heck, but it's also the easiest way to wait for
+ // the `window.location.replace` loads to finish.
+ await TestUtils.waitForCondition(
+ () =>
+ bc.children.every(
+ child =>
+ !child.currentWindowGlobal.documentURI.spec.includes(
+ "/client_replace"
+ )
+ ),
+ "wait for every client_replace global to be replaced"
+ );
+
+ let principals = {};
+ for (let child of bc.children) {
+ notEqual(child.name, "", "child frames must have names");
+ ok(!(child.name in principals), "duplicate child frame name");
+ principals[child.name] = child.currentWindowGlobal.documentPrincipal;
+ }
+
+ function principal_is(name, expected) {
+ let principal = principals[name];
+ info(`${name} = ${principal.origin}`);
+ ok(principal.equals(expected), `${name} is correct`);
+ }
+ function precursor_is(name, precursor) {
+ let principal = principals[name];
+ info(`${name} = ${principal.origin}`);
+ ok(principal.isNullPrincipal, `${name} is null`);
+ ok(
+ principal.precursorPrincipal.equals(precursor),
+ `${name} has the correct precursor`
+ );
+ }
+
+ // Basic loads should have the principals or precursor principals for the
+ // document being loaded.
+ principal_is("same_origin", comPrin);
+ precursor_is("same_origin_sandbox", comPrin);
+
+ principal_is("cross_origin", orgPrin);
+ precursor_is("cross_origin_sandbox", orgPrin);
+
+ // Loads of a data: URI should complete with a sandboxed principal based on
+ // the principal which tried to perform the load.
+ precursor_is("data_uri", comPrin);
+ precursor_is("data_uri_sandbox", comPrin);
+
+ // Loads which inherit principals, such as srcdoc an about:blank loads,
+ // should also inherit sandboxed precursor principals.
+ principal_is("srcdoc", comPrin);
+ precursor_is("srcdoc_sandbox", comPrin);
+
+ principal_is("blank", comPrin);
+ precursor_is("blank_sandbox", comPrin);
+
+ // Redirects shouldn't interfere with the final principal, and it should be
+ // based only on the final URI.
+ principal_is("redirect_com", comPrin);
+ precursor_is("redirect_com_sandbox", comPrin);
+
+ principal_is("redirect_org", orgPrin);
+ precursor_is("redirect_org_sandbox", orgPrin);
+
+ // Extension redirects should act like normal redirects, and still resolve
+ // with the principal or sandboxed principal of the final URI.
+ principal_is("ext_redirect_com_com", comPrin);
+ precursor_is("ext_redirect_com_com_sandbox", comPrin);
+
+ principal_is("ext_redirect_com_org", orgPrin);
+ precursor_is("ext_redirect_com_org_sandbox", orgPrin);
+
+ principal_is("ext_redirect_org_com", comPrin);
+ precursor_is("ext_redirect_org_com_sandbox", comPrin);
+
+ principal_is("ext_redirect_org_org", orgPrin);
+ precursor_is("ext_redirect_org_org_sandbox", orgPrin);
+
+ // When an extension redirects to a data: URI, we use the last non-data: URI
+ // in the chain as the precursor principal.
+ // FIXME: This should perhaps use the extension's principal instead?
+ precursor_is("ext_redirect_com_data", comPrin);
+ precursor_is("ext_redirect_com_data_sandbox", comPrin);
+
+ precursor_is("ext_redirect_org_data", orgPrin);
+ precursor_is("ext_redirect_org_data_sandbox", orgPrin);
+
+ // Check that navigations triggred by script within the frames will have the
+ // correct behaviour when navigating to blank and data URIs.
+ principal_is("client_replace_org_blank", orgPrin);
+ precursor_is("client_replace_org_blank_sandbox", orgPrin);
+
+ precursor_is("client_replace_org_data", orgPrin);
+ precursor_is("client_replace_org_data_sandbox", orgPrin);
+
+ await page.close();
+ }
+ Services.prefs.clearUserPref("dom.security.https_first");
+});
diff --git a/caps/tests/unit/test_site_origin.js b/caps/tests/unit/test_site_origin.js
new file mode 100644
index 0000000000..bbde787f00
--- /dev/null
+++ b/caps/tests/unit/test_site_origin.js
@@ -0,0 +1,184 @@
+const scriptSecMan = Services.scriptSecurityManager;
+
+// SystemPrincipal checks
+let systemPrincipal = scriptSecMan.getSystemPrincipal();
+Assert.ok(systemPrincipal.isSystemPrincipal);
+Assert.equal(systemPrincipal.origin, "[System Principal]");
+Assert.equal(systemPrincipal.originNoSuffix, "[System Principal]");
+Assert.equal(systemPrincipal.siteOrigin, "[System Principal]");
+Assert.equal(systemPrincipal.siteOriginNoSuffix, "[System Principal]");
+
+// ContentPrincipal checks
+let uri1 = Services.io.newURI("http://example.com");
+let prinicpal1 = scriptSecMan.createContentPrincipal(uri1, {
+ userContextId: 11,
+});
+Assert.ok(prinicpal1.isContentPrincipal);
+Assert.equal(prinicpal1.origin, "http://example.com^userContextId=11");
+Assert.equal(prinicpal1.originNoSuffix, "http://example.com");
+Assert.equal(prinicpal1.siteOrigin, "http://example.com^userContextId=11");
+Assert.equal(prinicpal1.siteOriginNoSuffix, "http://example.com");
+
+let uri2 = Services.io.newURI("http://test1.example.com");
+let prinicpal2 = scriptSecMan.createContentPrincipal(uri2, {
+ userContextId: 22,
+});
+Assert.ok(prinicpal2.isContentPrincipal);
+Assert.equal(prinicpal2.origin, "http://test1.example.com^userContextId=22");
+Assert.equal(prinicpal2.originNoSuffix, "http://test1.example.com");
+Assert.equal(prinicpal2.siteOrigin, "http://example.com^userContextId=22");
+Assert.equal(prinicpal2.siteOriginNoSuffix, "http://example.com");
+
+let uri3 = Services.io.newURI("https://test1.test2.example.com:5555");
+let prinicpal3 = scriptSecMan.createContentPrincipal(uri3, {
+ userContextId: 5555,
+});
+Assert.ok(prinicpal3.isContentPrincipal);
+Assert.equal(
+ prinicpal3.origin,
+ "https://test1.test2.example.com:5555^userContextId=5555"
+);
+Assert.equal(prinicpal3.originNoSuffix, "https://test1.test2.example.com:5555");
+Assert.equal(prinicpal3.siteOrigin, "https://example.com^userContextId=5555");
+Assert.equal(prinicpal3.siteOriginNoSuffix, "https://example.com");
+
+let uri4 = Services.io.newURI("https://.example.com:6666");
+let prinicpal4 = scriptSecMan.createContentPrincipal(uri4, {
+ userContextId: 6666,
+});
+Assert.ok(prinicpal4.isContentPrincipal);
+Assert.equal(prinicpal4.origin, "https://.example.com:6666^userContextId=6666");
+Assert.equal(prinicpal4.originNoSuffix, "https://.example.com:6666");
+Assert.equal(prinicpal4.siteOrigin, "https://.example.com^userContextId=6666");
+Assert.equal(prinicpal4.siteOriginNoSuffix, "https://.example.com");
+
+let aboutURI = Services.io.newURI("about:preferences");
+let aboutPrincipal = scriptSecMan.createContentPrincipal(aboutURI, {
+ userContextId: 66,
+});
+Assert.ok(aboutPrincipal.isContentPrincipal);
+Assert.equal(aboutPrincipal.origin, "about:preferences^userContextId=66");
+Assert.equal(aboutPrincipal.originNoSuffix, "about:preferences");
+Assert.equal(aboutPrincipal.siteOrigin, "about:preferences^userContextId=66");
+Assert.equal(aboutPrincipal.siteOriginNoSuffix, "about:preferences");
+
+let viewSourceURI = Services.io.newURI(
+ "view-source:https://test1.test2.example.com"
+);
+let viewSourcePrincipal = scriptSecMan.createContentPrincipal(viewSourceURI, {
+ userContextId: 101,
+});
+Assert.ok(viewSourcePrincipal.isContentPrincipal);
+Assert.ok(viewSourcePrincipal.schemeIs("view-source"));
+Assert.equal(
+ viewSourcePrincipal.origin,
+ "https://test1.test2.example.com^userContextId=101"
+);
+Assert.equal(
+ viewSourcePrincipal.originNoSuffix,
+ "https://test1.test2.example.com"
+);
+Assert.equal(
+ viewSourcePrincipal.siteOrigin,
+ "https://example.com^userContextId=101"
+);
+Assert.equal(viewSourcePrincipal.siteOriginNoSuffix, "https://example.com");
+
+let mozExtensionURI = Services.io.newURI(
+ "moz-extension://924f966d-c93b-4fbf-968a-16608461663c/meh.txt"
+);
+let mozExtensionPrincipal = scriptSecMan.createContentPrincipal(
+ mozExtensionURI,
+ {
+ userContextId: 102,
+ }
+);
+Assert.ok(mozExtensionPrincipal.isContentPrincipal);
+Assert.ok(mozExtensionPrincipal.schemeIs("moz-extension"));
+Assert.equal(
+ mozExtensionPrincipal.origin,
+ "moz-extension://924f966d-c93b-4fbf-968a-16608461663c^userContextId=102"
+);
+Assert.equal(
+ mozExtensionPrincipal.originNoSuffix,
+ "moz-extension://924f966d-c93b-4fbf-968a-16608461663c"
+);
+Assert.equal(
+ mozExtensionPrincipal.siteOrigin,
+ "moz-extension://924f966d-c93b-4fbf-968a-16608461663c^userContextId=102"
+);
+Assert.equal(
+ mozExtensionPrincipal.siteOriginNoSuffix,
+ "moz-extension://924f966d-c93b-4fbf-968a-16608461663c"
+);
+
+let localhostURI = Services.io.newURI(" http://localhost:44203");
+let localhostPrincipal = scriptSecMan.createContentPrincipal(localhostURI, {
+ userContextId: 144,
+});
+Assert.ok(localhostPrincipal.isContentPrincipal);
+Assert.ok(localhostPrincipal.schemeIs("http"));
+Assert.equal(
+ localhostPrincipal.origin,
+ "http://localhost:44203^userContextId=144"
+);
+Assert.equal(localhostPrincipal.originNoSuffix, "http://localhost:44203");
+Assert.equal(
+ localhostPrincipal.siteOrigin,
+ "http://localhost^userContextId=144"
+);
+Assert.equal(localhostPrincipal.siteOriginNoSuffix, "http://localhost");
+
+// NullPrincipal checks
+let nullPrincipal = scriptSecMan.createNullPrincipal({ userContextId: 33 });
+Assert.ok(nullPrincipal.isNullPrincipal);
+Assert.ok(nullPrincipal.origin.includes("moz-nullprincipal"));
+Assert.ok(nullPrincipal.origin.includes("^userContextId=33"));
+Assert.ok(nullPrincipal.originNoSuffix.includes("moz-nullprincipal"));
+Assert.ok(!nullPrincipal.originNoSuffix.includes("^userContextId=33"));
+Assert.ok(nullPrincipal.siteOrigin.includes("moz-nullprincipal"));
+Assert.ok(nullPrincipal.siteOrigin.includes("^userContextId=33"));
+Assert.ok(nullPrincipal.siteOriginNoSuffix.includes("moz-nullprincipal"));
+Assert.ok(!nullPrincipal.siteOriginNoSuffix.includes("^userContextId=33"));
+
+// ExpandedPrincipal checks
+let expandedPrincipal = Cu.getObjectPrincipal(Cu.Sandbox([prinicpal2]));
+Assert.ok(expandedPrincipal.isExpandedPrincipal);
+Assert.equal(
+ expandedPrincipal.origin,
+ "[Expanded Principal [http://test1.example.com^userContextId=22]]^userContextId=22"
+);
+Assert.equal(
+ expandedPrincipal.originNoSuffix,
+ "[Expanded Principal [http://test1.example.com^userContextId=22]]"
+);
+Assert.equal(
+ expandedPrincipal.siteOrigin,
+ "[Expanded Principal [http://test1.example.com^userContextId=22]]^userContextId=22"
+);
+Assert.equal(
+ expandedPrincipal.siteOriginNoSuffix,
+ "[Expanded Principal [http://test1.example.com^userContextId=22]]"
+);
+
+let ipv6URI = Services.io.newURI("https://[2001:db8::ff00:42:8329]:123");
+let ipv6Principal = scriptSecMan.createContentPrincipal(ipv6URI, {
+ userContextId: 6,
+});
+Assert.ok(ipv6Principal.isContentPrincipal);
+Assert.equal(
+ ipv6Principal.origin,
+ "https://[2001:db8::ff00:42:8329]:123^userContextId=6"
+);
+Assert.equal(
+ ipv6Principal.originNoSuffix,
+ "https://[2001:db8::ff00:42:8329]:123"
+);
+Assert.equal(
+ ipv6Principal.siteOrigin,
+ "https://[2001:db8::ff00:42:8329]^userContextId=6"
+);
+Assert.equal(
+ ipv6Principal.siteOriginNoSuffix,
+ "https://[2001:db8::ff00:42:8329]"
+);
diff --git a/caps/tests/unit/test_uri_escaping.js b/caps/tests/unit/test_uri_escaping.js
new file mode 100644
index 0000000000..7feb581295
--- /dev/null
+++ b/caps/tests/unit/test_uri_escaping.js
@@ -0,0 +1,28 @@
+var ssm = Services.scriptSecurityManager;
+
+function makeURI(uri) {
+ return Services.io.newURI(uri);
+}
+
+function createPrincipal(aURI) {
+ try {
+ var uri = makeURI(aURI);
+ var principal = ssm.createContentPrincipal(uri, {});
+ return principal;
+ } catch (e) {
+ return null;
+ }
+}
+
+function run_test() {
+ Assert.equal(createPrincipal("http://test^test/foo^bar#x^y"), null);
+
+ Assert.equal(createPrincipal("http://test^test/foo\\bar"), null);
+
+ Assert.equal(createPrincipal("http://test:2^3/foo\\bar"), null);
+
+ Assert.equal(
+ createPrincipal("http://test/foo^bar").exposableSpec,
+ "http://test/foo%5Ebar"
+ );
+}
diff --git a/caps/tests/unit/xpcshell.ini b/caps/tests/unit/xpcshell.ini
new file mode 100644
index 0000000000..9ff2b22617
--- /dev/null
+++ b/caps/tests/unit/xpcshell.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+head =
+
+[test_origin.js]
+[test_uri_escaping.js]
+[test_ipv6_host_literal.js]
+[test_oa_partitionKey_pattern.js]
+[test_site_origin.js]
+[test_precursor_principal.js]
+firefox-appdir = browser
+skip-if = toolkit == 'android'