From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- caps/BasePrincipal.cpp | 1407 ++++++++++++++++ caps/BasePrincipal.h | 429 +++++ caps/ContentPrincipal.cpp | 715 +++++++++ caps/ContentPrincipal.h | 92 ++ caps/DomainPolicy.cpp | 224 +++ caps/DomainPolicy.h | 64 + caps/ExpandedPrincipal.cpp | 385 +++++ caps/ExpandedPrincipal.h | 95 ++ caps/NullPrincipal.cpp | 294 ++++ caps/NullPrincipal.h | 123 ++ caps/NullPrincipalURI.cpp | 302 ++++ caps/NullPrincipalURI.h | 108 ++ caps/OriginAttributes.cpp | 382 +++++ caps/OriginAttributes.h | 227 +++ caps/PrincipalHashKey.h | 58 + caps/SystemPrincipal.cpp | 92 ++ caps/SystemPrincipal.h | 69 + caps/moz.build | 81 + caps/nsIAddonPolicyService.idl | 95 ++ caps/nsIDomainPolicy.idl | 76 + caps/nsIPrincipal.idl | 597 +++++++ caps/nsIScriptSecurityManager.idl | 314 ++++ caps/nsJSPrincipals.cpp | 394 +++++ caps/nsJSPrincipals.h | 89 ++ caps/nsScriptSecurityManager.cpp | 1674 ++++++++++++++++++++ caps/nsScriptSecurityManager.h | 138 ++ caps/tests/gtest/TestOriginAttributes.cpp | 128 ++ caps/tests/gtest/TestPrincipalAttributes.cpp | 39 + caps/tests/gtest/TestPrincipalSerialization.cpp | 215 +++ caps/tests/gtest/moz.build | 15 + caps/tests/mochitest/.eslintrc.js | 5 + caps/tests/mochitest/browser.ini | 2 + caps/tests/mochitest/browser_aboutOrigin.js | 12 + caps/tests/mochitest/browser_checkloaduri.js | 380 +++++ caps/tests/mochitest/chrome.ini | 12 + caps/tests/mochitest/file_bug1367586-followon.html | 1 + caps/tests/mochitest/file_bug1367586-redirect.sjs | 5 + caps/tests/mochitest/file_bug1367586-target.html | 6 + caps/tests/mochitest/file_data.txt | 1 + caps/tests/mochitest/file_disableScript.html | 11 + caps/tests/mochitest/mochitest.ini | 16 + caps/tests/mochitest/resource_test_file.html | 2 + caps/tests/mochitest/test_addonMayLoad.html | 95 ++ caps/tests/mochitest/test_bug1367586.html | 50 + caps/tests/mochitest/test_bug246699.html | 60 + caps/tests/mochitest/test_bug292789.html | 116 ++ caps/tests/mochitest/test_bug423375.html | 43 + caps/tests/mochitest/test_bug470804.html | 41 + caps/tests/mochitest/test_bug995943.xhtml | 112 ++ caps/tests/mochitest/test_disableScript.xhtml | 331 ++++ .../mochitest/test_disallowInheritPrincipal.html | 58 + caps/tests/unit/test_ipv6_host_literal.js | 39 + caps/tests/unit/test_origin.js | 323 ++++ caps/tests/unit/test_site_origin.js | 112 ++ caps/tests/unit/test_uri_escaping.js | 29 + caps/tests/unit/xpcshell.ini | 7 + 56 files changed, 10790 insertions(+) create mode 100644 caps/BasePrincipal.cpp create mode 100644 caps/BasePrincipal.h create mode 100644 caps/ContentPrincipal.cpp create mode 100644 caps/ContentPrincipal.h create mode 100644 caps/DomainPolicy.cpp create mode 100644 caps/DomainPolicy.h create mode 100644 caps/ExpandedPrincipal.cpp create mode 100644 caps/ExpandedPrincipal.h create mode 100644 caps/NullPrincipal.cpp create mode 100644 caps/NullPrincipal.h create mode 100644 caps/NullPrincipalURI.cpp create mode 100644 caps/NullPrincipalURI.h create mode 100644 caps/OriginAttributes.cpp create mode 100644 caps/OriginAttributes.h create mode 100644 caps/PrincipalHashKey.h create mode 100644 caps/SystemPrincipal.cpp create mode 100644 caps/SystemPrincipal.h create mode 100644 caps/moz.build create mode 100644 caps/nsIAddonPolicyService.idl create mode 100644 caps/nsIDomainPolicy.idl create mode 100644 caps/nsIPrincipal.idl create mode 100644 caps/nsIScriptSecurityManager.idl create mode 100644 caps/nsJSPrincipals.cpp create mode 100644 caps/nsJSPrincipals.h create mode 100644 caps/nsScriptSecurityManager.cpp create mode 100644 caps/nsScriptSecurityManager.h create mode 100644 caps/tests/gtest/TestOriginAttributes.cpp create mode 100644 caps/tests/gtest/TestPrincipalAttributes.cpp create mode 100644 caps/tests/gtest/TestPrincipalSerialization.cpp create mode 100644 caps/tests/gtest/moz.build create mode 100644 caps/tests/mochitest/.eslintrc.js create mode 100644 caps/tests/mochitest/browser.ini create mode 100644 caps/tests/mochitest/browser_aboutOrigin.js create mode 100644 caps/tests/mochitest/browser_checkloaduri.js create mode 100644 caps/tests/mochitest/chrome.ini create mode 100644 caps/tests/mochitest/file_bug1367586-followon.html create mode 100644 caps/tests/mochitest/file_bug1367586-redirect.sjs create mode 100644 caps/tests/mochitest/file_bug1367586-target.html create mode 100644 caps/tests/mochitest/file_data.txt create mode 100644 caps/tests/mochitest/file_disableScript.html create mode 100644 caps/tests/mochitest/mochitest.ini create mode 100644 caps/tests/mochitest/resource_test_file.html create mode 100644 caps/tests/mochitest/test_addonMayLoad.html create mode 100644 caps/tests/mochitest/test_bug1367586.html create mode 100644 caps/tests/mochitest/test_bug246699.html create mode 100644 caps/tests/mochitest/test_bug292789.html create mode 100644 caps/tests/mochitest/test_bug423375.html create mode 100644 caps/tests/mochitest/test_bug470804.html create mode 100644 caps/tests/mochitest/test_bug995943.xhtml create mode 100644 caps/tests/mochitest/test_disableScript.xhtml create mode 100644 caps/tests/mochitest/test_disallowInheritPrincipal.html create mode 100644 caps/tests/unit/test_ipv6_host_literal.js create mode 100644 caps/tests/unit/test_origin.js create mode 100644 caps/tests/unit/test_site_origin.js create mode 100644 caps/tests/unit/test_uri_escaping.js create mode 100644 caps/tests/unit/xpcshell.ini (limited to 'caps') diff --git a/caps/BasePrincipal.cpp b/caps/BasePrincipal.cpp new file mode 100644 index 0000000000..f32d628d08 --- /dev/null +++ b/caps/BasePrincipal.cpp @@ -0,0 +1,1407 @@ +/* -*- 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/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 "prnetdb.h" +#include "nsIURIFixup.h" +#include "mozilla/dom/StorageUtils.h" +#include "mozilla/ContentBlocking.h" +#include "nsPIDOMWindow.h" +#include "nsIURIMutator.h" + +#include "json/json.h" +#include "nsSerializationHelper.h" + +namespace mozilla { + +BasePrincipal::BasePrincipal(PrincipalKind aKind) + : mKind(aKind), mHasExplicitDomain(false), mInitialized(false) {} + +BasePrincipal::~BasePrincipal() {} + +NS_IMETHODIMP +BasePrincipal::GetOrigin(nsACString& aOrigin) { + MOZ_ASSERT(mInitialized); + + 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 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 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 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) { + MOZ_ASSERT(mInitialized); + mOriginNoSuffix->ToUTF8String(aOrigin); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetSiteOrigin(nsACString& aSiteOrigin) { + MOZ_ASSERT(mInitialized); + + 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) { + MOZ_ASSERT(mInitialized); + 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":","}} -> +// {"0":","} +// +// 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 +static nsTArray GetJSONKeys(const Json::Value* aInput) { + int size = T::eMax + 1; + nsTArray 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(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::FromJSON( + const nsACString& aJSON) { + Json::Value root; + Json::CharReaderBuilder builder; + std::unique_ptr 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; + } + + int principalKind = -1; + const Json::Value* value = GetPrincipalObject(root, principalKind); + if (!value) { +#ifdef DEBUG + fprintf(stderr, "Unexpected JSON principal %s\n", + root.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 principal = + BasePrincipal::Cast(nsContentUtils::GetSystemPrincipal()); + return principal.forget(); + } + + if (principalKind == eNullPrincipal) { + nsTArray res = GetJSONKeys(value); + return NullPrincipal::FromProperties(res); + } + + if (principalKind == eContentPrincipal) { + nsTArray res = + GetJSONKeys(value); + return ContentPrincipal::FromProperties(res); + } + + if (principalKind == eExpandedPrincipal) { + nsTArray res = + GetJSONKeys(value); + return ExpandedPrincipal::FromProperties(res); + } + + 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& aResult) { + MOZ_ASSERT(aResult.IsEmpty(), "ToJSON only supports an empty result input"); + aResult.Truncate(); + + Json::StreamWriterBuilder builder; + builder["indentation"] = ""; + Json::Value innerJSONObject = Json::objectValue; + + nsresult rv = PopulateJSONObject(innerJSONObject); + NS_ENSURE_SUCCESS(rv, rv); + + Json::Value root = Json::objectValue; + std::string key = std::to_string(Kind()); + root[key] = innerJSONObject; + std::string result = Json::writeString(builder, root); + aResult.Append(result); + if (aResult.Length() == 0) { + MOZ_ASSERT(false, "JSON writer failed to output a principal serialization"); + return NS_ERROR_UNEXPECTED; + } + 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); + + // If the principals are equal, then they match. + if (FastEquals(aOther)) { + *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; + } + + // Compare their OriginAttributes + const mozilla::OriginAttributes& theirAttrs = aOther->OriginAttributesRef(); + const mozilla::OriginAttributes& ourAttrs = OriginAttributesRef(); + + if (theirAttrs != ourAttrs) { + return NS_OK; + } + + nsCOMPtr ourURI; + nsresult rv = GetURI(getter_AddRefs(ourURI)); + NS_ENSURE_SUCCESS(rv, rv); + auto* basePrin = BasePrincipal::Cast(aOther); + + nsCOMPtr otherURI; + rv = basePrin->GetURI(getter_AddRefs(otherURI)); + NS_ENSURE_SUCCESS(rv, rv); + // 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 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 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) { + return CheckMayLoadHelper(aURI, aAllowIfInheritsPrincipal, false, 0); +} + +NS_IMETHODIMP +BasePrincipal::CheckMayLoadWithReporting(nsIURI* aURI, + bool aAllowIfInheritsPrincipal, + uint64_t aInnerWindowID) { + return CheckMayLoadHelper(aURI, aAllowIfInheritsPrincipal, true, + aInnerWindowID); +} + +nsresult BasePrincipal::CheckMayLoadHelper(nsIURI* aURI, + bool aAllowIfInheritsPrincipal, + bool aReport, + uint64_t aInnerWindowID) { + 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; + } + } + + bool fetchableByAnyone; + rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FETCHABLE_BY_ANYONE, + &fetchableByAnyone); + if (NS_SUCCEEDED(rv) && fetchableByAnyone) { + return NS_OK; + } + + if (aReport) { + nsCOMPtr prinURI; + rv = GetURI(getter_AddRefs(prinURI)); + if (NS_SUCCEEDED(rv) && prinURI) { + 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() || (AddonPolicy() && AddonAllowsLoad(aURI))) { + *aRes = false; + return NS_OK; + } + + *aRes = true; + // If we do not have a URI its always 3rd party. + nsCOMPtr 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 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) { + if (IsSystemPrincipal()) { + // Nothing is 3rd party to the system principal. + *aRes = false; + return NS_OK; + } + + nsCOMPtr prinURI; + GetURI(getter_AddRefs(prinURI)); + ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance(); + return thirdPartyUtil->IsThirdPartyChannel(aChan, prinURI, aRes); +} + +NS_IMETHODIMP +BasePrincipal::IsSameOrigin(nsIURI* aURI, bool aIsPrivateWin, bool* aRes) { + *aRes = false; + nsCOMPtr 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; + } + *aRes = NS_SUCCEEDED( + ssm->CheckSameOriginURI(prinURI, aURI, false, aIsPrivateWin)); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::IsL10nAllowed(nsIURI* aURI, bool* aRes) { + *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 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 = AddonPolicy(); + *aRes = (policy && policy->IsPrivileged()); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::AllowsRelaxStrictFileOriginPolicy(nsIURI* aURI, bool* aRes) { + *aRes = false; + nsCOMPtr 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 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) { + *aRejectedReason = 0; + *aOutAllowed = false; + + nsPIDOMWindowInner* win = nsPIDOMWindowInner::From(aCheckWindow); + nsCOMPtr uri; + nsresult rv = GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv)) { + return rv; + } + *aOutAllowed = + ContentBlocking::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 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 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 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 prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + + nsCOMPtr exposableURI = net::nsIOService::CreateExposableURI(prinURI); + return exposableURI->GetDisplayPrePath(aPrepath); +} + +NS_IMETHODIMP +BasePrincipal::GetExposableSpec(nsACString& aSpec) { + aSpec.Truncate(); + nsCOMPtr prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + nsCOMPtr 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 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 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 = AddonPolicy() || ContentScriptAddonPolicy(); + return NS_OK; +} + +NS_IMETHODIMP BasePrincipal::GetIsOnion(bool* aIsOnion) { + *aIsOnion = false; + nsCOMPtr 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 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 prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + + nsCOMPtr 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 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 prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + *aResult = prinURI->SchemeIs(aScheme); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::IsURIInPrefList(const char* aPref, bool* aResult) { + *aResult = false; + nsCOMPtr 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::GetIsOriginPotentiallyTrustworthy(bool* aResult) { + MOZ_ASSERT(NS_IsMainThread()); + *aResult = false; + + nsCOMPtr 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::GetAboutModuleFlags(uint32_t* flags) { + *flags = 0; + nsCOMPtr 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 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 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(nsISupports** aResult) { + RefPtr policy(AddonPolicy()); + policy.forget(aResult); + return NS_OK; +} + +extensions::WebExtensionPolicy* BasePrincipal::AddonPolicy() { + if (Is()) { + return As()->AddonPolicy(); + } + return nullptr; +} + +bool BasePrincipal::AddonHasPermission(const nsAtom* aPerm) { + if (auto policy = AddonPolicy()) { + return policy->HasPermission(aPerm); + } + return false; +} + +nsIPrincipal* BasePrincipal::PrincipalToInherit(nsIURI* aRequestedURI) { + if (Is()) { + return As()->PrincipalToInherit(aRequestedURI); + } + return this; +} + +already_AddRefed BasePrincipal::CreateContentPrincipal( + nsIURI* aURI, const OriginAttributes& aAttrs) { + 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); +} + +already_AddRefed BasePrincipal::CreateContentPrincipal( + nsIURI* aURI, const OriginAttributes& aAttrs, + const nsACString& aOriginNoSuffix) { + 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 uriWithSpecialOrigin = + do_QueryInterface(aURI); + if (uriWithSpecialOrigin) { + nsCOMPtr origin; + rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + MOZ_ASSERT(origin); + OriginAttributes attrs; + RefPtr principal = CreateContentPrincipal(origin, attrs); + return principal.forget(); + } +#endif + + nsCOMPtr blobPrincipal; + if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal( + aURI, getter_AddRefs(blobPrincipal))) { + MOZ_ASSERT(blobPrincipal); + RefPtr principal = Cast(blobPrincipal); + return principal.forget(); + } + + // Mint a content principal. + RefPtr principal = new ContentPrincipal(); + rv = principal->Init(aURI, aAttrs, aOriginNoSuffix); + NS_ENSURE_SUCCESS(rv, nullptr); + return principal.forget(); +} + +already_AddRefed 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 uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix); + NS_ENSURE_SUCCESS(rv, nullptr); + + return BasePrincipal::CreateContentPrincipal(uri, attrs); +} + +already_AddRefed BasePrincipal::CloneForcingOriginAttributes( + const OriginAttributes& aOriginAttributes) { + if (NS_WARN_IF(!IsContentPrincipal())) { + return nullptr; + } + + nsAutoCString originNoSuffix; + nsresult rv = GetOriginNoSuffix(originNoSuffix); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsIURI* uri = static_cast(this)->mURI; + RefPtr copy = new ContentPrincipal(); + rv = copy->Init(uri, aOriginAttributes, originNoSuffix); + NS_ENSURE_SUCCESS(rv, nullptr); + + return copy.forget(); +} + +extensions::WebExtensionPolicy* BasePrincipal::ContentScriptAddonPolicy() { + if (!Is()) { + return nullptr; + } + + auto expanded = As(); + for (auto& prin : expanded->AllowList()) { + if (auto policy = BasePrincipal::Cast(prin)->AddonPolicy()) { + return policy; + } + } + + return nullptr; +} + +bool BasePrincipal::AddonAllowsLoad(nsIURI* aURI, + bool aExplicit /* = false */) { + if (Is()) { + return As()->AddonAllowsLoad(aURI, aExplicit); + } + if (auto policy = AddonPolicy()) { + return policy->CanAccessURI(aURI, aExplicit); + } + return false; +} + +void BasePrincipal::FinishInit(const nsACString& aOriginNoSuffix, + const OriginAttributes& aOriginAttributes) { + mInitialized = true; + mOriginAttributes = aOriginAttributes; + + // First compute the origin suffix since it's infallible. + nsAutoCString originSuffix; + mOriginAttributes.CreateSuffix(originSuffix); + mOriginSuffix = NS_Atomize(originSuffix); + + MOZ_ASSERT(!aOriginNoSuffix.IsEmpty()); + mOriginNoSuffix = NS_Atomize(aOriginNoSuffix); +} + +void BasePrincipal::FinishInit(BasePrincipal* aOther, + const OriginAttributes& aOriginAttributes) { + mInitialized = true; + mOriginAttributes = aOriginAttributes; + + // First compute the origin suffix since it's infallible. + nsAutoCString originSuffix; + mOriginAttributes.CreateSuffix(originSuffix); + mOriginSuffix = NS_Atomize(originSuffix); + + mOriginNoSuffix = aOther->mOriginNoSuffix; + mHasExplicitDomain = aOther->mHasExplicitDomain; +} + +NS_IMETHODIMP +BasePrincipal::GetLocalStorageQuotaKey(nsACString& aKey) { + aKey.Truncate(); + + nsCOMPtr 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 url = do_QueryInterface(uri, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = url->GetDirectory(baseDomain); + NS_ENSURE_SUCCESS(rv, rv); + } else { + nsCOMPtr 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 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 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 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 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 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) { + *aIsScriptAllowedByPolicy = false; + nsCOMPtr 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 prinURI; + RefPtr 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; +} + +} // namespace mozilla diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h new file mode 100644 index 0000000000..30121c05a5 --- /dev/null +++ b/caps/BasePrincipal.h @@ -0,0 +1,429 @@ +/* -*- 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 +#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 "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 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 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 + }; + + explicit BasePrincipal(PrincipalKind aKind); + + template + bool Is() const { + return mKind == T::Kind(); + } + + template + T* As() { + MOZ_ASSERT(Is()); + return static_cast(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(nsISupports** 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 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 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 IsSameOrigin(nsIURI* aURI, bool aIsPrivateWin, + 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; + nsresult ToJSON(nsACString& aJSON); + static already_AddRefed FromJSON(const nsACString& 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(aPrin); + } + + static BasePrincipal& Cast(nsIPrincipal& aPrin) { + return *static_cast(&aPrin); + } + + static const BasePrincipal* Cast(const nsIPrincipal* aPrin) { + return static_cast(aPrin); + } + + static const BasePrincipal& Cast(const nsIPrincipal& aPrin) { + return *static_cast(&aPrin); + } + + static already_AddRefed CreateContentPrincipal( + const nsACString& aOrigin); + + // These following 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. + + static already_AddRefed CreateContentPrincipal( + nsIURI* aURI, const OriginAttributes& aAttrs); + + const OriginAttributes& OriginAttributesRef() final { + return mOriginAttributes; + } + extensions::WebExtensionPolicy* AddonPolicy(); + 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 CloneForcingOriginAttributes( + const OriginAttributes& aOriginAttributes); + + // If this is an add-on content script principal, returns its AddonPolicy. + // Otherwise returns null. + extensions::WebExtensionPolicy* ContentScriptAddonPolicy(); + + // 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) { + 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 non-extension principals. + // This is primarily for the sake of their stylesheets, which are usually + // loaded from channels and cannot have expanded principals. + return (AddonPolicy() && + !BasePrincipal::Cast(aDocumentPrincipal)->AddonPolicy()); + } + + uint32_t GetOriginNoSuffixHash() const { return mOriginNoSuffix->hash(); } + uint32_t GetOriginSuffixHash() const { return mOriginSuffix->hash(); } + + virtual nsresult GetSiteIdentifier(SiteIdentifier& aSite) = 0; + + protected: + 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. + 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; } + + // Either of these functions should be called as the last step of the + // initialization of the principal objects. It's typically called as the + // last step from the Init() method of the child classes. + void FinishInit(const nsACString& aOriginNoSuffix, + const OriginAttributes& aOriginAttributes); + void FinishInit(BasePrincipal* aOther, + const OriginAttributes& aOriginAttributes); + + // KeyValT holds a principal subtype-specific key value and the associated + // parsed value after JSON parsing. + template + struct KeyValT { + static_assert(sizeof(SerializedKey) == 1, + "SerializedKey should be a uint8_t"); + SerializedKey key; + bool valueWasSerialized; + nsCString value; + }; + + private: + static already_AddRefed CreateContentPrincipal( + nsIURI* aURI, const OriginAttributes& aAttrs, + const nsACString& aOriginNoSuffix); + + bool FastSubsumesIgnoringFPD(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration); + + RefPtr mOriginNoSuffix; + RefPtr mOriginSuffix; + + OriginAttributes mOriginAttributes; + PrincipalKind mKind; + bool mHasExplicitDomain; + bool mInitialized; +}; + +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); + } + + 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..d17679e714 --- /dev/null +++ b/caps/ContentPrincipal.cpp @@ -0,0 +1,715 @@ +/* -*- 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/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; + +static inline ExtensionPolicyService& EPS() { + return ExtensionPolicyService::GetSingleton(); +} + +NS_IMPL_CLASSINFO(ContentPrincipal, nullptr, nsIClassInfo::MAIN_THREAD_ONLY, + NS_PRINCIPAL_CID) +NS_IMPL_QUERY_INTERFACE_CI(ContentPrincipal, nsIPrincipal, nsISerializable) +NS_IMPL_CI_INTERFACE_GETTER(ContentPrincipal, nsIPrincipal, nsISerializable) + +ContentPrincipal::ContentPrincipal() : BasePrincipal(eContentPrincipal) {} + +ContentPrincipal::~ContentPrincipal() {} + +nsresult ContentPrincipal::Init(nsIURI* aURI, + const OriginAttributes& aOriginAttributes, + const nsACString& aOriginNoSuffix) { + NS_ENSURE_ARG(aURI); + + // 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; + Unused << hasFlag; // silence possible compiler warnings. + MOZ_DIAGNOSTIC_ASSERT( + NS_SUCCEEDED(NS_URIChainHasFlags( + aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &hasFlag)) && + !hasFlag); + + mURI = aURI; + FinishInit(aOriginNoSuffix, aOriginAttributes); + + return NS_OK; +} + +nsresult ContentPrincipal::Init(ContentPrincipal* aOther, + const OriginAttributes& aOriginAttributes) { + NS_ENSURE_ARG(aOther); + + mURI = aOther->mURI; + FinishInit(aOther, aOriginAttributes); + + mDomain = aOther->mDomain; + mAddon = aOther->mAddon; + return NS_OK; +} + +nsresult ContentPrincipal::GetScriptLocation(nsACString& aStr) { + return mURI->GetSpec(aStr); +} + +/* static */ +nsresult ContentPrincipal::GenerateOriginNoSuffixFromURI( + nsIURI* aURI, nsACString& aOriginNoSuffix) { + if (!aURI) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr 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 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 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) + nsresult rv; + if (aConsideration == ConsiderDocumentDomain) { + // Get .domain on each principal. + nsCOMPtr 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; + } + } + + // Compare uris. + bool isSameOrigin = false; + rv = aOther->IsSameOrigin(mURI, false, &isSameOrigin); + NS_ENSURE_SUCCESS(rv, false); + return isSameOrigin; +} + +NS_IMETHODIMP +ContentPrincipal::GetURI(nsIURI** aURI) { + NS_ADDREF(*aURI = mURI); + return NS_OK; +} + +bool ContentPrincipal::MayLoadInternal(nsIURI* aURI) { + MOZ_ASSERT(aURI); + +#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) + nsCOMPtr uriWithSpecialOrigin = + do_QueryInterface(aURI); + if (uriWithSpecialOrigin) { + nsCOMPtr origin; + nsresult rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + MOZ_ASSERT(origin); + OriginAttributes attrs; + RefPtr principal = + BasePrincipal::CreateContentPrincipal(origin, attrs); + return nsIPrincipal::Subsumes(principal); + } +#endif + + nsCOMPtr 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 uri; + GetDomain(getter_AddRefs(uri)); + if (!uri) { + GetURI(getter_AddRefs(uri)); + }; + return NS_SecurityHashURI(uri); +} + +NS_IMETHODIMP +ContentPrincipal::GetDomain(nsIURI** aDomain) { + if (!mDomain) { + *aDomain = nullptr; + return NS_OK; + } + + NS_ADDREF(*aDomain = mDomain); + return NS_OK; +} + +NS_IMETHODIMP +ContentPrincipal::SetDomain(nsIURI* aDomain) { + MOZ_ASSERT(aDomain); + + 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(this)); + + dom::AutoJSAPI jsapi; + jsapi.Init(); + JS::IterateRealmsWithPrincipals(jsapi.cx(), principals, nullptr, cb); + + return NS_OK; +} + +static nsresult GetSpecialBaseDomain(const nsCOMPtr& 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 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 thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID); + if (!thirdPartyUtil) { + return NS_ERROR_FAILURE; + } + + return thirdPartyUtil->GetBaseDomain(mURI, aBaseDomain); +} + +NS_IMETHODIMP +ContentPrincipal::GetSiteOriginNoSuffix(nsACString& aSiteOrigin) { + // Handle some special URIs first. + nsAutoCString baseDomain; + bool handled; + nsresult rv = GetSpecialBaseDomain(mURI, &handled, baseDomain); + NS_ENSURE_SUCCESS(rv, rv); + + if (handled) { + // This is a special URI ("file:", "about:", "view-source:", etc). Just + // return the origin. + return GetOriginNoSuffix(aSiteOrigin); + } + + // 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 tldService = + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + if (!tldService) { + return NS_ERROR_FAILURE; + } + + bool gotBaseDomain = false; + rv = tldService->GetBaseDomain(mURI, 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) { + 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 siteUri; + NS_MutateURI mutator(mURI); + 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); + + 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 principal = CreateContentPrincipal(siteOrigin); + if (!principal) { + NS_WARNING("could not instantiate content principal"); + return NS_ERROR_FAILURE; + } + + aSite.Init(principal); + return NS_OK; +} + +WebExtensionPolicy* ContentPrincipal::AddonPolicy() { + if (!mAddon.isSome()) { + NS_ENSURE_TRUE(mURI, nullptr); + + if (mURI->SchemeIs("moz-extension")) { + mAddon.emplace(EPS().GetByURL(mURI.get())); + } else { + mAddon.emplace(nullptr); + } + } + + return mAddon.value(); +} + +NS_IMETHODIMP +ContentPrincipal::GetAddonId(nsAString& aAddonId) { + auto policy = AddonPolicy(); + if (policy) { + policy->GetId(aAddonId); + } else { + aAddonId.Truncate(); + } + return NS_OK; +} + +NS_IMETHODIMP +ContentPrincipal::Read(nsIObjectInputStream* aStream) { + nsCOMPtr supports; + nsCOMPtr 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 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); + + rv = Init(principalURI, attrs, originNoSuffix); + NS_ENSURE_SUCCESS(rv, rv); + + // Note: we don't call SetDomain here because we don't need the wrapper + // recomputation code there (we just created this principal). + mDomain = domain; + if (mDomain) { + SetHasExplicitDomain(); + } + + return NS_OK; +} + +NS_IMETHODIMP +ContentPrincipal::Write(nsIObjectOutputStream* aStream) { + // Read is used still for legacy principals + MOZ_RELEASE_ASSERT(false, "Old style serialization is removed"); + 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 + aObject[std::to_string(eURI)] = principalURI.get(); + + if (mDomain) { + nsAutoCString domainStr; + rv = mDomain->GetSpec(domainStr); + NS_ENSURE_SUCCESS(rv, rv); + aObject[std::to_string(eDomain)] = domainStr.get(); + } + + nsAutoCString suffix; + OriginAttributesRef().CreateSuffix(suffix); + if (suffix.Length() > 0) { + aObject[std::to_string(eSuffix)] = suffix.get(); + } + + return NS_OK; +} + +already_AddRefed ContentPrincipal::FromProperties( + nsTArray& aFields) { + MOZ_ASSERT(aFields.Length() == eMax + 1, "Must have all the keys"); + nsresult rv; + nsCOMPtr principalURI; + nsCOMPtr domain; + nsCOMPtr 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 principal = new ContentPrincipal(); + rv = principal->Init(principalURI, attrs, originNoSuffix); + if (NS_FAILED(rv)) { + return nullptr; + } + + principal->mDomain = domain; + if (principal->mDomain) { + principal->SetHasExplicitDomain(); + } + + return principal.forget(); +} diff --git a/caps/ContentPrincipal.h b/caps/ContentPrincipal.h new file mode 100644 index 0000000000..2c7aac3c0b --- /dev/null +++ b/caps/ContentPrincipal.h @@ -0,0 +1,92 @@ +/* -*- 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/extensions/WebExtensionPolicy.h" + +namespace Json { +class Value; +} + +namespace mozilla { + +class ContentPrincipal final : public BasePrincipal { + public: + 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 GetSiteOriginNoSuffix(nsACString& aSiteOrigin) override; + bool IsContentPrincipal() const override { return true; } + + ContentPrincipal(); + + static PrincipalKind Kind() { return eContentPrincipal; } + + // Init() must be called before the principal is in a usable state. + nsresult Init(nsIURI* aURI, const OriginAttributes& aOriginAttributes, + const nsACString& aOriginNoSuffix); + nsresult Init(ContentPrincipal* aOther, + const OriginAttributes& aOriginAttributes); + + virtual nsresult GetScriptLocation(nsACString& aStr) override; + + nsresult GetSiteIdentifier(SiteIdentifier& aSite) override; + + static nsresult GenerateOriginNoSuffixFromURI(nsIURI* aURI, + nsACString& aOrigin); + + extensions::WebExtensionPolicy* AddonPolicy(); + + nsCOMPtr mDomain; + nsCOMPtr mURI; + + 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 KeyVal; + + static already_AddRefed FromProperties( + nsTArray& aFields); + + protected: + virtual ~ContentPrincipal(); + + bool SubsumesInternal(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration) override; + bool MayLoadInternal(nsIURI* aURI) override; + + private: + Maybe> mAddon; +}; + +} // namespace mozilla + +#define NS_PRINCIPAL_CONTRACTID "@mozilla.org/principal;1" +#define NS_PRINCIPAL_CID \ + { \ + 0x653e0e4d, 0x3ee4, 0x45fa, { \ + 0xb2, 0x72, 0x97, 0xc2, 0x0b, 0xc0, 0x1e, 0xb8 \ + } \ + } + +#endif // mozilla_ContentPrincipal_h diff --git a/caps/DomainPolicy.cpp b/caps/DomainPolicy.cpp new file mode 100644 index 0000000000..578d5963a8 --- /dev/null +++ b/caps/DomainPolicy.cpp @@ -0,0 +1,224 @@ +/* -*- 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 "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 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 set = mBlocklist.get(); + set.forget(aSet); + return NS_OK; +} + +NS_IMETHODIMP +DomainPolicy::GetSuperBlocklist(nsIDomainSet** aSet) { + nsCOMPtr set = mSuperBlocklist.get(); + set.forget(aSet); + return NS_OK; +} + +NS_IMETHODIMP +DomainPolicy::GetAllowlist(nsIDomainSet** aSet) { + nsCOMPtr set = mAllowlist.get(); + set.forget(aSet); + return NS_OK; +} + +NS_IMETHODIMP +DomainPolicy::GetSuperAllowlist(nsIDomainSet** aSet) { + nsCOMPtr 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>& 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 GetCanonicalClone(nsIURI* aURI) { + nsCOMPtr 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 clone = GetCanonicalClone(aDomain); + NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE); + mHashTable.PutEntry(clone); + if (XRE_IsParentProcess()) { + return BroadcastDomainSetChange(mType, ADD_DOMAIN, aDomain); + } + + return NS_OK; +} + +NS_IMETHODIMP +DomainSet::Remove(nsIURI* aDomain) { + nsCOMPtr clone = GetCanonicalClone(aDomain); + NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE); + mHashTable.RemoveEntry(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 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 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>* aDomains) { + for (auto iter = mHashTable.Iter(); !iter.Done(); iter.Next()) { + nsIURI* key = iter.Get()->GetKey(); + aDomains->AppendElement(key); + } +} + +} /* namespace mozilla */ diff --git a/caps/DomainPolicy.h b/caps/DomainPolicy.h new file mode 100644 index 0000000000..105b7fc1a0 --- /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 "nsTHashtable.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>* aDomains); + + protected: + virtual ~DomainSet() {} + nsTHashtable mHashTable; + DomainSetType mType; +}; + +class DomainPolicy final : public nsIDomainPolicy { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMAINPOLICY + DomainPolicy(); + + private: + virtual ~DomainPolicy(); + + RefPtr mBlocklist; + RefPtr mSuperBlocklist; + RefPtr mAllowlist; + RefPtr mSuperAllowlist; +}; + +} /* namespace mozilla */ + +#endif /* DomainPolicy_h__ */ diff --git a/caps/ExpandedPrincipal.cpp b/caps/ExpandedPrincipal.cpp new file mode 100644 index 0000000000..fe70c71a72 --- /dev/null +++ b/caps/ExpandedPrincipal.cpp @@ -0,0 +1,385 @@ +/* -*- 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 "nsReadableUtils.h" +#include "mozilla/Base64.h" + +using namespace mozilla; + +NS_IMPL_CLASSINFO(ExpandedPrincipal, nullptr, nsIClassInfo::MAIN_THREAD_ONLY, + NS_EXPANDEDPRINCIPAL_CID) +NS_IMPL_QUERY_INTERFACE_CI(ExpandedPrincipal, nsIPrincipal, + nsIExpandedPrincipal, nsISerializable) +NS_IMPL_CI_INTERFACE_GETTER(ExpandedPrincipal, nsIPrincipal, + nsIExpandedPrincipal, nsISerializable) + +struct OriginComparator { + bool LessThan(nsIPrincipal* a, nsIPrincipal* b) const { + nsAutoCString originA; + DebugOnly rv = a->GetOrigin(originA); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + nsAutoCString originB; + rv = b->GetOrigin(originB); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return originA < originB; + } + + bool Equals(nsIPrincipal* a, nsIPrincipal* b) const { + nsAutoCString originA; + DebugOnly rv = a->GetOrigin(originA); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + nsAutoCString originB; + rv = b->GetOrigin(originB); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return a == b; + } +}; + +ExpandedPrincipal::ExpandedPrincipal( + nsTArray>& aAllowList) + : BasePrincipal(eExpandedPrincipal) { + // We force the principals to be sorted by origin so that ExpandedPrincipal + // origins can have a canonical form. + OriginComparator c; + for (size_t i = 0; i < aAllowList.Length(); ++i) { + mPrincipals.InsertElementSorted(aAllowList[i], c); + } +} + +ExpandedPrincipal::ExpandedPrincipal() : BasePrincipal(eExpandedPrincipal) {} + +ExpandedPrincipal::~ExpandedPrincipal() {} + +already_AddRefed ExpandedPrincipal::Create( + nsTArray>& aAllowList, + const OriginAttributes& aAttrs) { + RefPtr ep = new ExpandedPrincipal(aAllowList); + + nsAutoCString origin; + origin.AssignLiteral("[Expanded Principal ["); + StringJoinAppend( + origin, ", "_ns, ep->mPrincipals, + [](nsACString& dest, const nsCOMPtr& principal) { + nsAutoCString subOrigin; + DebugOnly rv = principal->GetOrigin(subOrigin); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + dest.Append(subOrigin); + }); + origin.AppendLiteral("]]"); + + ep->FinishInit(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()) { + auto* expanded = Cast(aOther)->As(); + + 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>& 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) { mCSP = aCSP; } + +NS_IMETHODIMP +ExpandedPrincipal::GetCsp(nsIContentSecurityPolicy** aCsp) { + 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::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; + } + + if (!mPrincipals.SetCapacity(count, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + OriginComparator c; + for (uint32_t i = 0; i < count; ++i) { + nsCOMPtr read; + rv = aStream->ReadObject(true, getter_AddRefs(read)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr principal = do_QueryInterface(read); + if (!principal) { + return NS_ERROR_UNEXPECTED; + } + + // Play it safe and InsertElementSorted, in case the sort order + // changed for some bizarre reason. + mPrincipals.InsertElementSorted(std::move(principal), c); + } + + return NS_OK; +} + +NS_IMETHODIMP +ExpandedPrincipal::Write(nsIObjectOutputStream* aStream) { + // Read is used still for legacy principals + MOZ_RELEASE_ASSERT(false, "Old style serialization is removed"); + return NS_OK; +} + +nsresult ExpandedPrincipal::GetSiteIdentifier(SiteIdentifier& aSite) { + // Call GetSiteIdentifier on each of our principals and return a new + // ExpandedPrincipal. + + nsTArray> 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::Create(allowlist, OriginAttributesRef()); + MOZ_ASSERT(expandedPrincipal, "ExpandedPrincipal::Create returned nullptr?"); + + aSite.Init(expandedPrincipal); + return NS_OK; +} + +nsresult ExpandedPrincipal::PopulateJSONObject(Json::Value& aObject) { + nsAutoCString principalList; + // First item through we have a blank separator and append the next result + nsAutoCString sep; + for (auto& principal : mPrincipals) { + nsAutoCString JSON; + BasePrincipal::Cast(principal)->ToJSON(JSON); + // This is blank for the first run through so the last in the list doesn't + // add a separator + principalList.Append(sep); + sep = ','; + // Values currently only copes with strings so encode into base64 to allow a + // CSV safely. + nsresult rv; + rv = Base64EncodeAppend(JSON, principalList); + NS_ENSURE_SUCCESS(rv, rv); + } + aObject[std::to_string(eSpecs)] = principalList.get(); + + nsAutoCString suffix; + OriginAttributesRef().CreateSuffix(suffix); + if (suffix.Length() > 0) { + aObject[std::to_string(eSuffix)] = suffix.get(); + } + + return NS_OK; +} + +already_AddRefed ExpandedPrincipal::FromProperties( + nsTArray& aFields) { + MOZ_ASSERT(aFields.Length() == eMax + 1, "Must have all the keys"); + nsTArray> 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 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::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)->AddonPolicy()) { + 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..70173f4103 --- /dev/null +++ b/caps/ExpandedPrincipal.h @@ -0,0 +1,95 @@ +/* -*- 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 "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 Create( + nsTArray>& aAllowList, + const mozilla::OriginAttributes& aAttrs); + + static PrincipalKind Kind() { return eExpandedPrincipal; } + + // For use from the XPCOM factory constructor only. Do not ever use this + // constructor by hand! + ExpandedPrincipal(); + + NS_DECL_NSIEXPANDEDPRINCIPAL + NS_DECL_NSISERIALIZABLE + + 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 KeyVal; + + static already_AddRefed FromProperties( + nsTArray& aFields); + + protected: + explicit ExpandedPrincipal(nsTArray>& aAllowList); + + virtual ~ExpandedPrincipal(); + + bool SubsumesInternal(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration) override; + + bool MayLoadInternal(nsIURI* aURI) override; + + private: + nsTArray> mPrincipals; + nsCOMPtr mCSP; +}; + +#define NS_EXPANDEDPRINCIPAL_CONTRACTID "@mozilla.org/expandedprincipal;1" +#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..0047d06dec --- /dev/null +++ b/caps/NullPrincipal.cpp @@ -0,0 +1,294 @@ +/* -*- 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 "nsDocShell.h" +#include "NullPrincipal.h" +#include "NullPrincipalURI.h" +#include "nsMemory.h" +#include "nsIClassInfoImpl.h" +#include "nsNetCID.h" +#include "nsError.h" +#include "ContentPrincipal.h" +#include "nsScriptSecurityManager.h" +#include "pratom.h" + +#include "json/json.h" + +using namespace mozilla; + +NS_IMPL_CLASSINFO(NullPrincipal, nullptr, nsIClassInfo::MAIN_THREAD_ONLY, + NS_NULLPRINCIPAL_CID) +NS_IMPL_QUERY_INTERFACE_CI(NullPrincipal, nsIPrincipal, nsISerializable) +NS_IMPL_CI_INTERFACE_GETTER(NullPrincipal, nsIPrincipal, nsISerializable) + +/* static */ +already_AddRefed NullPrincipal::CreateWithInheritedAttributes( + nsIPrincipal* aInheritFrom) { + MOZ_ASSERT(aInheritFrom); + return CreateWithInheritedAttributes( + Cast(aInheritFrom)->OriginAttributesRef(), false); +} + +/* static */ +already_AddRefed NullPrincipal::CreateWithInheritedAttributes( + nsIDocShell* aDocShell, bool aIsFirstParty) { + MOZ_ASSERT(aDocShell); + + OriginAttributes attrs = nsDocShell::Cast(aDocShell)->GetOriginAttributes(); + return CreateWithInheritedAttributes(attrs, aIsFirstParty); +} + +/* static */ +already_AddRefed NullPrincipal::CreateWithInheritedAttributes( + const OriginAttributes& aOriginAttributes, bool aIsFirstParty) { + RefPtr nullPrin = new NullPrincipal(); + nsresult rv = nullPrin->Init(aOriginAttributes, aIsFirstParty); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + + return nullPrin.forget(); +} + +/* static */ +already_AddRefed NullPrincipal::Create( + const OriginAttributes& aOriginAttributes, nsIURI* aURI) { + RefPtr nullPrin = new NullPrincipal(); + nsresult rv = nullPrin->Init(aOriginAttributes, aURI); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + + return nullPrin.forget(); +} + +/* static */ +already_AddRefed NullPrincipal::CreateWithoutOriginAttributes() { + return NullPrincipal::Create(OriginAttributes(), nullptr); +} + +nsresult NullPrincipal::Init(const OriginAttributes& aOriginAttributes, + nsIURI* aURI) { + if (aURI) { + nsAutoCString scheme; + nsresult rv = aURI->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(scheme.EqualsLiteral(NS_NULLPRINCIPAL_SCHEME), + NS_ERROR_NOT_AVAILABLE); + + mURI = aURI; + } else { + mURI = new NullPrincipalURI(); + } + + nsAutoCString originNoSuffix; + DebugOnly rv = mURI->GetSpec(originNoSuffix); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + FinishInit(originNoSuffix, aOriginAttributes); + + return NS_OK; +} + +nsresult NullPrincipal::Init(const OriginAttributes& aOriginAttributes, + bool aIsFirstParty, nsIURI* aURI) { + if (aURI) { + nsAutoCString scheme; + nsresult rv = aURI->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(scheme.EqualsLiteral(NS_NULLPRINCIPAL_SCHEME), + NS_ERROR_NOT_AVAILABLE); + + mURI = aURI; + } else { + mURI = new NullPrincipalURI(); + } + + nsAutoCString originNoSuffix; + DebugOnly rv = mURI->GetSpec(originNoSuffix); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + nsAutoCString path; + rv = mURI->GetPathQueryRef(path); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + OriginAttributes attrs(aOriginAttributes); + if (aIsFirstParty) { + // remove the '{}' characters from both ends. + path.Mid(path, 1, path.Length() - 2); + path.AppendLiteral(".mozilla"); + attrs.SetFirstPartyDomain(true, path); + } + + FinishInit(originNoSuffix, attrs); + + return NS_OK; +} + +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 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 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 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::Read(nsIObjectInputStream* aStream) { + // Note - NullPrincipal use NS_GENERIC_FACTORY_CONSTRUCTOR_INIT, which means + // that the Init() method has already been invoked by the time we deserialize. + // This is in contrast to ContentPrincipal, which uses + // NS_GENERIC_FACTORY_CONSTRUCTOR, in which case ::Read needs to invoke + // Init(). + + nsAutoCString spec; + nsresult rv = aStream->ReadCString(spec); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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); + + return Init(attrs, uri); +} + +NS_IMETHODIMP +NullPrincipal::Write(nsIObjectOutputStream* aStream) { + // Read is used still for legacy principals + MOZ_RELEASE_ASSERT(false, "Old style serialization is removed"); + return NS_OK; +} + +nsresult NullPrincipal::PopulateJSONObject(Json::Value& aObject) { + nsAutoCString principalURI; + nsresult rv = mURI->GetSpec(principalURI); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_ASSERT(principalURI.Length() == + nsLiteralCString(NS_NULLPRINCIPAL_SCHEME ":").Length() + + NSID_LENGTH - 1, + "Length of the URI should be: (scheme, uuid, - nullptr)"); + aObject[std::to_string(eSpec)] = principalURI.get(); + + nsAutoCString suffix; + OriginAttributesRef().CreateSuffix(suffix); + if (suffix.Length() > 0) { + aObject[std::to_string(eSuffix)] = suffix.get(); + } + + return NS_OK; +} + +already_AddRefed NullPrincipal::FromProperties( + nsTArray& aFields) { + MOZ_ASSERT(aFields.Length() == eMax + 1, "Must have all the keys"); + nsresult rv; + nsCOMPtr 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; + } + + RefPtr nullPrincipal = new NullPrincipal(); + rv = nullPrincipal->Init(attrs, uri); + if (NS_FAILED(rv)) { + return nullptr; + } + return nullPrincipal.forget(); +} diff --git a/caps/NullPrincipal.h b/caps/NullPrincipal.h new file mode 100644 index 0000000000..8e5a164be8 --- /dev/null +++ b/caps/NullPrincipal.h @@ -0,0 +1,123 @@ +/* -*- 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_CONTRACTID "@mozilla.org/nullprincipal;1" + +#define NS_NULLPRINCIPAL_SCHEME "moz-nullprincipal" + +namespace mozilla { + +class NullPrincipal final : public BasePrincipal { + public: + // This should only be used by deserialization, and the factory constructor. + // Other consumers should use the Create and CreateWithInheritedAttributes + // methods. + NullPrincipal() : BasePrincipal(eNullPrincipal) {} + + static PrincipalKind Kind() { return eNullPrincipal; } + + NS_DECL_NSISERIALIZABLE + + 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; + + static already_AddRefed CreateWithInheritedAttributes( + nsIPrincipal* aInheritFrom); + + // Create NullPrincipal with origin attributes from docshell. + // If aIsFirstParty is true, and the pref 'privacy.firstparty.isolate' is also + // enabled, the mFirstPartyDomain value of the origin attributes will be set + // to an unique value. + static already_AddRefed CreateWithInheritedAttributes( + nsIDocShell* aDocShell, bool aIsFirstParty = false); + static already_AddRefed CreateWithInheritedAttributes( + const OriginAttributes& aOriginAttributes, bool aIsFirstParty = false); + + static already_AddRefed Create( + const OriginAttributes& aOriginAttributes, nsIURI* aURI = nullptr); + + static already_AddRefed CreateWithoutOriginAttributes(); + + nsresult Init(const OriginAttributes& aOriginAttributes = OriginAttributes(), + nsIURI* aURI = 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 KeyVal; + + static already_AddRefed FromProperties( + nsTArray& aFields); + + protected: + virtual ~NullPrincipal() = default; + + bool SubsumesInternal(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration) override { + MOZ_ASSERT(aOther); + return FastEquals(aOther); + } + + bool MayLoadInternal(nsIURI* aURI) override; + + nsCOMPtr mURI; + + private: + FRIEND_TEST(OriginAttributes, NullPrincipal); + + // If aIsFirstParty is true, this NullPrincipal will be initialized based on + // the aOriginAttributes with FirstPartyDomain set to a unique value. + // This value is generated from mURI.path, with ".mozilla" appended at the + // end. aURI is used for testing purpose to assign specific UUID rather than + // random generated one. + nsresult Init(const OriginAttributes& aOriginAttributes, bool aIsFirstParty, + nsIURI* aURI = nullptr); +}; + +} // namespace mozilla + +#endif // mozilla_NullPrincipal_h diff --git a/caps/NullPrincipalURI.cpp b/caps/NullPrincipalURI.cpp new file mode 100644 index 0000000000..02176f1ea3 --- /dev/null +++ b/caps/NullPrincipalURI.cpp @@ -0,0 +1,302 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * 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 "NullPrincipalURI.h" + +#include "mozilla/DebugOnly.h" +#include "mozilla/MemoryReporting.h" + +#include "mozilla/ipc/URIParams.h" + +#include "nsEscape.h" +#include "nsCRT.h" + +#include "mozilla/GkRustUtils.h" + +using namespace mozilla; + +//////////////////////////////////////////////////////////////////////////////// +//// NullPrincipalURI + +NullPrincipalURI::NullPrincipalURI() { + GkRustUtils::GenerateUUID(mPath); + MOZ_ASSERT(mPath.Length() == NSID_LENGTH - 1); + MOZ_ASSERT(strlen(mPath.get()) == NSID_LENGTH - 1); +} + +NullPrincipalURI::NullPrincipalURI(const NullPrincipalURI& aOther) { + mPath.Assign(aOther.mPath); +} + +static NS_DEFINE_CID(kNullPrincipalURIImplementationCID, + NS_NULLPRINCIPALURI_IMPLEMENTATION_CID); + +NS_IMPL_ADDREF(NullPrincipalURI) +NS_IMPL_RELEASE(NullPrincipalURI) + +NS_INTERFACE_MAP_BEGIN(NullPrincipalURI) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURI) + if (aIID.Equals(kNullPrincipalURIImplementationCID)) + foundInterface = static_cast(this); + else + NS_INTERFACE_MAP_ENTRY(nsIURI) + NS_INTERFACE_MAP_ENTRY(nsISizeOf) +NS_INTERFACE_MAP_END + +//////////////////////////////////////////////////////////////////////////////// +//// nsIURI + +NS_IMETHODIMP +NullPrincipalURI::GetAsciiHost(nsACString& _host) { + _host.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +NullPrincipalURI::GetAsciiHostPort(nsACString& _hostport) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NullPrincipalURI::GetAsciiSpec(nsACString& _spec) { + nsAutoCString buffer; + // Ignore the return value -- NullPrincipalURI::GetSpec() is infallible. + Unused << GetSpec(buffer); + // This uses the infallible version of |NS_EscapeURL| as |GetSpec| is + // already infallible. + NS_EscapeURL(buffer, esc_OnlyNonASCII | esc_AlwaysCopy, _spec); + return NS_OK; +} + +NS_IMETHODIMP +NullPrincipalURI::GetHost(nsACString& _host) { + _host.Truncate(); + return NS_OK; +} + +nsresult NullPrincipalURI::SetHost(const nsACString& aHost) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NullPrincipalURI::GetHostPort(nsACString& _host) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult NullPrincipalURI::SetHostPort(const nsACString& aHost) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NullPrincipalURI::GetPassword(nsACString& _password) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult NullPrincipalURI::SetPassword(const nsACString& aPassword) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NullPrincipalURI::GetPathQueryRef(nsACString& _path) { + _path = mPath; + return NS_OK; +} + +nsresult NullPrincipalURI::SetPathQueryRef(const nsACString& aPath) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NullPrincipalURI::GetFilePath(nsACString& aFilePath) { + aFilePath.Truncate(); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult NullPrincipalURI::SetFilePath(const nsACString& aFilePath) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NullPrincipalURI::GetQuery(nsACString& aQuery) { + aQuery.Truncate(); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult NullPrincipalURI::SetQuery(const nsACString& aQuery) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult NullPrincipalURI::SetQueryWithEncoding(const nsACString& aQuery, + const Encoding* aEncoding) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NullPrincipalURI::GetRef(nsACString& _ref) { + _ref.Truncate(); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult NullPrincipalURI::SetRef(const nsACString& aRef) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NullPrincipalURI::GetPrePath(nsACString& _prePath) { + _prePath = nsLiteralCString(NS_NULLPRINCIPAL_SCHEME ":"); + return NS_OK; +} + +NS_IMETHODIMP +NullPrincipalURI::GetPort(int32_t* _port) { return NS_ERROR_NOT_IMPLEMENTED; } + +nsresult NullPrincipalURI::SetPort(int32_t aPort) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NullPrincipalURI::GetScheme(nsACString& _scheme) { + _scheme = nsLiteralCString(NS_NULLPRINCIPAL_SCHEME); + return NS_OK; +} + +nsresult NullPrincipalURI::SetScheme(const nsACString& aScheme) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NullPrincipalURI::GetSpec(nsACString& _spec) { + _spec = nsLiteralCString(NS_NULLPRINCIPAL_SCHEME ":") + mPath; + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +NullPrincipalURI::GetSpecIgnoringRef(nsACString& _result) { + return GetSpec(_result); +} + +NS_IMETHODIMP +NullPrincipalURI::GetHasRef(bool* _result) { + *_result = false; + return NS_OK; +} + +nsresult NullPrincipalURI::SetSpecInternal(const nsACString& aSpec) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NullPrincipalURI::GetUsername(nsACString& _username) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult NullPrincipalURI::SetUsername(const nsACString& aUsername) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NullPrincipalURI::GetUserPass(nsACString& _userPass) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult NullPrincipalURI::SetUserPass(const nsACString& aUserPass) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult NullPrincipalURI::Clone(nsIURI** _newURI) { + nsCOMPtr uri = new NullPrincipalURI(*this); + uri.forget(_newURI); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(NullPrincipalURI::Mutator, nsIURISetters, nsIURIMutator) + +NS_IMETHODIMP +NullPrincipalURI::Mutate(nsIURIMutator** aMutator) { + RefPtr mutator = new NullPrincipalURI::Mutator(); + nsresult rv = mutator->InitFromURI(this); + if (NS_FAILED(rv)) { + return rv; + } + mutator.forget(aMutator); + return NS_OK; +} + +NS_IMETHODIMP +NullPrincipalURI::Equals(nsIURI* aOther, bool* _equals) { + *_equals = false; + RefPtr otherURI; + nsresult rv = aOther->QueryInterface(kNullPrincipalURIImplementationCID, + getter_AddRefs(otherURI)); + if (NS_SUCCEEDED(rv)) { + *_equals = mPath == otherURI->mPath; + } + return NS_OK; +} + +NS_IMETHODIMP +NullPrincipalURI::EqualsExceptRef(nsIURI* aOther, bool* _equals) { + // GetRef/SetRef not supported by NullPrincipalURI, so + // EqualsExceptRef() is the same as Equals(). + return Equals(aOther, _equals); +} + +NS_IMETHODIMP +NullPrincipalURI::Resolve(const nsACString& aRelativePath, + nsACString& _resolvedURI) { + _resolvedURI = aRelativePath; + return NS_OK; +} + +NS_IMETHODIMP +NullPrincipalURI::SchemeIs(const char* aScheme, bool* _schemeIs) { + *_schemeIs = (0 == nsCRT::strcasecmp(NS_NULLPRINCIPAL_SCHEME, aScheme)); + return NS_OK; +} + +NS_IMETHODIMP +NullPrincipalURI::GetDisplaySpec(nsACString& aUnicodeSpec) { + return GetSpec(aUnicodeSpec); +} + +NS_IMETHODIMP +NullPrincipalURI::GetDisplayHostPort(nsACString& aUnicodeHostPort) { + return GetHostPort(aUnicodeHostPort); +} + +NS_IMETHODIMP +NullPrincipalURI::GetDisplayHost(nsACString& aUnicodeHost) { + return GetHost(aUnicodeHost); +} + +NS_IMETHODIMP +NullPrincipalURI::GetDisplayPrePath(nsACString& aPrePath) { + return GetPrePath(aPrePath); +} + +void NullPrincipalURI::Serialize(mozilla::ipc::URIParams& aParams) { + aParams = mozilla::ipc::NullPrincipalURIParams(); +} + +bool NullPrincipalURI::Deserialize(const mozilla::ipc::URIParams& aParams) { + if (aParams.type() != mozilla::ipc::URIParams::TNullPrincipalURIParams) { + MOZ_ASSERT_UNREACHABLE("unexpected URIParams type"); + return false; + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//// nsISizeOf + +size_t NullPrincipalURI::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +size_t NullPrincipalURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} diff --git a/caps/NullPrincipalURI.h b/caps/NullPrincipalURI.h new file mode 100644 index 0000000000..905f233ff2 --- /dev/null +++ b/caps/NullPrincipalURI.h @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * 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 wraps nsSimpleURI so that all calls to it are done on the main thread. + */ + +#ifndef mozilla_NullPrincipalURI_h +#define mozilla_NullPrincipalURI_h + +#include "nsIURI.h" +#include "nsISizeOf.h" +#include "nsString.h" +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" +#include "NullPrincipal.h" +#include "nsID.h" +#include "nsIURIMutator.h" + +// {51fcd543-3b52-41f7-b91b-6b54102236e6} +#define NS_NULLPRINCIPALURI_IMPLEMENTATION_CID \ + { \ + 0x51fcd543, 0x3b52, 0x41f7, { \ + 0xb9, 0x1b, 0x6b, 0x54, 0x10, 0x22, 0x36, 0xe6 \ + } \ + } + +namespace mozilla { + +class Encoding; + +class NullPrincipalURI final : public nsIURI, public nsISizeOf { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURI + + NullPrincipalURI(); + + // nsISizeOf + virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override; + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override; + + private: + NullPrincipalURI(const NullPrincipalURI& aOther); + void operator=(const NullPrincipalURI& aOther) = delete; + + ~NullPrincipalURI() {} + + nsAutoCStringN mPath; + + nsresult Clone(nsIURI** aURI); + nsresult SetSpecInternal(const nsACString& input); + nsresult SetScheme(const nsACString& input); + nsresult SetUserPass(const nsACString& input); + nsresult SetUsername(const nsACString& input); + nsresult SetPassword(const nsACString& input); + nsresult SetHostPort(const nsACString& aValue); + nsresult SetHost(const nsACString& input); + nsresult SetPort(int32_t port); + nsresult SetPathQueryRef(const nsACString& input); + nsresult SetRef(const nsACString& input); + nsresult SetFilePath(const nsACString& input); + nsresult SetQuery(const nsACString& input); + nsresult SetQueryWithEncoding(const nsACString& input, + const Encoding* encoding); + bool Deserialize(const mozilla::ipc::URIParams&); + + public: + class Mutator final : public nsIURIMutator, + public BaseURIMutator { + NS_DECL_ISUPPORTS + NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI) + + NS_IMETHOD Deserialize(const mozilla::ipc::URIParams& aParams) override { + return InitFromIPCParams(aParams); + } + + NS_IMETHOD Finalize(nsIURI** aURI) override { + mURI.forget(aURI); + return NS_OK; + } + + NS_IMETHOD SetSpec(const nsACString& aSpec, + nsIURIMutator** aMutator) override { + if (aMutator) { + nsCOMPtr mutator = this; + mutator.forget(aMutator); + } + return NS_ERROR_NOT_IMPLEMENTED; + } + + explicit Mutator() {} + + private: + virtual ~Mutator() {} + + friend class NullPrincipalURI; + }; + + friend class BaseURIMutator; +}; + +} // namespace mozilla + +#endif // mozilla_NullPrincipalURI_h diff --git a/caps/OriginAttributes.cpp b/caps/OriginAttributes.cpp new file mode 100644 index 0000000000..3e30e1d4d1 --- /dev/null +++ b/caps/OriginAttributes.cpp @@ -0,0 +1,382 @@ +/* -*- 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/Preferences.h" +#include "mozilla/dom/BlobURLProtocolHandler.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "nsIEffectiveTLDService.h" +#include "nsIURI.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; + rv = aURI->GetScheme(scheme); + NS_ENSURE_SUCCESS_VOID(rv); + + if (scheme.EqualsLiteral("about")) { + MakeTopLevelInfo(scheme, nsLiteralCString(ABOUT_URI_FIRST_PARTY_DOMAIN), + aUseSite, topLevelInfo); + 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 blobPrincipal; + if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal( + aURI, getter_AddRefs(blobPrincipal))) { + MOZ_ASSERT(blobPrincipal); + topLevelInfo = blobPrincipal->OriginAttributesRef().*aTarget; + return; + } + + nsCOMPtr tldService = + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + MOZ_ASSERT(tldService); + NS_ENSURE_TRUE_VOID(tldService); + + nsAutoCString baseDomain; + rv = tldService->GetBaseDomain(aURI, 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 = aURI->GetPort(&port); + NS_ENSURE_SUCCESS_VOID(rv); + + nsAutoCString host; + rv = aURI->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(aURI, 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::kReplaceChars, 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); + 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 +} + +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; + + 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(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(val); + + return true; + } + + if (aName.EqualsLiteral("firstPartyDomain")) { + MOZ_RELEASE_ASSERT(mFirstPartyDomain.IsEmpty()); + nsAutoString firstPartyDomain(aValue); + firstPartyDomain.ReplaceChar(kSanitizedChar, kSourceChar); + mFirstPartyDomain.Assign(firstPartyDomain); + return true; + } + + if (aName.EqualsLiteral("geckoViewUserContextId")) { + MOZ_RELEASE_ASSERT(mGeckoViewSessionContextId.IsEmpty()); + mGeckoViewSessionContextId.Assign(aValue); + return true; + } + + if (aName.EqualsLiteral("partitionKey")) { + MOZ_RELEASE_ASSERT(mPartitionKey.IsEmpty()); + 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; +} + +} // namespace mozilla diff --git a/caps/OriginAttributes.h b/caps/OriginAttributes.h new file mode 100644 index 0000000000..00ec2b1b42 --- /dev/null +++ b/caps/OriginAttributes.h @@ -0,0 +1,227 @@ +/* -*- 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; + } + + // 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; + + // 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(); + } + + static inline bool UseSiteForFirstPartyDomain() { + if (IsFirstPartyEnabled()) { + return StaticPrefs::privacy_firstparty_isolate_use_site(); + } + return StaticPrefs::privacy_dynamic_firstparty_use_site(); + } + + // 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); +}; + +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 (mPartitionKey.WasPassed() && + mPartitionKey.Value() != aAttrs.mPartitionKey) { + 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; + } + + 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(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(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 mPrincipal; +}; + +} // namespace mozilla + +#endif diff --git a/caps/SystemPrincipal.cpp b/caps/SystemPrincipal.cpp new file mode 100644 index 0000000000..ddfb5c5239 --- /dev/null +++ b/caps/SystemPrincipal.cpp @@ -0,0 +1,92 @@ +/* -*- 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 "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 | nsIClassInfo::MAIN_THREAD_ONLY, + NS_SYSTEMPRINCIPAL_CID) +NS_IMPL_QUERY_INTERFACE_CI(SystemPrincipal, nsIPrincipal, nsISerializable) +NS_IMPL_CI_INTERFACE_GETTER(SystemPrincipal, nsIPrincipal, nsISerializable) + +#define SYSTEM_PRINCIPAL_SPEC "[System Principal]" + +already_AddRefed SystemPrincipal::Create() { + RefPtr sp = new SystemPrincipal(); + sp->FinishInit(nsLiteralCString(SYSTEM_PRINCIPAL_SPEC), OriginAttributes()); + return sp.forget(); +} + +nsresult SystemPrincipal::GetScriptLocation(nsACString& aStr) { + aStr.AssignLiteral(SYSTEM_PRINCIPAL_SPEC); + 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..4853632f34 --- /dev/null +++ b/caps/SystemPrincipal.h @@ -0,0 +1,69 @@ +/* -*- 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" + +namespace Json { +class Value; +} + +namespace mozilla { + +class SystemPrincipal final : public BasePrincipal { + SystemPrincipal() : BasePrincipal(eSystemPrincipal) {} + + public: + static already_AddRefed Create(); + + static PrincipalKind Kind() { return eSystemPrincipal; } + + 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: + virtual ~SystemPrincipal(void) {} + + 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..e35debe6da --- /dev/null +++ b/caps/moz.build @@ -0,0 +1,81 @@ +# -*- 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", + "ExpandedPrincipal.h", + "NullPrincipal.h", + "NullPrincipalURI.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", + "NullPrincipalURI.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..6cabefcd6b --- /dev/null +++ b/caps/nsIAddonPolicyService.idl @@ -0,0 +1,95 @@ +/* -*- 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; + + /** + * 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 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. + */ + boolean extensionURILoadableByAnyone(in nsIURI aURI); + + /** + * 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_LOCALHOST 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); + + /** + * 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..51a8963960 --- /dev/null +++ b/caps/nsIPrincipal.idl @@ -0,0 +1,597 @@ +/* -*- 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 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; + +[ptr] native JSContext(JSContext); +[ptr] native JSPrincipals(JSPrincipals); +[ref] native PrincipalArray(const nsTArray>); +[ref] native const_OriginAttributes(const mozilla::OriginAttributes); +native ReferrerPolicy(mozilla::dom::ReferrerPolicy); + +[scriptable, builtinclass, uuid(f75f502d-79fd-48be-a079-e5a7b8f80c8b)] +interface nsIPrincipal : nsISerializable +{ + /** + * 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. + */ + boolean equals(in nsIPrincipal other); + + /** + * Returns whether the other principal is equivalent to this principal + * for permission purposes + * Matches {originAttributes ,equalsURIForPermission} + */ + + boolean equalsForPermission(in nsIPrincipal other, in bool aExactHost); + + /** + * Like equals, but takes document.domain changes into account. + */ + 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 + */ + boolean equalsURI(in nsIURI aOtherURI); + + /** + * Returns a hash value for the principal. + */ + [notxpcom, nostdcall] readonly attribute unsigned long hashValue; + + /** + * The principal URI to which this principal pertains. This is + * generally the document URI. + */ + [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 + */ + [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. + */ + boolean subsumes(in nsIPrincipal other); + + /** + * Same as the previous method, subsumes(), but takes document.domain into + * account. + */ + boolean subsumesConsideringDomain(in nsIPrincipal other); + + /** + * Same as the subsumesConsideringDomain(), but ignores the first party + * domain in its originAttributes. + */ + 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. + * + * + * @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. + */ + 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. + * + * @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. + * + * @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 + * + * @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; + + [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. + */ + readonly attribute ACString origin; + + /** + * Returns an ASCII compatible representation + * of the principals Origin + */ + [noscript] readonly attribute ACString asciiOrigin; + + /** + * Returns the "host:port" portion of the + * Principals URI, if any. + */ + readonly attribute ACString hostPort; + + /** + * Returns the "host:port" portion of the + * Principals URI, if any. + */ + readonly attribute ACString asciiHost; + + /** + * Returns the "host" portion of the + * Principals URI, if any. + */ + readonly attribute ACString host; + + /** + * Returns the prepath of the principals uri + * follows the format scheme: + * "scheme://username:password@hostname:portnumber/" + */ + readonly attribute ACString prepath; + + /** + * Returns the filePath of the principals uri. See nsIURI. + */ + 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. + * + * 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. + * + * 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 + */ + readonly attribute ACString exposablePrePath; + + /* Returns the Spec of the Principals URI with + * user/pass/ref/query stripped for privacy and spoof prevention + */ + readonly attribute ACString exposableSpec; + + /** + * Return the scheme of the principals URI + */ + readonly attribute ACString scheme; + + /** + * Checks if the Principal's URI Scheme matches with the parameter + * + * @param scheme The scheme to be checked + */ + boolean schemeIs(in string scheme); + + // Nicer, C++ Callable Version of SchemeIs + %{C++ + inline bool SchemeIs(const char* aScheme) { + bool ret; + SchemeIs(aScheme, &ret); + return ret; + } + %} + + /* + * Checks if the Principal's URI is contained in the given Pref + * @param pref The pref to be checked + */ + bool isURIInPrefList(in string pref); + + /* + * Uses NS_Security Compare to determine if the + * other URI is same-origin as the uri of the Principal + */ + bool isSameOrigin(in nsIURI otherURI, in bool aIsPrivateWin); + + /* + * Checks if the Principal is allowed to load the Provided file:// URI + * using NS_RelaxStrictFileOriginPolicy + */ + bool allowsRelaxStrictFileOriginPolicy(in nsIURI aURI); + + + /* + * Generates a Cache-Key for the Cors-Preflight Cache + */ + [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 + */ + bool hasFirstpartyStorageAccess(in mozIDOMWindow aWindow, out uint32_t rejectedReason); + + + /* + * Returns a Key for the LocalStorage Manager, used to + * check the Principals Origin Storage usage. + */ + 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. + */ + [infallible] + readonly attribute boolean isOriginPotentiallyTrustworthy; + + /** + * Returns the Flags of the Principals + * associated AboutModule, in case there is one. + */ + uint32_t getAboutModuleFlags(); + + /* + * Returns the Key to access the Principals + * Origin Local/Session Storage + */ + readonly attribute ACString storageOriginKey; + + /** + * Creates and Returns a new ReferrerInfo with the + * Principals URI + */ + 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. + */ + 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. + */ + 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. + */ + readonly attribute ACString siteOrigin; + + /** + * The base part of |siteOrigin| without the concatenation with + * |originSuffix|. + */ + 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. + */ + readonly attribute ACString baseDomain; + + /** + * Gets the ID of the add-on this principal belongs to. + */ + readonly attribute AString addonId; + + readonly attribute nsISupports addonPolicy; + + /** + * 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. + */ + [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. + */ + [infallible] readonly attribute unsigned long privateBrowsingId; + + /** + * Returns true iff the principal is inside an isolated mozbrowser element. + * is not considered to be a mozbrowser element. + * +
+
+
+ + diff --git a/caps/tests/mochitest/test_bug246699.html b/caps/tests/mochitest/test_bug246699.html new file mode 100644 index 0000000000..13c92e3743 --- /dev/null +++ b/caps/tests/mochitest/test_bug246699.html @@ -0,0 +1,60 @@ + + + + + Test for Bug 246699 + + + + +Mozilla Bug 246699 +

+ +
+
+
+ + diff --git a/caps/tests/mochitest/test_bug292789.html b/caps/tests/mochitest/test_bug292789.html new file mode 100644 index 0000000000..855ca3bce2 --- /dev/null +++ b/caps/tests/mochitest/test_bug292789.html @@ -0,0 +1,116 @@ + + + + + Test for Bug 292789 + + + + +Mozilla Bug 292789 +

+ +
+
+
+ + diff --git a/caps/tests/mochitest/test_bug423375.html b/caps/tests/mochitest/test_bug423375.html new file mode 100644 index 0000000000..3d73b0b874 --- /dev/null +++ b/caps/tests/mochitest/test_bug423375.html @@ -0,0 +1,43 @@ + + + + + Test for Bug 423375 + + + + +Mozilla Bug 423375 +

+ +
+
+
+ + diff --git a/caps/tests/mochitest/test_bug470804.html b/caps/tests/mochitest/test_bug470804.html new file mode 100644 index 0000000000..a2d6c7e002 --- /dev/null +++ b/caps/tests/mochitest/test_bug470804.html @@ -0,0 +1,41 @@ + + + + + Test for Bug 470804 + + + + +Mozilla Bug 470804 +

+ +
+
+
+ + diff --git a/caps/tests/mochitest/test_bug995943.xhtml b/caps/tests/mochitest/test_bug995943.xhtml new file mode 100644 index 0000000000..fba1596ac9 --- /dev/null +++ b/caps/tests/mochitest/test_bug995943.xhtml @@ -0,0 +1,112 @@ + + + + + + +