michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsContentBlocker.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIDocShellTreeItem.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsString.h" michael@0: #include "nsContentPolicyUtils.h" michael@0: #include "nsIObjectLoadingContent.h" michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: // Possible behavior pref values michael@0: // Those map to the nsIPermissionManager values where possible michael@0: #define BEHAVIOR_ACCEPT nsIPermissionManager::ALLOW_ACTION michael@0: #define BEHAVIOR_REJECT nsIPermissionManager::DENY_ACTION michael@0: #define BEHAVIOR_NOFOREIGN 3 michael@0: michael@0: // From nsIContentPolicy michael@0: static const char *kTypeString[] = {"other", michael@0: "script", michael@0: "image", michael@0: "stylesheet", michael@0: "object", michael@0: "document", michael@0: "subdocument", michael@0: "refresh", michael@0: "xbl", michael@0: "ping", michael@0: "xmlhttprequest", michael@0: "objectsubrequest", michael@0: "dtd", michael@0: "font", michael@0: "media", michael@0: "websocket", michael@0: "csp_report", michael@0: "xslt"}; michael@0: michael@0: #define NUMBER_OF_TYPES MOZ_ARRAY_LENGTH(kTypeString) michael@0: uint8_t nsContentBlocker::mBehaviorPref[NUMBER_OF_TYPES]; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsContentBlocker, michael@0: nsIContentPolicy, michael@0: nsIObserver, michael@0: nsISupportsWeakReference) michael@0: michael@0: nsContentBlocker::nsContentBlocker() michael@0: { michael@0: memset(mBehaviorPref, BEHAVIOR_ACCEPT, NUMBER_OF_TYPES); michael@0: } michael@0: michael@0: nsresult michael@0: nsContentBlocker::Init() michael@0: { michael@0: nsresult rv; michael@0: mPermissionManager = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr prefService = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr prefBranch; michael@0: rv = prefService->GetBranch("permissions.default.", getter_AddRefs(prefBranch)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Migrate old image blocker pref michael@0: nsCOMPtr oldPrefBranch; michael@0: oldPrefBranch = do_QueryInterface(prefService); michael@0: int32_t oldPref; michael@0: rv = oldPrefBranch->GetIntPref("network.image.imageBehavior", &oldPref); michael@0: if (NS_SUCCEEDED(rv) && oldPref) { michael@0: int32_t newPref; michael@0: switch (oldPref) { michael@0: default: michael@0: newPref = BEHAVIOR_ACCEPT; michael@0: break; michael@0: case 1: michael@0: newPref = BEHAVIOR_NOFOREIGN; michael@0: break; michael@0: case 2: michael@0: newPref = BEHAVIOR_REJECT; michael@0: break; michael@0: } michael@0: prefBranch->SetIntPref("image", newPref); michael@0: oldPrefBranch->ClearUserPref("network.image.imageBehavior"); michael@0: } michael@0: michael@0: michael@0: // The branch is not a copy of the prefservice, but a new object, because michael@0: // it is a non-default branch. Adding obeservers to it will only work if michael@0: // we make sure that the object doesn't die. So, keep a reference to it. michael@0: mPrefBranchInternal = do_QueryInterface(prefBranch, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mPrefBranchInternal->AddObserver("", this, true); michael@0: PrefChanged(prefBranch, nullptr); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: #undef LIMIT michael@0: #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default)) michael@0: michael@0: void michael@0: nsContentBlocker::PrefChanged(nsIPrefBranch *aPrefBranch, michael@0: const char *aPref) michael@0: { michael@0: int32_t val; michael@0: michael@0: #define PREF_CHANGED(_P) (!aPref || !strcmp(aPref, _P)) michael@0: michael@0: for(uint32_t i = 0; i < NUMBER_OF_TYPES; ++i) { michael@0: if (PREF_CHANGED(kTypeString[i]) && michael@0: NS_SUCCEEDED(aPrefBranch->GetIntPref(kTypeString[i], &val))) michael@0: mBehaviorPref[i] = LIMIT(val, 1, 3, 1); michael@0: } michael@0: michael@0: } michael@0: michael@0: // nsIContentPolicy Implementation michael@0: NS_IMETHODIMP michael@0: nsContentBlocker::ShouldLoad(uint32_t aContentType, michael@0: nsIURI *aContentLocation, michael@0: nsIURI *aRequestingLocation, michael@0: nsISupports *aRequestingContext, michael@0: const nsACString &aMimeGuess, michael@0: nsISupports *aExtra, michael@0: nsIPrincipal *aRequestPrincipal, michael@0: int16_t *aDecision) michael@0: { michael@0: *aDecision = nsIContentPolicy::ACCEPT; michael@0: nsresult rv; michael@0: michael@0: // Ony support NUMBER_OF_TYPES content types. that all there is at the michael@0: // moment, but you never know... michael@0: if (aContentType > NUMBER_OF_TYPES) michael@0: return NS_OK; michael@0: michael@0: // we can't do anything without this michael@0: if (!aContentLocation) michael@0: return NS_OK; michael@0: michael@0: // The final type of an object tag may mutate before it reaches michael@0: // shouldProcess, so we cannot make any sane blocking decisions here michael@0: if (aContentType == nsIContentPolicy::TYPE_OBJECT) michael@0: return NS_OK; michael@0: michael@0: // we only want to check http, https, ftp michael@0: // for chrome:// and resources and others, no need to check. michael@0: nsAutoCString scheme; michael@0: aContentLocation->GetScheme(scheme); michael@0: if (!scheme.LowerCaseEqualsLiteral("ftp") && michael@0: !scheme.LowerCaseEqualsLiteral("http") && michael@0: !scheme.LowerCaseEqualsLiteral("https")) michael@0: return NS_OK; michael@0: michael@0: bool shouldLoad, fromPrefs; michael@0: rv = TestPermission(aContentLocation, aRequestingLocation, aContentType, michael@0: &shouldLoad, &fromPrefs); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!shouldLoad) { michael@0: if (fromPrefs) { michael@0: *aDecision = nsIContentPolicy::REJECT_TYPE; michael@0: } else { michael@0: *aDecision = nsIContentPolicy::REJECT_SERVER; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentBlocker::ShouldProcess(uint32_t aContentType, michael@0: nsIURI *aContentLocation, michael@0: nsIURI *aRequestingLocation, michael@0: nsISupports *aRequestingContext, michael@0: const nsACString &aMimeGuess, michael@0: nsISupports *aExtra, michael@0: nsIPrincipal *aRequestPrincipal, michael@0: int16_t *aDecision) michael@0: { michael@0: // For loads where aRequestingContext is chrome, we should just michael@0: // accept. Those are most likely toplevel loads in windows, and michael@0: // chrome generally knows what it's doing anyway. michael@0: nsCOMPtr item = michael@0: do_QueryInterface(NS_CP_GetDocShellFromContext(aRequestingContext)); michael@0: michael@0: if (item && item->ItemType() == nsIDocShellTreeItem::typeChrome) { michael@0: *aDecision = nsIContentPolicy::ACCEPT; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // For objects, we only check policy in shouldProcess, as the final type isn't michael@0: // determined until the channel is open -- We don't want to block images in michael@0: // object tags because plugins are disallowed. michael@0: // NOTE that this bypasses the aContentLocation checks in ShouldLoad - this is michael@0: // intentional, as aContentLocation may be null for plugins that load by type michael@0: // (e.g. java) michael@0: if (aContentType == nsIContentPolicy::TYPE_OBJECT) { michael@0: *aDecision = nsIContentPolicy::ACCEPT; michael@0: michael@0: bool shouldLoad, fromPrefs; michael@0: nsresult rv = TestPermission(aContentLocation, aRequestingLocation, michael@0: aContentType, &shouldLoad, &fromPrefs); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!shouldLoad) { michael@0: if (fromPrefs) { michael@0: *aDecision = nsIContentPolicy::REJECT_TYPE; michael@0: } else { michael@0: *aDecision = nsIContentPolicy::REJECT_SERVER; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This isn't a load from chrome or an object tag - Just do a ShouldLoad() michael@0: // check -- we want the same answer here michael@0: return ShouldLoad(aContentType, aContentLocation, aRequestingLocation, michael@0: aRequestingContext, aMimeGuess, aExtra, aRequestPrincipal, michael@0: aDecision); michael@0: } michael@0: michael@0: nsresult michael@0: nsContentBlocker::TestPermission(nsIURI *aCurrentURI, michael@0: nsIURI *aFirstURI, michael@0: int32_t aContentType, michael@0: bool *aPermission, michael@0: bool *aFromPrefs) michael@0: { michael@0: *aFromPrefs = false; michael@0: // This default will also get used if there is an unknown value in the michael@0: // permission list, or if the permission manager returns unknown values. michael@0: *aPermission = true; michael@0: michael@0: // check the permission list first; if we find an entry, it overrides michael@0: // default prefs. michael@0: // Don't forget the aContentType ranges from 1..8, while the michael@0: // array is indexed 0..7 michael@0: uint32_t permission; michael@0: nsresult rv = mPermissionManager->TestPermission(aCurrentURI, michael@0: kTypeString[aContentType - 1], michael@0: &permission); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If there is nothing on the list, use the default. michael@0: if (!permission) { michael@0: permission = mBehaviorPref[aContentType - 1]; michael@0: *aFromPrefs = true; michael@0: } michael@0: michael@0: // Use the fact that the nsIPermissionManager values map to michael@0: // the BEHAVIOR_* values above. michael@0: switch (permission) { michael@0: case BEHAVIOR_ACCEPT: michael@0: *aPermission = true; michael@0: break; michael@0: case BEHAVIOR_REJECT: michael@0: *aPermission = false; michael@0: break; michael@0: michael@0: case BEHAVIOR_NOFOREIGN: michael@0: // Third party checking michael@0: michael@0: // Need a requesting uri for third party checks to work. michael@0: if (!aFirstURI) michael@0: return NS_OK; michael@0: michael@0: bool trustedSource = false; michael@0: rv = aFirstURI->SchemeIs("chrome", &trustedSource); michael@0: NS_ENSURE_SUCCESS(rv,rv); michael@0: if (!trustedSource) { michael@0: rv = aFirstURI->SchemeIs("resource", &trustedSource); michael@0: NS_ENSURE_SUCCESS(rv,rv); michael@0: } michael@0: if (trustedSource) michael@0: return NS_OK; michael@0: michael@0: // compare tails of names checking to see if they have a common domain michael@0: // we do this by comparing the tails of both names where each tail michael@0: // includes at least one dot michael@0: michael@0: // A more generic method somewhere would be nice michael@0: michael@0: nsAutoCString currentHost; michael@0: rv = aCurrentURI->GetAsciiHost(currentHost); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Search for two dots, starting at the end. michael@0: // If there are no two dots found, ++dot will turn to zero, michael@0: // that will return the entire string. michael@0: int32_t dot = currentHost.RFindChar('.'); michael@0: dot = currentHost.RFindChar('.', dot-1); michael@0: ++dot; michael@0: michael@0: // Get the domain, ie the last part of the host (www.domain.com -> domain.com) michael@0: // This will break on co.uk michael@0: const nsCSubstring &tail = michael@0: Substring(currentHost, dot, currentHost.Length() - dot); michael@0: michael@0: nsAutoCString firstHost; michael@0: rv = aFirstURI->GetAsciiHost(firstHost); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If the tail is longer then the whole firstHost, it will never match michael@0: if (firstHost.Length() < tail.Length()) { michael@0: *aPermission = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Get the last part of the firstUri with the same length as |tail| michael@0: const nsCSubstring &firstTail = michael@0: Substring(firstHost, firstHost.Length() - tail.Length(), tail.Length()); michael@0: michael@0: // Check that both tails are the same, and that just before the tail in michael@0: // |firstUri| there is a dot. That means both url are in the same domain michael@0: if ((firstHost.Length() > tail.Length() && michael@0: firstHost.CharAt(firstHost.Length() - tail.Length() - 1) != '.') || michael@0: !tail.Equals(firstTail)) { michael@0: *aPermission = false; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentBlocker::Observe(nsISupports *aSubject, michael@0: const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: NS_ASSERTION(!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic), michael@0: "unexpected topic - we only deal with pref changes!"); michael@0: michael@0: if (mPrefBranchInternal) michael@0: PrefChanged(mPrefBranchInternal, NS_LossyConvertUTF16toASCII(aData).get()); michael@0: return NS_OK; michael@0: }