summaryrefslogtreecommitdiffstats
path: root/netwerk/base/nsChannelClassifier.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/base/nsChannelClassifier.cpp')
-rw-r--r--netwerk/base/nsChannelClassifier.cpp696
1 files changed, 696 insertions, 0 deletions
diff --git a/netwerk/base/nsChannelClassifier.cpp b/netwerk/base/nsChannelClassifier.cpp
new file mode 100644
index 000000000..6b9f9ede3
--- /dev/null
+++ b/netwerk/base/nsChannelClassifier.cpp
@@ -0,0 +1,696 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 sts=2 ts=8 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 "nsChannelClassifier.h"
+
+#include "mozIThirdPartyUtil.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsICacheEntry.h"
+#include "nsICachingChannel.h"
+#include "nsIChannel.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIIOService.h"
+#include "nsIParentChannel.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrivateBrowsingTrackingProtectionWhitelist.h"
+#include "nsIProtocolHandler.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISecureBrowserUI.h"
+#include "nsISecurityEventSink.h"
+#include "nsIURL.h"
+#include "nsIWebProgressListener.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsXULAppAPI.h"
+
+#include "mozilla/ErrorNames.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+
+namespace mozilla {
+namespace net {
+
+//
+// MOZ_LOG=nsChannelClassifier:5
+//
+static LazyLogModule gChannelClassifierLog("nsChannelClassifier");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gChannelClassifierLog, LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(gChannelClassifierLog, LogLevel::Debug)
+
+NS_IMPL_ISUPPORTS(nsChannelClassifier,
+ nsIURIClassifierCallback)
+
+nsChannelClassifier::nsChannelClassifier()
+ : mIsAllowListed(false),
+ mSuspendedChannel(false)
+{
+}
+
+nsresult
+nsChannelClassifier::ShouldEnableTrackingProtection(nsIChannel *aChannel,
+ bool *result)
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ NS_ENSURE_ARG(result);
+ *result = false;
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(aChannel, loadContext);
+ if (!loadContext || !(loadContext->UseTrackingProtection())) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannelInternal> chan = do_QueryInterface(aChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> topWinURI;
+ rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!topWinURI) {
+ LOG(("nsChannelClassifier[%p]: No window URI\n", this));
+ }
+
+ nsCOMPtr<nsIURI> chanURI;
+ rv = aChannel->GetURI(getter_AddRefs(chanURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Third party checks don't work for chrome:// URIs in mochitests, so just
+ // default to isThirdParty = true. We check isThirdPartyWindow to expand
+ // the list of domains that are considered first party (e.g., if
+ // facebook.com includes an iframe from fatratgames.com, all subsources
+ // included in that iframe are considered third-party with
+ // isThirdPartyChannel, even if they are not third-party w.r.t.
+ // facebook.com), and isThirdPartyChannel to prevent top-level navigations
+ // from being detected as third-party.
+ bool isThirdPartyChannel = true;
+ bool isThirdPartyWindow = true;
+ thirdPartyUtil->IsThirdPartyURI(chanURI, topWinURI, &isThirdPartyWindow);
+ thirdPartyUtil->IsThirdPartyChannel(aChannel, nullptr, &isThirdPartyChannel);
+ if (!isThirdPartyWindow || !isThirdPartyChannel) {
+ *result = false;
+ if (LOG_ENABLED()) {
+ LOG(("nsChannelClassifier[%p]: Skipping tracking protection checks "
+ "for first party or top-level load channel[%p] with uri %s",
+ this, aChannel, chanURI->GetSpecOrDefault().get()));
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char ALLOWLIST_EXAMPLE_PREF[] = "channelclassifier.allowlist_example";
+ if (!topWinURI && Preferences::GetBool(ALLOWLIST_EXAMPLE_PREF, false)) {
+ LOG(("nsChannelClassifier[%p]: Allowlisting test domain\n", this));
+ rv = ios->NewURI(NS_LITERAL_CSTRING("http://allowlisted.example.com"),
+ nullptr, nullptr, getter_AddRefs(topWinURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Take the host/port portion so we can allowlist by site. Also ignore the
+ // scheme, since users who put sites on the allowlist probably don't expect
+ // allowlisting to depend on scheme.
+ nsCOMPtr<nsIURL> url = do_QueryInterface(topWinURI, &rv);
+ if (NS_FAILED(rv)) {
+ return rv; // normal for some loads, no need to print a warning
+ }
+
+ nsCString escaped(NS_LITERAL_CSTRING("https://"));
+ nsAutoCString temp;
+ rv = url->GetHostPort(temp);
+ NS_ENSURE_SUCCESS(rv, rv);
+ escaped.Append(temp);
+
+ // Stuff the whole thing back into a URI for the permission manager.
+ rv = ios->NewURI(escaped, nullptr, nullptr, getter_AddRefs(topWinURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION;
+ rv = permMgr->TestPermission(topWinURI, "trackingprotection", &permissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (permissions == nsIPermissionManager::ALLOW_ACTION) {
+ LOG(("nsChannelClassifier[%p]: Allowlisting channel[%p] for %s", this,
+ aChannel, escaped.get()));
+ mIsAllowListed = true;
+ *result = false;
+ } else {
+ *result = true;
+ }
+
+ // In Private Browsing Mode we also check against an in-memory list.
+ if (NS_UsePrivateBrowsing(aChannel)) {
+ nsCOMPtr<nsIPrivateBrowsingTrackingProtectionWhitelist> pbmtpWhitelist =
+ do_GetService(NS_PBTRACKINGPROTECTIONWHITELIST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ rv = pbmtpWhitelist->ExistsInAllowList(topWinURI, &exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ mIsAllowListed = true;
+ LOG(("nsChannelClassifier[%p]: Allowlisting channel[%p] in PBM for %s",
+ this, aChannel, escaped.get()));
+ }
+
+ *result = !exists;
+ }
+
+ // Tracking protection will be enabled so return without updating
+ // the security state. If any channels are subsequently cancelled
+ // (page elements blocked) the state will be then updated.
+ if (*result) {
+ if (LOG_ENABLED()) {
+ LOG(("nsChannelClassifier[%p]: Enabling tracking protection checks on "
+ "channel[%p] with uri %s for toplevel window %s", this, aChannel,
+ chanURI->GetSpecOrDefault().get(),
+ topWinURI->GetSpecOrDefault().get()));
+ }
+ return NS_OK;
+ }
+
+ // Tracking protection will be disabled so update the security state
+ // of the document and fire a secure change event. If we can't get the
+ // window for the channel, then the shield won't show up so we can't send
+ // an event to the securityUI anyway.
+ return NotifyTrackingProtectionDisabled(aChannel);
+}
+
+// static
+nsresult
+nsChannelClassifier::NotifyTrackingProtectionDisabled(nsIChannel *aChannel)
+{
+ // Can be called in EITHER the parent or child process.
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(aChannel, parentChannel);
+ if (parentChannel) {
+ // This channel is a parent-process proxy for a child process request.
+ // Tell the child process channel to do this instead.
+ parentChannel->NotifyTrackingProtectionDisabled();
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ rv = thirdPartyUtil->GetTopWindowForChannel(aChannel, getter_AddRefs(win));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto* pwin = nsPIDOMWindowOuter::From(win);
+ nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell();
+ if (!docShell) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDocument> doc = docShell->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_OK);
+
+ // Notify nsIWebProgressListeners of this security event.
+ // Can be used to change the UI state.
+ nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ uint32_t state = 0;
+ nsCOMPtr<nsISecureBrowserUI> securityUI;
+ docShell->GetSecurityUI(getter_AddRefs(securityUI));
+ if (!securityUI) {
+ return NS_OK;
+ }
+ doc->SetHasTrackingContentLoaded(true);
+ securityUI->GetState(&state);
+ state |= nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT;
+ eventSink->OnSecurityChange(nullptr, state);
+
+ return NS_OK;
+}
+
+void
+nsChannelClassifier::Start(nsIChannel *aChannel)
+{
+ mChannel = aChannel;
+
+ nsresult rv = StartInternal();
+ if (NS_FAILED(rv)) {
+ // If we aren't getting a callback for any reason, assume a good verdict and
+ // make sure we resume the channel if necessary.
+ OnClassifyComplete(NS_OK);
+ }
+}
+
+nsresult
+nsChannelClassifier::StartInternal()
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // Don't bother to run the classifier on a load that has already failed.
+ // (this might happen after a redirect)
+ nsresult status;
+ mChannel->GetStatus(&status);
+ if (NS_FAILED(status))
+ return status;
+
+ // Don't bother to run the classifier on a cached load that was
+ // previously classified as good.
+ if (HasBeenClassified(mChannel)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't bother checking certain types of URIs.
+ bool hasFlags;
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
+ &hasFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasFlags) return NS_ERROR_UNEXPECTED;
+
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_IS_LOCAL_FILE,
+ &hasFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasFlags) return NS_ERROR_UNEXPECTED;
+
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_IS_UI_RESOURCE,
+ &hasFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasFlags) return NS_ERROR_UNEXPECTED;
+
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
+ &hasFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasFlags) return NS_ERROR_UNEXPECTED;
+
+ // Skip whitelisted hostnames.
+ nsAutoCString whitelisted;
+ Preferences::GetCString("urlclassifier.skipHostnames", &whitelisted);
+ if (!whitelisted.IsEmpty()) {
+ ToLowerCase(whitelisted);
+ LOG(("nsChannelClassifier[%p]:StartInternal whitelisted hostnames = %s",
+ this, whitelisted.get()));
+ if (IsHostnameWhitelisted(uri, whitelisted)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ nsCOMPtr<nsIURIClassifier> uriClassifier =
+ do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
+ if (rv == NS_ERROR_FACTORY_NOT_REGISTERED ||
+ rv == NS_ERROR_NOT_AVAILABLE) {
+ // no URI classifier, ignore this failure.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = securityManager->GetChannelURIPrincipal(mChannel, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool expectCallback;
+ bool trackingProtectionEnabled = false;
+ (void)ShouldEnableTrackingProtection(mChannel, &trackingProtectionEnabled);
+
+ if (LOG_ENABLED()) {
+ nsCOMPtr<nsIURI> principalURI;
+ principal->GetURI(getter_AddRefs(principalURI));
+ LOG(("nsChannelClassifier[%p]: Classifying principal %s on channel with "
+ "uri %s", this, principalURI->GetSpecOrDefault().get(),
+ uri->GetSpecOrDefault().get()));
+ }
+ rv = uriClassifier->Classify(principal, trackingProtectionEnabled, this,
+ &expectCallback);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (expectCallback) {
+ // Suspend the channel, it will be resumed when we get the classifier
+ // callback.
+ rv = mChannel->Suspend();
+ if (NS_FAILED(rv)) {
+ // Some channels (including nsJSChannel) fail on Suspend. This
+ // shouldn't be fatal, but will prevent malware from being
+ // blocked on these channels.
+ LOG(("nsChannelClassifier[%p]: Couldn't suspend channel", this));
+ return rv;
+ }
+
+ mSuspendedChannel = true;
+ LOG(("nsChannelClassifier[%p]: suspended channel %p",
+ this, mChannel.get()));
+ } else {
+ LOG(("nsChannelClassifier[%p]: not expecting callback", this));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+bool
+nsChannelClassifier::IsHostnameWhitelisted(nsIURI *aUri,
+ const nsACString &aWhitelisted)
+{
+ nsAutoCString host;
+ nsresult rv = aUri->GetHost(host);
+ if (NS_FAILED(rv) || host.IsEmpty()) {
+ return false;
+ }
+ ToLowerCase(host);
+
+ nsCCharSeparatedTokenizer tokenizer(aWhitelisted, ',');
+ while (tokenizer.hasMoreTokens()) {
+ const nsCSubstring& token = tokenizer.nextToken();
+ if (token.Equals(host)) {
+ LOG(("nsChannelClassifier[%p]:StartInternal skipping %s (whitelisted)",
+ this, host.get()));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Note in the cache entry that this URL was classified, so that future
+// cached loads don't need to be checked.
+void
+nsChannelClassifier::MarkEntryClassified(nsresult status)
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // Don't cache tracking classifications because we support allowlisting.
+ if (status == NS_ERROR_TRACKING_URI || mIsAllowListed) {
+ return;
+ }
+
+ if (LOG_ENABLED()) {
+ nsAutoCString errorName;
+ GetErrorName(status, errorName);
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ nsAutoCString spec;
+ uri->GetAsciiSpec(spec);
+ LOG(("nsChannelClassifier::MarkEntryClassified[%s] %s",
+ errorName.get(), spec.get()));
+ }
+
+ nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(mChannel);
+ if (!cachingChannel) {
+ return;
+ }
+
+ nsCOMPtr<nsISupports> cacheToken;
+ cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
+ if (!cacheToken) {
+ return;
+ }
+
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ do_QueryInterface(cacheToken);
+ if (!cacheEntry) {
+ return;
+ }
+
+ cacheEntry->SetMetaDataElement("necko:classified",
+ NS_SUCCEEDED(status) ? "1" : nullptr);
+}
+
+bool
+nsChannelClassifier::HasBeenClassified(nsIChannel *aChannel)
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsICachingChannel> cachingChannel =
+ do_QueryInterface(aChannel);
+ if (!cachingChannel) {
+ return false;
+ }
+
+ // Only check the tag if we are loading from the cache without
+ // validation.
+ bool fromCache;
+ if (NS_FAILED(cachingChannel->IsFromCache(&fromCache)) || !fromCache) {
+ return false;
+ }
+
+ nsCOMPtr<nsISupports> cacheToken;
+ cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
+ if (!cacheToken) {
+ return false;
+ }
+
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ do_QueryInterface(cacheToken);
+ if (!cacheEntry) {
+ return false;
+ }
+
+ nsXPIDLCString tag;
+ cacheEntry->GetMetaDataElement("necko:classified", getter_Copies(tag));
+ return tag.EqualsLiteral("1");
+}
+
+//static
+bool
+nsChannelClassifier::SameLoadingURI(nsIDocument *aDoc, nsIChannel *aChannel)
+{
+ nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
+ nsCOMPtr<nsILoadInfo> channelLoadInfo = aChannel->GetLoadInfo();
+ if (!channelLoadInfo || !docURI) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> channelLoadingPrincipal = channelLoadInfo->LoadingPrincipal();
+ if (!channelLoadingPrincipal) {
+ // TYPE_DOCUMENT loads will not have a channelLoadingPrincipal. But top level
+ // loads should not be blocked by Tracking Protection, so we will return
+ // false
+ return false;
+ }
+ nsCOMPtr<nsIURI> channelLoadingURI;
+ channelLoadingPrincipal->GetURI(getter_AddRefs(channelLoadingURI));
+ if (!channelLoadingURI) {
+ return false;
+ }
+ bool equals = false;
+ nsresult rv = docURI->EqualsExceptRef(channelLoadingURI, &equals);
+ return NS_SUCCEEDED(rv) && equals;
+}
+
+// static
+nsresult
+nsChannelClassifier::SetBlockedTrackingContent(nsIChannel *channel)
+{
+ // Can be called in EITHER the parent or child process.
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(channel, parentChannel);
+ if (parentChannel) {
+ // This channel is a parent-process proxy for a child process request. The
+ // actual channel will be notified via the status passed to
+ // nsIRequest::Cancel and do this for us.
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ rv = thirdPartyUtil->GetTopWindowForChannel(channel, getter_AddRefs(win));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ auto* pwin = nsPIDOMWindowOuter::From(win);
+ nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell();
+ if (!docShell) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDocument> doc = docShell->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_OK);
+
+ // This event might come after the user has navigated to another page.
+ // To prevent showing the TrackingProtection UI on the wrong page, we need to
+ // check that the loading URI for the channel is the same as the URI currently
+ // loaded in the document.
+ if (!SameLoadingURI(doc, channel)) {
+ return NS_OK;
+ }
+
+ // Notify nsIWebProgressListeners of this security event.
+ // Can be used to change the UI state.
+ nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ uint32_t state = 0;
+ nsCOMPtr<nsISecureBrowserUI> securityUI;
+ docShell->GetSecurityUI(getter_AddRefs(securityUI));
+ if (!securityUI) {
+ return NS_OK;
+ }
+ doc->SetHasTrackingContentBlocked(true);
+ securityUI->GetState(&state);
+ state |= nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT;
+ eventSink->OnSecurityChange(nullptr, state);
+
+ // Log a warning to the web console.
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ NS_ConvertUTF8toUTF16 spec(uri->GetSpecOrDefault());
+ const char16_t* params[] = { spec.get() };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Tracking Protection"),
+ doc,
+ nsContentUtils::eNECKO_PROPERTIES,
+ "TrackingUriBlocked",
+ params, ArrayLength(params));
+
+ return NS_OK;
+}
+
+nsresult
+nsChannelClassifier::IsTrackerWhitelisted()
+{
+ nsresult rv;
+ nsCOMPtr<nsIURIClassifier> uriClassifier =
+ do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString tables;
+ Preferences::GetCString("urlclassifier.trackingWhitelistTable", &tables);
+
+ if (tables.IsEmpty()) {
+ LOG(("nsChannelClassifier[%p]:IsTrackerWhitelisted whitelist disabled",
+ this));
+ return NS_ERROR_TRACKING_URI;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> chan = do_QueryInterface(mChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> topWinURI;
+ rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!topWinURI) {
+ LOG(("nsChannelClassifier[%p]: No window URI", this));
+ return NS_ERROR_TRACKING_URI;
+ }
+
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIPrincipal> chanPrincipal;
+ rv = securityManager->GetChannelURIPrincipal(mChannel,
+ getter_AddRefs(chanPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Craft a whitelist URL like "toplevel.page/?resource=third.party.domain"
+ nsAutoCString pageHostname, resourceDomain;
+ rv = topWinURI->GetHost(pageHostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = chanPrincipal->GetBaseDomain(resourceDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString whitelistEntry = NS_LITERAL_CSTRING("http://") +
+ pageHostname + NS_LITERAL_CSTRING("/?resource=") + resourceDomain;
+ LOG(("nsChannelClassifier[%p]: Looking for %s in the whitelist",
+ this, whitelistEntry.get()));
+
+ nsCOMPtr<nsIURI> whitelistURI;
+ rv = NS_NewURI(getter_AddRefs(whitelistURI), whitelistEntry);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check whether or not the tracker is in the entity whitelist
+ nsAutoCString results;
+ rv = uriClassifier->ClassifyLocalWithTables(whitelistURI, tables, results);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!results.IsEmpty()) {
+ return NS_OK; // found it on the whitelist, must not be blocked
+ }
+
+ LOG(("nsChannelClassifier[%p]: %s is not in the whitelist",
+ this, whitelistEntry.get()));
+ return NS_ERROR_TRACKING_URI;
+}
+
+NS_IMETHODIMP
+nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode)
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (aErrorCode == NS_ERROR_TRACKING_URI &&
+ NS_SUCCEEDED(IsTrackerWhitelisted())) {
+ LOG(("nsChannelClassifier[%p]:OnClassifyComplete tracker found "
+ "in whitelist so we won't block it", this));
+ aErrorCode = NS_OK;
+ }
+
+ if (mSuspendedChannel) {
+ nsAutoCString errorName;
+ if (LOG_ENABLED()) {
+ GetErrorName(aErrorCode, errorName);
+ LOG(("nsChannelClassifier[%p]:OnClassifyComplete %s (suspended channel)",
+ this, errorName.get()));
+ }
+ MarkEntryClassified(aErrorCode);
+
+ if (NS_FAILED(aErrorCode)) {
+ if (LOG_ENABLED()) {
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ LOG(("nsChannelClassifier[%p]: cancelling channel %p for %s "
+ "with error code %s", this, mChannel.get(),
+ uri->GetSpecOrDefault().get(), errorName.get()));
+ }
+
+ // Channel will be cancelled (page element blocked) due to tracking.
+ // Do update the security state of the document and fire a security
+ // change event.
+ if (aErrorCode == NS_ERROR_TRACKING_URI) {
+ SetBlockedTrackingContent(mChannel);
+ }
+
+ mChannel->Cancel(aErrorCode);
+ }
+ LOG(("nsChannelClassifier[%p]: resuming channel %p from "
+ "OnClassifyComplete", this, mChannel.get()));
+ mChannel->Resume();
+ }
+
+ mChannel = nullptr;
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla