diff -r 000000000000 -r 6474c204b198 caps/src/nsScriptSecurityManager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/caps/src/nsScriptSecurityManager.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1663 @@ +/* -*- 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 "nsScriptSecurityManager.h" + +#include "mozilla/ArrayUtils.h" + +#include "js/OldDebugAPI.h" +#include "xpcprivate.h" +#include "XPCWrapper.h" +#include "nsIServiceManager.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScriptContext.h" +#include "nsIURL.h" +#include "nsINestedURI.h" +#include "nspr.h" +#include "nsJSPrincipals.h" +#include "nsSystemPrincipal.h" +#include "nsPrincipal.h" +#include "nsNullPrincipal.h" +#include "DomainPolicy.h" +#include "nsXPIDLString.h" +#include "nsCRT.h" +#include "nsCRTGlue.h" +#include "nsError.h" +#include "nsDOMCID.h" +#include "nsIXPConnect.h" +#include "nsIXPCSecurityManager.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 "nsIXPConnect.h" +#include "nsIScriptGlobalObject.h" +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIPrompt.h" +#include "nsIWindowWatcher.h" +#include "nsIConsoleService.h" +#include "nsIJSRuntimeService.h" +#include "nsIObserverService.h" +#include "nsIContent.h" +#include "nsAutoPtr.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 "mozilla/Preferences.h" +#include "mozilla/dom/BindingUtils.h" +#include +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPtr.h" +#include "nsContentUtils.h" +#include "nsCxPusher.h" +#include "nsJSUtils.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; + +static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID); + +nsIIOService *nsScriptSecurityManager::sIOService = nullptr; +nsIStringBundle *nsScriptSecurityManager::sStrBundle = nullptr; +JSRuntime *nsScriptSecurityManager::sRuntime = 0; +bool nsScriptSecurityManager::sStrictFileOriginPolicy = true; + +bool +nsScriptSecurityManager::SubjectIsPrivileged() +{ + JSContext *cx = GetCurrentJSContext(); + if (cx && xpc::IsUniversalXPConnectEnabled(cx)) + return true; + bool isSystem = false; + return NS_SUCCEEDED(SubjectPrincipalIsSystem(&isSystem)) && isSystem; +} + +/////////////////////////// +// Convenience Functions // +/////////////////////////// +// Result of this function should not be freed. +static inline const char16_t * +IDToString(JSContext *cx, jsid id_) +{ + JS::RootedId id(cx, id_); + if (JSID_IS_STRING(id)) + return JS_GetInternedStringChars(JSID_TO_STRING(id)); + + JS::Rooted idval(cx); + if (!JS_IdToValue(cx, id, &idval)) + return nullptr; + JSString *str = JS::ToString(cx, idval); + if(!str) + return nullptr; + return JS_GetStringCharsZ(cx, str); +} + +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 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 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 SetPendingException(JSContext *cx, const char *aMsg) +{ + JS_ReportError(cx, "%s", aMsg); +} + +inline void SetPendingException(JSContext *cx, const char16_t *aMsg) +{ + JS_ReportError(cx, "%hs", aMsg); +} + +// 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(aName)), + mDidGetFlags(false), + mMustFreeName(false) + { + } + + ~ClassInfoData() + { + if (mMustFreeName) + nsMemory::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("UnnamedClass"); + } + } + + return mName; + } + +private: + nsIClassInfo *mClassInfo; // WEAK + uint32_t mFlags; + char *mName; + bool mDidGetFlags; + bool mMustFreeName; +}; + +JSContext * +nsScriptSecurityManager::GetCurrentJSContext() +{ + // Get JSContext from stack. + return nsXPConnect::XPConnect()->GetCurrentJSContext(); +} + +JSContext * +nsScriptSecurityManager::GetSafeJSContext() +{ + // Get JSContext from stack. + return nsXPConnect::XPConnect()->GetSafeJSContext(); +} + +/* 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); +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetChannelPrincipal(nsIChannel* aChannel, + nsIPrincipal** aPrincipal) +{ + NS_PRECONDITION(aChannel, "Must have channel!"); + nsCOMPtr owner; + aChannel->GetOwner(getter_AddRefs(owner)); + if (owner) { + CallQueryInterface(owner, aPrincipal); + if (*aPrincipal) { + return NS_OK; + } + } + + // OK, get the principal from the URI. Make sure this does the same thing + // as nsDocument::Reset and XULDocument::StartDocumentLoad. + nsCOMPtr uri; + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr docShell; + NS_QueryNotificationCallbacks(aChannel, docShell); + + if (docShell) { + return GetDocShellCodebasePrincipal(uri, docShell, aPrincipal); + } + + return GetCodebasePrincipalInternal(uri, UNKNOWN_APP_ID, + /* isInBrowserElement */ false, aPrincipal); +} + +NS_IMETHODIMP +nsScriptSecurityManager::IsSystemPrincipal(nsIPrincipal* aPrincipal, + bool* aIsSystem) +{ + *aIsSystem = (aPrincipal == mSystemPrincipal); + return NS_OK; +} + +NS_IMETHODIMP_(nsIPrincipal *) +nsScriptSecurityManager::GetCxSubjectPrincipal(JSContext *cx) +{ + NS_ASSERTION(cx == GetCurrentJSContext(), + "Uh, cx is not the current JS context!"); + + nsresult rv = NS_ERROR_FAILURE; + nsIPrincipal *principal = GetSubjectPrincipal(cx, &rv); + if (NS_FAILED(rv)) + return nullptr; + + return principal; +} + +///////////////////////////// +// nsScriptSecurityManager // +///////////////////////////// + +//////////////////////////////////// +// Methods implementing ISupports // +//////////////////////////////////// +NS_IMPL_ISUPPORTS(nsScriptSecurityManager, + nsIScriptSecurityManager, + nsIXPCSecurityManager, + nsIChannelEventSink, + nsIObserver) + +/////////////////////////////////////////////////// +// Methods implementing nsIScriptSecurityManager // +/////////////////////////////////////////////////// + +///////////////// Security Checks ///////////////// + +bool +nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx) +{ + // Get the security manager + nsScriptSecurityManager *ssm = + nsScriptSecurityManager::GetScriptSecurityManager(); + + NS_ASSERTION(ssm, "Failed to get security manager service"); + if (!ssm) + return false; + + nsresult rv; + nsIPrincipal* subjectPrincipal = ssm->GetSubjectPrincipal(cx, &rv); + + NS_ASSERTION(NS_SUCCEEDED(rv), "CSP: Failed to get nsIPrincipal from js context"); + if (NS_FAILED(rv)) + return false; // Not just absence of principal, but failure. + + if (!subjectPrincipal) + return true; + + nsCOMPtr csp; + 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); + } + } + 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::CheckSameOrigin(JSContext* cx, + nsIURI* aTargetURI) +{ + nsresult rv; + + // Get a context if necessary + if (!cx) + { + cx = GetCurrentJSContext(); + if (!cx) + return NS_OK; // No JS context, so allow access + } + + // Get a principal from the context + nsIPrincipal* sourcePrincipal = GetSubjectPrincipal(cx, &rv); + if (NS_FAILED(rv)) + return rv; + + if (!sourcePrincipal) + { + NS_WARNING("CheckSameOrigin called on script w/o principals; should this happen?"); + return NS_OK; + } + + if (sourcePrincipal == mSystemPrincipal) + { + // This is a system (chrome) script, so allow access + return NS_OK; + } + + // Get the original URI from the source principal. + // This has the effect of ignoring any change to document.domain + // which must be done to avoid DNS spoofing (bug 154930) + nsCOMPtr sourceURI; + sourcePrincipal->GetDomain(getter_AddRefs(sourceURI)); + if (!sourceURI) { + sourcePrincipal->GetURI(getter_AddRefs(sourceURI)); + NS_ENSURE_TRUE(sourceURI, NS_ERROR_FAILURE); + } + + // Compare origins + if (!SecurityCompareURIs(sourceURI, aTargetURI)) + { + ReportError(cx, NS_LITERAL_STRING("CheckSameOriginError"), sourceURI, aTargetURI); + return NS_ERROR_DOM_BAD_URI; + } + return NS_OK; +} + +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 uri; + aPrincipal->GetDomain(getter_AddRefs(uri)); + if (!uri) + aPrincipal->GetURI(getter_AddRefs(uri)); + return SecurityHashURI(uri); +} + +/* static */ bool +nsScriptSecurityManager::AppAttributesEqual(nsIPrincipal* aFirst, + nsIPrincipal* aSecond) +{ + MOZ_ASSERT(aFirst && aSecond, "Don't pass null pointers!"); + + uint32_t firstAppId = nsIScriptSecurityManager::UNKNOWN_APP_ID; + if (!aFirst->GetUnknownAppId()) { + firstAppId = aFirst->GetAppId(); + } + + uint32_t secondAppId = nsIScriptSecurityManager::UNKNOWN_APP_ID; + if (!aSecond->GetUnknownAppId()) { + secondAppId = aSecond->GetAppId(); + } + + return ((firstAppId == secondAppId) && + (aFirst->GetIsInBrowserElement() == aSecond->GetIsInBrowserElement())); +} + +NS_IMETHODIMP +nsScriptSecurityManager::CheckLoadURIFromScript(JSContext *cx, nsIURI *aURI) +{ + // Get principal of currently executing script. + nsresult rv; + nsIPrincipal* principal = GetSubjectPrincipal(cx, &rv); + if (NS_FAILED(rv)) + return rv; + + // Native code can load all URIs. + if (!principal) + return NS_OK; + + 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 (SubjectIsPrivileged()) + 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"); + SetPendingException(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 probe; + nsresult rv = aProbeArg->Clone(getter_AddRefs(probe)); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr tldService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + NS_ENSURE_TRUE(tldService, false); + while (true) { + if (nsScriptSecurityManager::SecurityCompareURIs(probe, aBase)) { + return true; + } + + nsAutoCString host, newHost; + nsresult 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 sourceURI; + aPrincipal->GetURI(getter_AddRefs(sourceURI)); + if (!sourceURI) { + nsCOMPtr expanded = do_QueryInterface(aPrincipal); + if (expanded) { + nsTArray< nsCOMPtr > *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 sourceBaseURI = NS_GetInnermostURI(sourceURI); + nsCOMPtr 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; + } + + NS_NAMED_LITERAL_STRING(errorTag, "CheckLoadURIError"); + bool reportErrors = !(aFlags & nsIScriptSecurityManager::DONT_REPORT_ERRORS); + + // 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) { + return aPrincipal->CheckMayLoad(targetBaseURI, true, false); + } + + //-- get the source scheme + nsAutoCString sourceScheme; + rv = sourceBaseURI->GetScheme(sourceScheme); + if (NS_FAILED(rv)) return rv; + + if (sourceScheme.LowerCaseEqualsLiteral(NS_NULLPRINCIPAL_SCHEME)) { + // A null principal can target its own URI. + if (sourceURI == aTargetURI) { + return NS_OK; + } + } + else if (targetScheme.Equals(sourceScheme, + nsCaseInsensitiveCStringComparator())) + { + // every scheme can access another URI from the same scheme, + // as long as they don't represent null principals... + // Or they don't require an special permission to do so + // See bug#773886 + + bool hasFlags; + rv = NS_URIChainHasFlags(targetBaseURI, + nsIProtocolHandler::URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + + if (hasFlags) { + // In this case, we allow opening only if the source and target URIS + // are on the same domain, or the opening URI has the webapps + // permision granted + if (!SecurityCompareURIs(sourceBaseURI,targetBaseURI) && + !nsContentUtils::IsExactSitePermAllow(aPrincipal,WEBAPPS_PERM_NAME)){ + return NS_ERROR_DOM_BAD_URI; + } + } + return NS_OK; + } + + // If the schemes don't match, the policy is specified by the protocol + // flags on the target URI. Note that the order of policy checks here is + // very important! We start from most restrictive and work our way down. + // Note that since we're working with the innermost URI, we can just use + // the methods that work on chains of nested URIs and they will only look + // at the flags for our one URI. + + // Check for system target URI + rv = DenyAccessIfURIHasFlags(targetBaseURI, + nsIProtocolHandler::URI_DANGEROUS_TO_LOAD); + if (NS_FAILED(rv)) { + // Deny access, since the origin principal is not system + if (reportErrors) { + ReportError(nullptr, errorTag, sourceURI, aTargetURI); + } + return rv; + } + + // Check for chrome target URI + rv = NS_URIChainHasFlags(targetBaseURI, + nsIProtocolHandler::URI_IS_UI_RESOURCE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) { + if (aFlags & nsIScriptSecurityManager::ALLOW_CHROME) { + if (!targetScheme.EqualsLiteral("chrome")) { + // for now don't change behavior for resource: or moz-icon: + return NS_OK; + } + + // allow load only if chrome package is whitelisted + nsCOMPtr reg(do_GetService( + NS_CHROMEREGISTRY_CONTRACTID)); + if (reg) { + bool accessAllowed = false; + reg->AllowContentToAccess(targetBaseURI, &accessAllowed); + if (accessAllowed) { + return NS_OK; + } + } + } + + // resource: and chrome: are equivalent, securitywise + // That's bogus!! Fix this. But watch out for + // the view-source stylesheet? + bool sourceIsChrome; + rv = NS_URIChainHasFlags(sourceBaseURI, + nsIProtocolHandler::URI_IS_UI_RESOURCE, + &sourceIsChrome); + NS_ENSURE_SUCCESS(rv, rv); + if (sourceIsChrome) { + return NS_OK; + } + if (reportErrors) { + ReportError(nullptr, errorTag, sourceURI, aTargetURI); + } + return NS_ERROR_DOM_BAD_URI; + } + + // Check for target URI pointing to a file + rv = NS_URIChainHasFlags(targetBaseURI, + 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 (size_t i = 0; i < mFileURIWhitelist.Length(); ++i) { + if (EqualOrSubdomain(sourceURI, mFileURIWhitelist[i])) { + return NS_OK; + } + } + + // resource: and chrome: are equivalent, securitywise + // That's bogus!! Fix this. But watch out for + // the view-source stylesheet? + bool sourceIsChrome; + rv = NS_URIChainHasFlags(sourceURI, + nsIProtocolHandler::URI_IS_UI_RESOURCE, + &sourceIsChrome); + NS_ENSURE_SUCCESS(rv, rv); + if (sourceIsChrome) { + return NS_OK; + } + + if (reportErrors) { + ReportError(nullptr, errorTag, sourceURI, 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(targetBaseURI, + nsIProtocolHandler::URI_LOADABLE_BY_ANYONE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (!hasFlags) { + nsXPIDLString message; + NS_ConvertASCIItoUTF16 ucsTargetScheme(targetScheme); + const char16_t* formatStrings[] = { ucsTargetScheme.get() }; + rv = sStrBundle-> + FormatStringFromName(MOZ_UTF16("ProtocolFlagError"), + formatStrings, + ArrayLength(formatStrings), + getter_Copies(message)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr 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 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 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 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; +} + +bool +nsScriptSecurityManager::ScriptAllowed(JSObject *aGlobal) +{ + MOZ_ASSERT(aGlobal); + MOZ_ASSERT(JS_IsGlobalObject(aGlobal) || js::IsOuterObject(aGlobal)); + AutoJSContext cx; + JS::RootedObject global(cx, js::UncheckedUnwrap(aGlobal, /* stopAtOuter = */ false)); + + // Check the bits on the compartment private. + return xpc::Scriptability::Get(aGlobal).Allowed(); +} + +///////////////// Principals /////////////////////// +NS_IMETHODIMP +nsScriptSecurityManager::GetSubjectPrincipal(nsIPrincipal **aSubjectPrincipal) +{ + nsresult rv; + *aSubjectPrincipal = doGetSubjectPrincipal(&rv); + if (NS_SUCCEEDED(rv)) + NS_IF_ADDREF(*aSubjectPrincipal); + return rv; +} + +nsIPrincipal* +nsScriptSecurityManager::doGetSubjectPrincipal(nsresult* rv) +{ + NS_PRECONDITION(rv, "Null out param"); + JSContext *cx = GetCurrentJSContext(); + if (!cx) + { + *rv = NS_OK; + return nullptr; + } + return GetSubjectPrincipal(cx, rv); +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetSystemPrincipal(nsIPrincipal **result) +{ + NS_ADDREF(*result = mSystemPrincipal); + + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::SubjectPrincipalIsSystem(bool* aIsSystem) +{ + NS_ENSURE_ARG_POINTER(aIsSystem); + *aIsSystem = false; + + if (!mSystemPrincipal) + return NS_OK; + + nsCOMPtr subject; + nsresult rv = GetSubjectPrincipal(getter_AddRefs(subject)); + if (NS_FAILED(rv)) + return rv; + + if(!subject) + { + // No subject principal means no JS is running; + // this is the equivalent of system principal code + *aIsSystem = true; + return NS_OK; + } + + return mSystemPrincipal->Equals(subject, aIsSystem); +} + +nsresult +nsScriptSecurityManager::CreateCodebasePrincipal(nsIURI* aURI, uint32_t aAppId, + bool aInMozBrowser, + nsIPrincipal **result) +{ + // I _think_ it's safe to not create null principals here based on aURI. + // At least all the callers would do the right thing in those cases, as far + // as I can tell. --bz + + nsCOMPtr uriPrinc = do_QueryInterface(aURI); + if (uriPrinc) { + nsCOMPtr principal; + uriPrinc->GetPrincipal(getter_AddRefs(principal)); + if (!principal || principal == mSystemPrincipal) { + return CallCreateInstance(NS_NULLPRINCIPAL_CONTRACTID, result); + } + + principal.forget(result); + + return NS_OK; + } + + nsRefPtr codebase = new nsPrincipal(); + if (!codebase) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = codebase->Init(aURI, aAppId, aInMozBrowser); + if (NS_FAILED(rv)) + return rv; + + NS_ADDREF(*result = codebase); + + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetSimpleCodebasePrincipal(nsIURI* aURI, + nsIPrincipal** aPrincipal) +{ + return GetCodebasePrincipalInternal(aURI, + nsIScriptSecurityManager::UNKNOWN_APP_ID, + false, aPrincipal); +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetNoAppCodebasePrincipal(nsIURI* aURI, + nsIPrincipal** aPrincipal) +{ + return GetCodebasePrincipalInternal(aURI, nsIScriptSecurityManager::NO_APP_ID, + false, aPrincipal); +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetCodebasePrincipal(nsIURI* aURI, + nsIPrincipal** aPrincipal) +{ + return GetNoAppCodebasePrincipal(aURI, aPrincipal); +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetAppCodebasePrincipal(nsIURI* aURI, + uint32_t aAppId, + bool aInMozBrowser, + nsIPrincipal** aPrincipal) +{ + NS_ENSURE_TRUE(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID, + NS_ERROR_INVALID_ARG); + + return GetCodebasePrincipalInternal(aURI, aAppId, aInMozBrowser, aPrincipal); +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetDocShellCodebasePrincipal(nsIURI* aURI, + nsIDocShell* aDocShell, + nsIPrincipal** aPrincipal) +{ + return GetCodebasePrincipalInternal(aURI, + aDocShell->GetAppId(), + aDocShell->GetIsInBrowserElement(), + aPrincipal); +} + +nsresult +nsScriptSecurityManager::GetCodebasePrincipalInternal(nsIURI *aURI, + uint32_t aAppId, + bool aInMozBrowser, + nsIPrincipal **result) +{ + NS_ENSURE_ARG(aURI); + + bool inheritsPrincipal; + nsresult rv = + NS_URIChainHasFlags(aURI, + nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, + &inheritsPrincipal); + if (NS_FAILED(rv) || inheritsPrincipal) { + return CallCreateInstance(NS_NULLPRINCIPAL_CONTRACTID, result); + } + + nsCOMPtr principal; + rv = CreateCodebasePrincipal(aURI, aAppId, aInMozBrowser, + getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + NS_IF_ADDREF(*result = principal); + + return NS_OK; +} + +nsIPrincipal* +nsScriptSecurityManager::GetSubjectPrincipal(JSContext *cx, + nsresult* rv) +{ + *rv = NS_OK; + JSCompartment *compartment = js::GetContextCompartment(cx); + + // The context should always be in a compartment, either one it has entered + // or the one associated with its global. + MOZ_ASSERT(!!compartment); + + JSPrincipals *principals = JS_GetCompartmentPrincipals(compartment); + return nsJSPrincipals::get(principals); +} + +// static +nsIPrincipal* +nsScriptSecurityManager::doGetObjectPrincipal(JSObject *aObj) +{ + JSCompartment *compartment = js::GetObjectCompartment(aObj); + JSPrincipals *principals = JS_GetCompartmentPrincipals(compartment); + return nsJSPrincipals::get(principals); +} + +//////////////////////////////////////////////// +// Methods implementing nsIXPCSecurityManager // +//////////////////////////////////////////////// + +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::AllowXBLScope(js::GetContextCompartment(cx))) + { + return NS_OK; + } + + if (SubjectIsPrivileged()) + { + return NS_OK; + } + + //-- Access denied, report an error + NS_ConvertUTF8toUTF16 strName("CreateWrapperDenied"); + nsAutoCString origin; + nsresult rv2; + nsIPrincipal* subjectPrincipal = doGetSubjectPrincipal(&rv2); + if (NS_SUCCEEDED(rv2) && 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; + // We need to keep our existing failure rv and not override it + // with a likely success code from the following string bundle + // call in order to throw the correct security exception later. + rv2 = sStrBundle->FormatStringFromName(strName.get(), + formatStrings, + length, + getter_Copies(errorMsg)); + NS_ENSURE_SUCCESS(rv2, rv2); + + SetPendingException(cx, errorMsg.get()); + return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CanCreateInstance(JSContext *cx, + const nsCID &aCID) +{ + if (SubjectIsPrivileged()) { + 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); + SetPendingException(cx, errorMsg.get()); + return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CanGetService(JSContext *cx, + const nsCID &aCID) +{ + if (SubjectIsPrivileged()) { + 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); + SetPendingException(cx, errorMsg.get()); + return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED; +} + +///////////////////////////////////////////// +// Method implementing nsIChannelEventSink // +///////////////////////////////////////////// +NS_IMETHODIMP +nsScriptSecurityManager::AsyncOnChannelRedirect(nsIChannel* oldChannel, + nsIChannel* newChannel, + uint32_t redirFlags, + nsIAsyncVerifyRedirectCallback *cb) +{ + nsCOMPtr oldPrincipal; + GetChannelPrincipal(oldChannel, getter_AddRefs(oldPrincipal)); + + nsCOMPtr newURI; + newChannel->GetURI(getter_AddRefs(newURI)); + nsCOMPtr newOriginalURI; + newChannel->GetOriginalURI(getter_AddRefs(newOriginalURI)); + + NS_ENSURE_STATE(oldPrincipal && newURI && newOriginalURI); + + const uint32_t flags = + nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT | + nsIScriptSecurityManager::DISALLOW_SCRIPT; + nsresult rv = CheckLoadURIWithPrincipal(oldPrincipal, newURI, flags); + if (NS_SUCCEEDED(rv) && newOriginalURI != newURI) { + rv = CheckLoadURIWithPrincipal(oldPrincipal, newOriginalURI, flags); + } + + if (NS_FAILED(rv)) + return rv; + + cb->OnRedirectVerifyCallback(NS_OK); + return NS_OK; +} + + +///////////////////////////////////// +// 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 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 + nsRefPtr system = new nsSystemPrincipal(); + NS_ENSURE_TRUE(system, NS_ERROR_OUT_OF_MEMORY); + + mSystemPrincipal = system; + + //-- Register security check callback in the JS engine + // Currently this is used to control access to function.caller + rv = nsXPConnect::XPConnect()->GetRuntime(&sRuntime); + NS_ENSURE_SUCCESS(rv, rv); + + static const JSSecurityCallbacks securityCallbacks = { + ContentSecurityPolicyPermitsJSAction, + JSPrincipalsSubsume, + }; + + MOZ_ASSERT(!JS_GetSecurityCallbacks(sRuntime)); + JS_SetSecurityCallbacks(sRuntime, &securityCallbacks); + JS_InitDestroyPrincipalsCallback(sRuntime, nsJSPrincipals::Destroy); + + JS_SetTrustedPrincipals(sRuntime, system); + + return NS_OK; +} + +static StaticRefPtr gScriptSecMan; + +nsScriptSecurityManager::~nsScriptSecurityManager(void) +{ + Preferences::RemoveObservers(this, kObservedPrefs); + if (mDomainPolicy) + mDomainPolicy->Deactivate(); + MOZ_ASSERT(!mDomainPolicy); +} + +void +nsScriptSecurityManager::Shutdown() +{ + if (sRuntime) { + JS_SetSecurityCallbacks(sRuntime, nullptr); + JS_SetTrustedPrincipals(sRuntime, nullptr); + sRuntime = nullptr; + } + + NS_IF_RELEASE(sIOService); + NS_IF_RELEASE(sStrBundle); +} + +nsScriptSecurityManager * +nsScriptSecurityManager::GetScriptSecurityManager() +{ + if (!gScriptSecMan && nsXPConnect::XPConnect()) + { + nsRefPtr ssManager = new nsScriptSecurityManager(); + + nsresult rv; + rv = ssManager->Init(); + if (NS_FAILED(rv)) { + return nullptr; + } + + rv = nsXPConnect::XPConnect()-> + SetDefaultSecurityManager(ssManager); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to install xpconnect security manager!"); + return nullptr; + } + + ClearOnShutdown(&gScriptSecMan); + gScriptSecMan = ssManager; + } + return gScriptSecMan; +} + +// 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(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 +uint32_t SkipPast(const nsCString& str, uint32_t base) +{ + while (base < str.Length() && Predicate::Test(str[base])) { + ++base; + } + return base; +} + +template +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); + + // + // 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.Clear(); + auto policies = mozilla::Preferences::GetCString("capability.policy.policynames"); + for (uint32_t base = SkipPast(policies, 0), bound = 0; + base < policies.Length(); + base = SkipPast(policies, bound)) + { + // Grab the current policy name. + bound = SkipUntil(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); + } +} + +void +nsScriptSecurityManager::AddSitesToFileURIWhitelist(const nsCString& aSiteList) +{ + for (uint32_t base = SkipPast(aSiteList, 0), bound = 0; + base < aSiteList.Length(); + base = SkipPast(aSiteList, bound)) + { + // Grab the current site. + bound = SkipUntil(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 uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), site, nullptr, nullptr, sIOService); + if (NS_SUCCEEDED(rv)) { + mFileURIWhitelist.AppendElement(uri); + } else { + nsCOMPtr 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; +} + +namespace mozilla { + +void +GetJarPrefix(uint32_t aAppId, bool aInMozBrowser, nsACString& aJarPrefix) +{ + MOZ_ASSERT(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID); + + if (aAppId == nsIScriptSecurityManager::UNKNOWN_APP_ID) { + aAppId = nsIScriptSecurityManager::NO_APP_ID; + } + + aJarPrefix.Truncate(); + + // Fallback. + if (aAppId == nsIScriptSecurityManager::NO_APP_ID && !aInMozBrowser) { + return; + } + + // aJarPrefix = appId + "+" + { 't', 'f' } + "+"; + aJarPrefix.AppendInt(aAppId); + aJarPrefix.Append('+'); + aJarPrefix.Append(aInMozBrowser ? 't' : 'f'); + aJarPrefix.Append('+'); + + return; +} + +} // namespace mozilla + +NS_IMETHODIMP +nsScriptSecurityManager::GetJarPrefix(uint32_t aAppId, + bool aInMozBrowser, + nsACString& aJarPrefix) +{ + MOZ_ASSERT(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID); + + mozilla::GetJarPrefix(aAppId, aInMozBrowser, aJarPrefix); + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetDomainPolicyActive(bool *aRv) +{ + *aRv = !!mDomainPolicy; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::ActivateDomainPolicy(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 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; +} + +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 exceptions; + nsCOMPtr 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; +}