diff options
Diffstat (limited to 'caps')
42 files changed, 8299 insertions, 0 deletions
diff --git a/caps/BasePrincipal.cpp b/caps/BasePrincipal.cpp new file mode 100644 index 000000000..b768d5c1f --- /dev/null +++ b/caps/BasePrincipal.cpp @@ -0,0 +1,777 @@ +/* -*- 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" +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif +#include "nsIAddonPolicyService.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIEffectiveTLDService.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" + +#include "nsPrincipal.h" +#include "nsNetUtil.h" +#include "nsIURIWithPrincipal.h" +#include "nsNullPrincipal.h" +#include "nsScriptSecurityManager.h" +#include "nsServiceManagerUtils.h" + +#include "mozilla/dom/ChromeUtils.h" +#include "mozilla/dom/CSPDictionariesBinding.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/dom/URLSearchParams.h" + +namespace mozilla { + +using dom::URLParams; + +void +PrincipalOriginAttributes::InheritFromDocShellToDoc(const DocShellOriginAttributes& aAttrs, + const nsIURI* aURI) +{ + mAppId = aAttrs.mAppId; + mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser; + + // addonId is computed from the principal URI and never propagated + mUserContextId = aAttrs.mUserContextId; + + mPrivateBrowsingId = aAttrs.mPrivateBrowsingId; + mFirstPartyDomain = aAttrs.mFirstPartyDomain; +} + +void +PrincipalOriginAttributes::InheritFromNecko(const NeckoOriginAttributes& aAttrs) +{ + mAppId = aAttrs.mAppId; + mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser; + + // addonId is computed from the principal URI and never propagated + mUserContextId = aAttrs.mUserContextId; + + mPrivateBrowsingId = aAttrs.mPrivateBrowsingId; + mFirstPartyDomain = aAttrs.mFirstPartyDomain; +} + +void +PrincipalOriginAttributes::StripUserContextIdAndFirstPartyDomain() +{ + mUserContextId = nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID; + mFirstPartyDomain.Truncate(); +} + +void +DocShellOriginAttributes::InheritFromDocToChildDocShell(const PrincipalOriginAttributes& aAttrs) +{ + mAppId = aAttrs.mAppId; + mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser; + + // addonId is computed from the principal URI and never propagated + mUserContextId = aAttrs.mUserContextId; + + mPrivateBrowsingId = aAttrs.mPrivateBrowsingId; + mFirstPartyDomain = aAttrs.mFirstPartyDomain; +} + +void +NeckoOriginAttributes::InheritFromDocToNecko(const PrincipalOriginAttributes& aAttrs) +{ + mAppId = aAttrs.mAppId; + mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser; + + // addonId is computed from the principal URI and never propagated + mUserContextId = aAttrs.mUserContextId; + + mPrivateBrowsingId = aAttrs.mPrivateBrowsingId; + mFirstPartyDomain = aAttrs.mFirstPartyDomain; +} + +void +NeckoOriginAttributes::InheritFromDocShellToNecko(const DocShellOriginAttributes& aAttrs, + const bool aIsTopLevelDocument, + nsIURI* aURI) +{ + mAppId = aAttrs.mAppId; + mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser; + + // addonId is computed from the principal URI and never propagated + mUserContextId = aAttrs.mUserContextId; + + mPrivateBrowsingId = aAttrs.mPrivateBrowsingId; + + bool isFirstPartyEnabled = IsFirstPartyEnabled(); + + // When the pref is on, we also compute the firstPartyDomain attribute + // if this is for top-level document. + if (isFirstPartyEnabled && aIsTopLevelDocument) { + nsCOMPtr<nsIEffectiveTLDService> tldService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + MOZ_ASSERT(tldService); + if (!tldService) { + return; + } + + nsAutoCString baseDomain; + tldService->GetBaseDomain(aURI, 0, baseDomain); + mFirstPartyDomain = NS_ConvertUTF8toUTF16(baseDomain); + } else { + mFirstPartyDomain = aAttrs.mFirstPartyDomain; + } +} + +void +OriginAttributes::CreateSuffix(nsACString& aStr) const +{ + UniquePtr<URLParams> params(new URLParams()); + 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 (see addonId below). + // + + if (mAppId != nsIScriptSecurityManager::NO_APP_ID) { + value.AppendInt(mAppId); + params->Set(NS_LITERAL_STRING("appId"), value); + } + + if (mInIsolatedMozBrowser) { + params->Set(NS_LITERAL_STRING("inBrowser"), NS_LITERAL_STRING("1")); + } + + if (!mAddonId.IsEmpty()) { + if (mAddonId.FindCharInSet(dom::quota::QuotaManager::kReplaceChars) != kNotFound) { +#ifdef MOZ_CRASHREPORTER + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Crash_AddonId"), + NS_ConvertUTF16toUTF8(mAddonId)); +#endif + MOZ_CRASH(); + } + params->Set(NS_LITERAL_STRING("addonId"), mAddonId); + } + + if (mUserContextId != nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) { + value.Truncate(); + value.AppendInt(mUserContextId); + params->Set(NS_LITERAL_STRING("userContextId"), value); + } + + + if (mPrivateBrowsingId) { + value.Truncate(); + value.AppendInt(mPrivateBrowsingId); + params->Set(NS_LITERAL_STRING("privateBrowsingId"), value); + } + + if (!mFirstPartyDomain.IsEmpty()) { + MOZ_RELEASE_ASSERT(mFirstPartyDomain.FindCharInSet(dom::quota::QuotaManager::kReplaceChars) == kNotFound); + params->Set(NS_LITERAL_STRING("firstPartyDomain"), mFirstPartyDomain); + } + + 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_"); + } + + attrs.CreateSuffix(aStr); +} + +namespace { + +class MOZ_STACK_CLASS PopulateFromSuffixIterator final + : public URLParams::ForEachIterator +{ +public: + explicit PopulateFromSuffixIterator(OriginAttributes* aOriginAttributes) + : mOriginAttributes(aOriginAttributes) + { + MOZ_ASSERT(aOriginAttributes); + // If mPrivateBrowsingId is passed in as >0 and is not present in the suffix, + // then it will remain >0 when it should be 0 according to the suffix. Set to 0 before + // iterating to fix this. + mOriginAttributes->mPrivateBrowsingId = 0; + } + + bool URLParamsIterator(const nsString& aName, + const nsString& aValue) override + { + if (aName.EqualsLiteral("appId")) { + nsresult rv; + int64_t val = aValue.ToInteger64(&rv); + NS_ENSURE_SUCCESS(rv, false); + NS_ENSURE_TRUE(val <= UINT32_MAX, false); + mOriginAttributes->mAppId = static_cast<uint32_t>(val); + + return true; + } + + if (aName.EqualsLiteral("inBrowser")) { + if (!aValue.EqualsLiteral("1")) { + return false; + } + + mOriginAttributes->mInIsolatedMozBrowser = true; + return true; + } + + if (aName.EqualsLiteral("addonId")) { + MOZ_RELEASE_ASSERT(mOriginAttributes->mAddonId.IsEmpty()); + mOriginAttributes->mAddonId.Assign(aValue); + 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); + mOriginAttributes->mUserContextId = static_cast<uint32_t>(val); + + return true; + } + + if (aName.EqualsLiteral("privateBrowsingId")) { + nsresult rv; + int64_t val = aValue.ToInteger64(&rv); + NS_ENSURE_SUCCESS(rv, false); + NS_ENSURE_TRUE(val >= 0 && val <= UINT32_MAX, false); + mOriginAttributes->mPrivateBrowsingId = static_cast<uint32_t>(val); + + return true; + } + + if (aName.EqualsLiteral("firstPartyDomain")) { + MOZ_RELEASE_ASSERT(mOriginAttributes->mFirstPartyDomain.IsEmpty()); + mOriginAttributes->mFirstPartyDomain.Assign(aValue); + return true; + } + + // No other attributes are supported. + return false; + } + +private: + OriginAttributes* mOriginAttributes; +}; + +} // namespace + +bool +OriginAttributes::PopulateFromSuffix(const nsACString& aStr) +{ + if (aStr.IsEmpty()) { + return true; + } + + if (aStr[0] != '^') { + return false; + } + + UniquePtr<URLParams> params(new URLParams()); + params->ParseInput(Substring(aStr, 1, aStr.Length() - 1)); + + PopulateFromSuffixIterator iterator(this); + return params->ForEach(iterator); +} + +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; +} + +void +OriginAttributes::SetFromGenericAttributes(const GenericOriginAttributes& aAttrs) +{ + mAppId = aAttrs.mAppId; + mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser; + mAddonId = aAttrs.mAddonId; + mUserContextId = aAttrs.mUserContextId; + mPrivateBrowsingId = aAttrs.mPrivateBrowsingId; + mFirstPartyDomain = aAttrs.mFirstPartyDomain; +} + +/* static */ +bool +OriginAttributes::IsFirstPartyEnabled() +{ + // Cache the privacy.firstparty.isolate pref. + static bool sFirstPartyIsolation = false; + static bool sCachedFirstPartyPref = false; + if (!sCachedFirstPartyPref) { + sCachedFirstPartyPref = true; + Preferences::AddBoolVarCache(&sFirstPartyIsolation, "privacy.firstparty.isolate"); + } + + return sFirstPartyIsolation; +} + +BasePrincipal::BasePrincipal() +{} + +BasePrincipal::~BasePrincipal() +{} + +NS_IMETHODIMP +BasePrincipal::GetOrigin(nsACString& aOrigin) +{ + nsresult rv = GetOriginInternal(aOrigin); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString suffix; + mOriginAttributes.CreateSuffix(suffix); + aOrigin.Append(suffix); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetOriginNoSuffix(nsACString& aOrigin) +{ + return GetOriginInternal(aOrigin); +} + +bool +BasePrincipal::Subsumes(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) +{ + MOZ_ASSERT(aOther); + + // 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 codebase principals. + if (Kind() == eCodebasePrincipal && + OriginAttributesRef() != Cast(aOther)->OriginAttributesRef()) { + return false; + } + + return SubsumesInternal(aOther, aConsideration); +} + +NS_IMETHODIMP +BasePrincipal::Equals(nsIPrincipal *aOther, bool *aResult) +{ + NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG); + *aResult = Subsumes(aOther, DontConsiderDocumentDomain) && + Cast(aOther)->Subsumes(this, DontConsiderDocumentDomain); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::EqualsConsideringDomain(nsIPrincipal *aOther, bool *aResult) +{ + NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG); + *aResult = Subsumes(aOther, ConsiderDocumentDomain) && + Cast(aOther)->Subsumes(this, ConsiderDocumentDomain); + return NS_OK; +} + +bool +BasePrincipal::EqualsIgnoringAddonId(nsIPrincipal *aOther) +{ + MOZ_ASSERT(aOther); + + // Note that this will not work for expanded principals, nor is it intended + // to. + if (!dom::ChromeUtils::IsOriginAttributesEqualIgnoringAddonId( + OriginAttributesRef(), Cast(aOther)->OriginAttributesRef())) { + return false; + } + + return SubsumesInternal(aOther, DontConsiderDocumentDomain) && + Cast(aOther)->SubsumesInternal(this, DontConsiderDocumentDomain); +} + +NS_IMETHODIMP +BasePrincipal::Subsumes(nsIPrincipal *aOther, bool *aResult) +{ + NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG); + *aResult = Subsumes(aOther, DontConsiderDocumentDomain); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::SubsumesConsideringDomain(nsIPrincipal *aOther, bool *aResult) +{ + NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG); + *aResult = Subsumes(aOther, ConsiderDocumentDomain); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::CheckMayLoad(nsIURI* aURI, bool aReport, bool aAllowIfInheritsPrincipal) +{ + // 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<nsIURI> prinURI; + rv = GetURI(getter_AddRefs(prinURI)); + if (NS_SUCCEEDED(rv) && prinURI) { + nsScriptSecurityManager::ReportError(nullptr, NS_LITERAL_STRING("CheckSameOriginError"), prinURI, aURI); + } + } + + return NS_ERROR_DOM_BAD_URI; +} + +NS_IMETHODIMP +BasePrincipal::GetCsp(nsIContentSecurityPolicy** aCsp) +{ + NS_IF_ADDREF(*aCsp = mCSP); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::SetCsp(nsIContentSecurityPolicy* aCsp) +{ + // Never destroy an existing CSP on the principal. + // This method should only be called in rare cases. + + MOZ_ASSERT(!mCSP, "do not destroy an existing CSP"); + if (mCSP) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + mCSP = aCsp; + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::EnsureCSP(nsIDOMDocument* aDocument, + nsIContentSecurityPolicy** aCSP) +{ + if (mCSP) { + // if there is a CSP already associated with this principal + // then just return that - do not overwrite it!!! + NS_IF_ADDREF(*aCSP = mCSP); + return NS_OK; + } + + nsresult rv = NS_OK; + mCSP = do_CreateInstance("@mozilla.org/cspcontext;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Store the request context for violation reports + rv = aDocument ? mCSP->SetRequestContext(aDocument, nullptr) + : mCSP->SetRequestContext(nullptr, this); + NS_ENSURE_SUCCESS(rv, rv); + NS_IF_ADDREF(*aCSP = mCSP); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetPreloadCsp(nsIContentSecurityPolicy** aPreloadCSP) +{ + NS_IF_ADDREF(*aPreloadCSP = mPreloadCSP); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::EnsurePreloadCSP(nsIDOMDocument* aDocument, + nsIContentSecurityPolicy** aPreloadCSP) +{ + if (mPreloadCSP) { + // if there is a speculative CSP already associated with this principal + // then just return that - do not overwrite it!!! + NS_IF_ADDREF(*aPreloadCSP = mPreloadCSP); + return NS_OK; + } + + nsresult rv = NS_OK; + mPreloadCSP = do_CreateInstance("@mozilla.org/cspcontext;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Store the request context for violation reports + rv = aDocument ? mPreloadCSP->SetRequestContext(aDocument, nullptr) + : mPreloadCSP->SetRequestContext(nullptr, this); + NS_ENSURE_SUCCESS(rv, rv); + NS_IF_ADDREF(*aPreloadCSP = mPreloadCSP); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetCspJSON(nsAString& outCSPinJSON) +{ + outCSPinJSON.Truncate(); + dom::CSPPolicies jsonPolicies; + + if (!mCSP) { + jsonPolicies.ToJSON(outCSPinJSON); + return NS_OK; + } + return mCSP->ToJSON(outCSPinJSON); +} + +NS_IMETHODIMP +BasePrincipal::GetIsNullPrincipal(bool* aResult) +{ + *aResult = Kind() == eNullPrincipal; + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetIsCodebasePrincipal(bool* aResult) +{ + *aResult = Kind() == eCodebasePrincipal; + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetIsExpandedPrincipal(bool* aResult) +{ + *aResult = Kind() == eExpandedPrincipal; + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetIsSystemPrincipal(bool* aResult) +{ + *aResult = Kind() == eSystemPrincipal; + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetOriginAttributes(JSContext* aCx, JS::MutableHandle<JS::Value> aVal) +{ + if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetOriginSuffix(nsACString& aOriginAttributes) +{ + mOriginAttributes.CreateSuffix(aOriginAttributes); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetAppStatus(uint16_t* aAppStatus) +{ + if (AppId() == nsIScriptSecurityManager::UNKNOWN_APP_ID) { + NS_WARNING("Asking for app status on a principal with an unknown app id"); + *aAppStatus = nsIPrincipal::APP_STATUS_NOT_INSTALLED; + return NS_OK; + } + + *aAppStatus = nsScriptSecurityManager::AppStatusForPrincipal(this); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetAppId(uint32_t* aAppId) +{ + if (AppId() == nsIScriptSecurityManager::UNKNOWN_APP_ID) { + MOZ_ASSERT(false); + *aAppId = nsIScriptSecurityManager::NO_APP_ID; + return NS_OK; + } + + *aAppId = AppId(); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetAddonId(nsAString& aAddonId) +{ + aAddonId.Assign(mOriginAttributes.mAddonId); + 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; +} + +NS_IMETHODIMP +BasePrincipal::GetUnknownAppId(bool* aUnknownAppId) +{ + *aUnknownAppId = AppId() == nsIScriptSecurityManager::UNKNOWN_APP_ID; + return NS_OK; +} + +bool +BasePrincipal::AddonHasPermission(const nsAString& aPerm) +{ + if (mOriginAttributes.mAddonId.IsEmpty()) { + return false; + } + nsCOMPtr<nsIAddonPolicyService> aps = + do_GetService("@mozilla.org/addons/policy-service;1"); + NS_ENSURE_TRUE(aps, false); + + bool retval = false; + nsresult rv = aps->AddonHasPermission(mOriginAttributes.mAddonId, aPerm, &retval); + NS_ENSURE_SUCCESS(rv, false); + return retval; +} + +already_AddRefed<BasePrincipal> +BasePrincipal::CreateCodebasePrincipal(nsIURI* aURI, const PrincipalOriginAttributes& aAttrs) +{ + // If the URI is supposed to inherit the security context of whoever loads it, + // we shouldn't make a codebase principal for it. + bool inheritsPrincipal; + nsresult rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, + &inheritsPrincipal); + if (NS_FAILED(rv) || inheritsPrincipal) { + return nsNullPrincipal::Create(aAttrs); + } + + // Check whether the URI knows what its principal is supposed to be. + nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(aURI); + if (uriPrinc) { + nsCOMPtr<nsIPrincipal> principal; + uriPrinc->GetPrincipal(getter_AddRefs(principal)); + if (!principal) { + return nsNullPrincipal::Create(aAttrs); + } + RefPtr<BasePrincipal> concrete = Cast(principal); + return concrete.forget(); + } + + // Mint a codebase principal. + RefPtr<nsPrincipal> codebase = new nsPrincipal(); + rv = codebase->Init(aURI, aAttrs); + NS_ENSURE_SUCCESS(rv, nullptr); + return codebase.forget(); +} + +already_AddRefed<BasePrincipal> +BasePrincipal::CreateCodebasePrincipal(const nsACString& aOrigin) +{ + MOZ_ASSERT(!StringBeginsWith(aOrigin, NS_LITERAL_CSTRING("[")), + "CreateCodebasePrincipal does not support System and Expanded principals"); + + MOZ_ASSERT(!StringBeginsWith(aOrigin, NS_LITERAL_CSTRING(NS_NULLPRINCIPAL_SCHEME ":")), + "CreateCodebasePrincipal does not support nsNullPrincipal"); + + nsAutoCString originNoSuffix; + mozilla::PrincipalOriginAttributes attrs; + if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) { + return nullptr; + } + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix); + NS_ENSURE_SUCCESS(rv, nullptr); + + return BasePrincipal::CreateCodebasePrincipal(uri, attrs); +} + +already_AddRefed<BasePrincipal> +BasePrincipal::CloneStrippingUserContextIdAndFirstPartyDomain() +{ + PrincipalOriginAttributes attrs = OriginAttributesRef(); + attrs.StripUserContextIdAndFirstPartyDomain(); + + nsAutoCString originNoSuffix; + nsresult rv = GetOriginNoSuffix(originNoSuffix); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix); + NS_ENSURE_SUCCESS(rv, nullptr); + + return BasePrincipal::CreateCodebasePrincipal(uri, attrs); +} + +bool +BasePrincipal::AddonAllowsLoad(nsIURI* aURI) +{ + if (mOriginAttributes.mAddonId.IsEmpty()) { + return false; + } + + nsCOMPtr<nsIAddonPolicyService> aps = do_GetService("@mozilla.org/addons/policy-service;1"); + NS_ENSURE_TRUE(aps, false); + + bool allowed = false; + nsresult rv = aps->AddonMayLoadURI(mOriginAttributes.mAddonId, aURI, &allowed); + return NS_SUCCEEDED(rv) && allowed; +} + +} // namespace mozilla diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h new file mode 100644 index 000000000..a2cc219bb --- /dev/null +++ b/caps/BasePrincipal.h @@ -0,0 +1,342 @@ +/* -*- 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 "nsIPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsJSPrincipals.h" + +#include "mozilla/Attributes.h" +#include "mozilla/dom/ChromeUtilsBinding.h" + +class nsIContentSecurityPolicy; +class nsIObjectOutputStream; +class nsIObjectInputStream; +class nsIURI; + +class nsExpandedPrincipal; + +namespace mozilla { + +class GenericOriginAttributes; + +// Base OriginAttributes class. This has several subclass flavors, and is not +// directly constructable itself. +class OriginAttributes : public dom::OriginAttributesDictionary +{ +public: + bool operator==(const OriginAttributes& aOther) const + { + return mAppId == aOther.mAppId && + mInIsolatedMozBrowser == aOther.mInIsolatedMozBrowser && + mAddonId == aOther.mAddonId && + mUserContextId == aOther.mUserContextId && + mPrivateBrowsingId == aOther.mPrivateBrowsingId && + mFirstPartyDomain == aOther.mFirstPartyDomain; + } + bool operator!=(const OriginAttributes& aOther) const + { + return !(*this == aOther); + } + + // 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; + + MOZ_MUST_USE bool PopulateFromSuffix(const nsACString& aStr); + + // Populates the attributes from a string like + // |uri!key1=value1&key2=value2| and returns the uri without the suffix. + MOZ_MUST_USE 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); + + void SetFromGenericAttributes(const GenericOriginAttributes& aAttrs); + + // check if "privacy.firstparty.isolate" is enabled. + static bool IsFirstPartyEnabled(); + +protected: + OriginAttributes() {} + explicit OriginAttributes(const OriginAttributesDictionary& aOther) + : OriginAttributesDictionary(aOther) {} +}; + +class PrincipalOriginAttributes; +class DocShellOriginAttributes; +class NeckoOriginAttributes; + +// Various classes in Gecko contain OriginAttributes members, and those +// OriginAttributes get propagated to other classes according to certain rules. +// For example, the OriginAttributes on the docshell affect the OriginAttributes +// for the principal of a document loaded inside it, whose OriginAttributes in +// turn affect those of network loads and child docshells. To codify and +// centralize these rules, we introduce separate subclasses for the different +// flavors, and a variety of InheritFrom* methods to implement the transfer +// behavior. + +// For OriginAttributes stored on principals. +class PrincipalOriginAttributes : public OriginAttributes +{ +public: + PrincipalOriginAttributes() {} + PrincipalOriginAttributes(uint32_t aAppId, bool aInIsolatedMozBrowser) + { + mAppId = aAppId; + mInIsolatedMozBrowser = aInIsolatedMozBrowser; + } + + // Inheriting OriginAttributes from docshell to document when user navigates. + // + // @param aAttrs Origin Attributes of the docshell. + // @param aURI The URI of the document. + void InheritFromDocShellToDoc(const DocShellOriginAttributes& aAttrs, + const nsIURI* aURI); + + // Inherit OriginAttributes from Necko. + void InheritFromNecko(const NeckoOriginAttributes& aAttrs); + + void StripUserContextIdAndFirstPartyDomain(); +}; + +// For OriginAttributes stored on docshells / loadcontexts / browsing contexts. +class DocShellOriginAttributes : public OriginAttributes +{ +public: + DocShellOriginAttributes() {} + DocShellOriginAttributes(uint32_t aAppId, bool aInIsolatedMozBrowser) + { + mAppId = aAppId; + mInIsolatedMozBrowser = aInIsolatedMozBrowser; + } + + // Inheriting OriginAttributes from document to child docshell when an + // <iframe> is created. + // + // @param aAttrs Origin Attributes of the document. + void + InheritFromDocToChildDocShell(const PrincipalOriginAttributes& aAttrs); +}; + +// For OriginAttributes stored on Necko. +class NeckoOriginAttributes : public OriginAttributes +{ +public: + NeckoOriginAttributes() {} + NeckoOriginAttributes(uint32_t aAppId, bool aInIsolatedMozBrowser) + { + mAppId = aAppId; + mInIsolatedMozBrowser = aInIsolatedMozBrowser; + } + + // Inheriting OriginAttributes from document to necko when a network request + // is made. + void InheritFromDocToNecko(const PrincipalOriginAttributes& aAttrs); + + // Inheriting OriginAttributes from a docshell when loading a top-level + // document. + void InheritFromDocShellToNecko(const DocShellOriginAttributes& aAttrs, + const bool aIsTopLevelDocument = false, + nsIURI* aURI = nullptr); +}; + +// For operating on OriginAttributes not associated with any data structure. +class GenericOriginAttributes : public OriginAttributes +{ +public: + GenericOriginAttributes() {} + explicit GenericOriginAttributes(const OriginAttributesDictionary& aOther) + : OriginAttributes(aOther) {} +}; + +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() {} + + explicit OriginAttributesPattern(const OriginAttributesPatternDictionary& aOther) + : OriginAttributesPatternDictionary(aOther) {} + + // Performs a match of |aAttrs| against this pattern. + bool Matches(const OriginAttributes& aAttrs) const + { + if (mAppId.WasPassed() && mAppId.Value() != aAttrs.mAppId) { + return false; + } + + if (mInIsolatedMozBrowser.WasPassed() && mInIsolatedMozBrowser.Value() != aAttrs.mInIsolatedMozBrowser) { + return false; + } + + if (mAddonId.WasPassed() && mAddonId.Value() != aAttrs.mAddonId) { + 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; + } + + return true; + } + + bool Overlaps(const OriginAttributesPattern& aOther) const + { + if (mAppId.WasPassed() && aOther.mAppId.WasPassed() && + mAppId.Value() != aOther.mAppId.Value()) { + return false; + } + + if (mInIsolatedMozBrowser.WasPassed() && + aOther.mInIsolatedMozBrowser.WasPassed() && + mInIsolatedMozBrowser.Value() != aOther.mInIsolatedMozBrowser.Value()) { + return false; + } + + if (mAddonId.WasPassed() && aOther.mAddonId.WasPassed() && + mAddonId.Value() != aOther.mAddonId.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; + } + + return true; + } +}; + +/* + * 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: + BasePrincipal(); + + enum DocumentDomainConsideration { DontConsiderDocumentDomain, ConsiderDocumentDomain}; + bool Subsumes(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration); + + NS_IMETHOD GetOrigin(nsACString& aOrigin) final; + NS_IMETHOD GetOriginNoSuffix(nsACString& aOrigin) final; + NS_IMETHOD Equals(nsIPrincipal* other, bool* _retval) final; + NS_IMETHOD EqualsConsideringDomain(nsIPrincipal* other, bool* _retval) final; + NS_IMETHOD Subsumes(nsIPrincipal* other, bool* _retval) final; + NS_IMETHOD SubsumesConsideringDomain(nsIPrincipal* other, bool* _retval) final; + NS_IMETHOD CheckMayLoad(nsIURI* uri, bool report, bool allowIfInheritsPrincipal) final; + NS_IMETHOD GetCsp(nsIContentSecurityPolicy** aCsp) override; + NS_IMETHOD SetCsp(nsIContentSecurityPolicy* aCsp) override; + NS_IMETHOD EnsureCSP(nsIDOMDocument* aDocument, nsIContentSecurityPolicy** aCSP) override; + NS_IMETHOD GetPreloadCsp(nsIContentSecurityPolicy** aPreloadCSP) override; + NS_IMETHOD EnsurePreloadCSP(nsIDOMDocument* aDocument, nsIContentSecurityPolicy** aCSP) override; + NS_IMETHOD GetCspJSON(nsAString& outCSPinJSON) override; + NS_IMETHOD GetIsNullPrincipal(bool* aResult) override; + NS_IMETHOD GetIsCodebasePrincipal(bool* aResult) override; + NS_IMETHOD GetIsExpandedPrincipal(bool* aResult) override; + NS_IMETHOD GetIsSystemPrincipal(bool* aResult) override; + NS_IMETHOD GetOriginAttributes(JSContext* aCx, JS::MutableHandle<JS::Value> aVal) final; + NS_IMETHOD GetOriginSuffix(nsACString& aOriginSuffix) final; + NS_IMETHOD GetAppStatus(uint16_t* aAppStatus) final; + NS_IMETHOD GetAppId(uint32_t* aAppStatus) final; + NS_IMETHOD GetAddonId(nsAString& aAddonId) final; + NS_IMETHOD GetIsInIsolatedMozBrowserElement(bool* aIsInIsolatedMozBrowserElement) final; + NS_IMETHOD GetUnknownAppId(bool* aUnknownAppId) final; + NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final; + NS_IMETHOD GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) final; + + bool EqualsIgnoringAddonId(nsIPrincipal *aOther); + + virtual bool AddonHasPermission(const nsAString& aPerm); + + virtual bool IsOnCSSUnprefixingWhitelist() override { return false; } + + virtual bool IsCodebasePrincipal() const { return false; }; + + static BasePrincipal* Cast(nsIPrincipal* aPrin) { return static_cast<BasePrincipal*>(aPrin); } + static already_AddRefed<BasePrincipal> + CreateCodebasePrincipal(nsIURI* aURI, const PrincipalOriginAttributes& aAttrs); + static already_AddRefed<BasePrincipal> CreateCodebasePrincipal(const nsACString& aOrigin); + + const PrincipalOriginAttributes& OriginAttributesRef() { return mOriginAttributes; } + uint32_t AppId() const { return mOriginAttributes.mAppId; } + uint32_t UserContextId() const { return mOriginAttributes.mUserContextId; } + uint32_t PrivateBrowsingId() const { return mOriginAttributes.mPrivateBrowsingId; } + bool IsInIsolatedMozBrowserElement() const { return mOriginAttributes.mInIsolatedMozBrowser; } + + enum PrincipalKind { + eNullPrincipal, + eCodebasePrincipal, + eExpandedPrincipal, + eSystemPrincipal + }; + + virtual PrincipalKind Kind() = 0; + + already_AddRefed<BasePrincipal> CloneStrippingUserContextIdAndFirstPartyDomain(); + +protected: + virtual ~BasePrincipal(); + + virtual nsresult GetOriginInternal(nsACString& aOrigin) = 0; + // 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 ::nsExpandedPrincipal; + + // Helper to check whether this principal is associated with an addon that + // allows unprivileged code to load aURI. + bool AddonAllowsLoad(nsIURI* aURI); + + nsCOMPtr<nsIContentSecurityPolicy> mCSP; + nsCOMPtr<nsIContentSecurityPolicy> mPreloadCSP; + PrincipalOriginAttributes mOriginAttributes; +}; + +} // namespace mozilla + +#endif /* mozilla_BasePrincipal_h */ diff --git a/caps/DomainPolicy.cpp b/caps/DomainPolicy.cpp new file mode 100644 index 000000000..16029a2bd --- /dev/null +++ b/caps/DomainPolicy.cpp @@ -0,0 +1,260 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=4 et sw=4 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 "nsIMessageManager.h" +#include "nsScriptSecurityManager.h" + +namespace mozilla { + +using namespace ipc; +using namespace dom; + +NS_IMPL_ISUPPORTS(DomainPolicy, nsIDomainPolicy) + +static nsresult +BroadcastDomainSetChange(DomainSetType aSetType, DomainSetChangeType aChangeType, + nsIURI* aDomain = nullptr) +{ + MOZ_ASSERT(XRE_IsParentProcess(), + "DomainPolicy should only be exposed to the chrome process."); + + nsTArray<ContentParent*> parents; + ContentParent::GetAll(parents); + if (!parents.Length()) { + return NS_OK; + } + + OptionalURIParams uri; + SerializeURI(aDomain, uri); + + for (uint32_t i = 0; i < parents.Length(); i++) { + Unused << parents[i]->SendDomainSetChanged(aSetType, aChangeType, uri); + } + return NS_OK; +} + +DomainPolicy::DomainPolicy() : mBlacklist(new DomainSet(BLACKLIST)) + , mSuperBlacklist(new DomainSet(SUPER_BLACKLIST)) + , mWhitelist(new DomainSet(WHITELIST)) + , mSuperWhitelist(new DomainSet(SUPER_WHITELIST)) +{ + 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(!mBlacklist && !mSuperBlacklist && + !mWhitelist && !mSuperWhitelist); +} + + +NS_IMETHODIMP +DomainPolicy::GetBlacklist(nsIDomainSet** aSet) +{ + nsCOMPtr<nsIDomainSet> set = mBlacklist.get(); + set.forget(aSet); + return NS_OK; +} + +NS_IMETHODIMP +DomainPolicy::GetSuperBlacklist(nsIDomainSet** aSet) +{ + nsCOMPtr<nsIDomainSet> set = mSuperBlacklist.get(); + set.forget(aSet); + return NS_OK; +} + +NS_IMETHODIMP +DomainPolicy::GetWhitelist(nsIDomainSet** aSet) +{ + nsCOMPtr<nsIDomainSet> set = mWhitelist.get(); + set.forget(aSet); + return NS_OK; +} + +NS_IMETHODIMP +DomainPolicy::GetSuperWhitelist(nsIDomainSet** aSet) +{ + nsCOMPtr<nsIDomainSet> set = mSuperWhitelist.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. + mBlacklist->Clear(); + mSuperBlacklist->Clear(); + mWhitelist->Clear(); + mSuperWhitelist->Clear(); + + // Null them out. + mBlacklist = nullptr; + mSuperBlacklist = nullptr; + mWhitelist = nullptr; + mSuperWhitelist = 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; + mBlacklist->CloneSet(&aClone->blacklist()); + mSuperBlacklist->CloneSet(&aClone->superBlacklist()); + mWhitelist->CloneSet(&aClone->whitelist()); + mSuperWhitelist->CloneSet(&aClone->superWhitelist()); +} + +static +void +CopyURIs(const InfallibleTArray<URIParams>& aDomains, nsIDomainSet* aSet) +{ + for (uint32_t i = 0; i < aDomains.Length(); i++) { + nsCOMPtr<nsIURI> uri = DeserializeURI(aDomains[i]); + aSet->Add(uri); + } +} + +void +DomainPolicy::ApplyClone(DomainPolicyClone* aClone) +{ + CopyURIs(aClone->blacklist(), mBlacklist); + CopyURIs(aClone->whitelist(), mWhitelist); + CopyURIs(aClone->superBlacklist(), mSuperBlacklist); + CopyURIs(aClone->superWhitelist(), mSuperWhitelist); +} + +static already_AddRefed<nsIURI> +GetCanonicalClone(nsIURI* aURI) +{ + nsCOMPtr<nsIURI> clone; + nsresult rv = aURI->Clone(getter_AddRefs(clone)); + NS_ENSURE_SUCCESS(rv, nullptr); + rv = clone->SetUserPass(EmptyCString()); + NS_ENSURE_SUCCESS(rv, nullptr); + rv = clone->SetPath(EmptyCString()); + NS_ENSURE_SUCCESS(rv, nullptr); + return clone.forget(); +} + +NS_IMPL_ISUPPORTS(DomainSet, nsIDomainSet) + +NS_IMETHODIMP +DomainSet::Add(nsIURI* aDomain) +{ + nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain); + NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE); + mHashTable.PutEntry(clone); + if (XRE_IsParentProcess()) + return BroadcastDomainSetChange(mType, ADD_DOMAIN, aDomain); + + return NS_OK; +} + +NS_IMETHODIMP +DomainSet::Remove(nsIURI* aDomain) +{ + nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain); + NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE); + mHashTable.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<nsIURI> clone = GetCanonicalClone(aDomain); + NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE); + *aContains = mHashTable.Contains(clone); + return NS_OK; +} + +NS_IMETHODIMP +DomainSet::ContainsSuperDomain(nsIURI* aDomain, bool* aContains) +{ + *aContains = false; + nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain); + NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE); + nsAutoCString domain; + nsresult rv = clone->GetHost(domain); + NS_ENSURE_SUCCESS(rv, rv); + while (true) { + // Check the current domain. + if (mHashTable.Contains(clone)) { + *aContains = true; + return NS_OK; + } + + // Chop off everything before the first dot, or break if there are no + // dots left. + int32_t index = domain.Find("."); + if (index == kNotFound) + break; + domain.Assign(Substring(domain, index + 1)); + rv = clone->SetHost(domain); + NS_ENSURE_SUCCESS(rv, rv); + } + + // No match. + return NS_OK; + +} + +NS_IMETHODIMP +DomainSet::GetType(uint32_t* aType) +{ + *aType = mType; + return NS_OK; +} + +void +DomainSet::CloneSet(InfallibleTArray<URIParams>* aDomains) +{ + for (auto iter = mHashTable.Iter(); !iter.Done(); iter.Next()) { + nsIURI* key = iter.Get()->GetKey(); + + URIParams uri; + SerializeURI(key, uri); + + aDomains->AppendElement(uri); + } +} + +} /* namespace mozilla */ diff --git a/caps/DomainPolicy.h b/caps/DomainPolicy.h new file mode 100644 index 000000000..86648fe0b --- /dev/null +++ b/caps/DomainPolicy.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 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 { + +namespace ipc { +class URIParams; +} // namespace ipc + +enum DomainSetChangeType{ + ACTIVATE_POLICY, + DEACTIVATE_POLICY, + ADD_DOMAIN, + REMOVE_DOMAIN, + CLEAR_DOMAINS +}; + +enum DomainSetType{ + NO_TYPE, + BLACKLIST, + SUPER_BLACKLIST, + WHITELIST, + SUPER_WHITELIST +}; + +class DomainSet final : public nsIDomainSet +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMAINSET + + explicit DomainSet(DomainSetType aType) + : mType(aType) + {} + + void CloneSet(InfallibleTArray<mozilla::ipc::URIParams>* aDomains); + +protected: + virtual ~DomainSet() {} + nsTHashtable<nsURIHashKey> mHashTable; + DomainSetType mType; +}; + +class DomainPolicy final : public nsIDomainPolicy +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMAINPOLICY + DomainPolicy(); + +private: + virtual ~DomainPolicy(); + + RefPtr<DomainSet> mBlacklist; + RefPtr<DomainSet> mSuperBlacklist; + RefPtr<DomainSet> mWhitelist; + RefPtr<DomainSet> mSuperWhitelist; +}; + +} /* namespace mozilla */ + +#endif /* DomainPolicy_h__ */ diff --git a/caps/moz.build b/caps/moz.build new file mode 100644 index 000000000..58b45e360 --- /dev/null +++ b/caps/moz.build @@ -0,0 +1,63 @@ +# -*- 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', + 'nsNullPrincipal.h', + 'nsNullPrincipalURI.h', +] + +EXPORTS.mozilla = [ + 'BasePrincipal.h' +] + +SOURCES += [ + # Compile this separately since nsExceptionHandler.h conflicts + # with something from nsNullPrincipal.cpp. + 'BasePrincipal.cpp', +] + +UNIFIED_SOURCES += [ + 'DomainPolicy.cpp', + 'nsJSPrincipals.cpp', + 'nsNullPrincipal.cpp', + 'nsNullPrincipalURI.cpp', + 'nsPrincipal.cpp', + 'nsScriptSecurityManager.cpp', + 'nsSystemPrincipal.cpp', +] + +LOCAL_INCLUDES += [ + '/docshell/base', + '/dom/base', + '/js/xpconnect/src', +] + +if CONFIG['ENABLE_TESTS']: + DIRS += ['tests/gtest'] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' diff --git a/caps/nsIAddonPolicyService.idl b/caps/nsIAddonPolicyService.idl new file mode 100644 index 000000000..cf444acaa --- /dev/null +++ b/caps/nsIAddonPolicyService.idl @@ -0,0 +1,78 @@ +/* -*- 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 base content security policy, which is applied to all + * extension documents, in addition to any custom policies. + */ + readonly attribute AString baseCSP; + + /** + * Returns the default content security policy which applies to extension + * documents which do not specify any custom policies. + */ + readonly attribute AString defaultCSP; + + /** + * 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 getAddonCSP(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|. + */ + boolean addonMayLoadURI(in AString aAddonId, in nsIURI aURI); + + /** + * 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 +{ + /** + * 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); +}; diff --git a/caps/nsIDomainPolicy.idl b/caps/nsIDomainPolicy.idl new file mode 100644 index 000000000..54b0de4db --- /dev/null +++ b/caps/nsIDomainPolicy.idl @@ -0,0 +1,80 @@ +/* -*- 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); + +/* + * 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 blacklists + * are consulted. If globally disabled, the whitelists are consulted. Lookups + * on blacklist and whitelist happen with contains(), and lookups on + * superBlacklist and superWhitelist 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 blacklist; + readonly attribute nsIDomainSet superBlacklist; + readonly attribute nsIDomainSet whitelist; + readonly attribute nsIDomainSet superWhitelist; + + void deactivate(); + + [noscript, notxpcom] void cloneDomainPolicy(in DomainPolicyClonePtr aClone); + [noscript, notxpcom] void applyClone(in DomainPolicyClonePtr aClone); +}; + +[scriptable, builtinclass, uuid(665c981b-0a0f-4229-ac06-a826e02d4f69)] +interface nsIDomainSet : nsISupports +{ + /* + * The type of the set. See: DomainSetType + */ + [noscript] readonly attribute uint32_t type; + + /* + * 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 000000000..d278decdb --- /dev/null +++ b/caps/nsIPrincipal.idl @@ -0,0 +1,367 @@ +/* -*- 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 "nsISerializable.idl" + +%{C++ +struct JSPrincipals; +#include "nsCOMPtr.h" +#include "nsTArray.h" +%} + +interface nsIURI; +interface nsIContentSecurityPolicy; +interface nsIDOMDocument; + +[ptr] native JSContext(JSContext); +[ptr] native JSPrincipals(JSPrincipals); +[ptr] native PrincipalArray(nsTArray<nsCOMPtr<nsIPrincipal> >); + +[scriptable, builtinclass, uuid(3da7b133-f1a0-4de9-a2bc-5c49014c1077)] +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); + + /** + * Like equals, but takes document.domain changes into account. + */ + boolean equalsConsideringDomain(in nsIPrincipal other); + + %{C++ + inline bool Equals(nsIPrincipal* aOther) { + bool equal = false; + return NS_SUCCEEDED(Equals(aOther, &equal)) && equal; + } + + inline bool EqualsConsideringDomain(nsIPrincipal* aOther) { + bool equal = false; + return NS_SUCCEEDED(EqualsConsideringDomain(aOther, &equal)) && equal; + } + %} + + /** + * Returns a hash value for the principal. + */ + [noscript] readonly attribute unsigned long hashValue; + + /** + * The codebase URI to which this principal pertains. This is + * generally the document URI. + */ + 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); + + %{C++ + inline bool Subsumes(nsIPrincipal* aOther) { + bool subsumes = false; + return NS_SUCCEEDED(Subsumes(aOther, &subsumes)) && subsumes; + } + + inline bool SubsumesConsideringDomain(nsIPrincipal* aOther) { + bool subsumes = false; + return NS_SUCCEEDED(SubsumesConsideringDomain(aOther, &subsumes)) && subsumes; + } + %} + + /** + * Checks whether this principal is allowed to load the network resource + * located at the given URI under the same-origin policy. This means that + * codebase 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 report If true, will report a warning to the console service + * if the load is not allowed. + * @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 report, + in boolean allowIfInheritsPrincipal); + + /** + * A Content Security Policy associated with this principal. + * Use this function to query the associated CSP with this principal. + * Please *only* use this function to *set* a CSP when you know exactly what you are doing. + * Most likely you want to call ensureCSP instead of setCSP. + */ + [noscript] attribute nsIContentSecurityPolicy csp; + + /* + * Use this function to query a CSP associated with this principal. + * If no CSP is associated with this principal then one is created + * internally and setRequestContext is called on the CSP using aDocument. + * + * Please note if aDocument is null, then setRequestContext on the + * CSP object is called using the current principal. + */ + [noscript] nsIContentSecurityPolicy ensureCSP(in nsIDOMDocument aDocument); + + /** + * A speculative Content Security Policy associated with this + * principal. Set during speculative loading (preloading) and + * used *only* for preloads. + * + * If you want to query the CSP associated with that principal, + * then this is *not* what you want. Instead query 'csp'. + */ + [noscript] readonly attribute nsIContentSecurityPolicy preloadCsp; + + /* + * Use this function to query a speculative CSP associated with this + * principal. If no speculative CSP is associated with this principal + * then one is created internally and setRequestContext is called on + * the CSP using aDocument. + * + * Please note if aDocument is null, then setRequestContext on the + * speculative CSP object is called using the current principal. + */ + [noscript] nsIContentSecurityPolicy ensurePreloadCSP(in nsIDOMDocument aDocument); + + /** + * The CSP of the principal in JSON notation. + * Note, that the CSP itself is not exposed to JS, but script + * should be able to obtain a JSON representation of the CSP. + */ + readonly attribute AString cspJSON; + + /** + * 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; + + /** + * A canonical representation of the origin for this principal. This + * consists of a base string (which, for codebase 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; + + /** + * 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; + + /** + * The base domain of the codebase URI to which this principal pertains + * (generally the document URI), handling null principals and + * non-hierarchical schemes correctly. + */ + readonly attribute ACString baseDomain; + + const short APP_STATUS_NOT_INSTALLED = 0; + const short APP_STATUS_INSTALLED = 1; + const short APP_STATUS_PRIVILEGED = 2; + const short APP_STATUS_CERTIFIED = 3; + + /** + * Gets the principal's app status, which indicates whether the principal + * corresponds to "app code", and if it does, how privileged that code is. + * This method returns one of the APP_STATUS constants above. + * + * Note that a principal may have + * + * appId != nsIScriptSecurityManager::NO_APP_ID && + * appId != nsIScriptSecurityManager::UNKNOWN_APP_ID + * + * and still have appStatus == APP_STATUS_NOT_INSTALLED. That's because + * appId identifies the app that contains this principal, but a window + * might be contained in an app and not be running code that the app has + * vouched for. For example, the window might be inside an <iframe + * mozbrowser>, or the window's origin might not match the app's origin. + * + * If you're doing a check to determine "does this principal correspond to + * app code?", you must check appStatus; checking appId != NO_APP_ID is not + * sufficient. + */ + [infallible] readonly attribute unsigned short appStatus; + + /** + * Gets the id of the app this principal is inside. If this principal is + * not inside an app, returns nsIScriptSecurityManager::NO_APP_ID. + * + * Note that this principal does not necessarily have the permissions of + * the app identified by appId. For example, this principal might + * correspond to an iframe whose origin differs from that of the app frame + * containing it. In this case, the iframe will have the appId of its + * containing app frame, but the iframe must not run with the app's + * permissions. + * + * Similarly, this principal might correspond to an <iframe mozbrowser> + * inside an app frame; in this case, the content inside the iframe should + * not have any of the app's permissions, even if the iframe is at the same + * origin as the app. + * + * If you're doing a security check based on appId, you must check + * appStatus as well. + */ + [infallible] readonly attribute unsigned long appId; + + /** + * Gets the ID of the add-on this principal belongs to. + */ + readonly attribute AString addonId; + + /** + * 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. + * <iframe mozbrowser mozapp> and <xul:browser> are not considered to be + * mozbrowser elements. <iframe mozbrowser noisolation> does not count as + * isolated since isolation is disabled. Isolation can only be disabled if + * the containing document is chrome. + */ + [infallible] readonly attribute boolean isInIsolatedMozBrowserElement; + + /** + * Returns true if this principal has an unknown appId. This shouldn't + * generally be used. We only expose it due to not providing the correct + * appId everywhere where we construct principals. + */ + [infallible] readonly attribute boolean unknownAppId; + + /** + * Returns true iff this is a null principal (corresponding to an + * unknown, hence assumed minimally privileged, security context). + */ + [infallible] readonly attribute boolean isNullPrincipal; + + /** + * Returns true iff this principal corresponds to a codebase origin. + */ + [infallible] readonly attribute boolean isCodebasePrincipal; + + /** + * Returns true iff this is an expanded principal. + */ + [infallible] readonly attribute boolean isExpandedPrincipal; + + /** + * Returns true iff this is the system principal. + */ + [infallible] readonly attribute boolean isSystemPrincipal; + + /** + * Returns true if this principal's origin is recognized as being on the + * whitelist of sites that can use the CSS Unprefixing Service. + * + * (This interface provides a trivial implementation, just returning false; + * subclasses can implement something more complex as-needed.) + */ + [noscript,notxpcom,nostdcall] bool IsOnCSSUnprefixingWhitelist(); +}; + +/** + * If nsSystemPrincipal is too risky to use, but we want a principal to access + * more than one origin, nsExpandedPrincipals letting us define an array of + * principals it subsumes. So script with an nsExpandedPrincipals will gain + * same origin access when at least one of its principals it contains gained + * sameorigin acccess. An nsExpandedPrincipal will be subsumed by the system + * principal, and by another nsExpandedPrincipal that has all its principals. + * It is added for jetpack content-scripts to let them interact with the + * content and a well defined set of other domains, without the risk of + * leaking out a system principal to the content. See: Bug 734891 + */ +[uuid(f3e177Df-6a5e-489f-80a7-2dd1481471d8)] +interface nsIExpandedPrincipal : nsISupports +{ + /** + * An array of principals that the expanded principal subsumes. + * Note: this list is not reference counted, it is shared, so + * should not be changed and should only be used ephemerally. + */ + [noscript] readonly attribute PrincipalArray whiteList; +}; diff --git a/caps/nsIScriptSecurityManager.idl b/caps/nsIScriptSecurityManager.idl new file mode 100644 index 000000000..08debc528 --- /dev/null +++ b/caps/nsIScriptSecurityManager.idl @@ -0,0 +1,305 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIPrincipal.idl" +interface nsIURI; +interface nsIChannel; +interface nsIClassInfo; +interface nsIDocShell; +interface nsIDomainPolicy; +interface nsILoadContext; + +%{ C++ +#include "jspubtd.h" + +namespace mozilla { +namespace dom { +class DomainPolicyClone; +} +} +%} + +[ptr] native JSContextPtr(JSContext); +[ptr] native JSObjectPtr(JSObject); +[ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone); + +[scriptable, uuid(51daad87-3a0c-44cc-b620-7356801c9022)] +interface nsIScriptSecurityManager : nsISupports +{ + /** + * For each of these hooks returning NS_OK means 'let the action continue'. + * Returning an error code means 'veto the action'. XPConnect will return + * false to the js engine if the action is vetoed. The implementor of this + * interface is responsible for setting a JS exception into the JSContext + * if that is appropriate. + */ + [noscript] void canCreateWrapper(in JSContextPtr aJSContext, + in nsIIDRef aIID, + in nsISupports aObj, + in nsIClassInfo aClassInfo); + + [noscript] void canCreateInstance(in JSContextPtr aJSContext, + in nsCIDRef aCID); + + [noscript] void canGetService(in JSContextPtr aJSContext, + in nsCIDRef aCID); + + /** + * Check that the script currently running in context "cx" can load "uri". + * + * Will return error code NS_ERROR_DOM_BAD_URI if the load request + * should be denied. + * + * @param cx the JSContext of the script causing the load + * @param uri the URI that is being loaded + */ + [noscript] void checkLoadURIFromScript(in JSContextPtr cx, in nsIURI uri); + + /** + * Default CheckLoadURI permissions + */ + // Default permissions + const unsigned long STANDARD = 0; + + // Indicate that the load is a load of a new document that is not + // user-triggered. Here "user-triggered" could be broadly interpreted -- + // for example, scripted sets of window.location.href might be treated as + // "user-triggered" in some circumstances. A typical example of a load + // that is not user-triggered is a <meta> refresh load. If this flag is + // set, the load will be denied if the originating principal's URI has the + // nsIProtocolHandler::URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT flag set. + const unsigned long LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT = 1 << 0; + + // Allow the loading of chrome URLs by non-chrome URLs. Use with great + // care! This will actually allow the loading of any URI which has the + // nsIProtocolHandler::URI_IS_UI_RESOURCE protocol handler flag set. Ths + // probably means at least chrome: and resource:. + const unsigned long ALLOW_CHROME = 1 << 1; + + // Don't allow URLs which would inherit the caller's principal (such as + // javascript: or data:) to load. See + // nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT. + const unsigned long DISALLOW_INHERIT_PRINCIPAL = 1 << 2; + + // Alias for DISALLOW_INHERIT_PRINCIPAL for backwards compat with + // JS-implemented extensions. + const unsigned long DISALLOW_SCRIPT_OR_DATA = DISALLOW_INHERIT_PRINCIPAL; + + // Don't allow javascript: URLs to load + // WARNING: Support for this value was added in Mozilla 1.7.8 and + // Firefox 1.0.4. Use in prior versions WILL BE IGNORED. + // When using this, make sure that you actually want DISALLOW_SCRIPT, not + // DISALLOW_INHERIT_PRINCIPAL + const unsigned long DISALLOW_SCRIPT = 1 << 3; + + // Do not report errors if we just want to check if a principal can load + // a URI to not unnecessarily spam the error console. + const unsigned long DONT_REPORT_ERRORS = 1 << 4; + + /** + * Check that content with principal aPrincipal can load "uri". + * + * Will return error code NS_ERROR_DOM_BAD_URI if the load request + * should be denied. + * + * @param aPrincipal the principal identifying the actor causing the load + * @param uri the URI that is being loaded + * @param flags the permission set, see above + */ + void checkLoadURIWithPrincipal(in nsIPrincipal aPrincipal, + in nsIURI uri, + in unsigned long flags); + + /** + * Similar to checkLoadURIWithPrincipal but there are two differences: + * + * 1) The URI is a string, not a URI object. + * 2) This function assumes that the URI may still be subject to fixup (and + * hence will check whether fixed-up versions of the URI are allowed to + * load as well); if any of the versions of this URI is not allowed, this + * function will return error code NS_ERROR_DOM_BAD_URI. + */ + void checkLoadURIStrWithPrincipal(in nsIPrincipal aPrincipal, + in AUTF8String uri, + in unsigned long flags); + + ///////////////// Principals /////////////////////// + + /** + * Return the all-powerful system principal. + */ + nsIPrincipal getSystemPrincipal(); + + /** + * Returns a principal that has the given information. + * @param appId is the app id of the principal. It can't be UNKNOWN_APP_ID. + * @param inMozBrowser is true if the principal has to be considered as + * inside a mozbrowser frame. + * + * @deprecated use createCodebasePrincipal instead. + */ + [deprecated] nsIPrincipal getAppCodebasePrincipal(in nsIURI uri, + in unsigned long appId, + in boolean inMozBrowser); + + /** + * Returns a principal that has the appId and inMozBrowser of the load + * context. + * @param loadContext to get appId/inMozBrowser from. + */ + nsIPrincipal getLoadContextCodebasePrincipal(in nsIURI uri, + in nsILoadContext loadContext); + + /** + * Returns a principal that has the appId and inMozBrowser of the docshell + * inside a mozbrowser frame. + * @param docShell to get appId/inMozBrowser from. + */ + nsIPrincipal getDocShellCodebasePrincipal(in nsIURI uri, + in nsIDocShell docShell); + + /** + * Returns a principal with that has the same origin as uri and is not part + * of an appliction. + * The returned principal will have appId = NO_APP_ID. + * + * @deprecated use createCodebasePrincipal instead. + */ + [deprecated] nsIPrincipal getNoAppCodebasePrincipal(in nsIURI uri); + + /** + * Legacy method for getting a principal with no origin attributes. + * + * @deprecated use createCodebasePrincipal instead. + */ + [deprecated] nsIPrincipal getCodebasePrincipal(in nsIURI uri); + + /** + * Returns a principal whose origin is composed of |uri| and |originAttributes|. + * See nsIPrincipal.idl for a description of origin attributes, and + * ChromeUtils.webidl for a list of origin attributes and their defaults. + */ + [implicit_jscontext] + nsIPrincipal createCodebasePrincipal(in nsIURI uri, in jsval originAttributes); + + /** + * Returns a principal whose origin is the one we pass in. + * See nsIPrincipal.idl for a description of origin attributes, and + * ChromeUtils.webidl for a list of origin attributes and their defaults. + */ + nsIPrincipal createCodebasePrincipalFromOrigin(in ACString origin); + + /** + * Returns a unique nonce principal with |originAttributes|. + * See nsIPrincipal.idl for a description of origin attributes, and + * ChromeUtils.webidl for a list of origin attributes and their defaults. + */ + [implicit_jscontext] + nsIPrincipal createNullPrincipal(in jsval originAttributes); + + /** + * Returns OK if aSourceURI and target have the same "origin" + * (scheme, host, and port). + * ReportError flag suppresses error reports for functions that + * don't need reporting. + */ + void checkSameOriginURI(in nsIURI aSourceURI, + in nsIURI aTargetURI, + in boolean reportError); + /** + * Get the principal for the given channel. This will typically be the + * channel owner if there is one, and the codebase principal for the + * channel's URI otherwise. aChannel must not be null. + */ + nsIPrincipal getChannelResultPrincipal(in nsIChannel aChannel); + + /** + * Temporary API until bug 1220687 is fixed. + * + * Returns the same value as getChannelResultPrincipal, but ignoring + * sandboxing. Specifically, if sandboxing would have prevented the + * channel's triggering principal from being returned by + * getChannelResultPrincipal, the triggering principal will be returned + * by this method. + * + * Note that this method only ignores sandboxing of the channel in + * question, it does not ignore sandboxing of any channels further up a + * document chain. The triggering principal itself may still be the null + * principal due to sandboxing further up a document chain. In that regard + * the ignoring of sandboxing is limited. + */ + [noscript, nostdcall] + nsIPrincipal getChannelResultPrincipalIfNotSandboxed(in nsIChannel aChannel); + + /** + * Get the codebase principal for the channel's URI. + * aChannel must not be null. + */ + nsIPrincipal getChannelURIPrincipal(in nsIChannel aChannel); + + /** + * Check whether a given principal is a system principal. This allows us + * to avoid handing back the system principal to script while allowing + * script to check whether a given principal is system. + */ + boolean isSystemPrincipal(in nsIPrincipal aPrincipal); +%{C++ + bool IsSystemPrincipal(nsIPrincipal* aPrincipal) { + bool isSystem = false; + IsSystemPrincipal(aPrincipal, &isSystem); + return isSystem; + } +%} + + const unsigned long NO_APP_ID = 0; + const unsigned long UNKNOWN_APP_ID = 4294967295; // UINT32_MAX + const unsigned long SAFEBROWSING_APP_ID = 4294967294; // UINT32_MAX - 1 + + const unsigned long DEFAULT_USER_CONTEXT_ID = 0; + + /** + * Per-domain controls to enable and disable script. This system is designed + * to be used by at most one consumer, and enforces this with its semantics. + * + * Initially, domainPolicyActive is false. When activateDomainPolicy() is + * invoked, domainPolicyActive becomes true, and subsequent calls to + * activateDomainPolicy() will fail until deactivate() is invoked on the + * nsIDomainPolicy returned from activateDomainPolicy(). At this point, + * domainPolicyActive becomes false again, and a new consumer may acquire + * control of the system by invoking activateDomainPolicy(). + */ + nsIDomainPolicy activateDomainPolicy(); + readonly attribute boolean domainPolicyActive; + + /** + * Only the parent process can directly access domain policies, child + * processes only have a read-only mirror to the one in the parent. + * For child processes the mirror is updated via messages + * and ContentChild will hold the DomainPolicy by calling + * ActivateDomainPolicyInternal directly. New consumer to this + * function should not be addded. + */ + [noscript] nsIDomainPolicy activateDomainPolicyInternal(); + + /** + * This function is for internal use only. Every time a child process is spawned, we + * must clone any active domain policies in the parent to the new child. + */ + [noscript, notxpcom] void cloneDomainPolicy(in DomainPolicyClonePtr aClone); + + /** + * Query mechanism for the above policy. + * + * If domainPolicyEnabled is false, this simply returns the current value + * of javascript.enabled. Otherwise, it returns the same value, but taking + * the various blacklist/whitelist exceptions into account. + */ + bool policyAllowsScript(in nsIURI aDomain); +}; + +%{C++ +#define NS_SCRIPTSECURITYMANAGER_CONTRACTID "@mozilla.org/scriptsecuritymanager;1" +%} diff --git a/caps/nsJSPrincipals.cpp b/caps/nsJSPrincipals.cpp new file mode 100644 index 000000000..0f3afa14e --- /dev/null +++ b/caps/nsJSPrincipals.cpp @@ -0,0 +1,299 @@ +/* -*- 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 "xpcpublic.h" +#include "nsString.h" +#include "nsIObjectOutputStream.h" +#include "nsIObjectInputStream.h" +#include "nsJSPrincipals.h" +#include "plstr.h" +#include "nsXPIDLString.h" +#include "nsCOMPtr.h" +#include "nsIServiceManager.h" +#include "nsMemory.h" +#include "nsStringBuffer.h" + +#include "mozilla/dom/StructuredCloneTags.h" +// for mozilla::dom::workers::kJSPrincipalsDebugToken +#include "mozilla/dom/workers/Workers.h" +#include "mozilla/ipc/BackgroundUtils.h" + +using namespace mozilla; +using namespace mozilla::ipc; + +NS_IMETHODIMP_(MozExternalRefCountType) +nsJSPrincipals::AddRef() +{ + MOZ_ASSERT(NS_IsMainThread()); + NS_PRECONDITION(int32_t(refcount) >= 0, "illegal refcnt"); + nsrefcnt count = ++refcount; + NS_LOG_ADDREF(this, count, "nsJSPrincipals", sizeof(*this)); + return count; +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsJSPrincipals::Release() +{ + MOZ_ASSERT(NS_IsMainThread()); + NS_PRECONDITION(0 != refcount, "dup release"); + nsrefcnt count = --refcount; + NS_LOG_RELEASE(this, count, "nsJSPrincipals"); + if (count == 0) { + delete this; + } + + return count; +} + +/* static */ bool +nsJSPrincipals::Subsume(JSPrincipals *jsprin, JSPrincipals *other) +{ + bool result; + nsresult rv = nsJSPrincipals::get(jsprin)->Subsumes(nsJSPrincipals::get(other), &result); + return NS_SUCCEEDED(rv) && result; +} + +/* static */ void +nsJSPrincipals::Destroy(JSPrincipals *jsprin) +{ + // The JS runtime can call this method during the last GC when + // nsScriptSecurityManager is destroyed. So we must not assume here that + // the security manager still exists. + + nsJSPrincipals *nsjsprin = nsJSPrincipals::get(jsprin); + + // We need to destroy the nsIPrincipal. We'll do this by adding + // to the refcount and calling release + +#ifdef NS_BUILD_REFCNT_LOGGING + // The refcount logging considers AddRef-to-1 to indicate creation, + // so trick it into thinking it's otherwise, but balance the + // Release() we do below. + nsjsprin->refcount++; + nsjsprin->AddRef(); + nsjsprin->refcount--; +#else + nsjsprin->refcount++; +#endif + nsjsprin->Release(); +} + +#ifdef DEBUG + +// Defined here so one can do principals->dump() in the debugger +JS_PUBLIC_API(void) +JSPrincipals::dump() +{ + if (debugToken == nsJSPrincipals::DEBUG_TOKEN) { + nsAutoCString str; + nsresult rv = static_cast<nsJSPrincipals *>(this)->GetScriptLocation(str); + fprintf(stderr, "nsIPrincipal (%p) = %s\n", static_cast<void*>(this), + NS_SUCCEEDED(rv) ? str.get() : "(unknown)"); + } else if (debugToken == dom::workers::kJSPrincipalsDebugToken) { + fprintf(stderr, "Web Worker principal singleton (%p)\n", this); + } else { + fprintf(stderr, + "!!! JSPrincipals (%p) is not nsJSPrincipals instance - bad token: " + "actual=0x%x expected=0x%x\n", + this, unsigned(debugToken), unsigned(nsJSPrincipals::DEBUG_TOKEN)); + } +} + +#endif + +/* static */ bool +nsJSPrincipals::ReadPrincipals(JSContext* aCx, JSStructuredCloneReader* aReader, + JSPrincipals** aOutPrincipals) +{ + uint32_t tag; + uint32_t unused; + if (!JS_ReadUint32Pair(aReader, &tag, &unused)) { + return false; + } + + if (!(tag == SCTAG_DOM_NULL_PRINCIPAL || + tag == SCTAG_DOM_SYSTEM_PRINCIPAL || + tag == SCTAG_DOM_CONTENT_PRINCIPAL || + tag == SCTAG_DOM_EXPANDED_PRINCIPAL)) { + xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + return false; + } + + return ReadKnownPrincipalType(aCx, aReader, tag, aOutPrincipals); +} + +static bool +ReadSuffixAndSpec(JSStructuredCloneReader* aReader, + PrincipalOriginAttributes& aAttrs, + nsACString& aSpec) +{ + uint32_t suffixLength, specLength; + if (!JS_ReadUint32Pair(aReader, &suffixLength, &specLength)) { + return false; + } + + nsAutoCString suffix; + if (!suffix.SetLength(suffixLength, fallible)) { + return false; + } + + if (!JS_ReadBytes(aReader, suffix.BeginWriting(), suffixLength)) { + return false; + } + + if (!aAttrs.PopulateFromSuffix(suffix)) { + return false; + } + + if (!aSpec.SetLength(specLength, fallible)) { + return false; + } + + if (!JS_ReadBytes(aReader, aSpec.BeginWriting(), specLength)) { + return false; + } + + return true; +} + +static bool +ReadPrincipalInfo(JSStructuredCloneReader* aReader, + uint32_t aTag, + PrincipalInfo& aInfo) +{ + if (aTag == SCTAG_DOM_SYSTEM_PRINCIPAL) { + aInfo = SystemPrincipalInfo(); + } else if (aTag == SCTAG_DOM_NULL_PRINCIPAL) { + PrincipalOriginAttributes attrs; + nsAutoCString dummy; + if (!ReadSuffixAndSpec(aReader, attrs, dummy)) { + return false; + } + aInfo = NullPrincipalInfo(attrs); + } else if (aTag == SCTAG_DOM_EXPANDED_PRINCIPAL) { + uint32_t length, unused; + if (!JS_ReadUint32Pair(aReader, &length, &unused)) { + return false; + } + + ExpandedPrincipalInfo expanded; + + for (uint32_t i = 0; i < length; i++) { + uint32_t tag; + if (!JS_ReadUint32Pair(aReader, &tag, &unused)) { + return false; + } + + PrincipalInfo sub; + if (!ReadPrincipalInfo(aReader, tag, sub)) { + return false; + } + expanded.whitelist().AppendElement(sub); + } + + aInfo = expanded; + } else if (aTag == SCTAG_DOM_CONTENT_PRINCIPAL) { + PrincipalOriginAttributes attrs; + nsAutoCString spec; + if (!ReadSuffixAndSpec(aReader, attrs, spec)) { + return false; + } + + aInfo = ContentPrincipalInfo(attrs, void_t(), spec); + } else { + MOZ_CRASH("unexpected principal structured clone tag"); + } + + return true; +} + +/* static */ bool +nsJSPrincipals::ReadKnownPrincipalType(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + JSPrincipals** aOutPrincipals) +{ + MOZ_ASSERT(aTag == SCTAG_DOM_NULL_PRINCIPAL || + aTag == SCTAG_DOM_SYSTEM_PRINCIPAL || + aTag == SCTAG_DOM_CONTENT_PRINCIPAL || + aTag == SCTAG_DOM_EXPANDED_PRINCIPAL); + + if (NS_WARN_IF(!NS_IsMainThread())) { + xpc::Throw(aCx, NS_ERROR_UNCATCHABLE_EXCEPTION); + return false; + } + + PrincipalInfo info; + if (!ReadPrincipalInfo(aReader, aTag, info)) { + return false; + } + + nsresult rv; + nsCOMPtr<nsIPrincipal> prin = PrincipalInfoToPrincipal(info, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + return false; + } + + *aOutPrincipals = get(prin.forget().take()); + return true; +} + +static bool +WriteSuffixAndSpec(JSStructuredCloneWriter* aWriter, + const PrincipalOriginAttributes& aAttrs, + const nsCString& aSpec) +{ + nsAutoCString suffix; + aAttrs.CreateSuffix(suffix); + + return JS_WriteUint32Pair(aWriter, suffix.Length(), aSpec.Length()) && + JS_WriteBytes(aWriter, suffix.get(), suffix.Length()) && + JS_WriteBytes(aWriter, aSpec.get(), aSpec.Length()); +} + +static bool +WritePrincipalInfo(JSStructuredCloneWriter* aWriter, const PrincipalInfo& aInfo) +{ + if (aInfo.type() == PrincipalInfo::TNullPrincipalInfo) { + const NullPrincipalInfo& nullInfo = aInfo; + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_NULL_PRINCIPAL, 0) && + WriteSuffixAndSpec(aWriter, nullInfo.attrs(), EmptyCString()); + } + if (aInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_SYSTEM_PRINCIPAL, 0); + } + if (aInfo.type() == PrincipalInfo::TExpandedPrincipalInfo) { + const ExpandedPrincipalInfo& expanded = aInfo; + if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_EXPANDED_PRINCIPAL, 0) || + !JS_WriteUint32Pair(aWriter, expanded.whitelist().Length(), 0)) { + return false; + } + + for (uint32_t i = 0; i < expanded.whitelist().Length(); i++) { + if (!WritePrincipalInfo(aWriter, expanded.whitelist()[i])) { + return false; + } + } + return true; + } + + MOZ_ASSERT(aInfo.type() == PrincipalInfo::TContentPrincipalInfo); + const ContentPrincipalInfo& cInfo = aInfo; + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CONTENT_PRINCIPAL, 0) && + WriteSuffixAndSpec(aWriter, cInfo.attrs(), cInfo.spec()); +} + +bool +nsJSPrincipals::write(JSContext* aCx, JSStructuredCloneWriter* aWriter) +{ + PrincipalInfo info; + if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(this, &info)))) { + xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + return false; + } + + return WritePrincipalInfo(aWriter, info); +} diff --git a/caps/nsJSPrincipals.h b/caps/nsJSPrincipals.h new file mode 100644 index 000000000..5499345da --- /dev/null +++ b/caps/nsJSPrincipals.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* describes principals by their orginating uris*/ + +#ifndef nsJSPrincipals_h__ +#define nsJSPrincipals_h__ +#include "jsapi.h" +#include "nsIPrincipal.h" + +class nsJSPrincipals : public nsIPrincipal, public JSPrincipals +{ +public: + /* SpiderMonkey security callbacks. */ + static bool Subsume(JSPrincipals *jsprin, JSPrincipals *other); + static void Destroy(JSPrincipals *jsprin); + + /* JSReadPrincipalsOp for nsJSPrincipals */ + static bool ReadPrincipals(JSContext* aCx, JSStructuredCloneReader* aReader, + JSPrincipals** aOutPrincipals); + + static bool ReadKnownPrincipalType(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + JSPrincipals** aOutPrincipals); + + bool write(JSContext* aCx, JSStructuredCloneWriter* aWriter) final; + + /* + * Get a weak reference to nsIPrincipal associated with the given JS + * principal, and vice-versa. + */ + static nsJSPrincipals* get(JSPrincipals *principals) { + nsJSPrincipals *self = static_cast<nsJSPrincipals *>(principals); + MOZ_ASSERT_IF(self, self->debugToken == DEBUG_TOKEN); + return self; + } + static nsJSPrincipals* get(nsIPrincipal *principal) { + nsJSPrincipals *self = static_cast<nsJSPrincipals *>(principal); + MOZ_ASSERT_IF(self, self->debugToken == DEBUG_TOKEN); + return self; + } + + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; + + nsJSPrincipals() { + refcount = 0; + setDebugToken(DEBUG_TOKEN); + } + + /** + * Return a string that can be used as JS script filename in error reports. + */ + virtual nsresult GetScriptLocation(nsACString &aStr) = 0; + static const uint32_t DEBUG_TOKEN = 0x0bf41760; + +protected: + virtual ~nsJSPrincipals() { + setDebugToken(0); + } + +}; + +#endif /* nsJSPrincipals_h__ */ diff --git a/caps/nsNullPrincipal.cpp b/caps/nsNullPrincipal.cpp new file mode 100644 index 000000000..6ebf0f129 --- /dev/null +++ b/caps/nsNullPrincipal.cpp @@ -0,0 +1,193 @@ +/* -*- 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 "nsNullPrincipal.h" +#include "nsNullPrincipalURI.h" +#include "nsMemory.h" +#include "nsIURIWithPrincipal.h" +#include "nsIClassInfoImpl.h" +#include "nsNetCID.h" +#include "nsError.h" +#include "nsIScriptSecurityManager.h" +#include "nsPrincipal.h" +#include "nsScriptSecurityManager.h" +#include "pratom.h" + +using namespace mozilla; + +NS_IMPL_CLASSINFO(nsNullPrincipal, nullptr, nsIClassInfo::MAIN_THREAD_ONLY, + NS_NULLPRINCIPAL_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsNullPrincipal, + nsIPrincipal, + nsISerializable) +NS_IMPL_CI_INTERFACE_GETTER(nsNullPrincipal, + nsIPrincipal, + nsISerializable) + +/* static */ already_AddRefed<nsNullPrincipal> +nsNullPrincipal::CreateWithInheritedAttributes(nsIPrincipal* aInheritFrom) +{ + RefPtr<nsNullPrincipal> nullPrin = new nsNullPrincipal(); + nsresult rv = nullPrin->Init(Cast(aInheritFrom)->OriginAttributesRef()); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + return nullPrin.forget(); +} + +/* static */ already_AddRefed<nsNullPrincipal> +nsNullPrincipal::CreateWithInheritedAttributes(nsIDocShell* aDocShell) +{ + PrincipalOriginAttributes attrs; + attrs.InheritFromDocShellToDoc(nsDocShell::Cast(aDocShell)->GetOriginAttributes(), nullptr); + + RefPtr<nsNullPrincipal> nullPrin = new nsNullPrincipal(); + nsresult rv = nullPrin->Init(attrs); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + return nullPrin.forget(); +} + +/* static */ already_AddRefed<nsNullPrincipal> +nsNullPrincipal::Create(const PrincipalOriginAttributes& aOriginAttributes) +{ + RefPtr<nsNullPrincipal> nullPrin = new nsNullPrincipal(); + nsresult rv = nullPrin->Init(aOriginAttributes); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + + return nullPrin.forget(); +} + +nsresult +nsNullPrincipal::Init(const PrincipalOriginAttributes& aOriginAttributes) +{ + mOriginAttributes = aOriginAttributes; + + mURI = nsNullPrincipalURI::Create(); + NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_AVAILABLE); + + return NS_OK; +} + +nsresult +nsNullPrincipal::GetScriptLocation(nsACString &aStr) +{ + return mURI->GetSpec(aStr); +} + +/** + * nsIPrincipal implementation + */ + +NS_IMETHODIMP +nsNullPrincipal::GetHashValue(uint32_t *aResult) +{ + *aResult = (NS_PTR_TO_INT32(this) >> 2); + return NS_OK; +} + +NS_IMETHODIMP +nsNullPrincipal::SetCsp(nsIContentSecurityPolicy* aCsp) { + // Never destroy an existing CSP on the principal. + // This method should only be called in rare cases. + + MOZ_ASSERT(!mCSP, "do not destroy an existing CSP"); + if (mCSP) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + mCSP = aCsp; + return NS_OK; +} + +NS_IMETHODIMP +nsNullPrincipal::GetURI(nsIURI** aURI) +{ + return NS_EnsureSafeToReturn(mURI, aURI); +} + +NS_IMETHODIMP +nsNullPrincipal::GetDomain(nsIURI** aDomain) +{ + return NS_EnsureSafeToReturn(mURI, aDomain); +} + +NS_IMETHODIMP +nsNullPrincipal::SetDomain(nsIURI* aDomain) +{ + // I think the right thing to do here is to just throw... Silently failing + // seems counterproductive. + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult +nsNullPrincipal::GetOriginInternal(nsACString& aOrigin) +{ + return mURI->GetSpec(aOrigin); +} + +bool +nsNullPrincipal::MayLoadInternal(nsIURI* aURI) +{ + // Also allow the load if we are the principal of the URI being checked. + nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(aURI); + if (uriPrinc) { + nsCOMPtr<nsIPrincipal> principal; + uriPrinc->GetPrincipal(getter_AddRefs(principal)); + + if (principal == this) { + return true; + } + } + + return false; +} + +NS_IMETHODIMP +nsNullPrincipal::GetBaseDomain(nsACString& aBaseDomain) +{ + // For a null principal, we use our unique uuid as the base domain. + return mURI->GetPath(aBaseDomain); +} + +/** + * nsISerializable implementation + */ +NS_IMETHODIMP +nsNullPrincipal::Read(nsIObjectInputStream* aStream) +{ + // Note - nsNullPrincipal 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 nsPrincipal, which uses NS_GENERIC_FACTORY_CONSTRUCTOR, + // in which case ::Read needs to invoke Init(). + nsAutoCString suffix; + nsresult rv = aStream->ReadCString(suffix); + NS_ENSURE_SUCCESS(rv, rv); + + bool ok = mOriginAttributes.PopulateFromSuffix(suffix); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + + return NS_OK; +} + +NS_IMETHODIMP +nsNullPrincipal::Write(nsIObjectOutputStream* aStream) +{ + nsAutoCString suffix; + OriginAttributesRef().CreateSuffix(suffix); + + nsresult rv = aStream->WriteStringZ(suffix.get()); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + diff --git a/caps/nsNullPrincipal.h b/caps/nsNullPrincipal.h new file mode 100644 index 000000000..b29474773 --- /dev/null +++ b/caps/nsNullPrincipal.h @@ -0,0 +1,78 @@ +/* -*- 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 nsNullPrincipal_h__ +#define nsNullPrincipal_h__ + +#include "nsIPrincipal.h" +#include "nsJSPrincipals.h" +#include "nsIScriptSecurityManager.h" +#include "nsCOMPtr.h" +#include "nsIContentSecurityPolicy.h" + +#include "mozilla/BasePrincipal.h" + +class nsIDocShell; +class nsIURI; + +#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" + +class nsNullPrincipal final : public mozilla::BasePrincipal +{ +public: + // This should only be used by deserialization, and the factory constructor. + // Other consumers should use the Create and CreateWithInheritedAttributes + // methods. + nsNullPrincipal() {} + + NS_DECL_NSISERIALIZABLE + + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + NS_IMETHOD GetHashValue(uint32_t* aHashValue) override; + NS_IMETHOD SetCsp(nsIContentSecurityPolicy* aCsp) 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; + nsresult GetOriginInternal(nsACString& aOrigin) override; + + static already_AddRefed<nsNullPrincipal> CreateWithInheritedAttributes(nsIPrincipal* aInheritFrom); + + static already_AddRefed<nsNullPrincipal> CreateWithInheritedAttributes(nsIDocShell* aDocShell); + + static already_AddRefed<nsNullPrincipal> + Create(const mozilla::PrincipalOriginAttributes& aOriginAttributes = mozilla::PrincipalOriginAttributes()); + + nsresult Init(const mozilla::PrincipalOriginAttributes& aOriginAttributes = mozilla::PrincipalOriginAttributes()); + + virtual nsresult GetScriptLocation(nsACString &aStr) override; + + PrincipalKind Kind() override { return eNullPrincipal; } + + protected: + virtual ~nsNullPrincipal() {} + + bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) override + { + return aOther == this; + } + + bool MayLoadInternal(nsIURI* aURI) override; + + nsCOMPtr<nsIURI> mURI; +}; + +#endif // nsNullPrincipal_h__ diff --git a/caps/nsNullPrincipalURI.cpp b/caps/nsNullPrincipalURI.cpp new file mode 100644 index 000000000..891a29bd8 --- /dev/null +++ b/caps/nsNullPrincipalURI.cpp @@ -0,0 +1,362 @@ +/* -*- 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 "nsNullPrincipalURI.h" + +#include "mozilla/DebugOnly.h" +#include "mozilla/MemoryReporting.h" + +#include "mozilla/ipc/URIParams.h" + +#include "nsEscape.h" +#include "nsCRT.h" +#include "nsIUUIDGenerator.h" + +//////////////////////////////////////////////////////////////////////////////// +//// nsNullPrincipalURI + +nsNullPrincipalURI::nsNullPrincipalURI() + : mPath(mPathBytes, ArrayLength(mPathBytes), ArrayLength(mPathBytes) - 1) +{ +} + +nsNullPrincipalURI::nsNullPrincipalURI(const nsNullPrincipalURI& aOther) + : mPath(mPathBytes, ArrayLength(mPathBytes), ArrayLength(mPathBytes) - 1) +{ + mPath.Assign(aOther.mPath); +} + +nsresult +nsNullPrincipalURI::Init() +{ + // FIXME: bug 327161 -- make sure the uuid generator is reseeding-resistant. + nsCOMPtr<nsIUUIDGenerator> uuidgen = services::GetUUIDGenerator(); + NS_ENSURE_TRUE(uuidgen, NS_ERROR_NOT_AVAILABLE); + + nsID id; + nsresult rv = uuidgen->GenerateUUIDInPlace(&id); + NS_ENSURE_SUCCESS(rv, rv); + + MOZ_ASSERT(mPathBytes == mPath.BeginWriting()); + + id.ToProvidedString(mPathBytes); + + MOZ_ASSERT(mPath.Length() == NSID_LENGTH - 1); + MOZ_ASSERT(strlen(mPath.get()) == NSID_LENGTH - 1); + + return NS_OK; +} + +/* static */ +already_AddRefed<nsNullPrincipalURI> +nsNullPrincipalURI::Create() +{ + RefPtr<nsNullPrincipalURI> uri = new nsNullPrincipalURI(); + nsresult rv = uri->Init(); + NS_ENSURE_SUCCESS(rv, nullptr); + return uri.forget(); +} + +static NS_DEFINE_CID(kNullPrincipalURIImplementationCID, + NS_NULLPRINCIPALURI_IMPLEMENTATION_CID); + +NS_IMPL_ADDREF(nsNullPrincipalURI) +NS_IMPL_RELEASE(nsNullPrincipalURI) + +NS_INTERFACE_MAP_BEGIN(nsNullPrincipalURI) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURI) + if (aIID.Equals(kNullPrincipalURIImplementationCID)) + foundInterface = static_cast<nsIURI *>(this); + else + NS_INTERFACE_MAP_ENTRY(nsIURI) + NS_INTERFACE_MAP_ENTRY(nsISizeOf) + NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableURI) +NS_INTERFACE_MAP_END + +//////////////////////////////////////////////////////////////////////////////// +//// nsIURI + +NS_IMETHODIMP +nsNullPrincipalURI::GetAsciiHost(nsACString &_host) +{ + _host.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsNullPrincipalURI::GetAsciiHostPort(nsACString &_hostport) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::GetAsciiSpec(nsACString &_spec) +{ + nsAutoCString buffer; + // Ignore the return value -- nsNullPrincipalURI::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 +nsNullPrincipalURI::GetHost(nsACString &_host) +{ + _host.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsNullPrincipalURI::SetHost(const nsACString &aHost) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::GetHostPort(nsACString &_host) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::SetHostPort(const nsACString &aHost) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::SetHostAndPort(const nsACString &aHost) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::GetOriginCharset(nsACString &_charset) +{ + _charset.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsNullPrincipalURI::GetPassword(nsACString &_password) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::SetPassword(const nsACString &aPassword) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::GetPath(nsACString &_path) +{ + _path = mPath; + return NS_OK; +} + +NS_IMETHODIMP +nsNullPrincipalURI::SetPath(const nsACString &aPath) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::GetRef(nsACString &_ref) +{ + _ref.Truncate(); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::SetRef(const nsACString &aRef) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::GetPrePath(nsACString &_prePath) +{ + _prePath = NS_LITERAL_CSTRING(NS_NULLPRINCIPAL_SCHEME ":"); + return NS_OK; +} + +NS_IMETHODIMP +nsNullPrincipalURI::GetPort(int32_t *_port) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::SetPort(int32_t aPort) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::GetScheme(nsACString &_scheme) +{ + _scheme = NS_LITERAL_CSTRING(NS_NULLPRINCIPAL_SCHEME); + return NS_OK; +} + +NS_IMETHODIMP +nsNullPrincipalURI::SetScheme(const nsACString &aScheme) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::GetSpec(nsACString &_spec) +{ + _spec = NS_LITERAL_CSTRING(NS_NULLPRINCIPAL_SCHEME ":") + mPath; + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsNullPrincipalURI::GetSpecIgnoringRef(nsACString &result) +{ + return GetSpec(result); +} + +NS_IMETHODIMP +nsNullPrincipalURI::GetHasRef(bool *result) +{ + *result = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNullPrincipalURI::SetSpec(const nsACString &aSpec) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::GetUsername(nsACString &_username) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::SetUsername(const nsACString &aUsername) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::GetUserPass(nsACString &_userPass) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::SetUserPass(const nsACString &aUserPass) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNullPrincipalURI::Clone(nsIURI **_newURI) +{ + nsCOMPtr<nsIURI> uri = new nsNullPrincipalURI(*this); + uri.forget(_newURI); + return NS_OK; +} + +NS_IMETHODIMP +nsNullPrincipalURI::CloneIgnoringRef(nsIURI **_newURI) +{ + // GetRef/SetRef not supported by nsNullPrincipalURI, so + // CloneIgnoringRef() is the same as Clone(). + return Clone(_newURI); +} + +NS_IMETHODIMP +nsNullPrincipalURI::CloneWithNewRef(const nsACString& newRef, nsIURI **_newURI) +{ + // GetRef/SetRef not supported by nsNullPrincipalURI, so + // CloneWithNewRef() is the same as Clone(). + return Clone(_newURI); +} + +NS_IMETHODIMP +nsNullPrincipalURI::Equals(nsIURI *aOther, bool *_equals) +{ + *_equals = false; + RefPtr<nsNullPrincipalURI> otherURI; + nsresult rv = aOther->QueryInterface(kNullPrincipalURIImplementationCID, + getter_AddRefs(otherURI)); + if (NS_SUCCEEDED(rv)) { + *_equals = mPath == otherURI->mPath; + } + return NS_OK; +} + +NS_IMETHODIMP +nsNullPrincipalURI::EqualsExceptRef(nsIURI *aOther, bool *_equals) +{ + // GetRef/SetRef not supported by nsNullPrincipalURI, so + // EqualsExceptRef() is the same as Equals(). + return Equals(aOther, _equals); +} + +NS_IMETHODIMP +nsNullPrincipalURI::Resolve(const nsACString &aRelativePath, + nsACString &_resolvedURI) +{ + _resolvedURI = aRelativePath; + return NS_OK; +} + +NS_IMETHODIMP +nsNullPrincipalURI::SchemeIs(const char *aScheme, bool *_schemeIs) +{ + *_schemeIs = (0 == nsCRT::strcasecmp(NS_NULLPRINCIPAL_SCHEME, aScheme)); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// nsIIPCSerializableURI + +void +nsNullPrincipalURI::Serialize(mozilla::ipc::URIParams &aParams) +{ + aParams = mozilla::ipc::NullPrincipalURIParams(); +} + +bool +nsNullPrincipalURI::Deserialize(const mozilla::ipc::URIParams &aParams) +{ + if (aParams.type() != mozilla::ipc::URIParams::TNullPrincipalURIParams) { + MOZ_ASSERT_UNREACHABLE("unexpected URIParams type"); + return false; + } + + nsresult rv = Init(); + NS_ENSURE_SUCCESS(rv, false); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//// nsISizeOf + +size_t +nsNullPrincipalURI::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + return mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +size_t +nsNullPrincipalURI::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + diff --git a/caps/nsNullPrincipalURI.h b/caps/nsNullPrincipalURI.h new file mode 100644 index 000000000..63d7221c2 --- /dev/null +++ b/caps/nsNullPrincipalURI.h @@ -0,0 +1,59 @@ +/* -*- 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 __nsNullPrincipalURI_h__ +#define __nsNullPrincipalURI_h__ + +#include "nsIURI.h" +#include "nsISizeOf.h" +#include "nsString.h" +#include "mozilla/Attributes.h" +#include "nsIIPCSerializableURI.h" +#include "mozilla/MemoryReporting.h" +#include "nsNullPrincipal.h" +#include "nsID.h" + +// {51fcd543-3b52-41f7-b91b-6b54102236e6} +#define NS_NULLPRINCIPALURI_IMPLEMENTATION_CID \ + {0x51fcd543, 0x3b52, 0x41f7, \ + {0xb9, 0x1b, 0x6b, 0x54, 0x10, 0x22, 0x36, 0xe6} } + +class nsNullPrincipalURI final : public nsIURI + , public nsISizeOf + , public nsIIPCSerializableURI +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURI + NS_DECL_NSIIPCSERIALIZABLEURI + + // nsISizeOf + virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override; + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override; + + // NB: This constructor exists only for deserialization. Everyone + // else should call Create. + nsNullPrincipalURI(); + + // Returns null on failure. + static already_AddRefed<nsNullPrincipalURI> Create(); + +private: + nsNullPrincipalURI(const nsNullPrincipalURI& aOther); + + ~nsNullPrincipalURI() {} + + nsresult Init(); + + char mPathBytes[NSID_LENGTH]; + nsFixedCString mPath; +}; + +#endif // __nsNullPrincipalURI_h__ diff --git a/caps/nsPrincipal.cpp b/caps/nsPrincipal.cpp new file mode 100644 index 000000000..4cb472369 --- /dev/null +++ b/caps/nsPrincipal.cpp @@ -0,0 +1,854 @@ +/* -*- 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 "nsPrincipal.h" + +#include "mozIThirdPartyUtil.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 "nsIURIWithPrincipal.h" +#include "nsJSPrincipals.h" +#include "nsIEffectiveTLDService.h" +#include "nsIClassInfoImpl.h" +#include "nsIProtocolHandler.h" +#include "nsError.h" +#include "nsIContentSecurityPolicy.h" +#include "nsNetCID.h" +#include "jswrapper.h" + +#include "mozilla/dom/nsCSPContext.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/Preferences.h" +#include "mozilla/HashFunctions.h" + +#include "nsIAppsService.h" + +using namespace mozilla; + +static bool gIsWhitelistingTestDomains = false; +static bool gCodeBasePrincipalSupport = false; + +static bool URIIsImmutable(nsIURI* aURI) +{ + nsCOMPtr<nsIMutable> mutableObj(do_QueryInterface(aURI)); + bool isMutable; + return + mutableObj && + NS_SUCCEEDED(mutableObj->GetMutable(&isMutable)) && + !isMutable; +} + +NS_IMPL_CLASSINFO(nsPrincipal, nullptr, nsIClassInfo::MAIN_THREAD_ONLY, + NS_PRINCIPAL_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsPrincipal, + nsIPrincipal, + nsISerializable) +NS_IMPL_CI_INTERFACE_GETTER(nsPrincipal, + nsIPrincipal, + nsISerializable) + +// Called at startup: +/* static */ void +nsPrincipal::InitializeStatics() +{ + Preferences::AddBoolVarCache( + &gIsWhitelistingTestDomains, + "layout.css.unprefixing-service.include-test-domains"); + + Preferences::AddBoolVarCache(&gCodeBasePrincipalSupport, + "signed.applets.codebase_principal_support", + false); +} + +nsPrincipal::nsPrincipal() + : mCodebaseImmutable(false) + , mDomainImmutable(false) + , mInitialized(false) +{ } + +nsPrincipal::~nsPrincipal() +{ + // let's clear the principal within the csp to avoid a tangling pointer + if (mCSP) { + static_cast<nsCSPContext*>(mCSP.get())->clearLoadingPrincipal(); + } +} + +nsresult +nsPrincipal::Init(nsIURI *aCodebase, const PrincipalOriginAttributes& aOriginAttributes) +{ + NS_ENSURE_STATE(!mInitialized); + NS_ENSURE_ARG(aCodebase); + + mInitialized = true; + + mCodebase = NS_TryToMakeImmutable(aCodebase); + mCodebaseImmutable = URIIsImmutable(mCodebase); + mOriginAttributes = aOriginAttributes; + + return NS_OK; +} + +nsresult +nsPrincipal::GetScriptLocation(nsACString &aStr) +{ + return mCodebase->GetSpec(aStr); +} + +/* static */ nsresult +nsPrincipal::GetOriginForURI(nsIURI* aURI, nsACString& aOrigin) +{ + if (!aURI) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIURI> origin = NS_GetInnermostURI(aURI); + if (!origin) { + return NS_ERROR_FAILURE; + } + + 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(aOrigin); + } +#endif + + nsAutoCString hostPort; + + // chrome: URLs don't have a meaningful origin, so make + // sure we just get the full spec for them. + // XXX this should be removed in favor of the solution in + // bug 160042. + bool isChrome; + rv = origin->SchemeIs("chrome", &isChrome); + if (NS_SUCCEEDED(rv) && !isChrome) { + rv = origin->GetAsciiHostPort(hostPort); + // Some implementations return an empty string, treat it as no support + // for asciiHost by that implementation. + if (hostPort.IsEmpty()) { + rv = NS_ERROR_FAILURE; + } + } + + // 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. + bool isBehaved; + if ((NS_SUCCEEDED(origin->SchemeIs("about", &isBehaved)) && isBehaved) || + (NS_SUCCEEDED(origin->SchemeIs("moz-safe-about", &isBehaved)) && isBehaved) || + (NS_SUCCEEDED(origin->SchemeIs("indexeddb", &isBehaved)) && isBehaved)) { + rv = origin->GetAsciiSpec(aOrigin); + NS_ENSURE_SUCCESS(rv, rv); + // These URIs could technically contain a '^', but they never should. + if (NS_WARN_IF(aOrigin.FindChar('^', 0) != -1)) { + aOrigin.Truncate(); + return NS_ERROR_FAILURE; + } + return NS_OK; + } + + if (NS_SUCCEEDED(rv) && !isChrome) { + rv = origin->GetScheme(aOrigin); + NS_ENSURE_SUCCESS(rv, rv); + aOrigin.AppendLiteral("://"); + aOrigin.Append(hostPort); + } + else { + // If we reached this branch, we can only create an origin if we have a nsIStandardURL. + // So, we query to a nsIStandardURL, and fail if we aren't an instance of an nsIStandardURL + // nsIStandardURLs have the good property of escaping the '^' character in their specs, + // which means that we can be sure that the caret character (which is reserved for delimiting + // the end of the spec, and the beginning of the origin attributes) is not present in the + // origin string + nsCOMPtr<nsIStandardURL> standardURL = do_QueryInterface(origin); + NS_ENSURE_TRUE(standardURL, NS_ERROR_FAILURE); + rv = origin->GetAsciiSpec(aOrigin); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +nsPrincipal::GetOriginInternal(nsACString& aOrigin) +{ + return GetOriginForURI(mCodebase, aOrigin); +} + +bool +nsPrincipal::SubsumesInternal(nsIPrincipal* aOther, + BasePrincipal::DocumentDomainConsideration aConsideration) +{ + MOZ_ASSERT(aOther); + + // For nsPrincipal, 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<nsIURI> thisDomain, otherDomain; + GetDomain(getter_AddRefs(thisDomain)); + aOther->GetDomain(getter_AddRefs(otherDomain)); + + // If either has .domain set, we have equality i.f.f. the domains match. + // Otherwise, we fall through to the non-document-domain-considering case. + if (thisDomain || otherDomain) { + return nsScriptSecurityManager::SecurityCompareURIs(thisDomain, otherDomain); + } + } + + nsCOMPtr<nsIURI> otherURI; + rv = aOther->GetURI(getter_AddRefs(otherURI)); + NS_ENSURE_SUCCESS(rv, false); + + // Compare codebases. + return nsScriptSecurityManager::SecurityCompareURIs(mCodebase, otherURI); +} + +NS_IMETHODIMP +nsPrincipal::GetURI(nsIURI** aURI) +{ + if (mCodebaseImmutable) { + NS_ADDREF(*aURI = mCodebase); + return NS_OK; + } + + if (!mCodebase) { + *aURI = nullptr; + return NS_OK; + } + + return NS_EnsureSafeToReturn(mCodebase, aURI); +} + +bool +nsPrincipal::MayLoadInternal(nsIURI* aURI) +{ + // See if aURI is something like a Blob URI that is actually associated with + // a principal. + nsCOMPtr<nsIURIWithPrincipal> uriWithPrin = do_QueryInterface(aURI); + nsCOMPtr<nsIPrincipal> uriPrin; + if (uriWithPrin) { + uriWithPrin->GetPrincipal(getter_AddRefs(uriPrin)); + } + if (uriPrin) { + return nsIPrincipal::Subsumes(uriPrin); + } + + // 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(mCodebase, 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, mCodebase)) { + return true; + } + + return false; +} + +void +nsPrincipal::SetURI(nsIURI* aURI) +{ + mCodebase = NS_TryToMakeImmutable(aURI); + mCodebaseImmutable = URIIsImmutable(mCodebase); +} + +NS_IMETHODIMP +nsPrincipal::GetHashValue(uint32_t* aValue) +{ + NS_PRECONDITION(mCodebase, "Need a codebase"); + + *aValue = nsScriptSecurityManager::HashPrincipalByOrigin(this); + return NS_OK; +} + +NS_IMETHODIMP +nsPrincipal::GetDomain(nsIURI** aDomain) +{ + if (!mDomain) { + *aDomain = nullptr; + return NS_OK; + } + + if (mDomainImmutable) { + NS_ADDREF(*aDomain = mDomain); + return NS_OK; + } + + return NS_EnsureSafeToReturn(mDomain, aDomain); +} + +NS_IMETHODIMP +nsPrincipal::SetDomain(nsIURI* aDomain) +{ + mDomain = NS_TryToMakeImmutable(aDomain); + mDomainImmutable = URIIsImmutable(mDomain); + + // Recompute all wrappers between compartments using this principal and other + // non-chrome compartments. + AutoSafeJSContext cx; + JSPrincipals *principals = nsJSPrincipals::get(static_cast<nsIPrincipal*>(this)); + bool success = js::RecomputeWrappers(cx, js::ContentCompartmentsOnly(), + js::CompartmentsWithPrincipals(principals)); + NS_ENSURE_TRUE(success, NS_ERROR_FAILURE); + success = js::RecomputeWrappers(cx, js::CompartmentsWithPrincipals(principals), + js::ContentCompartmentsOnly()); + NS_ENSURE_TRUE(success, NS_ERROR_FAILURE); + + return NS_OK; +} + +NS_IMETHODIMP +nsPrincipal::GetBaseDomain(nsACString& aBaseDomain) +{ + // For a file URI, we return the file path. + if (NS_URIIsLocalFile(mCodebase)) { + nsCOMPtr<nsIURL> url = do_QueryInterface(mCodebase); + + if (url) { + return url->GetFilePath(aBaseDomain); + } + } + + bool hasNoRelativeFlag; + nsresult rv = NS_URIChainHasFlags(mCodebase, + nsIProtocolHandler::URI_NORELATIVE, + &hasNoRelativeFlag); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (hasNoRelativeFlag) { + return mCodebase->GetSpec(aBaseDomain); + } + + // For everything else, we ask the TLD service via + // the ThirdPartyUtil. + nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID); + if (thirdPartyUtil) { + return thirdPartyUtil->GetBaseDomain(mCodebase, aBaseDomain); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPrincipal::Read(nsIObjectInputStream* aStream) +{ + nsCOMPtr<nsISupports> supports; + nsCOMPtr<nsIURI> codebase; + nsresult rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports)); + if (NS_FAILED(rv)) { + return rv; + } + + codebase = do_QueryInterface(supports); + + nsCOMPtr<nsIURI> domain; + rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports)); + if (NS_FAILED(rv)) { + return rv; + } + + domain = do_QueryInterface(supports); + + nsAutoCString suffix; + rv = aStream->ReadCString(suffix); + NS_ENSURE_SUCCESS(rv, rv); + + PrincipalOriginAttributes attrs; + bool ok = attrs.PopulateFromSuffix(suffix); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + + rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = Init(codebase, attrs); + NS_ENSURE_SUCCESS(rv, rv); + + mCSP = do_QueryInterface(supports, &rv); + // make sure setRequestContext is called after Init(), + // to make sure the principals URI been initalized. + if (mCSP) { + mCSP->SetRequestContext(nullptr, this); + } + + SetDomain(domain); + + return NS_OK; +} + +NS_IMETHODIMP +nsPrincipal::Write(nsIObjectOutputStream* aStream) +{ + NS_ENSURE_STATE(mCodebase); + nsresult rv = NS_WriteOptionalCompoundObject(aStream, mCodebase, NS_GET_IID(nsIURI), + true); + if (NS_FAILED(rv)) { + return rv; + } + + rv = NS_WriteOptionalCompoundObject(aStream, mDomain, NS_GET_IID(nsIURI), + true); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoCString suffix; + OriginAttributesRef().CreateSuffix(suffix); + + rv = aStream->WriteStringZ(suffix.get()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_WriteOptionalCompoundObject(aStream, mCSP, + NS_GET_IID(nsIContentSecurityPolicy), + true); + if (NS_FAILED(rv)) { + return rv; + } + + // mCodebaseImmutable and mDomainImmutable will be recomputed based + // on the deserialized URIs in Read(). + + return NS_OK; +} + +// Helper-function to indicate whether the CSS Unprefixing Service +// whitelist should include dummy domains that are only intended for +// use in testing. (Controlled by a pref.) +static inline bool +IsWhitelistingTestDomains() +{ + return gIsWhitelistingTestDomains; +} + +// Checks if the given URI's host is on our "full domain" whitelist +// (i.e. if it's an exact match against a domain that needs unprefixing) +static bool +IsOnFullDomainWhitelist(nsIURI* aURI) +{ + nsAutoCString hostStr; + nsresult rv = aURI->GetHost(hostStr); + NS_ENSURE_SUCCESS(rv, false); + + // NOTE: This static whitelist is expected to be short. If that changes, + // we should consider a different representation; e.g. hash-set, prefix tree. + static const nsLiteralCString sFullDomainsOnWhitelist[] = { + // 0th entry only active when testing: + NS_LITERAL_CSTRING("test1.example.org"), + NS_LITERAL_CSTRING("map.baidu.com"), + NS_LITERAL_CSTRING("3g.163.com"), + NS_LITERAL_CSTRING("3glogo.gtimg.com"), // for 3g.163.com + NS_LITERAL_CSTRING("info.3g.qq.com"), // for 3g.qq.com + NS_LITERAL_CSTRING("3gimg.qq.com"), // for 3g.qq.com + NS_LITERAL_CSTRING("img.m.baidu.com"), // for [shucheng|ks].baidu.com + NS_LITERAL_CSTRING("m.mogujie.com"), + NS_LITERAL_CSTRING("touch.qunar.com"), + NS_LITERAL_CSTRING("mjs.sinaimg.cn"), // for sina.cn + NS_LITERAL_CSTRING("static.qiyi.com"), // for m.iqiyi.com + NS_LITERAL_CSTRING("cdn.kuaidi100.com"), // for m.kuaidi100.com + NS_LITERAL_CSTRING("m.pc6.com"), + NS_LITERAL_CSTRING("m.haosou.com"), + NS_LITERAL_CSTRING("m.mi.com"), + NS_LITERAL_CSTRING("wappass.baidu.com"), + NS_LITERAL_CSTRING("m.video.baidu.com"), + NS_LITERAL_CSTRING("m.video.baidu.com"), + NS_LITERAL_CSTRING("imgcache.gtimg.cn"), // for m.v.qq.com + NS_LITERAL_CSTRING("s.tabelog.jp"), + NS_LITERAL_CSTRING("s.yimg.jp"), // for s.tabelog.jp + NS_LITERAL_CSTRING("i.yimg.jp"), // for *.yahoo.co.jp + NS_LITERAL_CSTRING("ai.yimg.jp"), // for *.yahoo.co.jp + NS_LITERAL_CSTRING("m.finance.yahoo.co.jp"), + NS_LITERAL_CSTRING("daily.c.yimg.jp"), // for sp.daily.co.jp + NS_LITERAL_CSTRING("stat100.ameba.jp"), // for ameblo.jp + NS_LITERAL_CSTRING("user.ameba.jp"), // for ameblo.jp + NS_LITERAL_CSTRING("www.goo.ne.jp"), + NS_LITERAL_CSTRING("x.gnst.jp"), // for mobile.gnavi.co.jp + NS_LITERAL_CSTRING("c.x.gnst.jp"), // for mobile.gnavi.co.jp + NS_LITERAL_CSTRING("www.smbc-card.com"), + NS_LITERAL_CSTRING("static.card.jp.rakuten-static.com"), // for rakuten-card.co.jp + NS_LITERAL_CSTRING("img.travel.rakuten.co.jp"), // for travel.rakuten.co.jp + NS_LITERAL_CSTRING("img.mixi.net"), // for mixi.jp + NS_LITERAL_CSTRING("girlschannel.net"), + NS_LITERAL_CSTRING("www.fancl.co.jp"), + NS_LITERAL_CSTRING("s.cosme.net"), + NS_LITERAL_CSTRING("www.sapporobeer.jp"), + NS_LITERAL_CSTRING("www.mapion.co.jp"), + NS_LITERAL_CSTRING("touch.navitime.co.jp"), + NS_LITERAL_CSTRING("sp.mbga.jp"), + NS_LITERAL_CSTRING("ava-a.sp.mbga.jp"), // for sp.mbga.jp + NS_LITERAL_CSTRING("www.ntv.co.jp"), + NS_LITERAL_CSTRING("mobile.suntory.co.jp"), // for suntory.jp + NS_LITERAL_CSTRING("www.aeonsquare.net"), + NS_LITERAL_CSTRING("mw.nikkei.com"), + NS_LITERAL_CSTRING("www.nhk.or.jp"), + NS_LITERAL_CSTRING("www.tokyo-sports.co.jp"), + NS_LITERAL_CSTRING("www.bellemaison.jp"), + NS_LITERAL_CSTRING("www.kuronekoyamato.co.jp"), + NS_LITERAL_CSTRING("formassist.jp"), // for orico.jp + NS_LITERAL_CSTRING("sp.m.reuters.co.jp"), + NS_LITERAL_CSTRING("www.atre.co.jp"), + NS_LITERAL_CSTRING("www.jtb.co.jp"), + NS_LITERAL_CSTRING("www.sharp.co.jp"), + NS_LITERAL_CSTRING("www.biccamera.com"), + NS_LITERAL_CSTRING("weathernews.jp"), + NS_LITERAL_CSTRING("cache.ymail.jp"), // for www.yamada-denkiweb.com + }; + static const size_t sNumFullDomainsOnWhitelist = + MOZ_ARRAY_LENGTH(sFullDomainsOnWhitelist); + + // Skip 0th (dummy) entry in whitelist, unless a pref is enabled. + const size_t firstWhitelistIdx = IsWhitelistingTestDomains() ? 0 : 1; + + for (size_t i = firstWhitelistIdx; i < sNumFullDomainsOnWhitelist; ++i) { + if (hostStr == sFullDomainsOnWhitelist[i]) { + return true; + } + } + return false; +} + +// Checks if the given URI's host is on our "base domain" whitelist +// (i.e. if it's a subdomain of some host that we've whitelisted as needing +// unprefixing for all its subdomains) +static bool +IsOnBaseDomainWhitelist(nsIURI* aURI) +{ + static const nsLiteralCString sBaseDomainsOnWhitelist[] = { + // 0th entry only active when testing: + NS_LITERAL_CSTRING("test2.example.org"), + NS_LITERAL_CSTRING("tbcdn.cn"), // for m.taobao.com + NS_LITERAL_CSTRING("alicdn.com"), // for m.taobao.com + NS_LITERAL_CSTRING("dpfile.com"), // for m.dianping.com + NS_LITERAL_CSTRING("hao123img.com"), // for hao123.com + NS_LITERAL_CSTRING("tabelog.k-img.com"), // for s.tabelog.com + NS_LITERAL_CSTRING("tsite.jp"), // for *.tsite.jp + }; + static const size_t sNumBaseDomainsOnWhitelist = + MOZ_ARRAY_LENGTH(sBaseDomainsOnWhitelist); + + nsCOMPtr<nsIEffectiveTLDService> tldService = + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + + if (tldService) { + // Skip 0th test-entry in whitelist, unless the testing pref is enabled. + const size_t firstWhitelistIdx = IsWhitelistingTestDomains() ? 0 : 1; + + // Right now, the test base-domain "test2.example.org" is the only entry in + // its whitelist with a nonzero "depth". So we'll only bother going beyond + // 0 depth (to 1) if that entry is enabled. (No point in slowing down the + // normal codepath, for the benefit of a disabled test domain.) If we add a + // "real" base-domain with a depth of >= 1 to our whitelist, we can get rid + // of this conditional & just make this a static variable. + const uint32_t maxSubdomainDepth = IsWhitelistingTestDomains() ? 1 : 0; + + for (uint32_t subdomainDepth = 0; + subdomainDepth <= maxSubdomainDepth; ++subdomainDepth) { + + // Get the base domain (to depth |subdomainDepth|) from passed-in URI: + nsAutoCString baseDomainStr; + nsresult rv = tldService->GetBaseDomain(aURI, subdomainDepth, + baseDomainStr); + if (NS_FAILED(rv)) { + // aURI doesn't have |subdomainDepth| levels of subdomains. If we got + // here without a match yet, then aURI is not on our whitelist. + return false; + } + + // Compare the base domain against each entry in our whitelist: + for (size_t i = firstWhitelistIdx; i < sNumBaseDomainsOnWhitelist; ++i) { + if (baseDomainStr == sBaseDomainsOnWhitelist[i]) { + return true; + } + } + } + } + + return false; +} + +// The actual (non-cached) implementation of IsOnCSSUnprefixingWhitelist(): +static bool +IsOnCSSUnprefixingWhitelistImpl(nsIURI* aURI) +{ + // Check scheme, so we can drop any non-HTTP/HTTPS URIs right away + nsAutoCString schemeStr; + nsresult rv = aURI->GetScheme(schemeStr); + NS_ENSURE_SUCCESS(rv, false); + + // Only proceed if scheme is "http" or "https" + if (!(StringBeginsWith(schemeStr, NS_LITERAL_CSTRING("http")) && + (schemeStr.Length() == 4 || + (schemeStr.Length() == 5 && schemeStr[4] == 's')))) { + return false; + } + + return (IsOnFullDomainWhitelist(aURI) || + IsOnBaseDomainWhitelist(aURI)); +} + + +bool +nsPrincipal::IsOnCSSUnprefixingWhitelist() +{ + if (mIsOnCSSUnprefixingWhitelist.isNothing()) { + // Value not cached -- perform our lazy whitelist-check. + // (NOTE: If our URI is mutable, we just assume it's not on the whitelist, + // since our caching strategy won't work. This isn't expected to be common.) + mIsOnCSSUnprefixingWhitelist.emplace( + mCodebaseImmutable && + IsOnCSSUnprefixingWhitelistImpl(mCodebase)); + } + + return *mIsOnCSSUnprefixingWhitelist; +} + +/************************************************************************************************************************/ + +NS_IMPL_CLASSINFO(nsExpandedPrincipal, nullptr, nsIClassInfo::MAIN_THREAD_ONLY, + NS_EXPANDEDPRINCIPAL_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsExpandedPrincipal, + nsIPrincipal, + nsIExpandedPrincipal) +NS_IMPL_CI_INTERFACE_GETTER(nsExpandedPrincipal, + nsIPrincipal, + nsIExpandedPrincipal) + +struct OriginComparator +{ + bool LessThan(nsIPrincipal* a, nsIPrincipal* b) const + { + nsAutoCString originA; + nsresult rv = a->GetOrigin(originA); + NS_ENSURE_SUCCESS(rv, false); + nsAutoCString originB; + rv = b->GetOrigin(originB); + NS_ENSURE_SUCCESS(rv, false); + return originA < originB; + } + + bool Equals(nsIPrincipal* a, nsIPrincipal* b) const + { + nsAutoCString originA; + nsresult rv = a->GetOrigin(originA); + NS_ENSURE_SUCCESS(rv, false); + nsAutoCString originB; + rv = b->GetOrigin(originB); + NS_ENSURE_SUCCESS(rv, false); + return a == b; + } +}; + +nsExpandedPrincipal::nsExpandedPrincipal(nsTArray<nsCOMPtr<nsIPrincipal>> &aWhiteList, + const PrincipalOriginAttributes& aAttrs) +{ + // We force the principals to be sorted by origin so that nsExpandedPrincipal + // origins can have a canonical form. + OriginComparator c; + for (size_t i = 0; i < aWhiteList.Length(); ++i) { + mPrincipals.InsertElementSorted(aWhiteList[i], c); + } + mOriginAttributes = aAttrs; +} + +nsExpandedPrincipal::~nsExpandedPrincipal() +{ } + +NS_IMETHODIMP +nsExpandedPrincipal::GetDomain(nsIURI** aDomain) +{ + *aDomain = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsExpandedPrincipal::SetDomain(nsIURI* aDomain) +{ + return NS_OK; +} + +nsresult +nsExpandedPrincipal::GetOriginInternal(nsACString& aOrigin) +{ + aOrigin.AssignLiteral("[Expanded Principal ["); + for (size_t i = 0; i < mPrincipals.Length(); ++i) { + if (i != 0) { + aOrigin.AppendLiteral(", "); + } + + nsAutoCString subOrigin; + nsresult rv = mPrincipals.ElementAt(i)->GetOrigin(subOrigin); + NS_ENSURE_SUCCESS(rv, rv); + aOrigin.Append(subOrigin); + } + + aOrigin.Append("]]"); + return NS_OK; +} + +bool +nsExpandedPrincipal::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. + nsCOMPtr<nsIExpandedPrincipal> expanded = do_QueryInterface(aOther); + if (expanded) { + nsTArray< nsCOMPtr<nsIPrincipal> >* otherList; + expanded->GetWhiteList(&otherList); + for (uint32_t i = 0; i < otherList->Length(); ++i){ + // 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((*otherList)[i], 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 +nsExpandedPrincipal::MayLoadInternal(nsIURI* uri) +{ + for (uint32_t i = 0; i < mPrincipals.Length(); ++i){ + if (BasePrincipal::Cast(mPrincipals[i])->MayLoadInternal(uri)) { + return true; + } + } + + return false; +} + +NS_IMETHODIMP +nsExpandedPrincipal::GetHashValue(uint32_t* result) +{ + MOZ_CRASH("extended principal should never be used as key in a hash map"); +} + +NS_IMETHODIMP +nsExpandedPrincipal::GetURI(nsIURI** aURI) +{ + *aURI = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsExpandedPrincipal::GetWhiteList(nsTArray<nsCOMPtr<nsIPrincipal> >** aWhiteList) +{ + *aWhiteList = &mPrincipals; + return NS_OK; +} + +NS_IMETHODIMP +nsExpandedPrincipal::GetBaseDomain(nsACString& aBaseDomain) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +bool +nsExpandedPrincipal::AddonHasPermission(const nsAString& aPerm) +{ + for (size_t i = 0; i < mPrincipals.Length(); ++i) { + if (BasePrincipal::Cast(mPrincipals[i])->AddonHasPermission(aPerm)) { + return true; + } + } + return false; +} + +bool +nsExpandedPrincipal::IsOnCSSUnprefixingWhitelist() +{ + // CSS Unprefixing Whitelist is a per-origin thing; doesn't really make sense + // for an expanded principal. (And probably shouldn't be needed.) + return false; +} + + +nsresult +nsExpandedPrincipal::GetScriptLocation(nsACString& aStr) +{ + aStr.Assign("[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.Append("]]"); + return NS_OK; +} + +////////////////////////////////////////// +// Methods implementing nsISerializable // +////////////////////////////////////////// + +NS_IMETHODIMP +nsExpandedPrincipal::Read(nsIObjectInputStream* aStream) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsExpandedPrincipal::Write(nsIObjectOutputStream* aStream) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/caps/nsPrincipal.h b/caps/nsPrincipal.h new file mode 100644 index 000000000..d20d81ee3 --- /dev/null +++ b/caps/nsPrincipal.h @@ -0,0 +1,110 @@ +/* -*- 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 nsPrincipal_h__ +#define nsPrincipal_h__ + +#include "nsCOMPtr.h" +#include "nsJSPrincipals.h" +#include "nsTArray.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIProtocolHandler.h" +#include "nsNetUtil.h" +#include "nsScriptSecurityManager.h" +#include "mozilla/BasePrincipal.h" + +class nsPrincipal final : public mozilla::BasePrincipal +{ +public: + NS_DECL_NSISERIALIZABLE + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + NS_IMETHOD GetHashValue(uint32_t* aHashValue) 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; + virtual bool IsOnCSSUnprefixingWhitelist() override; + bool IsCodebasePrincipal() const override { return true; } + nsresult GetOriginInternal(nsACString& aOrigin) override; + + nsPrincipal(); + + // Init() must be called before the principal is in a usable state. + nsresult Init(nsIURI* aCodebase, const mozilla::PrincipalOriginAttributes& aOriginAttributes); + + virtual nsresult GetScriptLocation(nsACString& aStr) override; + void SetURI(nsIURI* aURI); + + /** + * Computes the puny-encoded origin of aURI. + */ + static nsresult GetOriginForURI(nsIURI* aURI, nsACString& aOrigin); + + /** + * Called at startup to setup static data, e.g. about:config pref-observers. + */ + static void InitializeStatics(); + + PrincipalKind Kind() override { return eCodebasePrincipal; } + + nsCOMPtr<nsIURI> mDomain; + nsCOMPtr<nsIURI> mCodebase; + // If mCodebaseImmutable is true, mCodebase is non-null and immutable + bool mCodebaseImmutable; + bool mDomainImmutable; + bool mInitialized; + mozilla::Maybe<bool> mIsOnCSSUnprefixingWhitelist; // Lazily-computed + +protected: + virtual ~nsPrincipal(); + + bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) override; + bool MayLoadInternal(nsIURI* aURI) override; +}; + +class nsExpandedPrincipal : public nsIExpandedPrincipal, public mozilla::BasePrincipal +{ +public: + nsExpandedPrincipal(nsTArray<nsCOMPtr<nsIPrincipal>> &aWhiteList, + const mozilla::PrincipalOriginAttributes& aAttrs); + + 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; + NS_IMETHOD GetHashValue(uint32_t* aHashValue) 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; + virtual bool AddonHasPermission(const nsAString& aPerm) override; + virtual bool IsOnCSSUnprefixingWhitelist() override; + virtual nsresult GetScriptLocation(nsACString &aStr) override; + nsresult GetOriginInternal(nsACString& aOrigin) override; + + PrincipalKind Kind() override { return eExpandedPrincipal; } + +protected: + virtual ~nsExpandedPrincipal(); + + bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) override; + bool MayLoadInternal(nsIURI* aURI) override; + +private: + nsTArray< nsCOMPtr<nsIPrincipal> > mPrincipals; +}; + +#define NS_PRINCIPAL_CONTRACTID "@mozilla.org/principal;1" +#define NS_PRINCIPAL_CID \ +{ 0x653e0e4d, 0x3ee4, 0x45fa, \ + { 0xb2, 0x72, 0x97, 0xc2, 0x0b, 0xc0, 0x1e, 0xb8 } } + +#define NS_EXPANDEDPRINCIPAL_CONTRACTID "@mozilla.org/expandedprincipal;1" +#define NS_EXPANDEDPRINCIPAL_CID \ +{ 0xe8ee88b0, 0x5571, 0x4086, \ + { 0xa4, 0x5b, 0x39, 0xa7, 0x16, 0x90, 0x6b, 0xdb } } + +#endif // nsPrincipal_h__ diff --git a/caps/nsScriptSecurityManager.cpp b/caps/nsScriptSecurityManager.cpp new file mode 100644 index 000000000..a219dcaed --- /dev/null +++ b/caps/nsScriptSecurityManager.cpp @@ -0,0 +1,1759 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsScriptSecurityManager.h" + +#include "mozilla/ArrayUtils.h" + +#include "xpcpublic.h" +#include "XPCWrapper.h" +#include "nsIAppsService.h" +#include "nsIInputStreamChannel.h" +#include "nsILoadContext.h" +#include "nsIServiceManager.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScriptContext.h" +#include "nsIURL.h" +#include "nsINestedURI.h" +#include "nspr.h" +#include "nsJSPrincipals.h" +#include "mozilla/BasePrincipal.h" +#include "nsSystemPrincipal.h" +#include "nsPrincipal.h" +#include "nsNullPrincipal.h" +#include "DomainPolicy.h" +#include "nsXPIDLString.h" +#include "nsCRT.h" +#include "nsCRTGlue.h" +#include "nsDocShell.h" +#include "nsError.h" +#include "nsDOMCID.h" +#include "nsTextFormatter.h" +#include "nsIStringBundle.h" +#include "nsNetUtil.h" +#include "nsIEffectiveTLDService.h" +#include "nsIProperties.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsIFileURL.h" +#include "nsIZipReader.h" +#include "nsIScriptGlobalObject.h" +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIPrompt.h" +#include "nsIWindowWatcher.h" +#include "nsIConsoleService.h" +#include "nsIObserverService.h" +#include "nsIContent.h" +#include "nsDOMJSUtils.h" +#include "nsAboutProtocolUtils.h" +#include "nsIClassInfo.h" +#include "nsIURIFixup.h" +#include "nsCDefaultURIFixup.h" +#include "nsIChromeRegistry.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "mozIApplication.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/BindingUtils.h" +#include <stdint.h> +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPtr.h" +#include "nsContentUtils.h" +#include "nsJSUtils.h" +#include "nsILoadInfo.h" +#include "nsXPCOMStrings.h" + +// This should be probably defined on some other place... but I couldn't find it +#define WEBAPPS_PERM_NAME "webapps-manage" + +using namespace mozilla; +using namespace mozilla::dom; + +nsIIOService *nsScriptSecurityManager::sIOService = nullptr; +nsIStringBundle *nsScriptSecurityManager::sStrBundle = nullptr; +JSContext *nsScriptSecurityManager::sContext = nullptr; +bool nsScriptSecurityManager::sStrictFileOriginPolicy = true; + +/////////////////////////// +// Convenience Functions // +/////////////////////////// + +class nsAutoInPrincipalDomainOriginSetter { +public: + nsAutoInPrincipalDomainOriginSetter() { + ++sInPrincipalDomainOrigin; + } + ~nsAutoInPrincipalDomainOriginSetter() { + --sInPrincipalDomainOrigin; + } + static uint32_t sInPrincipalDomainOrigin; +}; +uint32_t nsAutoInPrincipalDomainOriginSetter::sInPrincipalDomainOrigin; + +static +nsresult +GetOriginFromURI(nsIURI* aURI, nsACString& aOrigin) +{ + if (nsAutoInPrincipalDomainOriginSetter::sInPrincipalDomainOrigin > 1) { + // Allow a single recursive call to GetPrincipalDomainOrigin, since that + // might be happening on a different principal from the first call. But + // after that, cut off the recursion; it just indicates that something + // we're doing in this method causes us to reenter a security check here. + return NS_ERROR_NOT_AVAILABLE; + } + + nsAutoInPrincipalDomainOriginSetter autoSetter; + + nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI); + NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED); + + nsAutoCString hostPort; + + nsresult rv = uri->GetHostPort(hostPort); + if (NS_SUCCEEDED(rv)) { + nsAutoCString scheme; + rv = uri->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + aOrigin = scheme + NS_LITERAL_CSTRING("://") + hostPort; + } + else { + // Some URIs (e.g., nsSimpleURI) don't support host. Just + // get the full spec. + rv = uri->GetSpec(aOrigin); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +static +nsresult +GetPrincipalDomainOrigin(nsIPrincipal* aPrincipal, + nsACString& aOrigin) +{ + + nsCOMPtr<nsIURI> uri; + aPrincipal->GetDomain(getter_AddRefs(uri)); + if (!uri) { + aPrincipal->GetURI(getter_AddRefs(uri)); + } + NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED); + + return GetOriginFromURI(uri, aOrigin); +} + +inline void SetPendingExceptionASCII(JSContext *cx, const char *aMsg) +{ + JS_ReportErrorASCII(cx, "%s", aMsg); +} + +inline void SetPendingException(JSContext *cx, const char16_t *aMsg) +{ + NS_ConvertUTF16toUTF8 msg(aMsg); + JS_ReportErrorUTF8(cx, "%s", msg.get()); +} + +// Helper class to get stuff from the ClassInfo and not waste extra time with +// virtual method calls for things it has already gotten +class ClassInfoData +{ +public: + ClassInfoData(nsIClassInfo *aClassInfo, const char *aName) + : mClassInfo(aClassInfo), + mName(const_cast<char *>(aName)), + mDidGetFlags(false), + mMustFreeName(false) + { + } + + ~ClassInfoData() + { + if (mMustFreeName) + free(mName); + } + + uint32_t GetFlags() + { + if (!mDidGetFlags) { + if (mClassInfo) { + nsresult rv = mClassInfo->GetFlags(&mFlags); + if (NS_FAILED(rv)) { + mFlags = 0; + } + } else { + mFlags = 0; + } + + mDidGetFlags = true; + } + + return mFlags; + } + + bool IsDOMClass() + { + return !!(GetFlags() & nsIClassInfo::DOM_OBJECT); + } + + const char* GetName() + { + if (!mName) { + if (mClassInfo) { + mClassInfo->GetClassDescription(&mName); + } + + if (mName) { + mMustFreeName = true; + } else { + mName = const_cast<char *>("UnnamedClass"); + } + } + + return mName; + } + +private: + nsIClassInfo *mClassInfo; // WEAK + uint32_t mFlags; + char *mName; + bool mDidGetFlags; + bool mMustFreeName; +}; + +/* static */ +bool +nsScriptSecurityManager::SecurityCompareURIs(nsIURI* aSourceURI, + nsIURI* aTargetURI) +{ + return NS_SecurityCompareURIs(aSourceURI, aTargetURI, sStrictFileOriginPolicy); +} + +// SecurityHashURI is consistent with SecurityCompareURIs because NS_SecurityHashURI +// is consistent with NS_SecurityCompareURIs. See nsNetUtil.h. +uint32_t +nsScriptSecurityManager::SecurityHashURI(nsIURI* aURI) +{ + return NS_SecurityHashURI(aURI); +} + +uint16_t +nsScriptSecurityManager::AppStatusForPrincipal(nsIPrincipal *aPrin) +{ + uint32_t appId = aPrin->GetAppId(); + + // After bug 1238160, the principal no longer knows how to answer "is this a + // browser element", which is really what this code path wants. Currently, + // desktop is the only platform where we intend to disable isolation on a + // browser frame, so non-desktop should be able to assume that + // inIsolatedMozBrowser is true for all mozbrowser frames. Additionally, + // apps are no longer used on desktop, so appId is always NO_APP_ID. We use + // a release assertion in nsFrameLoader::OwnerIsIsolatedMozBrowserFrame so + // that platforms with apps can assume inIsolatedMozBrowser is true for all + // mozbrowser frames. + bool inIsolatedMozBrowser = aPrin->GetIsInIsolatedMozBrowserElement(); + + NS_WARNING_ASSERTION( + appId != nsIScriptSecurityManager::UNKNOWN_APP_ID, + "Asking for app status on a principal with an unknown app id"); + + // Installed apps have a valid app id (not NO_APP_ID or UNKNOWN_APP_ID) + // and they are not inside a mozbrowser. + if (appId == nsIScriptSecurityManager::NO_APP_ID || + appId == nsIScriptSecurityManager::UNKNOWN_APP_ID || + inIsolatedMozBrowser) + { + return nsIPrincipal::APP_STATUS_NOT_INSTALLED; + } + + nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(appsService, nsIPrincipal::APP_STATUS_NOT_INSTALLED); + + nsCOMPtr<mozIApplication> app; + appsService->GetAppByLocalId(appId, getter_AddRefs(app)); + NS_ENSURE_TRUE(app, nsIPrincipal::APP_STATUS_NOT_INSTALLED); + + uint16_t status = nsIPrincipal::APP_STATUS_INSTALLED; + NS_ENSURE_SUCCESS(app->GetAppStatus(&status), + nsIPrincipal::APP_STATUS_NOT_INSTALLED); + + nsString appOrigin; + NS_ENSURE_SUCCESS(app->GetOrigin(appOrigin), + nsIPrincipal::APP_STATUS_NOT_INSTALLED); + nsCOMPtr<nsIURI> appURI; + NS_ENSURE_SUCCESS(NS_NewURI(getter_AddRefs(appURI), appOrigin), + nsIPrincipal::APP_STATUS_NOT_INSTALLED); + + // The app could contain a cross-origin iframe - make sure that the content + // is actually same-origin with the app. + MOZ_ASSERT(inIsolatedMozBrowser == false, "Checked this above"); + nsAutoCString suffix; + PrincipalOriginAttributes attrs; + NS_ENSURE_TRUE(attrs.PopulateFromOrigin(NS_ConvertUTF16toUTF8(appOrigin), suffix), + nsIPrincipal::APP_STATUS_NOT_INSTALLED); + attrs.mAppId = appId; + attrs.mInIsolatedMozBrowser = false; + nsCOMPtr<nsIPrincipal> appPrin = BasePrincipal::CreateCodebasePrincipal(appURI, attrs); + NS_ENSURE_TRUE(appPrin, nsIPrincipal::APP_STATUS_NOT_INSTALLED); + return aPrin->Equals(appPrin) ? status + : nsIPrincipal::APP_STATUS_NOT_INSTALLED; +} + +/* + * GetChannelResultPrincipal will return the principal that the resource + * returned by this channel will use. For example, if the resource is in + * a sandbox, it will return the nullprincipal. If the resource is forced + * to inherit principal, it will return the principal of its parent. If + * the load doesn't require sandboxing or inheriting, it will return the same + * principal as GetChannelURIPrincipal. Namely the principal of the URI + * that is being loaded. + */ +NS_IMETHODIMP +nsScriptSecurityManager::GetChannelResultPrincipal(nsIChannel* aChannel, + nsIPrincipal** aPrincipal) +{ + return GetChannelResultPrincipal(aChannel, aPrincipal, + /*aIgnoreSandboxing*/ false); +} + +nsresult +nsScriptSecurityManager::GetChannelResultPrincipalIfNotSandboxed(nsIChannel* aChannel, + nsIPrincipal** aPrincipal) +{ + return GetChannelResultPrincipal(aChannel, aPrincipal, + /*aIgnoreSandboxing*/ true); +} + +nsresult +nsScriptSecurityManager::GetChannelResultPrincipal(nsIChannel* aChannel, + nsIPrincipal** aPrincipal, + bool aIgnoreSandboxing) +{ + NS_PRECONDITION(aChannel, "Must have channel!"); + // Check whether we have an nsILoadInfo that says what we should do. + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); + if (loadInfo && loadInfo->GetForceInheritPrincipalOverruleOwner()) { + nsCOMPtr<nsIPrincipal> principalToInherit = loadInfo->PrincipalToInherit(); + if (!principalToInherit) { + principalToInherit = loadInfo->TriggeringPrincipal(); + } + principalToInherit.forget(aPrincipal); + return NS_OK; + } + + nsCOMPtr<nsISupports> owner; + aChannel->GetOwner(getter_AddRefs(owner)); + if (owner) { + CallQueryInterface(owner, aPrincipal); + if (*aPrincipal) { + return NS_OK; + } + } + + if (loadInfo) { + if (!aIgnoreSandboxing && loadInfo->GetLoadingSandboxed()) { + RefPtr<nsNullPrincipal> prin; + if (loadInfo->LoadingPrincipal()) { + prin = + nsNullPrincipal::CreateWithInheritedAttributes(loadInfo->LoadingPrincipal()); + } else { + NeckoOriginAttributes nAttrs; + loadInfo->GetOriginAttributes(&nAttrs); + PrincipalOriginAttributes pAttrs; + pAttrs.InheritFromNecko(nAttrs); + prin = nsNullPrincipal::Create(pAttrs); + } + prin.forget(aPrincipal); + + // if the new NullPrincipal (above) loads an iframe[srcdoc], we + // need to inherit an existing CSP to avoid bypasses (bug 1073952). + // We continue inheriting for nested frames with e.g., data: URLs. + if (loadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_SUBDOCUMENT) { + nsCOMPtr<nsIURI> uri; + aChannel->GetURI(getter_AddRefs(uri)); + nsAutoCString URISpec; + uri->GetSpec(URISpec); + bool isData = (NS_SUCCEEDED(uri->SchemeIs("data", &isData)) && isData); + if (URISpec.EqualsLiteral("about:srcdoc") || isData) { + nsCOMPtr<nsIPrincipal> principalToInherit = loadInfo->PrincipalToInherit(); + if (!principalToInherit) { + principalToInherit = loadInfo->TriggeringPrincipal(); + } + nsCOMPtr<nsIContentSecurityPolicy> originalCSP; + principalToInherit->GetCsp(getter_AddRefs(originalCSP)); + if (originalCSP) { + // if the principalToInherit had a CSP, + // add it to the newly created NullPrincipal + // (unless it already has one) + nsCOMPtr<nsIContentSecurityPolicy> nullPrincipalCSP; + (*aPrincipal)->GetCsp(getter_AddRefs(nullPrincipalCSP)); + if (nullPrincipalCSP) { + MOZ_ASSERT(nullPrincipalCSP == originalCSP, + "There should be no other CSP here."); + // CSPs are equal, no need to set it again. + return NS_OK; + } else { + nsresult rv = (*aPrincipal)->SetCsp(originalCSP); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + } + return NS_OK; + } + + bool forceInherit = loadInfo->GetForceInheritPrincipal(); + if (aIgnoreSandboxing && !forceInherit) { + // Check if SEC_FORCE_INHERIT_PRINCIPAL was dropped because of + // sandboxing: + if (loadInfo->GetLoadingSandboxed() && + loadInfo->GetForceInheritPrincipalDropped()) { + forceInherit = true; + } + } + if (forceInherit) { + nsCOMPtr<nsIPrincipal> principalToInherit = loadInfo->PrincipalToInherit(); + if (!principalToInherit) { + principalToInherit = loadInfo->TriggeringPrincipal(); + } + principalToInherit.forget(aPrincipal); + return NS_OK; + } + + nsSecurityFlags securityFlags = loadInfo->GetSecurityMode(); + // The data: inheritance flags should only apply to the initial load, + // not to loads that it might have redirected to. + if (loadInfo->RedirectChain().IsEmpty() && + (securityFlags == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS || + securityFlags == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS || + securityFlags == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS)) { + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIPrincipal> principalToInherit = loadInfo->PrincipalToInherit(); + if (!principalToInherit) { + principalToInherit = loadInfo->TriggeringPrincipal(); + } + bool inheritForAboutBlank = loadInfo->GetAboutBlankInherits(); + + if (nsContentUtils::ChannelShouldInheritPrincipal(principalToInherit, + uri, + inheritForAboutBlank, + false)) { + principalToInherit.forget(aPrincipal); + return NS_OK; + } + } + } + return GetChannelURIPrincipal(aChannel, aPrincipal); +} + +nsresult +nsScriptSecurityManager::MaybeSetAddonIdFromURI(PrincipalOriginAttributes& aAttrs, nsIURI* aURI) +{ + nsAutoCString scheme; + nsresult rv = aURI->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + if (scheme.EqualsLiteral("moz-extension") && GetAddonPolicyService()) { + rv = GetAddonPolicyService()->ExtensionURIToAddonId(aURI, aAttrs.mAddonId); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +/* The principal of the URI that this channel is loading. This is never + * affected by things like sandboxed loads, or loads where we forcefully + * inherit the principal. Think of this as the principal of the server + * which this channel is loading from. Most callers should use + * GetChannelResultPrincipal instead of GetChannelURIPrincipal. Only + * call GetChannelURIPrincipal if you are sure that you want the + * principal that matches the uri, even in cases when the load is + * sandboxed or when the load could be a blob or data uri (i.e even when + * you encounter loads that may or may not be sandboxed and loads + * that may or may not inherit)." + */ +NS_IMETHODIMP +nsScriptSecurityManager::GetChannelURIPrincipal(nsIChannel* aChannel, + nsIPrincipal** aPrincipal) +{ + NS_PRECONDITION(aChannel, "Must have channel!"); + + // Get the principal from the URI. Make sure this does the same thing + // as nsDocument::Reset and XULDocument::StartDocumentLoad. + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILoadInfo> loadInfo; + aChannel->GetLoadInfo(getter_AddRefs(loadInfo)); + + // Inherit the origin attributes from loadInfo. + // If this is a top-level document load, the origin attributes of the + // loadInfo will be set from nsDocShell::DoURILoad. + // For subresource loading, the origin attributes of the loadInfo is from + // its loadingPrincipal. + PrincipalOriginAttributes attrs; + + // For addons loadInfo might be null. + if (loadInfo) { + attrs.InheritFromNecko(loadInfo->GetOriginAttributes()); + } + rv = MaybeSetAddonIdFromURI(attrs, uri); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(uri, attrs); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsScriptSecurityManager::IsSystemPrincipal(nsIPrincipal* aPrincipal, + bool* aIsSystem) +{ + *aIsSystem = (aPrincipal == mSystemPrincipal); + return NS_OK; +} + +///////////////////////////// +// nsScriptSecurityManager // +///////////////////////////// + +//////////////////////////////////// +// Methods implementing ISupports // +//////////////////////////////////// +NS_IMPL_ISUPPORTS(nsScriptSecurityManager, + nsIScriptSecurityManager, + nsIObserver) + +/////////////////////////////////////////////////// +// Methods implementing nsIScriptSecurityManager // +/////////////////////////////////////////////////// + +///////////////// Security Checks ///////////////// + +bool +nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx) +{ + MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext()); + nsCOMPtr<nsIPrincipal> subjectPrincipal = nsContentUtils::SubjectPrincipal(); + nsCOMPtr<nsIContentSecurityPolicy> csp; + nsresult rv = subjectPrincipal->GetCsp(getter_AddRefs(csp)); + NS_ASSERTION(NS_SUCCEEDED(rv), "CSP: Failed to get CSP from principal."); + + // don't do anything unless there's a CSP + if (!csp) + return true; + + bool evalOK = true; + bool reportViolation = false; + rv = csp->GetAllowsEval(&reportViolation, &evalOK); + + if (NS_FAILED(rv)) + { + NS_WARNING("CSP: failed to get allowsEval"); + return true; // fail open to not break sites. + } + + if (reportViolation) { + nsAutoString fileName; + unsigned lineNum = 0; + NS_NAMED_LITERAL_STRING(scriptSample, "call to eval() or related function blocked by CSP"); + + JS::AutoFilename scriptFilename; + if (JS::DescribeScriptedCaller(cx, &scriptFilename, &lineNum)) { + if (const char *file = scriptFilename.get()) { + CopyUTF8toUTF16(nsDependentCString(file), fileName); + } + } else { + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + } + csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL, + fileName, + scriptSample, + lineNum, + EmptyString(), + EmptyString()); + } + + return evalOK; +} + +// static +bool +nsScriptSecurityManager::JSPrincipalsSubsume(JSPrincipals *first, + JSPrincipals *second) +{ + return nsJSPrincipals::get(first)->Subsumes(nsJSPrincipals::get(second)); +} + +NS_IMETHODIMP +nsScriptSecurityManager::CheckSameOriginURI(nsIURI* aSourceURI, + nsIURI* aTargetURI, + bool reportError) +{ + if (!SecurityCompareURIs(aSourceURI, aTargetURI)) + { + if (reportError) { + ReportError(nullptr, NS_LITERAL_STRING("CheckSameOriginError"), + aSourceURI, aTargetURI); + } + return NS_ERROR_DOM_BAD_URI; + } + return NS_OK; +} + +/*static*/ uint32_t +nsScriptSecurityManager::HashPrincipalByOrigin(nsIPrincipal* aPrincipal) +{ + nsCOMPtr<nsIURI> uri; + aPrincipal->GetDomain(getter_AddRefs(uri)); + if (!uri) + aPrincipal->GetURI(getter_AddRefs(uri)); + return SecurityHashURI(uri); +} + +NS_IMETHODIMP +nsScriptSecurityManager::CheckLoadURIFromScript(JSContext *cx, nsIURI *aURI) +{ + // Get principal of currently executing script. + MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext()); + nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(); + nsresult rv = CheckLoadURIWithPrincipal(principal, aURI, + nsIScriptSecurityManager::STANDARD); + if (NS_SUCCEEDED(rv)) { + // OK to load + return NS_OK; + } + + // See if we're attempting to load a file: URI. If so, let a + // UniversalXPConnect capability trump the above check. + bool isFile = false; + bool isRes = false; + if (NS_FAILED(aURI->SchemeIs("file", &isFile)) || + NS_FAILED(aURI->SchemeIs("resource", &isRes))) + return NS_ERROR_FAILURE; + if (isFile || isRes) + { + if (nsContentUtils::IsCallerChrome()) + return NS_OK; + } + + // Report error. + nsAutoCString spec; + if (NS_FAILED(aURI->GetAsciiSpec(spec))) + return NS_ERROR_FAILURE; + nsAutoCString msg("Access to '"); + msg.Append(spec); + msg.AppendLiteral("' from script denied"); + SetPendingExceptionASCII(cx, msg.get()); + return NS_ERROR_DOM_BAD_URI; +} + +/** + * Helper method to handle cases where a flag passed to + * CheckLoadURIWithPrincipal means denying loading if the given URI has certain + * nsIProtocolHandler flags set. + * @return if success, access is allowed. Otherwise, deny access + */ +static nsresult +DenyAccessIfURIHasFlags(nsIURI* aURI, uint32_t aURIFlags) +{ + NS_PRECONDITION(aURI, "Must have URI!"); + + bool uriHasFlags; + nsresult rv = + NS_URIChainHasFlags(aURI, aURIFlags, &uriHasFlags); + NS_ENSURE_SUCCESS(rv, rv); + + if (uriHasFlags) { + return NS_ERROR_DOM_BAD_URI; + } + + return NS_OK; +} + +static bool +EqualOrSubdomain(nsIURI* aProbeArg, nsIURI* aBase) +{ + // Make a clone of the incoming URI, because we're going to mutate it. + nsCOMPtr<nsIURI> probe; + nsresult rv = aProbeArg->Clone(getter_AddRefs(probe)); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr<nsIEffectiveTLDService> tldService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + NS_ENSURE_TRUE(tldService, false); + while (true) { + if (nsScriptSecurityManager::SecurityCompareURIs(probe, aBase)) { + return true; + } + + nsAutoCString host, newHost; + rv = probe->GetHost(host); + NS_ENSURE_SUCCESS(rv, false); + + rv = tldService->GetNextSubDomain(host, newHost); + if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { + return false; + } + NS_ENSURE_SUCCESS(rv, false); + rv = probe->SetHost(newHost); + NS_ENSURE_SUCCESS(rv, false); + } +} + +NS_IMETHODIMP +nsScriptSecurityManager::CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal, + nsIURI *aTargetURI, + uint32_t aFlags) +{ + NS_PRECONDITION(aPrincipal, "CheckLoadURIWithPrincipal must have a principal"); + // If someone passes a flag that we don't understand, we should + // fail, because they may need a security check that we don't + // provide. + NS_ENSURE_FALSE(aFlags & ~(nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT | + nsIScriptSecurityManager::ALLOW_CHROME | + nsIScriptSecurityManager::DISALLOW_SCRIPT | + nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL | + nsIScriptSecurityManager::DONT_REPORT_ERRORS), + NS_ERROR_UNEXPECTED); + NS_ENSURE_ARG_POINTER(aPrincipal); + NS_ENSURE_ARG_POINTER(aTargetURI); + + // If DISALLOW_INHERIT_PRINCIPAL is set, we prevent loading of URIs which + // would do such inheriting. That would be URIs that do not have their own + // security context. We do this even for the system principal. + if (aFlags & nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL) { + nsresult rv = + DenyAccessIfURIHasFlags(aTargetURI, + nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aPrincipal == mSystemPrincipal) { + // Allow access + return NS_OK; + } + + nsCOMPtr<nsIURI> sourceURI; + aPrincipal->GetURI(getter_AddRefs(sourceURI)); + if (!sourceURI) { + nsCOMPtr<nsIExpandedPrincipal> expanded = do_QueryInterface(aPrincipal); + if (expanded) { + nsTArray< nsCOMPtr<nsIPrincipal> > *whiteList; + expanded->GetWhiteList(&whiteList); + for (uint32_t i = 0; i < whiteList->Length(); ++i) { + nsresult rv = CheckLoadURIWithPrincipal((*whiteList)[i], + aTargetURI, + aFlags); + if (NS_SUCCEEDED(rv)) { + // Allow access if it succeeded with one of the white listed principals + return NS_OK; + } + } + // None of our whitelisted principals worked. + return NS_ERROR_DOM_BAD_URI; + } + NS_ERROR("Non-system principals or expanded principal passed to CheckLoadURIWithPrincipal " + "must have a URI!"); + return NS_ERROR_UNEXPECTED; + } + + // Automatic loads are not allowed from certain protocols. + if (aFlags & nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT) { + nsresult rv = + DenyAccessIfURIHasFlags(sourceURI, + nsIProtocolHandler::URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT); + NS_ENSURE_SUCCESS(rv, rv); + } + + // If either URI is a nested URI, get the base URI + nsCOMPtr<nsIURI> sourceBaseURI = NS_GetInnermostURI(sourceURI); + nsCOMPtr<nsIURI> targetBaseURI = NS_GetInnermostURI(aTargetURI); + + //-- get the target scheme + nsAutoCString targetScheme; + nsresult rv = targetBaseURI->GetScheme(targetScheme); + if (NS_FAILED(rv)) return rv; + + //-- Some callers do not allow loading javascript: + if ((aFlags & nsIScriptSecurityManager::DISALLOW_SCRIPT) && + targetScheme.EqualsLiteral("javascript")) + { + return NS_ERROR_DOM_BAD_URI; + } + + // Check for uris that are only loadable by principals that subsume them + bool hasFlags; + rv = NS_URIChainHasFlags(targetBaseURI, + nsIProtocolHandler::URI_LOADABLE_BY_SUBSUMERS, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + + if (hasFlags) { + // check nothing else in the URI chain has flags that prevent + // access: + rv = CheckLoadURIFlags(sourceURI, aTargetURI, sourceBaseURI, + targetBaseURI, aFlags); + NS_ENSURE_SUCCESS(rv, rv); + // Check the principal is allowed to load the target. + return aPrincipal->CheckMayLoad(targetBaseURI, true, false); + } + + //-- get the source scheme + nsAutoCString sourceScheme; + rv = sourceBaseURI->GetScheme(sourceScheme); + if (NS_FAILED(rv)) return rv; + + // When comparing schemes, if the relevant pref is set, view-source URIs + // are reachable from same-protocol (so e.g. file: can link to + // view-source:file). This is required for reftests. + static bool sViewSourceReachableFromInner = false; + static bool sCachedViewSourcePref = false; + if (!sCachedViewSourcePref) { + sCachedViewSourcePref = true; + mozilla::Preferences::AddBoolVarCache(&sViewSourceReachableFromInner, + "security.view-source.reachable-from-inner-protocol"); + } + + bool targetIsViewSource = false; + + if (sourceScheme.LowerCaseEqualsLiteral(NS_NULLPRINCIPAL_SCHEME)) { + // A null principal can target its own URI. + if (sourceURI == aTargetURI) { + return NS_OK; + } + } + else if (sViewSourceReachableFromInner && + sourceScheme.EqualsIgnoreCase(targetScheme.get()) && + NS_SUCCEEDED(aTargetURI->SchemeIs("view-source", &targetIsViewSource)) && + targetIsViewSource) + { + // exception for foo: linking to view-source:foo for reftests... + return NS_OK; + } + + // If we get here, check all the schemes can link to each other, from the top down: + nsCaseInsensitiveCStringComparator stringComparator; + nsCOMPtr<nsIURI> currentURI = sourceURI; + nsCOMPtr<nsIURI> currentOtherURI = aTargetURI; + + bool denySameSchemeLinks = false; + rv = NS_URIChainHasFlags(aTargetURI, nsIProtocolHandler::URI_SCHEME_NOT_SELF_LINKABLE, + &denySameSchemeLinks); + if (NS_FAILED(rv)) return rv; + + while (currentURI && currentOtherURI) { + nsAutoCString scheme, otherScheme; + currentURI->GetScheme(scheme); + currentOtherURI->GetScheme(otherScheme); + + bool schemesMatch = scheme.Equals(otherScheme, stringComparator); + bool isSamePage = false; + // about: URIs are special snowflakes. + if (scheme.EqualsLiteral("about") && schemesMatch) { + nsAutoCString moduleName, otherModuleName; + // about: pages can always link to themselves: + isSamePage = + NS_SUCCEEDED(NS_GetAboutModuleName(currentURI, moduleName)) && + NS_SUCCEEDED(NS_GetAboutModuleName(currentOtherURI, otherModuleName)) && + moduleName.Equals(otherModuleName); + if (!isSamePage) { + // We will have allowed the load earlier if the source page has + // system principal. So we know the source has a content + // principal, and it's trying to link to something else. + // Linkable about: pages are always reachable, even if we hit + // the CheckLoadURIFlags call below. + // We punch only 1 other hole: iff the source is unlinkable, + // we let them link to other pages explicitly marked SAFE + // for content. This avoids world-linkable about: pages linking + // to non-world-linkable about: pages. + nsCOMPtr<nsIAboutModule> module, otherModule; + bool knowBothModules = + NS_SUCCEEDED(NS_GetAboutModule(currentURI, getter_AddRefs(module))) && + NS_SUCCEEDED(NS_GetAboutModule(currentOtherURI, getter_AddRefs(otherModule))); + uint32_t aboutModuleFlags = 0; + uint32_t otherAboutModuleFlags = 0; + knowBothModules = knowBothModules && + NS_SUCCEEDED(module->GetURIFlags(currentURI, &aboutModuleFlags)) && + NS_SUCCEEDED(otherModule->GetURIFlags(currentOtherURI, &otherAboutModuleFlags)); + if (knowBothModules) { + isSamePage = + !(aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) && + (otherAboutModuleFlags & nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT); + if (isSamePage && otherAboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) { + //XXXgijs: this is a hack. The target will be nested + // (with innerURI of moz-safe-about:whatever), and + // the source isn't, so we won't pass if we finish + // the loop. We *should* pass, though, so return here. + // This hack can go away when bug 1228118 is fixed. + return NS_OK; + } + } + } + } else { + bool equalExceptRef = false; + rv = currentURI->EqualsExceptRef(currentOtherURI, &equalExceptRef); + isSamePage = NS_SUCCEEDED(rv) && equalExceptRef; + } + + // If schemes are not equal, or they're equal but the target URI + // is different from the source URI and doesn't always allow linking + // from the same scheme, check if the URI flags of the current target + // URI allow the current source URI to link to it. + // The policy is specified by the protocol flags on both URIs. + if (!schemesMatch || (denySameSchemeLinks && !isSamePage)) { + return CheckLoadURIFlags(currentURI, currentOtherURI, + sourceBaseURI, targetBaseURI, aFlags); + } + // Otherwise... check if we can nest another level: + nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(currentURI); + nsCOMPtr<nsINestedURI> nestedOtherURI = do_QueryInterface(currentOtherURI); + + // If schemes match and neither URI is nested further, we're OK. + if (!nestedURI && !nestedOtherURI) { + return NS_OK; + } + // If one is nested and the other isn't, something is wrong. + if (!nestedURI != !nestedOtherURI) { + return NS_ERROR_DOM_BAD_URI; + } + // Otherwise, both should be nested and we'll go through the loop again. + nestedURI->GetInnerURI(getter_AddRefs(currentURI)); + nestedOtherURI->GetInnerURI(getter_AddRefs(currentOtherURI)); + } + + // We should never get here. We should always return from inside the loop. + return NS_ERROR_DOM_BAD_URI; +} + +/** + * Helper method to check whether the target URI and its innermost ("base") URI + * has protocol flags that should stop it from being loaded by the source URI + * (and/or the source URI's innermost ("base") URI), taking into account any + * nsIScriptSecurityManager flags originally passed to + * CheckLoadURIWithPrincipal and friends. + * + * @return if success, access is allowed. Otherwise, deny access + */ +nsresult +nsScriptSecurityManager::CheckLoadURIFlags(nsIURI *aSourceURI, + nsIURI *aTargetURI, + nsIURI *aSourceBaseURI, + nsIURI *aTargetBaseURI, + uint32_t aFlags) +{ + // Note that the order of policy checks here is very important! + // We start from most restrictive and work our way down. + bool reportErrors = !(aFlags & nsIScriptSecurityManager::DONT_REPORT_ERRORS); + NS_NAMED_LITERAL_STRING(errorTag, "CheckLoadURIError"); + + nsAutoCString targetScheme; + nsresult rv = aTargetBaseURI->GetScheme(targetScheme); + if (NS_FAILED(rv)) return rv; + + // Check for system target URI + rv = DenyAccessIfURIHasFlags(aTargetURI, + nsIProtocolHandler::URI_DANGEROUS_TO_LOAD); + if (NS_FAILED(rv)) { + // Deny access, since the origin principal is not system + if (reportErrors) { + ReportError(nullptr, errorTag, aSourceURI, aTargetURI); + } + return rv; + } + + // Check for chrome target URI + bool hasFlags = false; + rv = NS_URIChainHasFlags(aTargetBaseURI, + nsIProtocolHandler::URI_IS_UI_RESOURCE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) { + if (aFlags & nsIScriptSecurityManager::ALLOW_CHROME) { + + // For now, don't change behavior for resource:// or moz-icon:// and + // just allow them. + if (!targetScheme.EqualsLiteral("chrome")) { + return NS_OK; + } + + // Allow a URI_IS_UI_RESOURCE source to link to a URI_IS_UI_RESOURCE + // target if ALLOW_CHROME is set. + // + // ALLOW_CHROME is a flag that we pass on all loads _except_ docshell + // loads (since docshell loads run the loaded content with its origin + // principal). So we're effectively allowing resource://, chrome://, + // and moz-icon:// source URIs to load resource://, chrome://, and + // moz-icon:// files, so long as they're not loading it as a document. + bool sourceIsUIResource; + rv = NS_URIChainHasFlags(aSourceBaseURI, + nsIProtocolHandler::URI_IS_UI_RESOURCE, + &sourceIsUIResource); + NS_ENSURE_SUCCESS(rv, rv); + if (sourceIsUIResource) { + return NS_OK; + } + + // Allow the load only if the chrome package is whitelisted. + nsCOMPtr<nsIXULChromeRegistry> reg(do_GetService( + NS_CHROMEREGISTRY_CONTRACTID)); + if (reg) { + bool accessAllowed = false; + reg->AllowContentToAccess(aTargetBaseURI, &accessAllowed); + if (accessAllowed) { + return NS_OK; + } + } + } + + // Special-case the hidden window: it's allowed to load + // URI_IS_UI_RESOURCE no matter what. Bug 1145470 tracks removing this. + nsAutoCString sourceSpec; + if (NS_SUCCEEDED(aSourceBaseURI->GetSpec(sourceSpec)) && + sourceSpec.EqualsLiteral("resource://gre-resources/hiddenWindow.html")) { + return NS_OK; + } + + if (reportErrors) { + ReportError(nullptr, errorTag, aSourceURI, aTargetURI); + } + return NS_ERROR_DOM_BAD_URI; + } + + // Check for target URI pointing to a file + rv = NS_URIChainHasFlags(aTargetURI, + nsIProtocolHandler::URI_IS_LOCAL_FILE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) { + // Allow domains that were whitelisted in the prefs. In 99.9% of cases, + // this array is empty. + for (nsIURI* uri : EnsureFileURIWhitelist()) { + if (EqualOrSubdomain(aSourceURI, uri)) { + return NS_OK; + } + } + + // Allow chrome:// + bool isChrome = false; + if (NS_SUCCEEDED(aSourceBaseURI->SchemeIs("chrome", &isChrome)) && isChrome) { + return NS_OK; + } + + // Nothing else. + if (reportErrors) { + ReportError(nullptr, errorTag, aSourceURI, aTargetURI); + } + return NS_ERROR_DOM_BAD_URI; + } + + // OK, everyone is allowed to load this, since unflagged handlers are + // deprecated but treated as URI_LOADABLE_BY_ANYONE. But check whether we + // need to warn. At some point we'll want to make this warning into an + // error and treat unflagged handlers as URI_DANGEROUS_TO_LOAD. + rv = NS_URIChainHasFlags(aTargetBaseURI, + nsIProtocolHandler::URI_LOADABLE_BY_ANYONE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + // NB: we also get here if the base URI is URI_LOADABLE_BY_SUBSUMERS, + // and none of the rest of the nested chain of URIs for aTargetURI + // prohibits the load, so avoid warning in that case: + bool hasSubsumersFlag = false; + rv = NS_URIChainHasFlags(aTargetBaseURI, + nsIProtocolHandler::URI_LOADABLE_BY_SUBSUMERS, + &hasSubsumersFlag); + NS_ENSURE_SUCCESS(rv, rv); + if (!hasFlags && !hasSubsumersFlag) { + nsXPIDLString message; + NS_ConvertASCIItoUTF16 ucsTargetScheme(targetScheme); + const char16_t* formatStrings[] = { ucsTargetScheme.get() }; + rv = sStrBundle-> + FormatStringFromName(u"ProtocolFlagError", + formatStrings, + ArrayLength(formatStrings), + getter_Copies(message)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIConsoleService> console( + do_GetService("@mozilla.org/consoleservice;1")); + NS_ENSURE_TRUE(console, NS_ERROR_FAILURE); + + console->LogStringMessage(message.get()); + } + } + + return NS_OK; +} + +nsresult +nsScriptSecurityManager::ReportError(JSContext* cx, const nsAString& messageTag, + nsIURI* aSource, nsIURI* aTarget) +{ + nsresult rv; + NS_ENSURE_TRUE(aSource && aTarget, NS_ERROR_NULL_POINTER); + + // Get the source URL spec + nsAutoCString sourceSpec; + rv = aSource->GetAsciiSpec(sourceSpec); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the target URL spec + nsAutoCString targetSpec; + rv = aTarget->GetAsciiSpec(targetSpec); + NS_ENSURE_SUCCESS(rv, rv); + + // Localize the error message + nsXPIDLString message; + NS_ConvertASCIItoUTF16 ucsSourceSpec(sourceSpec); + NS_ConvertASCIItoUTF16 ucsTargetSpec(targetSpec); + const char16_t *formatStrings[] = { ucsSourceSpec.get(), ucsTargetSpec.get() }; + rv = sStrBundle->FormatStringFromName(PromiseFlatString(messageTag).get(), + formatStrings, + ArrayLength(formatStrings), + getter_Copies(message)); + NS_ENSURE_SUCCESS(rv, rv); + + // If a JS context was passed in, set a JS exception. + // Otherwise, print the error message directly to the JS console + // and to standard output + if (cx) + { + SetPendingException(cx, message.get()); + } + else // Print directly to the console + { + nsCOMPtr<nsIConsoleService> console( + do_GetService("@mozilla.org/consoleservice;1")); + NS_ENSURE_TRUE(console, NS_ERROR_FAILURE); + + console->LogStringMessage(message.get()); + } + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CheckLoadURIStrWithPrincipal(nsIPrincipal* aPrincipal, + const nsACString& aTargetURIStr, + uint32_t aFlags) +{ + nsresult rv; + nsCOMPtr<nsIURI> target; + rv = NS_NewURI(getter_AddRefs(target), aTargetURIStr, + nullptr, nullptr, sIOService); + NS_ENSURE_SUCCESS(rv, rv); + + rv = CheckLoadURIWithPrincipal(aPrincipal, target, aFlags); + if (rv == NS_ERROR_DOM_BAD_URI) { + // Don't warn because NS_ERROR_DOM_BAD_URI is one of the expected + // return values. + return rv; + } + NS_ENSURE_SUCCESS(rv, rv); + + // Now start testing fixup -- since aTargetURIStr is a string, not + // an nsIURI, we may well end up fixing it up before loading. + // Note: This needs to stay in sync with the nsIURIFixup api. + nsCOMPtr<nsIURIFixup> fixup = do_GetService(NS_URIFIXUP_CONTRACTID); + if (!fixup) { + return rv; + } + + uint32_t flags[] = { + nsIURIFixup::FIXUP_FLAG_NONE, + nsIURIFixup::FIXUP_FLAG_FIX_SCHEME_TYPOS, + nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP, + nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI, + nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP | + nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI + }; + + for (uint32_t i = 0; i < ArrayLength(flags); ++i) { + rv = fixup->CreateFixupURI(aTargetURIStr, flags[i], nullptr, + getter_AddRefs(target)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = CheckLoadURIWithPrincipal(aPrincipal, target, aFlags); + if (rv == NS_ERROR_DOM_BAD_URI) { + // Don't warn because NS_ERROR_DOM_BAD_URI is one of the expected + // return values. + return rv; + } + NS_ENSURE_SUCCESS(rv, rv); + } + + return rv; +} + +///////////////// Principals /////////////////////// + +NS_IMETHODIMP +nsScriptSecurityManager::GetSystemPrincipal(nsIPrincipal **result) +{ + NS_ADDREF(*result = mSystemPrincipal); + + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetNoAppCodebasePrincipal(nsIURI* aURI, + nsIPrincipal** aPrincipal) +{ + PrincipalOriginAttributes attrs(NO_APP_ID, false); + nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetCodebasePrincipal(nsIURI* aURI, + nsIPrincipal** aPrincipal) +{ + return GetNoAppCodebasePrincipal(aURI, aPrincipal); +} + +NS_IMETHODIMP +nsScriptSecurityManager::CreateCodebasePrincipal(nsIURI* aURI, JS::Handle<JS::Value> aOriginAttributes, + JSContext* aCx, nsIPrincipal** aPrincipal) +{ + PrincipalOriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CreateCodebasePrincipalFromOrigin(const nsACString& aOrigin, + nsIPrincipal** aPrincipal) +{ + if (StringBeginsWith(aOrigin, NS_LITERAL_CSTRING("["))) { + return NS_ERROR_INVALID_ARG; + } + + if (StringBeginsWith(aOrigin, NS_LITERAL_CSTRING(NS_NULLPRINCIPAL_SCHEME ":"))) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aOrigin); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CreateNullPrincipal(JS::Handle<JS::Value> aOriginAttributes, + JSContext* aCx, nsIPrincipal** aPrincipal) +{ + PrincipalOriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsIPrincipal> prin = nsNullPrincipal::Create(attrs); + prin.forget(aPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetAppCodebasePrincipal(nsIURI* aURI, + uint32_t aAppId, + bool aInIsolatedMozBrowser, + nsIPrincipal** aPrincipal) +{ + NS_ENSURE_TRUE(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID, + NS_ERROR_INVALID_ARG); + + PrincipalOriginAttributes attrs(aAppId, aInIsolatedMozBrowser); + nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsScriptSecurityManager:: + GetLoadContextCodebasePrincipal(nsIURI* aURI, + nsILoadContext* aLoadContext, + nsIPrincipal** aPrincipal) +{ + NS_ENSURE_STATE(aLoadContext); + DocShellOriginAttributes docShellAttrs; + bool result = aLoadContext->GetOriginAttributes(docShellAttrs);; + NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); + + PrincipalOriginAttributes attrs; + attrs.InheritFromDocShellToDoc(docShellAttrs, aURI); + + nsresult rv = MaybeSetAddonIdFromURI(attrs, aURI); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetDocShellCodebasePrincipal(nsIURI* aURI, + nsIDocShell* aDocShell, + nsIPrincipal** aPrincipal) +{ + PrincipalOriginAttributes attrs; + attrs.InheritFromDocShellToDoc(nsDocShell::Cast(aDocShell)->GetOriginAttributes(), aURI); + + nsresult rv = MaybeSetAddonIdFromURI(attrs, aURI); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + +// static +nsIPrincipal* +nsScriptSecurityManager::doGetObjectPrincipal(JSObject *aObj) +{ + JSCompartment *compartment = js::GetObjectCompartment(aObj); + JSPrincipals *principals = JS_GetCompartmentPrincipals(compartment); + return nsJSPrincipals::get(principals); +} + +NS_IMETHODIMP +nsScriptSecurityManager::CanCreateWrapper(JSContext *cx, + const nsIID &aIID, + nsISupports *aObj, + nsIClassInfo *aClassInfo) +{ +// XXX Special case for nsIXPCException ? + ClassInfoData objClassInfo = ClassInfoData(aClassInfo, nullptr); + if (objClassInfo.IsDOMClass()) + { + return NS_OK; + } + + // We give remote-XUL whitelisted domains a free pass here. See bug 932906. + if (!xpc::AllowContentXBLScope(js::GetContextCompartment(cx))) + { + return NS_OK; + } + + if (nsContentUtils::IsCallerChrome()) + { + return NS_OK; + } + + //-- Access denied, report an error + NS_ConvertUTF8toUTF16 strName("CreateWrapperDenied"); + nsAutoCString origin; + nsIPrincipal* subjectPrincipal = nsContentUtils::SubjectPrincipal(); + GetPrincipalDomainOrigin(subjectPrincipal, origin); + NS_ConvertUTF8toUTF16 originUnicode(origin); + NS_ConvertUTF8toUTF16 classInfoName(objClassInfo.GetName()); + const char16_t* formatStrings[] = { + classInfoName.get(), + originUnicode.get() + }; + uint32_t length = ArrayLength(formatStrings); + if (originUnicode.IsEmpty()) { + --length; + } else { + strName.AppendLiteral("ForOrigin"); + } + nsXPIDLString errorMsg; + nsresult rv = sStrBundle->FormatStringFromName(strName.get(), + formatStrings, + length, + getter_Copies(errorMsg)); + NS_ENSURE_SUCCESS(rv, rv); + + SetPendingException(cx, errorMsg.get()); + return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CanCreateInstance(JSContext *cx, + const nsCID &aCID) +{ + if (nsContentUtils::IsCallerChrome()) { + return NS_OK; + } + + //-- Access denied, report an error + nsAutoCString errorMsg("Permission denied to create instance of class. CID="); + char cidStr[NSID_LENGTH]; + aCID.ToProvidedString(cidStr); + errorMsg.Append(cidStr); + SetPendingExceptionASCII(cx, errorMsg.get()); + return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CanGetService(JSContext *cx, + const nsCID &aCID) +{ + if (nsContentUtils::IsCallerChrome()) { + return NS_OK; + } + + //-- Access denied, report an error + nsAutoCString errorMsg("Permission denied to get service. CID="); + char cidStr[NSID_LENGTH]; + aCID.ToProvidedString(cidStr); + errorMsg.Append(cidStr); + SetPendingExceptionASCII(cx, errorMsg.get()); + return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED; +} + +///////////////////////////////////// +// Method implementing nsIObserver // +///////////////////////////////////// +const char sJSEnabledPrefName[] = "javascript.enabled"; +const char sFileOriginPolicyPrefName[] = + "security.fileuri.strict_origin_policy"; + +static const char* kObservedPrefs[] = { + sJSEnabledPrefName, + sFileOriginPolicyPrefName, + "capability.policy.", + nullptr +}; + + +NS_IMETHODIMP +nsScriptSecurityManager::Observe(nsISupports* aObject, const char* aTopic, + const char16_t* aMessage) +{ + ScriptSecurityPrefChanged(); + return NS_OK; +} + +///////////////////////////////////////////// +// Constructor, Destructor, Initialization // +///////////////////////////////////////////// +nsScriptSecurityManager::nsScriptSecurityManager(void) + : mPrefInitialized(false) + , mIsJavaScriptEnabled(false) +{ + static_assert(sizeof(intptr_t) == sizeof(void*), + "intptr_t and void* have different lengths on this platform. " + "This may cause a security failure with the SecurityLevel union."); +} + +nsresult nsScriptSecurityManager::Init() +{ + nsresult rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService); + NS_ENSURE_SUCCESS(rv, rv); + + InitPrefs(); + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (!bundleService) + return NS_ERROR_FAILURE; + + rv = bundleService->CreateBundle("chrome://global/locale/security/caps.properties", &sStrBundle); + NS_ENSURE_SUCCESS(rv, rv); + + // Create our system principal singleton + RefPtr<nsSystemPrincipal> system = new nsSystemPrincipal(); + + mSystemPrincipal = system; + + //-- Register security check callback in the JS engine + // Currently this is used to control access to function.caller + sContext = danger::GetJSContext(); + + static const JSSecurityCallbacks securityCallbacks = { + ContentSecurityPolicyPermitsJSAction, + JSPrincipalsSubsume, + }; + + MOZ_ASSERT(!JS_GetSecurityCallbacks(sContext)); + JS_SetSecurityCallbacks(sContext, &securityCallbacks); + JS_InitDestroyPrincipalsCallback(sContext, nsJSPrincipals::Destroy); + + JS_SetTrustedPrincipals(sContext, system); + + return NS_OK; +} + +static StaticRefPtr<nsScriptSecurityManager> gScriptSecMan; + +nsScriptSecurityManager::~nsScriptSecurityManager(void) +{ + Preferences::RemoveObservers(this, kObservedPrefs); + if (mDomainPolicy) { + mDomainPolicy->Deactivate(); + } + // ContentChild might hold a reference to the domain policy, + // and it might release it only after the security manager is + // gone. But we can still assert this for the main process. + MOZ_ASSERT_IF(XRE_IsParentProcess(), + !mDomainPolicy); +} + +void +nsScriptSecurityManager::Shutdown() +{ + if (sContext) { + JS_SetSecurityCallbacks(sContext, nullptr); + JS_SetTrustedPrincipals(sContext, nullptr); + sContext = nullptr; + } + + NS_IF_RELEASE(sIOService); + NS_IF_RELEASE(sStrBundle); +} + +nsScriptSecurityManager * +nsScriptSecurityManager::GetScriptSecurityManager() +{ + return gScriptSecMan; +} + +/* static */ void +nsScriptSecurityManager::InitStatics() +{ + RefPtr<nsScriptSecurityManager> ssManager = new nsScriptSecurityManager(); + nsresult rv = ssManager->Init(); + if (NS_FAILED(rv)) { + MOZ_CRASH("ssManager->Init() failed"); + } + + ClearOnShutdown(&gScriptSecMan); + gScriptSecMan = ssManager; +} + +// Currently this nsGenericFactory constructor is used only from FastLoad +// (XPCOM object deserialization) code, when "creating" the system principal +// singleton. +nsSystemPrincipal * +nsScriptSecurityManager::SystemPrincipalSingletonConstructor() +{ + nsIPrincipal *sysprin = nullptr; + if (gScriptSecMan) + NS_ADDREF(sysprin = gScriptSecMan->mSystemPrincipal); + return static_cast<nsSystemPrincipal*>(sysprin); +} + +struct IsWhitespace { + static bool Test(char aChar) { return NS_IsAsciiWhitespace(aChar); }; +}; +struct IsWhitespaceOrComma { + static bool Test(char aChar) { return aChar == ',' || NS_IsAsciiWhitespace(aChar); }; +}; + +template <typename Predicate> +uint32_t SkipPast(const nsCString& str, uint32_t base) +{ + while (base < str.Length() && Predicate::Test(str[base])) { + ++base; + } + return base; +} + +template <typename Predicate> +uint32_t SkipUntil(const nsCString& str, uint32_t base) +{ + while (base < str.Length() && !Predicate::Test(str[base])) { + ++base; + } + return base; +} + +inline void +nsScriptSecurityManager::ScriptSecurityPrefChanged() +{ + MOZ_ASSERT(mPrefInitialized); + mIsJavaScriptEnabled = + Preferences::GetBool(sJSEnabledPrefName, mIsJavaScriptEnabled); + sStrictFileOriginPolicy = + Preferences::GetBool(sFileOriginPolicyPrefName, false); + mFileURIWhitelist.reset(); +} + +void +nsScriptSecurityManager::AddSitesToFileURIWhitelist(const nsCString& aSiteList) +{ + for (uint32_t base = SkipPast<IsWhitespace>(aSiteList, 0), bound = 0; + base < aSiteList.Length(); + base = SkipPast<IsWhitespace>(aSiteList, bound)) + { + // Grab the current site. + bound = SkipUntil<IsWhitespace>(aSiteList, base); + nsAutoCString site(Substring(aSiteList, base, bound - base)); + + // Check if the URI is schemeless. If so, add both http and https. + nsAutoCString unused; + if (NS_FAILED(sIOService->ExtractScheme(site, unused))) { + AddSitesToFileURIWhitelist(NS_LITERAL_CSTRING("http://") + site); + AddSitesToFileURIWhitelist(NS_LITERAL_CSTRING("https://") + site); + continue; + } + + // Convert it to a URI and add it to our list. + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), site, nullptr, nullptr, sIOService); + if (NS_SUCCEEDED(rv)) { + mFileURIWhitelist.ref().AppendElement(uri); + } else { + nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1")); + if (console) { + nsAutoString msg = NS_LITERAL_STRING("Unable to to add site to file:// URI whitelist: ") + + NS_ConvertASCIItoUTF16(site); + console->LogStringMessage(msg.get()); + } + } + } +} + +nsresult +nsScriptSecurityManager::InitPrefs() +{ + nsIPrefBranch* branch = Preferences::GetRootBranch(); + NS_ENSURE_TRUE(branch, NS_ERROR_FAILURE); + + mPrefInitialized = true; + + // Set the initial value of the "javascript.enabled" prefs + ScriptSecurityPrefChanged(); + + // set observer callbacks in case the value of the prefs change + Preferences::AddStrongObservers(this, kObservedPrefs); + + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetDomainPolicyActive(bool *aRv) +{ + *aRv = !!mDomainPolicy; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::ActivateDomainPolicy(nsIDomainPolicy** aRv) +{ + if (!XRE_IsParentProcess()) { + return NS_ERROR_SERVICE_NOT_AVAILABLE; + } + + return ActivateDomainPolicyInternal(aRv); +} + +NS_IMETHODIMP +nsScriptSecurityManager::ActivateDomainPolicyInternal(nsIDomainPolicy** aRv) +{ + // We only allow one domain policy at a time. The holder of the previous + // policy must explicitly deactivate it first. + if (mDomainPolicy) { + return NS_ERROR_SERVICE_NOT_AVAILABLE; + } + + mDomainPolicy = new DomainPolicy(); + nsCOMPtr<nsIDomainPolicy> ptr = mDomainPolicy; + ptr.forget(aRv); + return NS_OK; +} + +// Intentionally non-scriptable. Script must have a reference to the +// nsIDomainPolicy to deactivate it. +void +nsScriptSecurityManager::DeactivateDomainPolicy() +{ + mDomainPolicy = nullptr; +} + +void +nsScriptSecurityManager::CloneDomainPolicy(DomainPolicyClone* aClone) +{ + MOZ_ASSERT(aClone); + if (mDomainPolicy) { + mDomainPolicy->CloneDomainPolicy(aClone); + } else { + aClone->active() = false; + } +} + +NS_IMETHODIMP +nsScriptSecurityManager::PolicyAllowsScript(nsIURI* aURI, bool *aRv) +{ + nsresult rv; + + // Compute our rule. If we don't have any domain policy set up that might + // provide exceptions to this rule, we're done. + *aRv = mIsJavaScriptEnabled; + if (!mDomainPolicy) { + return NS_OK; + } + + // We have a domain policy. Grab the appropriate set of exceptions to the + // rule (either the blacklist or the whitelist, depending on whether script + // is enabled or disabled by default). + nsCOMPtr<nsIDomainSet> exceptions; + nsCOMPtr<nsIDomainSet> superExceptions; + if (*aRv) { + mDomainPolicy->GetBlacklist(getter_AddRefs(exceptions)); + mDomainPolicy->GetSuperBlacklist(getter_AddRefs(superExceptions)); + } else { + mDomainPolicy->GetWhitelist(getter_AddRefs(exceptions)); + mDomainPolicy->GetSuperWhitelist(getter_AddRefs(superExceptions)); + } + + bool contains; + rv = exceptions->Contains(aURI, &contains); + NS_ENSURE_SUCCESS(rv, rv); + if (contains) { + *aRv = !*aRv; + return NS_OK; + } + rv = superExceptions->ContainsSuperDomain(aURI, &contains); + NS_ENSURE_SUCCESS(rv, rv); + if (contains) { + *aRv = !*aRv; + } + + return NS_OK; +} + +const nsTArray<nsCOMPtr<nsIURI>>& +nsScriptSecurityManager::EnsureFileURIWhitelist() +{ + if (mFileURIWhitelist.isSome()) { + return mFileURIWhitelist.ref(); + } + + // + // Rebuild the set of principals for which we allow file:// URI loads. This + // implements a small subset of an old pref-based CAPS people that people + // have come to depend on. See bug 995943. + // + + mFileURIWhitelist.emplace(); + auto policies = mozilla::Preferences::GetCString("capability.policy.policynames"); + for (uint32_t base = SkipPast<IsWhitespaceOrComma>(policies, 0), bound = 0; + base < policies.Length(); + base = SkipPast<IsWhitespaceOrComma>(policies, bound)) + { + // Grab the current policy name. + bound = SkipUntil<IsWhitespaceOrComma>(policies, base); + auto policyName = Substring(policies, base, bound - base); + + // Figure out if this policy allows loading file:// URIs. If not, we can skip it. + nsCString checkLoadURIPrefName = NS_LITERAL_CSTRING("capability.policy.") + + policyName + + NS_LITERAL_CSTRING(".checkloaduri.enabled"); + if (!Preferences::GetString(checkLoadURIPrefName.get()).LowerCaseEqualsLiteral("allaccess")) { + continue; + } + + // Grab the list of domains associated with this policy. + nsCString domainPrefName = NS_LITERAL_CSTRING("capability.policy.") + + policyName + + NS_LITERAL_CSTRING(".sites"); + auto siteList = Preferences::GetCString(domainPrefName.get()); + AddSitesToFileURIWhitelist(siteList); + } + + return mFileURIWhitelist.ref(); +} diff --git a/caps/nsScriptSecurityManager.h b/caps/nsScriptSecurityManager.h new file mode 100644 index 000000000..5dc0b393a --- /dev/null +++ b/caps/nsScriptSecurityManager.h @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=4 et sw=4 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsScriptSecurityManager_h__ +#define nsScriptSecurityManager_h__ + +#include "nsIScriptSecurityManager.h" + +#include "nsIAddonPolicyService.h" +#include "mozilla/Maybe.h" +#include "nsIAddonPolicyService.h" +#include "nsIPrincipal.h" +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsServiceManagerUtils.h" +#include "plstr.h" +#include "js/TypeDecls.h" + +#include <stdint.h> + +class nsCString; +class nsIIOService; +class nsIStringBundle; +class nsSystemPrincipal; + +namespace mozilla { +class PrincipalOriginAttributes; +} // namespace mozilla + +///////////////////////////// +// nsScriptSecurityManager // +///////////////////////////// +#define NS_SCRIPTSECURITYMANAGER_CID \ +{ 0x7ee2a4c0, 0x4b93, 0x17d3, \ +{ 0xba, 0x18, 0x00, 0x60, 0xb0, 0xf1, 0x99, 0xa2 }} + +class nsScriptSecurityManager final : public nsIScriptSecurityManager, + public nsIObserver +{ +public: + static void Shutdown(); + + NS_DEFINE_STATIC_CID_ACCESSOR(NS_SCRIPTSECURITYMANAGER_CID) + + NS_DECL_ISUPPORTS + NS_DECL_NSISCRIPTSECURITYMANAGER + NS_DECL_NSIOBSERVER + + static nsScriptSecurityManager* + GetScriptSecurityManager(); + + // Invoked exactly once, by XPConnect. + static void InitStatics(); + + static nsSystemPrincipal* + SystemPrincipalSingletonConstructor(); + + /** + * Utility method for comparing two URIs. For security purposes, two URIs + * are equivalent if their schemes, hosts, and ports (if any) match. This + * method returns true if aSubjectURI and aObjectURI have the same origin, + * false otherwise. + */ + static bool SecurityCompareURIs(nsIURI* aSourceURI, nsIURI* aTargetURI); + static uint32_t SecurityHashURI(nsIURI* aURI); + + static uint16_t AppStatusForPrincipal(nsIPrincipal *aPrin); + + static nsresult + ReportError(JSContext* cx, const nsAString& messageTag, + nsIURI* aSource, nsIURI* aTarget); + + static uint32_t + HashPrincipalByOrigin(nsIPrincipal* aPrincipal); + + static bool + GetStrictFileOriginPolicy() + { + return sStrictFileOriginPolicy; + } + + void DeactivateDomainPolicy(); + +private: + + // GetScriptSecurityManager is the only call that can make one + nsScriptSecurityManager(); + virtual ~nsScriptSecurityManager(); + + // Decides, based on CSP, whether or not eval() and stuff can be executed. + static bool + ContentSecurityPolicyPermitsJSAction(JSContext *cx); + + static bool + JSPrincipalsSubsume(JSPrincipals *first, JSPrincipals *second); + + // Returns null if a principal cannot be found; generally callers + // should error out at that point. + static nsIPrincipal* doGetObjectPrincipal(JSObject* obj); + + nsresult + Init(); + + nsresult + InitPrefs(); + + inline void + ScriptSecurityPrefChanged(); + + inline void + AddSitesToFileURIWhitelist(const nsCString& aSiteList); + + // If aURI is a moz-extension:// URI, set mAddonId to the associated addon. + nsresult MaybeSetAddonIdFromURI(mozilla::PrincipalOriginAttributes& aAttrs, nsIURI* aURI); + + nsresult GetChannelResultPrincipal(nsIChannel* aChannel, + nsIPrincipal** aPrincipal, + bool aIgnoreSandboxing); + + nsresult + CheckLoadURIFlags(nsIURI* aSourceURI, nsIURI* aTargetURI, nsIURI* aSourceBaseURI, + nsIURI* aTargetBaseURI, uint32_t aFlags); + + // Returns the file URI whitelist, initializing it if it has not been + // initialized. + const nsTArray<nsCOMPtr<nsIURI>>& EnsureFileURIWhitelist(); + + nsCOMPtr<nsIPrincipal> mSystemPrincipal; + bool mPrefInitialized; + bool mIsJavaScriptEnabled; + + // List of URIs whose domains and sub-domains are whitelisted to allow + // access to file: URIs. Lazily initialized; isNothing() when not yet + // initialized. + mozilla::Maybe<nsTArray<nsCOMPtr<nsIURI>>> mFileURIWhitelist; + + // This machinery controls new-style domain policies. The old-style + // policy machinery will be removed soon. + nsCOMPtr<nsIDomainPolicy> mDomainPolicy; + + // Cached addon policy service. We can't generate this in Init() because + // that's too early to get a service. + mozilla::Maybe<nsCOMPtr<nsIAddonPolicyService>> mAddonPolicyService; + nsIAddonPolicyService* GetAddonPolicyService() + { + if (mAddonPolicyService.isNothing()) { + mAddonPolicyService.emplace(do_GetService("@mozilla.org/addons/policy-service;1")); + } + return mAddonPolicyService.ref(); + } + + static bool sStrictFileOriginPolicy; + + static nsIIOService *sIOService; + static nsIStringBundle *sStrBundle; + static JSContext *sContext; +}; + +#endif // nsScriptSecurityManager_h__ diff --git a/caps/nsSystemPrincipal.cpp b/caps/nsSystemPrincipal.cpp new file mode 100644 index 000000000..7bfa20efc --- /dev/null +++ b/caps/nsSystemPrincipal.cpp @@ -0,0 +1,141 @@ +/* -*- 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/. */ + +/* The privileged system principal. */ + +#include "nscore.h" +#include "nsSystemPrincipal.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsIURL.h" +#include "nsCOMPtr.h" +#include "nsXPIDLString.h" +#include "nsReadableUtils.h" +#include "nsCRT.h" +#include "nsString.h" +#include "nsIClassInfoImpl.h" +#include "nsIScriptSecurityManager.h" +#include "pratom.h" + +NS_IMPL_CLASSINFO(nsSystemPrincipal, nullptr, + nsIClassInfo::SINGLETON | nsIClassInfo::MAIN_THREAD_ONLY, + NS_SYSTEMPRINCIPAL_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsSystemPrincipal, + nsIPrincipal, + nsISerializable) +NS_IMPL_CI_INTERFACE_GETTER(nsSystemPrincipal, + nsIPrincipal, + nsISerializable) + +#define SYSTEM_PRINCIPAL_SPEC "[System Principal]" + +nsresult +nsSystemPrincipal::GetScriptLocation(nsACString &aStr) +{ + aStr.AssignLiteral(SYSTEM_PRINCIPAL_SPEC); + return NS_OK; +} + +/////////////////////////////////////// +// Methods implementing nsIPrincipal // +/////////////////////////////////////// + +NS_IMETHODIMP +nsSystemPrincipal::GetHashValue(uint32_t *result) +{ + *result = NS_PTR_TO_INT32(this); + return NS_OK; +} + +NS_IMETHODIMP +nsSystemPrincipal::GetURI(nsIURI** aURI) +{ + *aURI = nullptr; + return NS_OK; +} + +nsresult +nsSystemPrincipal::GetOriginInternal(nsACString& aOrigin) +{ + aOrigin.AssignLiteral(SYSTEM_PRINCIPAL_SPEC); + return NS_OK; +} + +NS_IMETHODIMP +nsSystemPrincipal::GetCsp(nsIContentSecurityPolicy** aCsp) +{ + *aCsp = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSystemPrincipal::SetCsp(nsIContentSecurityPolicy* aCsp) +{ + // Never destroy an existing CSP on the principal. + // This method should only be called in rare cases. + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSystemPrincipal::EnsureCSP(nsIDOMDocument* aDocument, + nsIContentSecurityPolicy** aCSP) +{ + // CSP on a system principal makes no sense + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSystemPrincipal::GetPreloadCsp(nsIContentSecurityPolicy** aPreloadCSP) +{ + *aPreloadCSP = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSystemPrincipal::EnsurePreloadCSP(nsIDOMDocument* aDocument, + nsIContentSecurityPolicy** aPreloadCSP) +{ + // CSP on a system principal makes no sense + return NS_OK; +} + +NS_IMETHODIMP +nsSystemPrincipal::GetDomain(nsIURI** aDomain) +{ + *aDomain = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSystemPrincipal::SetDomain(nsIURI* aDomain) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSystemPrincipal::GetBaseDomain(nsACString& aBaseDomain) +{ + // No base domain for chrome. + return NS_OK; +} + +////////////////////////////////////////// +// Methods implementing nsISerializable // +////////////////////////////////////////// + +NS_IMETHODIMP +nsSystemPrincipal::Read(nsIObjectInputStream* aStream) +{ + // no-op: CID is sufficient to identify the mSystemPrincipal singleton + return NS_OK; +} + +NS_IMETHODIMP +nsSystemPrincipal::Write(nsIObjectOutputStream* aStream) +{ + // no-op: CID is sufficient to identify the mSystemPrincipal singleton + return NS_OK; +} diff --git a/caps/nsSystemPrincipal.h b/caps/nsSystemPrincipal.h new file mode 100644 index 000000000..b7466e70a --- /dev/null +++ b/caps/nsSystemPrincipal.h @@ -0,0 +1,60 @@ +/* -*- 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 nsSystemPrincipal_h__ +#define nsSystemPrincipal_h__ + +#include "nsIPrincipal.h" +#include "nsJSPrincipals.h" + +#include "mozilla/BasePrincipal.h" + +#define NS_SYSTEMPRINCIPAL_CID \ +{ 0x4a6212db, 0xaccb, 0x11d3, \ +{ 0xb7, 0x65, 0x0, 0x60, 0xb0, 0xb6, 0xce, 0xcb }} +#define NS_SYSTEMPRINCIPAL_CONTRACTID "@mozilla.org/systemprincipal;1" + + +class nsSystemPrincipal final : public mozilla::BasePrincipal +{ +public: + NS_DECL_NSISERIALIZABLE + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + NS_IMETHOD GetHashValue(uint32_t* aHashValue) override; + NS_IMETHOD GetURI(nsIURI** aURI) override; + NS_IMETHOD GetDomain(nsIURI** aDomain) override; + NS_IMETHOD SetDomain(nsIURI* aDomain) override; + NS_IMETHOD GetCsp(nsIContentSecurityPolicy** aCsp) override; + NS_IMETHOD SetCsp(nsIContentSecurityPolicy* aCsp) override; + NS_IMETHOD EnsureCSP(nsIDOMDocument* aDocument, nsIContentSecurityPolicy** aCSP) override; + NS_IMETHOD GetPreloadCsp(nsIContentSecurityPolicy** aPreloadCSP) override; + NS_IMETHOD EnsurePreloadCSP(nsIDOMDocument* aDocument, nsIContentSecurityPolicy** aCSP) override; + NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override; + nsresult GetOriginInternal(nsACString& aOrigin) override; + + nsSystemPrincipal() {} + + virtual nsresult GetScriptLocation(nsACString &aStr) override; + +protected: + virtual ~nsSystemPrincipal(void) {} + + bool SubsumesInternal(nsIPrincipal *aOther, DocumentDomainConsideration aConsideration) override + { + return true; + } + + bool MayLoadInternal(nsIURI* aURI) override + { + return true; + } + + PrincipalKind Kind() override { return eSystemPrincipal; } +}; + +#endif // nsSystemPrincipal_h__ diff --git a/caps/tests/gtest/TestOriginAttributes.cpp b/caps/tests/gtest/TestOriginAttributes.cpp new file mode 100644 index 000000000..e11bf28eb --- /dev/null +++ b/caps/tests/gtest/TestOriginAttributes.cpp @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "gtest/gtest.h" +#include "mozilla/BasePrincipal.h" + +using mozilla::PrincipalOriginAttributes; + +static void +TestSuffix(const PrincipalOriginAttributes& attrs) +{ + nsAutoCString suffix; + attrs.CreateSuffix(suffix); + + PrincipalOriginAttributes attrsFromSuffix; + bool success = attrsFromSuffix.PopulateFromSuffix(suffix); + EXPECT_TRUE(success); + + EXPECT_EQ(attrs, attrsFromSuffix); +} + +TEST(PrincipalOriginAttributes, Suffix_default) +{ + PrincipalOriginAttributes attrs; + TestSuffix(attrs); +} + +TEST(PrincipalOriginAttributes, Suffix_appId_inIsolatedMozBrowser) +{ + PrincipalOriginAttributes attrs(1, true); + TestSuffix(attrs); +} + +TEST(PrincipalOriginAttributes, Suffix_maxAppId_inIsolatedMozBrowser) +{ + PrincipalOriginAttributes attrs(4294967295, true); + TestSuffix(attrs); +} diff --git a/caps/tests/gtest/moz.build b/caps/tests/gtest/moz.build new file mode 100644 index 000000000..26447f54c --- /dev/null +++ b/caps/tests/gtest/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + 'TestOriginAttributes.cpp' +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul-gtest' diff --git a/caps/tests/mochitest/browser.ini b/caps/tests/mochitest/browser.ini new file mode 100644 index 000000000..d8a1278f9 --- /dev/null +++ b/caps/tests/mochitest/browser.ini @@ -0,0 +1 @@ +[browser_checkloaduri.js] diff --git a/caps/tests/mochitest/browser_checkloaduri.js b/caps/tests/mochitest/browser_checkloaduri.js new file mode 100644 index 000000000..24a97c1c4 --- /dev/null +++ b/caps/tests/mochitest/browser_checkloaduri.js @@ -0,0 +1,292 @@ +"use strict"; + +let ssm = Services.scriptSecurityManager; +// This will show a directory listing, but we never actually load these so that's OK. +const kDummyPage = getRootDirectory(gTestPath); + +const kAboutPagesRegistered = Promise.all([ + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, "test-chrome-privs", kDummyPage, + Ci.nsIAboutModule.ALLOW_SCRIPT), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, "test-chrome-privs2", kDummyPage, + Ci.nsIAboutModule.ALLOW_SCRIPT), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, "test-unknown-linkable", kDummyPage, + Ci.nsIAboutModule.MAKE_LINKABLE | Ci.nsIAboutModule.ALLOW_SCRIPT), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, "test-unknown-linkable2", kDummyPage, + Ci.nsIAboutModule.MAKE_LINKABLE | Ci.nsIAboutModule.ALLOW_SCRIPT), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, "test-unknown-unlinkable", kDummyPage, + Ci.nsIAboutModule.ALLOW_SCRIPT), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, "test-unknown-unlinkable2", kDummyPage, + Ci.nsIAboutModule.ALLOW_SCRIPT), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, "test-content-unlinkable", kDummyPage, + Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT | Ci.nsIAboutModule.ALLOW_SCRIPT), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, "test-content-unlinkable2", kDummyPage, + Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT | Ci.nsIAboutModule.ALLOW_SCRIPT), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, "test-content-linkable", kDummyPage, + Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT | Ci.nsIAboutModule.MAKE_LINKABLE | + Ci.nsIAboutModule.ALLOW_SCRIPT), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, "test-content-linkable2", kDummyPage, + Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT | Ci.nsIAboutModule.MAKE_LINKABLE | + Ci.nsIAboutModule.ALLOW_SCRIPT), +]); + +const URLs = new Map([ + ["http://www.example.com", [ + // For each of these entries, the booleans represent whether the parent URI can: + // - load them + // - load them without principal inheritance + // - whether the URI can be created at all (some protocol handlers will + // refuse to create certain variants) + ["http://www.example2.com", true, true, true], + ["feed:http://www.example2.com", false, false, true], + ["https://www.example2.com", true, true, true], + ["chrome://foo/content/bar.xul", false, false, true], + ["feed:chrome://foo/content/bar.xul", false, false, false], + ["view-source:http://www.example2.com", false, false, true], + ["view-source:https://www.example2.com", false, false, true], + ["view-source:feed:http://www.example2.com", false, false, true], + ["feed:view-source:http://www.example2.com", false, false, false], + ["data:text/html,Hi", true, false, true], + ["view-source:data:text/html,Hi", false, false, true], + ["javascript:alert('hi')", true, false, true], + ["moz://a", false, false, true], + ["about:test-chrome-privs", false, false, true], + ["about:test-unknown-unlinkable", false, false, true], + ["about:test-content-unlinkable", false, false, true], + ["about:test-content-linkable", true, true, true], + // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it: + ["about:test-unknown-linkable", false, false, true], + ]], + ["feed:http://www.example.com", [ + ["http://www.example2.com", true, true, true], + ["feed:http://www.example2.com", true, true, true], + ["https://www.example2.com", true, true, true], + ["feed:https://www.example2.com", true, true, true], + ["chrome://foo/content/bar.xul", false, false, true], + ["feed:chrome://foo/content/bar.xul", false, false, false], + ["view-source:http://www.example2.com", false, false, true], + ["view-source:https://www.example2.com", false, false, true], + ["view-source:feed:http://www.example2.com", false, false, true], + ["feed:view-source:http://www.example2.com", false, false, false], + ["data:text/html,Hi", true, false, true], + ["view-source:data:text/html,Hi", false, false, true], + ["javascript:alert('hi')", true, false, true], + ["moz://a", false, false, true], + ["about:test-chrome-privs", false, false, true], + ["about:test-unknown-unlinkable", false, false, true], + ["about:test-content-unlinkable", false, false, true], + ["about:test-content-linkable", true, true, true], + // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it: + ["about:test-unknown-linkable", false, false, true], + ]], + ["view-source:http://www.example.com", [ + ["http://www.example2.com", true, true, true], + ["feed:http://www.example2.com", false, false, true], + ["https://www.example2.com", true, true, true], + ["feed:https://www.example2.com", false, false, true], + ["chrome://foo/content/bar.xul", false, false, true], + ["feed:chrome://foo/content/bar.xul", false, false, false], + ["view-source:http://www.example2.com", true, true, true], + ["view-source:https://www.example2.com", true, true, true], + ["view-source:feed:http://www.example2.com", false, false, true], + ["feed:view-source:http://www.example2.com", false, false, false], + ["data:text/html,Hi", true, false, true], + ["view-source:data:text/html,Hi", true, false, true], + ["javascript:alert('hi')", true, false, true], + ["moz://a", false, false, true], + ["about:test-chrome-privs", false, false, true], + ["about:test-unknown-unlinkable", false, false, true], + ["about:test-content-unlinkable", false, false, true], + ["about:test-content-linkable", true, true, true], + // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it: + ["about:test-unknown-linkable", false, false, true], + ]], + // about: related tests. + ["about:test-chrome-privs", [ + ["about:test-chrome-privs", true, true, true], + ["about:test-chrome-privs2", true, true, true], + ["about:test-chrome-privs2?foo#bar", true, true, true], + ["about:test-chrome-privs2?foo", true, true, true], + ["about:test-chrome-privs2#bar", true, true, true], + + ["about:test-unknown-unlinkable", true, true, true], + + ["about:test-content-unlinkable", true, true, true], + ["about:test-content-unlinkable?foo", true, true, true], + ["about:test-content-unlinkable?foo#bar", true, true, true], + ["about:test-content-unlinkable#bar", true, true, true], + + ["about:test-content-linkable", true, true, true], + + ["about:test-unknown-linkable", true, true, true], + ]], + ["about:test-unknown-unlinkable", [ + ["about:test-chrome-privs", false, false, true], + + // Can link to ourselves: + ["about:test-unknown-unlinkable", true, true, true], + // Can't link to unlinkable content if we're not sure it's privileged: + ["about:test-unknown-unlinkable2", false, false, true], + + ["about:test-content-unlinkable", true, true, true], + ["about:test-content-unlinkable2", true, true, true], + ["about:test-content-unlinkable2?foo", true, true, true], + ["about:test-content-unlinkable2?foo#bar", true, true, true], + ["about:test-content-unlinkable2#bar", true, true, true], + + ["about:test-content-linkable", true, true, true], + + // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it: + ["about:test-unknown-linkable", false, false, true], + ]], + ["about:test-content-unlinkable", [ + ["about:test-chrome-privs", false, false, true], + + // Can't link to unlinkable content if we're not sure it's privileged: + ["about:test-unknown-unlinkable", false, false, true], + + ["about:test-content-unlinkable", true, true, true], + ["about:test-content-unlinkable2", true, true, true], + ["about:test-content-unlinkable2?foo", true, true, true], + ["about:test-content-unlinkable2?foo#bar", true, true, true], + ["about:test-content-unlinkable2#bar", true, true, true], + + ["about:test-content-linkable", true, true, true], + ["about:test-unknown-linkable", false, false, true], + ]], + ["about:test-unknown-linkable", [ + ["about:test-chrome-privs", false, false, true], + + // Linkable content can't link to unlinkable content. + ["about:test-unknown-unlinkable", false, false, true], + + ["about:test-content-unlinkable", false, false, true], + ["about:test-content-unlinkable2", false, false, true], + ["about:test-content-unlinkable2?foo", false, false, true], + ["about:test-content-unlinkable2?foo#bar", false, false, true], + ["about:test-content-unlinkable2#bar", false, false, true], + + // ... but it can link to other linkable content. + ["about:test-content-linkable", true, true, true], + + // Can link to ourselves: + ["about:test-unknown-linkable", true, true, true], + + // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it: + ["about:test-unknown-linkable2", false, false, true], + ]], + ["about:test-content-linkable", [ + ["about:test-chrome-privs", false, false, true], + + // Linkable content can't link to unlinkable content. + ["about:test-unknown-unlinkable", false, false, true], + + ["about:test-content-unlinkable", false, false, true], + + // ... but it can link to itself and other linkable content. + ["about:test-content-linkable", true, true, true], + ["about:test-content-linkable2", true, true, true], + + // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it: + ["about:test-unknown-linkable", false, false, true], + ]], +]); + +function testURL(source, target, canLoad, canLoadWithoutInherit, canCreate, flags) { + function getPrincipalDesc(principal) { + if (principal.URI) { + return principal.URI.spec; + } + if (principal.isSystemPrincipal) { + return "system principal"; + } + if (principal.isNullPrincipal) { + return "null principal"; + } + return "unknown principal"; + } + let threw = false; + let targetURI; + try { + targetURI = makeURI(target); + } catch (ex) { + ok(!canCreate, "Shouldn't be passing URIs that we can't create. Failed to create: " + target); + return; + } + ok(canCreate, "Created a URI for " + target + " which should " + + (canCreate ? "" : "not ") + "be possible."); + try { + ssm.checkLoadURIWithPrincipal(source, targetURI, flags); + } catch (ex) { + info(ex.message); + threw = true; + } + let inheritDisallowed = flags & ssm.DISALLOW_INHERIT_PRINCIPAL; + let shouldThrow = inheritDisallowed ? !canLoadWithoutInherit : !canLoad; + ok(threw == shouldThrow, + "Should " + (shouldThrow ? "" : "not ") + "throw an error when loading " + + target + " from " + getPrincipalDesc(source) + + (inheritDisallowed ? " without" : " with") + " principal inheritance."); +} + +add_task(function* () { + yield kAboutPagesRegistered; + let baseFlags = ssm.STANDARD | ssm.DONT_REPORT_ERRORS; + for (let [sourceString, targetsAndExpectations] of URLs) { + let source; + if (sourceString.startsWith("about:test-chrome-privs")) { + source = ssm.getSystemPrincipal(); + } else { + source = ssm.createCodebasePrincipal(makeURI(sourceString), {}); + } + for (let [target, canLoad, canLoadWithoutInherit, canCreate] of targetsAndExpectations) { + testURL(source, target, canLoad, canLoadWithoutInherit, canCreate, baseFlags); + testURL(source, target, canLoad, canLoadWithoutInherit, canCreate, + baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL); + } + } + + // Now test blob URIs, which we need to do in-content. + yield BrowserTestUtils.withNewTab("http://www.example.com/", function* (browser) { + yield ContentTask.spawn( + browser, + testURL.toString(), + function* (testURLFn) { + let testURL = eval("(" + testURLFn + ")"); + let ssm = Services.scriptSecurityManager; + let baseFlags = ssm.STANDARD | ssm.DONT_REPORT_ERRORS; + let makeURI = Cu.import("resource://gre/modules/BrowserUtils.jsm", {}).BrowserUtils.makeURI; + let b = new content.Blob(["I am a blob"]); + let contentBlobURI = content.URL.createObjectURL(b); + let contentPrincipal = content.document.nodePrincipal; + // Loading this blob URI from the content page should work: + testURL(contentPrincipal, contentBlobURI, true, true, true, baseFlags); + testURL(contentPrincipal, contentBlobURI, true, true, true, + baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL); + + testURL(contentPrincipal, "view-source:" + contentBlobURI, false, false, true, + baseFlags); + testURL(contentPrincipal, "view-source:" + contentBlobURI, false, false, true, + baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL); + + // Feed URIs for blobs can't be created, so need to pass false as the fourth param. + for (let prefix of ["feed:", "view-source:feed:", "feed:view-source:"]) { + testURL(contentPrincipal, prefix + contentBlobURI, false, false, false, + baseFlags); + testURL(contentPrincipal, prefix + contentBlobURI, false, false, false, + baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL); + } + } + ); + + }); +}); diff --git a/caps/tests/mochitest/chrome.ini b/caps/tests/mochitest/chrome.ini new file mode 100644 index 000000000..ee71ad30b --- /dev/null +++ b/caps/tests/mochitest/chrome.ini @@ -0,0 +1,9 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = + file_disableScript.html + !/caps/tests/mochitest/file_disableScript.html + +[test_bug995943.xul] +[test_addonMayLoad.html] +[test_disableScript.xul] diff --git a/caps/tests/mochitest/file_data.txt b/caps/tests/mochitest/file_data.txt new file mode 100644 index 000000000..26d7bd848 --- /dev/null +++ b/caps/tests/mochitest/file_data.txt @@ -0,0 +1 @@ +server data fetched over XHR diff --git a/caps/tests/mochitest/file_disableScript.html b/caps/tests/mochitest/file_disableScript.html new file mode 100644 index 000000000..f4888cd58 --- /dev/null +++ b/caps/tests/mochitest/file_disableScript.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head> +<script> +var gFiredOnload = false; +var gFiredOnclick = false; +</script> +</head> +<body onload="gFiredOnload = true;" onclick="gFiredOnclick = true;"> +</body> +</html> diff --git a/caps/tests/mochitest/mochitest.ini b/caps/tests/mochitest/mochitest.ini new file mode 100644 index 000000000..866553ea2 --- /dev/null +++ b/caps/tests/mochitest/mochitest.ini @@ -0,0 +1,13 @@ +[DEFAULT] +support-files = + file_data.txt + file_disableScript.html + !/js/xpconnect/tests/mochitest/file_empty.html + +[test_app_principal_equality.html] +[test_bug246699.html] +[test_bug292789.html] +[test_bug423375.html] +[test_bug470804.html] +[test_disallowInheritPrincipal.html] +[test_extensionURL.html] diff --git a/caps/tests/mochitest/resource_test_file.html b/caps/tests/mochitest/resource_test_file.html new file mode 100644 index 000000000..8201bd70e --- /dev/null +++ b/caps/tests/mochitest/resource_test_file.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<html><head><title>resource test file</title></head><body></body></html> diff --git a/caps/tests/mochitest/test_addonMayLoad.html b/caps/tests/mochitest/test_addonMayLoad.html new file mode 100644 index 000000000..286284bfe --- /dev/null +++ b/caps/tests/mochitest/test_addonMayLoad.html @@ -0,0 +1,97 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1180921 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1180921</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript;version=1.8"> + + /** Test for Bug 1180921 **/ + const Cc = Components.classes; + const Ci = Components.interfaces; + const Cu = Components.utils; + Cu.import("resource://gre/modules/Services.jsm"); + let ssm = Services.scriptSecurityManager; + let aps = Cc["@mozilla.org/addons/policy-service;1"].getService(Ci.nsIAddonPolicyService).wrappedJSObject; + + SimpleTest.waitForExplicitFinish(); + SimpleTest.registerCleanupFunction(function() { + aps.setAddonLoadURICallback('addonA', null); + aps.setAddonLoadURICallback('addonB', null); + }); + + function tryLoad(sb, uri) { + let p = new Promise(function(resolve, reject) { + Cu.exportFunction(resolve, sb, { defineAs: "finish" }); + Cu.exportFunction(reject, sb, { defineAs: "error" }); + sb.eval("try { (function () { " + + " var xhr = new XMLHttpRequest();" + + " xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { finish(xhr.status == 200); } };" + + " xhr.open('GET', '" + uri + "', true);" + + " xhr.send();" + + "})() } catch (e) { error(e); }"); + }); + return p; + } + + let exampleCom_addonA = new Cu.Sandbox(ssm.createCodebasePrincipal(Services.io.newURI('http://example.com', null, null), {addonId: 'addonA'}), + {wantGlobalProperties: ['XMLHttpRequest']}); + let nullPrin_addonA = new Cu.Sandbox(ssm.createNullPrincipal({addonId: 'addonA'}), + {wantGlobalProperties: ['XMLHttpRequest']}); + let exampleCom_addonB = new Cu.Sandbox(ssm.createCodebasePrincipal(Services.io.newURI('http://example.com', null, null), {addonId: 'addonB'}), + {wantGlobalProperties: ['XMLHttpRequest']}); + + function uriForDomain(d) { return d + '/tests/caps/tests/mochitest/file_data.txt' } + + tryLoad(exampleCom_addonA, uriForDomain('http://example.com')) + .then(function(success) { + ok(success, "same-origin load should succeed for addon A"); + return tryLoad(nullPrin_addonA, uriForDomain('http://example.com')); + }).then(function(success) { + ok(!success, "null-principal load should fail for addon A"); + return tryLoad(exampleCom_addonB, uriForDomain('http://example.com')); + }).then(function(success) { + ok(success, "same-origin load should succeed for addon B"); + return tryLoad(exampleCom_addonA, uriForDomain('http://test1.example.org')); + }).then(function(success) { + ok(!success, "cross-origin load should fail for addon A"); + aps.setAddonLoadURICallback('addonA', function(uri) { return /test1/.test(uri.host); }); + aps.setAddonLoadURICallback('addonB', function(uri) { return /test2/.test(uri.host); }); + return tryLoad(exampleCom_addonA, uriForDomain('http://test1.example.org')); + }).then(function(success) { + ok(success, "whitelisted cross-origin load of test1 should succeed for addon A"); + return tryLoad(nullPrin_addonA, uriForDomain('http://test1.example.org')); + }).then(function(success) { + ok(!success, "whitelisted null principal load of test1 should still fail for addon A"); + return tryLoad(exampleCom_addonB, uriForDomain('http://test1.example.org')); + }).then(function(success) { + ok(!success, "non-whitelisted cross-origin load of test1 should fail for addon B"); + return tryLoad(exampleCom_addonB, uriForDomain('http://test2.example.org')); + }).then(function(success) { + ok(success, "whitelisted cross-origin load of test2 should succeed for addon B"); + return tryLoad(exampleCom_addonA, uriForDomain('http://test2.example.org')); + }).then(function(success) { + ok(!success, "non-whitelisted cross-origin load of test2 should fail for addon A"); + SimpleTest.finish(); + }, function(e) { + ok(false, "Rejected promise chain: " + e); + SimpleTest.finish(); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1180921">Mozilla Bug 1180921</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/caps/tests/mochitest/test_app_principal_equality.html b/caps/tests/mochitest/test_app_principal_equality.html new file mode 100644 index 000000000..f59f1f789 --- /dev/null +++ b/caps/tests/mochitest/test_app_principal_equality.html @@ -0,0 +1,88 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=777467 +--> +<head> + <meta charset="utf-8"> + <title>Test app principal's equality</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=777467">Mozilla Bug 777467</a> +<p id="display"></p> +<script> + +/** Test for app principal's equality **/ + +SimpleTest.waitForExplicitFinish(); + +var permissions = new Promise(resolve => { + SpecialPowers.pushPermissions( + [{ type: "browser", allow: true, context: document }, + { type: "embed-apps", allow: true, context: document }], + resolve); +}); + +permissions.then(() => { + $('content').innerHTML = + '<iframe src="error404"></iframe>\n' + + '<iframe mozbrowser src="error404"></iframe>\n' + + '<iframe mozapp="http://example.org/manifest.webapp" mozbrowser src="error404"></iframe>'; + + var iframes = document.getElementsByTagName("iframe"); + var promises = [] + for (var i = 0; i < promises.length; ++i) { + promises.push(new Promise(resolve => { + iframes[i].addEventListener("load", resolve); + })); + } + + return Promise.all(promises); +}); + +var prefs = new Promise(resolve => { + SpecialPowers.pushPrefEnv( + { set: [[ "dom.mozBrowserFramesEnabled", true ], + [ "dom.ipc.browser_frames.oop_by_default", false ]] }, + resolve); +}); +</script> +<div id="content" style="display: none;"> +</div> +<pre id="test"> +<script type="application/javascript"> + +function canAccessDocument(win) { + var result = true; + try { + win.document; + } catch(e) { + result = false; + } + return result; +} + +var loaded = new Promise(resolve => addLoadEvent(resolve)); + +Promise.all([ permissions, prefs, loaded ]).then(runTest); + +function runTest() { + // Test the witness frame (we can access same-origin frame). + is(canAccessDocument(frames[0]), true, + "should be able to access the first frame"); + + // Test different app/browserElement frames. + for (var i=1; i<frames.length; ++i) { + is(canAccessDocument(frames[i]), false, + "should not be able to access the other frames"); + } + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/caps/tests/mochitest/test_bug246699.html b/caps/tests/mochitest/test_bug246699.html new file mode 100644 index 000000000..bb733e5df --- /dev/null +++ b/caps/tests/mochitest/test_bug246699.html @@ -0,0 +1,65 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=246699 +--> +<head> + <title>Test for Bug 246699</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=246699">Mozilla Bug 246699</a> +<p id="display"></p> +<div id="content" style="display: none"> +<iframe id="load-frame"></iframe> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** + ** Test for Bug 246699 + ** (should produce stack information for caps errors) + **/ +function isError(e) +{ + return e.constructor.name === "Error" || e.constructor.name === "TypeError"; +} + +function hasStack(e) +{ + return isError(e) && /inciteCaps/.test(e.stack); +} + +function inciteCaps(f) +{ + try { + f(); + return "operation succeeded"; + } catch (e if hasStack(e)) { + return "denied-stack"; + } catch (e) { + return "unexpected: " + e; + } +} + +function tryChromeLoad() +{ + window.frames[0].location = "chrome://global/content/mozilla.xhtml"; +} + +function tryComponentsClasses() +{ + return SpecialPowers.Components.classes["@mozilla.org/dummy;1"]; +} + + +is(inciteCaps(tryChromeLoad), "denied-stack", + "should get stack for content-loading-chrome rejection"); +is(inciteCaps(tryComponentsClasses), "denied-stack", + "should get stack for SpecialPowers.Components.classes rejection"); +</script> +</pre> +</body> +</html> + diff --git a/caps/tests/mochitest/test_bug292789.html b/caps/tests/mochitest/test_bug292789.html new file mode 100644 index 000000000..291ba00c1 --- /dev/null +++ b/caps/tests/mochitest/test_bug292789.html @@ -0,0 +1,105 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=292789 +--> +<head> + <title>Test for Bug 292789</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=292789">Mozilla Bug 292789</a> +<p id="display"></p> +<div id="content" style="display: none"> + <script src="chrome://global/content/treeUtils.js"></script> + <script type="application/javascript;version=1.8" src="chrome://mozapps/content/xpinstall/xpinstallConfirm.js"></script> + <script id="resjs" type="application/javascript;version=1.8"></script> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 292789 + ** + ** Selectively allow access to whitelisted chrome packages + ** even for ALLOW_CHROME mechanisms (<script>, <img> etc) + **/ + +SimpleTest.waitForExplicitFinish(); + +/** <script src=""> test **/ +function testScriptSrc(aCallback) { + is(typeof gTreeUtils.sort, "function", + "content can still load <script> from chrome://global"); + is(typeof XPInstallConfirm, "undefined", + "content should not be able to load <script> from chrome://mozapps"); + + /** make sure the last one didn't pass because someone + ** moved the resource + **/ + var resjs = document.getElementById("resjs"); + resjs.onload = scriptOnload; + resjs.src = "resource://gre/chrome/toolkit/content/mozapps/xpinstall/xpinstallConfirm.js"; + document.getElementById("content").appendChild(resjs); + + function scriptOnload() { + is(typeof XPInstallConfirm, "object", + "xpinstallConfirm.js has not moved unexpectedly"); + + // trigger the callback + if (aCallback) + aCallback(); + } +} + +/** <img src=""> tests **/ +var img_global = "chrome://global/skin/icons/Error.png"; +var img_mozapps = "chrome://mozapps/skin/plugins/contentPluginClose.png"; +var res_mozapps = "resource://gre/chrome/toolkit/skin/classic/mozapps/plugins/contentPluginClose.png"; + +var imgTests = [[img_global, "success"], + [img_mozapps, "fail"], + [res_mozapps, "success"]]; + +var curImgTest = 0; + +function runImgTest() { + var test = imgTests[curImgTest++]; + var callback = curImgTest == imgTests.length ? finishTest : runImgTest; + loadImage(test[0], test[1], callback); +} + +function finishTest() { + SimpleTest.finish(); +} + +function fail(event) { + is("fail", event.target.expected, + "content should not be allowed to load "+event.target.src); + if (event.target.callback) + event.target.callback(); +} + +function success(event) { + is("success", event.target.expected, + "content should be able to load "+event.target.src); + if (event.target.callback) + event.target.callback(); +} + +function loadImage(uri, expect, callback) { + var img = document.createElement("img"); + img.onerror = fail; + img.onload = success; + img.expected = expect; + img.callback = callback; + img.src = uri; + //document.getElementById("content").appendChild(img); +} + +// Start off the script src test, and have it start the img tests when complete. +testScriptSrc(runImgTest); +</script> +</pre> +</body> +</html> diff --git a/caps/tests/mochitest/test_bug423375.html b/caps/tests/mochitest/test_bug423375.html new file mode 100644 index 000000000..1cd2a7a82 --- /dev/null +++ b/caps/tests/mochitest/test_bug423375.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=423375 +--> +<head> + <title>Test for Bug 423375</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=423375">Mozilla Bug 423375</a> +<p id="display"></p> +<div id="content" style="display: none"> +<iframe id="load-frame"></iframe> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** + ** Test for Bug 423375 + ** (content shouldn't be able to load chrome: or resource:) + **/ +function tryLoad(url) +{ + try { + window.frames[0].location = url; + return "loaded"; + } catch (e if /Access.*denied/.test(String(e))) { + return "denied"; + } catch (e) { + return "unexpected: " + e; + } +} + +is(tryLoad("chrome://global/content/mozilla.xhtml"), "denied", + "content should have been prevented from loading chrome: URL"); +is(tryLoad("resource://gre-resources/html.css"), "denied", + "content should have been prevented from loading resource: URL"); +</script> +</pre> +</body> +</html> + diff --git a/caps/tests/mochitest/test_bug470804.html b/caps/tests/mochitest/test_bug470804.html new file mode 100644 index 000000000..9ac88c8d7 --- /dev/null +++ b/caps/tests/mochitest/test_bug470804.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=470804 +--> +<head> + <title>Test for Bug 470804</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=470804">Mozilla Bug 470804</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 470804 + Passing a null targetURL to checkLoadURIWithPrincipal shouldn't crash + **/ + +const nsIScriptSecurityManager = SpecialPowers.Ci.nsIScriptSecurityManager; +var secMan = SpecialPowers.Services.scriptSecurityManager; +var principal = SpecialPowers.wrap(document).nodePrincipal; +isnot(principal, undefined, "Should have a principal"); +isnot(principal, null, "Should have a non-null principal"); +is(secMan.isSystemPrincipal(principal), false, + "Shouldn't have system principal here"); +try { + secMan.checkLoadURIWithPrincipal(principal, null, + nsIScriptSecurityManager.STANDARD); +} catch (e) { + // throwing is fine, it's just crashing that's bad +} +ok(true, "Survival", "We should get here without crashing"); +</script> +</pre> +</body> +</html> diff --git a/caps/tests/mochitest/test_bug995943.xul b/caps/tests/mochitest/test_bug995943.xul new file mode 100644 index 000000000..e9eebb736 --- /dev/null +++ b/caps/tests/mochitest/test_bug995943.xul @@ -0,0 +1,115 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=995943 +--> +<window title="Mozilla Bug 995943" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=995943" + target="_blank">Mozilla Bug 995943</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + const Cu = Components.utils; + const Cc = Components.classes; + const Ci = Components.interfaces; + Cu.import("resource://gre/modules/Services.jsm"); + function debug(msg) { info(msg); } + + /** Test for CAPS file:// URI prefs. **/ + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestCompleteLog(); + if (navigator.userAgent.indexOf("Mac OS X 10.10") != -1) + SimpleTest.expectAssertions(5, 11); // See bug 1067022, 1307988 + else if (Services.appinfo.OS == "WINNT") + SimpleTest.expectAssertions(0, 1); // See bug 1067022 + else + SimpleTest.expectAssertions(0, 2); // See bug 1305241 + + var rootdir = Services.appinfo.OS == "WINNT" ? "file:///C:" : "file:///"; + + function checkLoadFileURI(domain, shouldLoad) { + debug("Invoking checkLoadFileURI with domain: " + domain + ", shouldLoad: " + shouldLoad); + return new Promise(function(resolve, reject) { + $('ifr').addEventListener('load', function l1() { + debug("Invoked l1 for " + domain); + $('ifr').removeEventListener('load', l1); + function l2() { + debug("Invoked l2 for " + domain); + $('ifr').removeEventListener('load', l2); + ok(shouldLoad, "Successfully loaded file:// URI for domain: " + domain); + resolve(); + } + $('ifr').addEventListener('load', l2); + try { + window[0].wrappedJSObject.location = rootdir; + debug("Successfully navigated for " + domain); + } catch (e) { + ok(!shouldLoad && /denied|insecure/.test(e), + "Prevented loading of file:// URI for domain: " + domain + " - " + e); + $('ifr').removeEventListener('load', l2); + resolve(); + } + }); + let targetURI = domain + '/tests/js/xpconnect/tests/mochitest/file_empty.html'; + debug("Navigating iframe to " + targetURI); + $('ifr').contentWindow.location = targetURI; + }); + } + + function pushPrefs(prefs) { + return new Promise(function(resolve) { SpecialPowers.pushPrefEnv({ set: prefs }, resolve); }); + } + + function popPrefs() { + return new Promise(function(resolve) { SpecialPowers.popPrefEnv(resolve); }); + } + + var gGoCount = 0; + function go() { + debug("Invoking go for window with id: " + window.getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID); + is(++gGoCount, 1, "Should only call go once!"); + checkLoadFileURI('http://example.com', false).then( + pushPrefs.bind(null, [['capability.policy.policynames', ' somepolicy '], + ['capability.policy.somepolicy.checkloaduri.enabled', 'AlLAcCeSs'], + ['capability.policy.somepolicy.sites', 'http://example.com']])) + .then(checkLoadFileURI.bind(null, 'http://example.com', true)) + .then(popPrefs) + .then(checkLoadFileURI.bind(null, 'http://example.com', false)) + .then( + pushPrefs.bind(null, [['capability.policy.policynames', ',somepolicy, someotherpolicy, '], + ['capability.policy.somepolicy.checkloaduri.enabled', 'allaccess'], + ['capability.policy.someotherpolicy.checkloaduri.enabled', 'nope'], + ['capability.policy.somepolicy.sites', ' http://example.org test1.example.com https://test2.example.com '], + ['capability.policy.someotherpolicy.sites', 'http://example.net ']])) + .then(checkLoadFileURI.bind(null, 'http://example.org', true)) + .then(checkLoadFileURI.bind(null, 'http://test2.example.com', false)) + .then(checkLoadFileURI.bind(null, 'https://test2.example.com', true)) + .then(checkLoadFileURI.bind(null, 'http://sub1.test2.example.com', false)) + .then(checkLoadFileURI.bind(null, 'https://sub1.test2.example.com', true)) + .then(checkLoadFileURI.bind(null, 'http://example.net', false)) + .then(checkLoadFileURI.bind(null, 'http://test1.example.com', true)) + .then(checkLoadFileURI.bind(null, 'https://test1.example.com', true)) + .then(checkLoadFileURI.bind(null, 'http://sub1.test1.example.com', true)) + .then(checkLoadFileURI.bind(null, 'https://sub1.test1.example.com', true)) + .then(pushPrefs.bind(null, [['capability.policy.someotherpolicy.checkloaduri.enabled', 'allAccess']])) + .then(checkLoadFileURI.bind(null, 'http://example.net', true)) + .then(popPrefs) + .then(popPrefs) + .then(checkLoadFileURI.bind(null, 'http://example.net', false)) + .then(SimpleTest.finish.bind(SimpleTest)); + + } + addLoadEvent(go); + + ]]> + </script> + <iframe id="ifr" type="content" /> +</window> diff --git a/caps/tests/mochitest/test_disableScript.xul b/caps/tests/mochitest/test_disableScript.xul new file mode 100644 index 000000000..cef5f401a --- /dev/null +++ b/caps/tests/mochitest/test_disableScript.xul @@ -0,0 +1,339 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=840488 +--> +<window title="Mozilla Bug 840488" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=840488" + target="_blank">Mozilla Bug 840488</a> + </body> + + <iframe id="root" name="root" type="content"/> + <iframe id="chromeFrame" name="chromeFrame" type="content"/> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for all the different ways that script can be disabled for a given global. **/ + + SimpleTest.waitForExplicitFinish(); + const Cu = Components.utils; + const Ci = Components.interfaces; + Cu.import("resource://gre/modules/Promise.jsm"); + Cu.import("resource://gre/modules/Services.jsm"); + const ssm = Services.scriptSecurityManager; + function makeURI(uri) { return Services.io.newURI(uri, null, null); } + const path = "/tests/caps/tests/mochitest/file_disableScript.html"; + const uri = "http://www.example.com" + path; + var rootFrame = document.getElementById('root'); + var chromeFrame = document.getElementById('chromeFrame'); + navigateFrame(rootFrame, uri + "?name=rootframe").then(function() { + navigateFrame(chromeFrame, "file_disableScript.html").then(go); + }); + + function navigateFrame(ifr, src) { + let deferred = Promise.defer(); + function onload() { + ifr.removeEventListener('load', onload); + deferred.resolve(); + } + ifr.addEventListener('load', onload, false); + ifr.setAttribute('src', src); + return deferred.promise; + } + + function navigateBack(ifr) { + let deferred = Promise.defer(); + + // pageshow events don't fire on the iframe element, so we need to use the + // chrome event handler for the docshell. + var browser = ifr.contentWindow + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell) + .chromeEventHandler; + function onpageshow(evt) { + info("Navigated back. Persisted: " + evt.persisted); + browser.removeEventListener('pageshow', onpageshow); + deferred.resolve(); + } + browser.addEventListener('pageshow', onpageshow, false); + ifr.contentWindow.history.back(); + return deferred.promise; + } + + function addFrame(parentWin, name, expectOnload) { + let ifr = parentWin.document.createElement('iframe'); + parentWin.document.body.appendChild(ifr); + ifr.setAttribute('name', name); + let deferred = Promise.defer(); + // We need to append 'name' to avoid running afoul of recursive frame detection. + let frameURI = uri + "?name=" + name; + navigateFrame(ifr, frameURI).then(function() { + is(String(ifr.contentWindow.location), frameURI, "Successful load"); + is(!!ifr.contentWindow.wrappedJSObject.gFiredOnload, expectOnload, + "onload should only fire when scripts are enabled"); + deferred.resolve(); + }); + return deferred.promise; + } + + function checkScriptEnabled(win, expectEnabled) { + win.wrappedJSObject.gFiredOnclick = false; + win.document.body.dispatchEvent(new win.Event('click')); + is(win.wrappedJSObject.gFiredOnclick, expectEnabled, "Checking script-enabled for " + win.name + " (" + win.location + ")"); + } + + function setScriptEnabledForDocShell(win, enabled) { + win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .allowJavascript = enabled; + } + + function testList(expectEnabled, win, list, idx) { + idx = idx || 0; + let deferred = Promise.defer(); + let target = list[idx] + path; + info("Testing scriptability for: " + target + ". expecting " + expectEnabled); + navigateFrame(win.frameElement, target).then(function() { + checkScriptEnabled(win, expectEnabled); + if (idx == list.length - 1) + deferred.resolve(); + else + testList(expectEnabled, win, list, idx + 1).then(function() { deferred.resolve(); }); + }); + return deferred.promise; + } + + function testDomainPolicy(defaultScriptability, exceptions, superExceptions, + exempt, notExempt, set, superSet, win) { + // Populate our sets. + for (var e of exceptions) + set.add(makeURI(e)); + for (var e of superExceptions) + superSet.add(makeURI(e)); + + return testList(defaultScriptability, win, notExempt).then(function() { + return testList(!defaultScriptability, win, exempt); + }); + } + + function setScriptEnabledForBrowser(enabled) { + var prefname = "javascript.enabled"; + Services.prefs.setBoolPref(prefname, enabled); + } + + function reloadFrame(frame) { + let deferred = Promise.defer(); + frame.addEventListener('load', function onload() { + deferred.resolve(); + frame.removeEventListener('load', onload); + }, false); + frame.contentWindow.location.reload(true); + return deferred.promise; + } + + function go() { + var rootWin = rootFrame.contentWindow; + var chromeWin = chromeFrame.contentWindow; + + // Test simple docshell enable/disable. + checkScriptEnabled(rootWin, true); + setScriptEnabledForDocShell(rootWin, false); + checkScriptEnabled(rootWin, false); + setScriptEnabledForDocShell(rootWin, true); + checkScriptEnabled(rootWin, true); + + // Privileged frames are immune to docshell flags. + ok(ssm.isSystemPrincipal(chromeWin.document.nodePrincipal), "Sanity check for System Principal"); + setScriptEnabledForDocShell(chromeWin, false); + checkScriptEnabled(chromeWin, true); + setScriptEnabledForDocShell(chromeWin, true); + + // Play around with the docshell tree and make sure everything works as + // we expect. + addFrame(rootWin, 'parent', true).then(function() { + checkScriptEnabled(rootWin[0], true); + return addFrame(rootWin[0], 'childA', true); + }).then(function() { + checkScriptEnabled(rootWin[0][0], true); + setScriptEnabledForDocShell(rootWin[0], false); + checkScriptEnabled(rootWin, true); + checkScriptEnabled(rootWin[0], false); + checkScriptEnabled(rootWin[0][0], false); + return addFrame(rootWin[0], 'childB', false); + }).then(function() { + checkScriptEnabled(rootWin[0][1], false); + setScriptEnabledForDocShell(rootWin[0][0], false); + setScriptEnabledForDocShell(rootWin[0], true); + checkScriptEnabled(rootWin[0], true); + checkScriptEnabled(rootWin[0][0], false); + setScriptEnabledForDocShell(rootWin[0][0], true); + + // Flags are inherited from the parent docshell at attach time. Note that + // the flag itself is inherited, regardless of whether or not scripts are + // currently allowed on the parent (which could depend on the parent's + // parent). Check that. + checkScriptEnabled(rootWin[0][1], false); + setScriptEnabledForDocShell(rootWin[0], false); + setScriptEnabledForDocShell(rootWin[0][1], true); + return addFrame(rootWin[0][1], 'grandchild', false); + }).then(function() { + checkScriptEnabled(rootWin[0], false); + checkScriptEnabled(rootWin[0][1], false); + checkScriptEnabled(rootWin[0][1][0], false); + setScriptEnabledForDocShell(rootWin[0], true); + checkScriptEnabled(rootWin[0], true); + checkScriptEnabled(rootWin[0][1], true); + checkScriptEnabled(rootWin[0][1][0], true); + + // Try navigating two frames, then munging docshell scriptability, then + // pulling the frames out of the bfcache to make sure that flags are + // properly propagated to inactive inner windows. We do this both for an + // 'own' docshell, as well as for an ancestor docshell. + return navigateFrame(rootWin[0][0].frameElement, rootWin[0][0].location + '-navigated'); + }).then(function() { return navigateFrame(rootWin[0][1][0].frameElement, rootWin[0][1][0].location + '-navigated'); }) + .then(function() { + checkScriptEnabled(rootWin[0][0], true); + checkScriptEnabled(rootWin[0][1][0], true); + setScriptEnabledForDocShell(rootWin[0][0], false); + setScriptEnabledForDocShell(rootWin[0][1], false); + checkScriptEnabled(rootWin[0][0], false); + checkScriptEnabled(rootWin[0][1][0], false); + return navigateBack(rootWin[0][0].frameElement); + }).then(function() { return navigateBack(rootWin[0][1][0].frameElement); }) + .then(function() { + checkScriptEnabled(rootWin[0][0], false); + checkScriptEnabled(rootWin[0][1][0], false); + + // Disable JS via the global pref pref. This is only guaranteed to have an effect + // for subsequent loads. + setScriptEnabledForBrowser(false); + return reloadFrame(rootFrame); + }).then(function() { + checkScriptEnabled(rootWin, false); + checkScriptEnabled(chromeWin, true); + setScriptEnabledForBrowser(true); + return reloadFrame(rootFrame); + }).then(function() { + checkScriptEnabled(rootWin, true); + + // Play around with dynamically blocking script for a given global. + // This takes effect immediately. + Cu.blockScriptForGlobal(rootWin); + Cu.blockScriptForGlobal(rootWin); + Cu.unblockScriptForGlobal(rootWin); + checkScriptEnabled(rootWin, false); + Cu.unblockScriptForGlobal(rootWin); + checkScriptEnabled(rootWin, true); + Cu.blockScriptForGlobal(rootWin); + try { + Cu.blockScriptForGlobal(chromeWin); + ok(false, "Should have thrown"); + } catch (e) { + ok(/may not be disabled/.test(e), + "Shouldn't be able to programmatically block script for system globals"); + } + return reloadFrame(rootFrame); + }).then(function() { + checkScriptEnabled(rootWin, true); + + // Test system-wide domain policy. This only takes effect for subsequently- + // loaded globals. + + // Check the basic semantics of the sets. + is(ssm.domainPolicyActive, false, "not enabled"); + window.policy = ssm.activateDomainPolicy(); + ok(policy instanceof Ci.nsIDomainPolicy, "Got a policy"); + try { + ssm.activateDomainPolicy(); + ok(false, "Should have thrown"); + } catch (e) { + ok(true, "can't have two live domain policies"); + } + var sbRef = policy.superBlacklist; + isnot(sbRef, null, "superBlacklist non-null"); + ok(!sbRef.contains(makeURI('http://www.example.com'))); + sbRef.add(makeURI('http://www.example.com/foopy')); + ok(sbRef.contains(makeURI('http://www.example.com'))); + sbRef.remove(makeURI('http://www.example.com')); + ok(!sbRef.contains(makeURI('http://www.example.com'))); + sbRef.add(makeURI('http://www.example.com/foopy/this.that/')); + ok(sbRef.contains(makeURI('http://www.example.com/baz'))); + ok(!sbRef.contains(makeURI('https://www.example.com'))); + ok(!sbRef.contains(makeURI('https://www.example.com:88'))); + ok(!sbRef.contains(makeURI('http://foo.www.example.com'))); + ok(sbRef.containsSuperDomain(makeURI('http://foo.www.example.com'))); + ok(sbRef.containsSuperDomain(makeURI('http://foo.bar.www.example.com'))); + ok(!sbRef.containsSuperDomain(makeURI('http://foo.bar.www.exxample.com'))); + ok(!sbRef.containsSuperDomain(makeURI('http://example.com'))); + ok(!sbRef.containsSuperDomain(makeURI('http://com/this.that/'))); + ok(!sbRef.containsSuperDomain(makeURI('https://foo.www.example.com'))); + ok(sbRef.contains(makeURI('http://www.example.com'))); + policy.deactivate(); + is(ssm.domainPolicyActive, false, "back to inactive"); + ok(!sbRef.contains(makeURI('http://www.example.com')), + "Disabling domain policy clears the set"); + policy = ssm.activateDomainPolicy(); + ok(policy.superBlacklist); + isnot(sbRef, policy.superBlacklist, "Mint new sets each time!"); + policy.deactivate(); + is(policy.blacklist, null, "blacklist nulled out"); + policy = ssm.activateDomainPolicy(); + isnot(policy.blacklist, null, "non-null again"); + isnot(policy.blacklist, sbRef, "freshly minted"); + policy.deactivate(); + + // + // Now, create and apply a mock-policy. We check the same policy both as + // a blacklist and as a whitelist. + // + + window.testPolicy = { + // The policy. + exceptions: ['http://test1.example.com', 'http://example.com'], + superExceptions: ['http://test2.example.org', 'https://test1.example.com'], + + // The testcases. + exempt: ['http://test1.example.com', 'http://example.com', + 'http://test2.example.org', 'http://sub1.test2.example.org', + 'https://sub1.test1.example.com'], + + notExempt: ['http://test2.example.com', 'http://sub1.test1.example.com', + 'http://www.example.com', 'https://test2.example.com', + 'https://example.com', 'http://test1.example.org'], + }; + + policy = ssm.activateDomainPolicy(); + info("Testing Blacklist-style Domain Policy"); + return testDomainPolicy(true, testPolicy.exceptions, + testPolicy.superExceptions, testPolicy.exempt, + testPolicy.notExempt, policy.blacklist, + policy.superBlacklist, rootWin); + }).then(function() { + policy.deactivate(); + policy = ssm.activateDomainPolicy(); + info("Testing Whitelist-style Domain Policy"); + setScriptEnabledForBrowser(false); + return testDomainPolicy(false, testPolicy.exceptions, + testPolicy.superExceptions, testPolicy.exempt, + testPolicy.notExempt, policy.whitelist, + policy.superWhitelist, rootWin); + }).then(function() { + setScriptEnabledForBrowser(true); + policy.deactivate(); + + SimpleTest.finish(); + }); + } + + ]]> + </script> +</window> diff --git a/caps/tests/mochitest/test_disallowInheritPrincipal.html b/caps/tests/mochitest/test_disallowInheritPrincipal.html new file mode 100644 index 000000000..ec59bec3c --- /dev/null +++ b/caps/tests/mochitest/test_disallowInheritPrincipal.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=732413 +--> +<head> + <title>Test for Bug 732413</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=732413">Mozilla Bug 732413</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 732413 + Passing DISALLOW_INHERIT_PRINCIPAL flag should be effective even if + aPrincipal is the system principal. + **/ + +const nsIScriptSecurityManager = SpecialPowers.Ci.nsIScriptSecurityManager; +var secMan = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"] + .getService(nsIScriptSecurityManager); +var sysPrincipal = secMan.getSystemPrincipal(); +isnot(sysPrincipal, undefined, "Should have a principal"); +isnot(sysPrincipal, null, "Should have a non-null principal"); +is(secMan.isSystemPrincipal(sysPrincipal), true, + "Should have system principal here"); + + +var ioService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]. + getService(SpecialPowers.Ci.nsIIOService); +var inheritingURI = ioService.newURI("javascript:1+1", null, null); + +// First try a normal call to checkLoadURIWithPrincipal +try { + secMan.checkLoadURIWithPrincipal(sysPrincipal, inheritingURI, + nsIScriptSecurityManager.STANDARD); + ok(true, "checkLoadURI allowed the load"); +} catch (e) { + ok(false, "checkLoadURI failed unexpectedly: " + e); +} + +// Now call checkLoadURIWithPrincipal with DISALLOW_INHERIT_PRINCIPAL +try { + secMan.checkLoadURIWithPrincipal(sysPrincipal, inheritingURI, + nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL); + ok(false, "checkLoadURI allowed the load unexpectedly"); +} catch (e) { + ok(true, "checkLoadURI prevented load of principal-inheriting URI"); +} + +</script> +</pre> +</body> +</html> diff --git a/caps/tests/mochitest/test_extensionURL.html b/caps/tests/mochitest/test_extensionURL.html new file mode 100644 index 000000000..315c47321 --- /dev/null +++ b/caps/tests/mochitest/test_extensionURL.html @@ -0,0 +1,166 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1161831 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1161831</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1161831 **/ + SimpleTest.waitForExplicitFinish(); + + var aps = SpecialPowers.Cc["@mozilla.org/addons/policy-service;1"] + .getService(SpecialPowers.Ci.nsIAddonPolicyService).wrappedJSObject; + var oldLoadCallback = aps.setExtensionURILoadCallback(null); + var oldMapCallback = aps.setExtensionURIToAddonIdCallback(null); + var resourceHandler = SpecialPowers.Services.io.getProtocolHandler("resource") + .QueryInterface(SpecialPowers.Ci.nsISubstitutingProtocolHandler); + var extensionHandler = SpecialPowers.Services.io.getProtocolHandler("moz-extension") + .QueryInterface(SpecialPowers.Ci.nsISubstitutingProtocolHandler); + + SimpleTest.registerCleanupFunction(function() { + extensionHandler.setSubstitution('cherise', null); + extensionHandler.setSubstitution('liebchen', null); + aps.setExtensionURILoadCallback(oldLoadCallback); + aps.setExtensionURIToAddonIdCallback(oldMapCallback); + }); + + addLoadEvent(function() { + + // First, get a file:// URI to something - open to suggestions on how to do + // this more easily. + var resURI = SpecialPowers.Services.io.newURI('resource://testing-common/resource_test_file.html', null, null); + var filePath = resourceHandler.resolveURI(resURI); + ok(filePath.startsWith('file://'), 'resource:// URI resolves where we expect: ' + filePath); + var fileURI = SpecialPowers.Services.io.newURI(filePath, null, null); + + // Register a moz-extension:// URI. + extensionHandler.setSubstitution('cherise', fileURI); + + // Alias the above. + extensionHandler.setSubstitution('liebchen', SpecialPowers.Services.io.newURI('moz-extension://cherise', null, null)); + + // + // Make sure that non-file:// URIs don't work. + // + + // resource:// + try { + extensionHandler.setSubstitution('interdit', resURI); + ok(false, "Should have thrown for mapping moz-extension to resource"); + } catch (e) { + ok(true, "Threw correctly: " + e); + } + + // chrome:// + try { + var chromeURI = SpecialPowers.Services.io.newURI('chrome://global/content/mozilla.xhtml', null, null); + extensionHandler.setSubstitution('verboten', chromeURI); + ok(false, "Should have thrown for mapping moz-extension to chrome"); + } catch (e) { + ok(true, "Threw correctly: " + e); + } + + function navigateWithLocation(ifr, url) { ifr.contentWindow.location = url; } + function navigateWithSrc(ifr, url) { ifr.setAttribute('src', url); } + function navigateFromChromeWithLocation(ifr, url) { SpecialPowers.wrap(ifr).contentWindow.location = url; } + function navigateFromChromeWithWebNav(ifr, url) { + SpecialPowers.wrap(ifr).contentWindow + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIWebNavigation) + .loadURI(url, 0, null, null, null); + } + + + function setWhitelistCallback(rgxp) { + var cb = SpecialPowers.wrapCallback(function(uri) { return rgxp.test(uri.spec); }); + aps.setExtensionURILoadCallback(cb); + } + + aps.setExtensionURIToAddonIdCallback(SpecialPowers.wrapCallback(function (uri) { return 'imaginaryaddon-' + uri.host[0]; })); + + function testLoad(url, navigate, shouldThrow) { + var ifr = document.createElement('iframe'); + var p = new Promise(function(resolve, reject) { + ifr.onload = function() { + ok(true, 'Loaded ' + url); + var prin = SpecialPowers.wrap(ifr.contentWindow).document.nodePrincipal; + function stripTrailingSlash(s) { return s.replace(/\/$/, ''); }; + is(stripTrailingSlash(prin.URI.spec), url, 'Principal uri is correct: ' + url); + function stripPath(s) { return s.replace(/(.*\/\/.+)\/.*/, '$1'); }; + is(prin.originNoSuffix, stripPath(url), 'Principal origin is correct: ' + prin.originNoSuffix); + is(prin.originAttributes.addonId, 'imaginaryaddon-' + url[url.indexOf('/') + 2], 'addonId is correct'); + if (/_blank/.test(url)) { + is(SpecialPowers.wrap(ifr.contentWindow).document.documentElement.innerHTML, + '<head></head><body></body>', 'blank document looks right'); + } else { + is(SpecialPowers.wrap(ifr.contentWindow).document.title, 'resource test file', + 'document looks right'); + } + ifr.remove(); + resolve(); + }; + document.body.appendChild(ifr); + + var threw = false; + try { + navigate(ifr, url); + } catch (e) { + ifr.remove(); + threw = true; + ok(/denied|insecure/.test(e), "exception correct: " + e); + } + is(threw, !!shouldThrow, "Correct throwing behavior for: " + url); + !threw || resolve(); + }); + + return p; + } + + function testXHR(url, shouldError) { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.addEventListener("load", () => { ok(!shouldError, `XHR to ${url} should succeed`); resolve(); }); + xhr.addEventListener("error", () => { ok(shouldError, `XHR to ${url} should fail`); resolve(); }); + xhr.open("GET", url, true); + xhr.send(); + }); + } + + // + // Perform some loads and make sure they work correctly. + // + testLoad.bind(null, 'moz-extension://cherise', navigateFromChromeWithLocation)() + .then(testLoad.bind(null, 'moz-extension://cherise', navigateFromChromeWithWebNav)) + .then(testLoad.bind(null, 'moz-extension://cherise', navigateWithLocation, /* shouldThrow = */ true)) + .then(testXHR.bind(null, 'moz-extension://cherise', /* shouldError = */ true)) + .then(setWhitelistCallback.bind(null, /cherise/)) + .then(testLoad.bind(null, 'moz-extension://cherise', navigateWithLocation)) + .then(testXHR.bind(null, 'moz-extension://cherise')) + .then(testLoad.bind(null, 'moz-extension://liebchen', navigateWithLocation, /* shouldThrow = */ true)) + .then(testXHR.bind(null, 'moz-extension://liebchen', /* shouldError = */ true)) + .then(setWhitelistCallback.bind(null, /cherise|liebchen/)) + .then(testLoad.bind(null, 'moz-extension://liebchen', navigateWithLocation)) + .then(testLoad.bind(null, 'moz-extension://liebchen', navigateWithSrc)) + .then(testLoad.bind(null, 'moz-extension://cherise', navigateWithSrc)) + .then(testLoad.bind(null, 'moz-extension://cherise/_blank.html', navigateWithSrc)) + .then(SimpleTest.finish.bind(SimpleTest), + function(e) { ok(false, "rejected promise: " + e); SimpleTest.finish() } + ); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1161831">Mozilla Bug 1161831</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/caps/tests/unit/test_origin.js b/caps/tests/unit/test_origin.js new file mode 100644 index 000000000..0fa125b61 --- /dev/null +++ b/caps/tests/unit/test_origin.js @@ -0,0 +1,307 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); +var ssm = Services.scriptSecurityManager; +function makeURI(uri) { return Services.io.newURI(uri, null, null); } + +function checkThrows(f) { + var threw = false; + try { f(); } catch (e) { threw = true } + do_check_true(threw); +} + +function checkCrossOrigin(a, b) { + do_check_false(a.equals(b)); + do_check_false(a.equalsConsideringDomain(b)); + do_check_false(a.subsumes(b)); + do_check_false(a.subsumesConsideringDomain(b)); + do_check_false(b.subsumes(a)); + do_check_false(b.subsumesConsideringDomain(a)); +} + +function checkOriginAttributes(prin, attrs, suffix) { + attrs = attrs || {}; + do_check_eq(prin.originAttributes.appId, attrs.appId || 0); + do_check_eq(prin.originAttributes.inIsolatedMozBrowser, attrs.inIsolatedMozBrowser || false); + do_check_eq(prin.originSuffix, suffix || ''); + do_check_eq(ChromeUtils.originAttributesToSuffix(attrs), suffix || ''); + do_check_true(ChromeUtils.originAttributesMatchPattern(prin.originAttributes, attrs)); + if (!prin.isNullPrincipal && !prin.origin.startsWith('[')) { + do_check_true(ssm.createCodebasePrincipalFromOrigin(prin.origin).equals(prin)); + } else { + checkThrows(() => ssm.createCodebasePrincipalFromOrigin(prin.origin)); + } +} + +function checkSandboxOriginAttributes(arr, attrs, options) { + options = options || {}; + var sandbox = Cu.Sandbox(arr, options); + checkOriginAttributes(Cu.getObjectPrincipal(sandbox), attrs, + ChromeUtils.originAttributesToSuffix(attrs)); +} + +// utility function useful for debugging +function printAttrs(name, attrs) { + do_print(name + " {\n" + + "\tappId: " + attrs.appId + ",\n" + + "\tuserContextId: " + attrs.userContextId + ",\n" + + "\tinIsolatedMozBrowser: " + attrs.inIsolatedMozBrowser + ",\n" + + "\taddonId: '" + attrs.addonId + "',\n" + + "\tprivateBrowsingId: '" + attrs.privateBrowsingId + "',\n" + + "\tfirstPartyDomain: '" + attrs.firstPartyDomain + "'\n}"); +} + + +function checkValues(attrs, values) { + values = values || {}; + //printAttrs("attrs", attrs); + //printAttrs("values", values); + do_check_eq(attrs.appId, values.appId || 0); + do_check_eq(attrs.userContextId, values.userContextId || 0); + do_check_eq(attrs.inIsolatedMozBrowser, values.inIsolatedMozBrowser || false); + do_check_eq(attrs.addonId, values.addonId || ''); + do_check_eq(attrs.privateBrowsingId, values.privateBrowsingId || ''); + do_check_eq(attrs.firstPartyDomain, values.firstPartyDomain || ''); +} + +function run_test() { + // Attributeless origins. + do_check_eq(ssm.getSystemPrincipal().origin, '[System Principal]'); + checkOriginAttributes(ssm.getSystemPrincipal()); + var exampleOrg = ssm.createCodebasePrincipal(makeURI('http://example.org'), {}); + do_check_eq(exampleOrg.origin, 'http://example.org'); + checkOriginAttributes(exampleOrg); + var exampleCom = ssm.createCodebasePrincipal(makeURI('https://www.example.com:123'), {}); + do_check_eq(exampleCom.origin, 'https://www.example.com:123'); + checkOriginAttributes(exampleCom); + var nullPrin = Cu.getObjectPrincipal(new Cu.Sandbox(null)); + do_check_true(/^moz-nullprincipal:\{([0-9]|[a-z]|\-){36}\}$/.test(nullPrin.origin)); + checkOriginAttributes(nullPrin); + var ipv6Prin = ssm.createCodebasePrincipal(makeURI('https://[2001:db8::ff00:42:8329]:123'), {}); + do_check_eq(ipv6Prin.origin, 'https://[2001:db8::ff00:42:8329]:123'); + checkOriginAttributes(ipv6Prin); + var ipv6NPPrin = ssm.createCodebasePrincipal(makeURI('https://[2001:db8::ff00:42:8329]'), {}); + do_check_eq(ipv6NPPrin.origin, 'https://[2001:db8::ff00:42:8329]'); + checkOriginAttributes(ipv6NPPrin); + var ep = Cu.getObjectPrincipal(Cu.Sandbox([exampleCom, nullPrin, exampleOrg])); + checkOriginAttributes(ep); + checkCrossOrigin(exampleCom, exampleOrg); + checkCrossOrigin(exampleOrg, nullPrin); + + // nsEP origins should be in lexical order. + do_check_eq(ep.origin, `[Expanded Principal [${exampleOrg.origin}, ${exampleCom.origin}, ${nullPrin.origin}]]`); + + // Make sure createCodebasePrincipal does what the rest of gecko does. + do_check_true(exampleOrg.equals(Cu.getObjectPrincipal(new Cu.Sandbox('http://example.org')))); + + // + // Test origin attributes. + // + + // Just app. + var exampleOrg_app = ssm.createCodebasePrincipal(makeURI('http://example.org'), {appId: 42}); + var nullPrin_app = ssm.createNullPrincipal({appId: 42}); + checkOriginAttributes(exampleOrg_app, {appId: 42}, '^appId=42'); + checkOriginAttributes(nullPrin_app, {appId: 42}, '^appId=42'); + do_check_eq(exampleOrg_app.origin, 'http://example.org^appId=42'); + + // Just browser. + var exampleOrg_browser = ssm.createCodebasePrincipal(makeURI('http://example.org'), {inIsolatedMozBrowser: true}); + var nullPrin_browser = ssm.createNullPrincipal({inIsolatedMozBrowser: true}); + checkOriginAttributes(exampleOrg_browser, {inIsolatedMozBrowser: true}, '^inBrowser=1'); + checkOriginAttributes(nullPrin_browser, {inIsolatedMozBrowser: true}, '^inBrowser=1'); + do_check_eq(exampleOrg_browser.origin, 'http://example.org^inBrowser=1'); + + // App and browser. + var exampleOrg_appBrowser = ssm.createCodebasePrincipal(makeURI('http://example.org'), {inIsolatedMozBrowser: true, appId: 42}); + var nullPrin_appBrowser = ssm.createNullPrincipal({inIsolatedMozBrowser: true, appId: 42}); + checkOriginAttributes(exampleOrg_appBrowser, {appId: 42, inIsolatedMozBrowser: true}, '^appId=42&inBrowser=1'); + checkOriginAttributes(nullPrin_appBrowser, {appId: 42, inIsolatedMozBrowser: true}, '^appId=42&inBrowser=1'); + do_check_eq(exampleOrg_appBrowser.origin, 'http://example.org^appId=42&inBrowser=1'); + + // App and browser, different domain. + var exampleCom_appBrowser = ssm.createCodebasePrincipal(makeURI('https://www.example.com:123'), {appId: 42, inIsolatedMozBrowser: true}); + checkOriginAttributes(exampleCom_appBrowser, {appId: 42, inIsolatedMozBrowser: true}, '^appId=42&inBrowser=1'); + do_check_eq(exampleCom_appBrowser.origin, 'https://www.example.com:123^appId=42&inBrowser=1'); + + // Addon. + var exampleOrg_addon = ssm.createCodebasePrincipal(makeURI('http://example.org'), {addonId: 'dummy'}); + checkOriginAttributes(exampleOrg_addon, { addonId: "dummy" }, '^addonId=dummy'); + do_check_eq(exampleOrg_addon.origin, 'http://example.org^addonId=dummy'); + + // First party Uri + var exampleOrg_firstPartyDomain = ssm.createCodebasePrincipal(makeURI('http://example.org'), {firstPartyDomain: 'example.org'}); + checkOriginAttributes(exampleOrg_firstPartyDomain, { firstPartyDomain: "example.org" }, '^firstPartyDomain=example.org'); + do_check_eq(exampleOrg_firstPartyDomain.origin, 'http://example.org^firstPartyDomain=example.org'); + + // Make sure we don't crash when serializing principals with UNKNOWN_APP_ID. + try { + let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"]. + createInstance(Ci.nsIObjectOutputStream); + let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); + pipe.init(false, false, 0, 0xffffffff, null); + binaryStream.setOutputStream(pipe.outputStream); + binaryStream.writeCompoundObject(simplePrin, Ci.nsISupports, true); + binaryStream.close(); + } catch (e) { + do_check_true(true); + } + + + // Just userContext. + var exampleOrg_userContext = ssm.createCodebasePrincipal(makeURI('http://example.org'), {userContextId: 42}); + checkOriginAttributes(exampleOrg_userContext, { userContextId: 42 }, '^userContextId=42'); + do_check_eq(exampleOrg_userContext.origin, 'http://example.org^userContextId=42'); + + // UserContext and Addon. + var exampleOrg_userContextAddon = ssm.createCodebasePrincipal(makeURI('http://example.org'), {addonId: 'dummy', userContextId: 42}); + var nullPrin_userContextAddon = ssm.createNullPrincipal({addonId: 'dummy', userContextId: 42}); + checkOriginAttributes(exampleOrg_userContextAddon, {addonId: 'dummy', userContextId: 42}, '^addonId=dummy&userContextId=42'); + checkOriginAttributes(nullPrin_userContextAddon, {addonId: 'dummy', userContextId: 42}, '^addonId=dummy&userContextId=42'); + do_check_eq(exampleOrg_userContextAddon.origin, 'http://example.org^addonId=dummy&userContextId=42'); + + // UserContext and App. + var exampleOrg_userContextApp = ssm.createCodebasePrincipal(makeURI('http://example.org'), {appId: 24, userContextId: 42}); + var nullPrin_userContextApp = ssm.createNullPrincipal({appId: 24, userContextId: 42}); + checkOriginAttributes(exampleOrg_userContextApp, {appId: 24, userContextId: 42}, '^appId=24&userContextId=42'); + checkOriginAttributes(nullPrin_userContextApp, {appId: 24, userContextId: 42}, '^appId=24&userContextId=42'); + do_check_eq(exampleOrg_userContextApp.origin, 'http://example.org^appId=24&userContextId=42'); + + checkSandboxOriginAttributes(null, {}); + checkSandboxOriginAttributes('http://example.org', {}); + checkSandboxOriginAttributes('http://example.org', {}, {originAttributes: {}}); + checkSandboxOriginAttributes('http://example.org', {appId: 42}, {originAttributes: {appId: 42}}); + checkSandboxOriginAttributes(['http://example.org'], {}); + checkSandboxOriginAttributes(['http://example.org'], {}, {originAttributes: {}}); + checkSandboxOriginAttributes(['http://example.org'], {appId: 42}, {originAttributes: {appId: 42}}); + + // Check that all of the above are cross-origin. + checkCrossOrigin(exampleOrg_app, exampleOrg); + checkCrossOrigin(exampleOrg_app, nullPrin_app); + checkCrossOrigin(exampleOrg_browser, exampleOrg_app); + checkCrossOrigin(exampleOrg_browser, nullPrin_browser); + checkCrossOrigin(exampleOrg_appBrowser, exampleOrg_app); + checkCrossOrigin(exampleOrg_appBrowser, nullPrin_appBrowser); + checkCrossOrigin(exampleOrg_appBrowser, exampleCom_appBrowser); + checkCrossOrigin(exampleOrg_addon, exampleOrg); + checkCrossOrigin(exampleOrg_firstPartyDomain, exampleOrg); + checkCrossOrigin(exampleOrg_userContext, exampleOrg); + checkCrossOrigin(exampleOrg_userContextAddon, exampleOrg); + checkCrossOrigin(exampleOrg_userContext, exampleOrg_userContextAddon); + checkCrossOrigin(exampleOrg_userContext, exampleOrg_userContextApp); + + // Check Principal kinds. + function checkKind(prin, kind) { + do_check_eq(prin.isNullPrincipal, kind == 'nullPrincipal'); + do_check_eq(prin.isCodebasePrincipal, kind == 'codebasePrincipal'); + do_check_eq(prin.isExpandedPrincipal, kind == 'expandedPrincipal'); + do_check_eq(prin.isSystemPrincipal, kind == 'systemPrincipal'); + } + checkKind(ssm.createNullPrincipal({}), 'nullPrincipal'); + checkKind(ssm.createCodebasePrincipal(makeURI('http://www.example.com'), {}), 'codebasePrincipal'); + checkKind(Cu.getObjectPrincipal(Cu.Sandbox([ssm.createCodebasePrincipal(makeURI('http://www.example.com'), {})])), 'expandedPrincipal'); + checkKind(ssm.getSystemPrincipal(), 'systemPrincipal'); + + // + // Test Origin Attribute Manipulation + // + + // check that we can create an empty origin attributes dict with default + // members and values. + var emptyAttrs = ChromeUtils.fillNonDefaultOriginAttributes({}); + checkValues(emptyAttrs); + + var uri = "http://example.org"; + var tests = [ + [ "", {} ], + [ "^appId=5", {appId: 5} ], + [ "^userContextId=3", {userContextId: 3} ], + [ "^addonId=fooBar", {addonId: "fooBar"} ], + [ "^inBrowser=1", {inIsolatedMozBrowser: true} ], + [ "^firstPartyDomain=example.org", {firstPartyDomain: "example.org"} ], + [ "^appId=3&inBrowser=1&userContextId=6", + {appId: 3, userContextId: 6, inIsolatedMozBrowser: true} ] ]; + + // check that we can create an origin attributes from an origin properly + tests.forEach(t => { + let attrs = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]); + checkValues(attrs, t[1]); + do_check_eq(ChromeUtils.originAttributesToSuffix(attrs), t[0]); + }); + + // check that we can create an origin attributes from a dict properly + tests.forEach(t => { + let attrs = ChromeUtils.fillNonDefaultOriginAttributes(t[1]); + checkValues(attrs, t[1]); + do_check_eq(ChromeUtils.originAttributesToSuffix(attrs), t[0]); + }); + + // each row in the set_tests array has these values: + // [0] - the suffix used to create an origin attribute from + // [1] - the expected result of creating an origin attribute from [0] + // [2] - the pattern to set on the origin attributes + // [3] - the expected result of setting [2] values on [1] + // [4] - the expected result of creating a suffix from [3] + var set_tests = [ + [ "", {}, {appId: 5}, {appId: 5}, "^appId=5" ], + [ "^appId=5", {appId: 5}, {appId: 3}, {appId: 3}, "^appId=3" ], + [ "^appId=5", {appId: 5}, {userContextId: 3}, {appId: 5, userContextId: 3}, "^appId=5&userContextId=3" ], + [ "^appId=5", {appId: 5}, {appId: 3, userContextId: 7}, {appId: 3, userContextId: 7}, "^appId=3&userContextId=7" ] ]; + + // check that we can set origin attributes values properly + set_tests.forEach(t => { + let orig = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]); + checkValues(orig, t[1]); + let mod = orig; + for (var key in t[2]) { + mod[key] = t[2][key]; + } + checkValues(mod, t[3]); + do_check_eq(ChromeUtils.originAttributesToSuffix(mod), t[4]); + }); + + // each row in the dflt_tests array has these values: + // [0] - the suffix used to create an origin attribute from + // [1] - the expected result of creating an origin attributes from [0] + // [2] - the expected result after setting userContextId to the default + // [3] - the expected result of creating a suffix from [2] + var dflt_tests = [ + [ "", {}, {}, "" ], + [ "^userContextId=3", {userContextId: 3}, {}, "" ], + [ "^appId=5", {appId: 5}, {appId: 5}, "^appId=5" ], + [ "^appId=5&userContextId=3", {appId: 5, userContextId: 3}, {appId: 5}, "^appId=5" ] ]; + + // check that we can set the userContextId to default properly + dflt_tests.forEach(t => { + let orig = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]); + checkValues(orig, t[1]); + let mod = orig; + mod['userContextId'] = 0; + checkValues(mod, t[2]); + do_check_eq(ChromeUtils.originAttributesToSuffix(mod), t[3]); + }); + + // each row in the dflt2_tests array has these values: + // [0] - the suffix used to create an origin attribute from + // [1] - the expected result of creating an origin attributes from [0] + // [2] - the expected result after setting firstPartyUri to the default + // [3] - the expected result of creating a suffix from [2] + var dflt2_tests = [ + [ "", {}, {}, "" ], + [ "^firstPartyDomain=foo.com", {firstPartyDomain: "foo.com"}, {}, "" ], + [ "^appId=5", {appId: 5}, {appId: 5}, "^appId=5" ], + [ "^appId=5&firstPartyDomain=foo.com", {appId: 5, firstPartyDomain: "foo.com"}, {appId: 5}, "^appId=5" ] ]; + + // check that we can set the userContextId to default properly + dflt2_tests.forEach(t => { + let orig = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]); + checkValues(orig, t[1]); + let mod = orig; + mod['firstPartyDomain'] = ""; + checkValues(mod, t[2]); + do_check_eq(ChromeUtils.originAttributesToSuffix(mod), t[3]); + }); + +} diff --git a/caps/tests/unit/xpcshell.ini b/caps/tests/unit/xpcshell.ini new file mode 100644 index 000000000..c06a6b4e9 --- /dev/null +++ b/caps/tests/unit/xpcshell.ini @@ -0,0 +1,5 @@ +[DEFAULT] +head = +tail = + +[test_origin.js] |