Bug 1514892 [wpt PR 14571] - Replace XRCoodinateSystem/FrameOfReference with XRSpace...
[gecko.git] / netwerk / url-classifier / nsChannelClassifier.cpp
bloba6a6b7ed672e50a97b8f0dad51f71dbed904be3b
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 sts=2 ts=8 et tw=80 : */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsChannelClassifier.h"
9 #include "nsCharSeparatedTokenizer.h"
10 #include "nsICacheEntry.h"
11 #include "nsICachingChannel.h"
12 #include "nsIChannel.h"
13 #include "nsIIOService.h"
14 #include "nsIObserverService.h"
15 #include "nsIPermissionManager.h"
16 #include "nsIProtocolHandler.h"
17 #include "nsIScriptSecurityManager.h"
18 #include "nsISecureBrowserUI.h"
19 #include "nsISupportsPriority.h"
20 #include "nsNetUtil.h"
21 #include "nsXULAppAPI.h"
22 #include "nsQueryObject.h"
23 #include "nsIUrlClassifierDBService.h"
24 #include "nsIUrlClassifierFeature.h"
25 #include "nsPrintfCString.h"
27 #include "mozilla/Components.h"
28 #include "mozilla/ErrorNames.h"
29 #include "mozilla/Logging.h"
30 #include "mozilla/Preferences.h"
31 #include "mozilla/net/UrlClassifierCommon.h"
32 #include "mozilla/net/UrlClassifierFeatureFactory.h"
33 #include "mozilla/ClearOnShutdown.h"
34 #include "mozilla/Services.h"
36 namespace mozilla {
37 namespace net {
40 // MOZ_LOG=nsChannelClassifier:5
42 static LazyLogModule gChannelClassifierLog("nsChannelClassifier");
44 #undef LOG
45 #define LOG(args) MOZ_LOG(gChannelClassifierLog, LogLevel::Info, args)
46 #define LOG_DEBUG(args) MOZ_LOG(gChannelClassifierLog, LogLevel::Debug, args)
47 #define LOG_WARN(args) MOZ_LOG(gChannelClassifierLog, LogLevel::Warning, args)
48 #define LOG_ENABLED() MOZ_LOG_TEST(gChannelClassifierLog, LogLevel::Info)
50 #define URLCLASSIFIER_SKIP_HOSTNAMES "urlclassifier.skipHostnames"
52 // Put CachedPrefs in anonymous namespace to avoid any collision from outside of
53 // this file.
54 namespace {
56 /**
57 * It is not recommended to read from Preference everytime a channel is
58 * connected.
59 * That is not fast and we should cache preference values and reuse them
61 class CachedPrefs final {
62 public:
63 static CachedPrefs* GetInstance();
65 void Init();
67 nsCString GetSkipHostnames() const { return mSkipHostnames; }
68 void SetSkipHostnames(const nsACString& aHostnames) {
69 mSkipHostnames = aHostnames;
72 private:
73 friend class StaticAutoPtr<CachedPrefs>;
74 CachedPrefs();
75 ~CachedPrefs();
77 static void OnPrefsChange(const char* aPrefName, CachedPrefs*);
79 nsCString mSkipHostnames;
81 static StaticAutoPtr<CachedPrefs> sInstance;
84 StaticAutoPtr<CachedPrefs> CachedPrefs::sInstance;
86 // static
87 void CachedPrefs::OnPrefsChange(const char* aPref, CachedPrefs* aPrefs) {
88 if (!strcmp(aPref, URLCLASSIFIER_SKIP_HOSTNAMES)) {
89 nsCString skipHostnames;
90 Preferences::GetCString(URLCLASSIFIER_SKIP_HOSTNAMES, skipHostnames);
91 ToLowerCase(skipHostnames);
92 aPrefs->SetSkipHostnames(skipHostnames);
96 void CachedPrefs::Init() {
97 Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange,
98 URLCLASSIFIER_SKIP_HOSTNAMES, this);
101 // static
102 CachedPrefs* CachedPrefs::GetInstance() {
103 if (!sInstance) {
104 sInstance = new CachedPrefs();
105 sInstance->Init();
106 ClearOnShutdown(&sInstance);
108 MOZ_ASSERT(sInstance);
109 return sInstance;
112 CachedPrefs::CachedPrefs() { MOZ_COUNT_CTOR(CachedPrefs); }
114 CachedPrefs::~CachedPrefs() {
115 MOZ_COUNT_DTOR(CachedPrefs);
117 Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange,
118 URLCLASSIFIER_SKIP_HOSTNAMES, this);
121 } // anonymous namespace
123 NS_IMPL_ISUPPORTS(nsChannelClassifier, nsIURIClassifierCallback, nsIObserver)
125 nsChannelClassifier::nsChannelClassifier(nsIChannel* aChannel)
126 : mIsAllowListed(false), mSuspendedChannel(false), mChannel(aChannel) {
127 LOG_DEBUG(("nsChannelClassifier::nsChannelClassifier %p", this));
128 MOZ_ASSERT(mChannel);
131 nsChannelClassifier::~nsChannelClassifier() {
132 LOG_DEBUG(("nsChannelClassifier::~nsChannelClassifier %p", this));
135 void nsChannelClassifier::Start() {
136 nsresult rv = StartInternal();
137 if (NS_FAILED(rv)) {
138 // If we aren't getting a callback for any reason, assume a good verdict and
139 // make sure we resume the channel if necessary.
140 OnClassifyComplete(NS_OK, NS_LITERAL_CSTRING(""), NS_LITERAL_CSTRING(""),
141 NS_LITERAL_CSTRING(""));
145 nsresult nsChannelClassifier::StartInternal() {
146 // Should only be called in the parent process.
147 MOZ_ASSERT(XRE_IsParentProcess());
149 // Don't bother to run the classifier on a load that has already failed.
150 // (this might happen after a redirect)
151 nsresult status;
152 mChannel->GetStatus(&status);
153 if (NS_FAILED(status)) return status;
155 // Don't bother to run the classifier on a cached load that was
156 // previously classified as good.
157 if (HasBeenClassified(mChannel)) {
158 return NS_ERROR_UNEXPECTED;
161 nsCOMPtr<nsIURI> uri;
162 nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
163 NS_ENSURE_SUCCESS(rv, rv);
165 // Don't bother checking certain types of URIs.
166 bool isAbout = false;
167 rv = uri->SchemeIs("about", &isAbout);
168 NS_ENSURE_SUCCESS(rv, rv);
169 if (isAbout) return NS_ERROR_UNEXPECTED;
171 bool hasFlags;
172 rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
173 &hasFlags);
174 NS_ENSURE_SUCCESS(rv, rv);
175 if (hasFlags) return NS_ERROR_UNEXPECTED;
177 rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_FILE,
178 &hasFlags);
179 NS_ENSURE_SUCCESS(rv, rv);
180 if (hasFlags) return NS_ERROR_UNEXPECTED;
182 rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
183 &hasFlags);
184 NS_ENSURE_SUCCESS(rv, rv);
185 if (hasFlags) return NS_ERROR_UNEXPECTED;
187 rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
188 &hasFlags);
189 NS_ENSURE_SUCCESS(rv, rv);
190 if (hasFlags) return NS_ERROR_UNEXPECTED;
192 nsCString skipHostnames = CachedPrefs::GetInstance()->GetSkipHostnames();
193 if (!skipHostnames.IsEmpty()) {
194 LOG(("nsChannelClassifier[%p]:StartInternal whitelisted hostnames = %s",
195 this, skipHostnames.get()));
196 if (IsHostnameWhitelisted(uri, skipHostnames)) {
197 return NS_ERROR_UNEXPECTED;
201 nsCOMPtr<nsIURIClassifier> uriClassifier =
202 do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
203 if (rv == NS_ERROR_FACTORY_NOT_REGISTERED || rv == NS_ERROR_NOT_AVAILABLE) {
204 // no URI classifier, ignore this failure.
205 return NS_ERROR_NOT_AVAILABLE;
207 NS_ENSURE_SUCCESS(rv, rv);
209 nsCOMPtr<nsIScriptSecurityManager> securityManager =
210 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
211 NS_ENSURE_SUCCESS(rv, rv);
213 nsCOMPtr<nsIPrincipal> principal;
214 rv = securityManager->GetChannelURIPrincipal(mChannel,
215 getter_AddRefs(principal));
216 NS_ENSURE_SUCCESS(rv, rv);
218 bool expectCallback;
219 if (LOG_ENABLED()) {
220 nsCOMPtr<nsIURI> principalURI;
221 principal->GetURI(getter_AddRefs(principalURI));
222 nsCString spec = principalURI->GetSpecOrDefault();
223 spec.Truncate(std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
224 LOG(("nsChannelClassifier[%p]: Classifying principal %s on channel[%p]",
225 this, spec.get(), mChannel.get()));
227 // The classify is running in parent process, no need to give a valid event
228 // target
229 rv = uriClassifier->Classify(principal, nullptr, this, &expectCallback);
230 if (NS_FAILED(rv)) {
231 return rv;
234 if (expectCallback) {
235 // Suspend the channel, it will be resumed when we get the classifier
236 // callback.
237 rv = mChannel->Suspend();
238 if (NS_FAILED(rv)) {
239 // Some channels (including nsJSChannel) fail on Suspend. This
240 // shouldn't be fatal, but will prevent malware from being
241 // blocked on these channels.
242 LOG_WARN(("nsChannelClassifier[%p]: Couldn't suspend channel", this));
243 return rv;
246 mSuspendedChannel = true;
247 LOG_DEBUG(("nsChannelClassifier[%p]: suspended channel %p", this,
248 mChannel.get()));
249 } else {
250 LOG(("nsChannelClassifier[%p]: not expecting callback", this));
251 return NS_ERROR_FAILURE;
254 // Add an observer for shutdown
255 AddShutdownObserver();
256 return NS_OK;
259 bool nsChannelClassifier::IsHostnameWhitelisted(
260 nsIURI* aUri, const nsACString& aWhitelisted) {
261 nsAutoCString host;
262 nsresult rv = aUri->GetHost(host);
263 if (NS_FAILED(rv) || host.IsEmpty()) {
264 return false;
266 ToLowerCase(host);
268 nsCCharSeparatedTokenizer tokenizer(aWhitelisted, ',');
269 while (tokenizer.hasMoreTokens()) {
270 const nsACString& token = tokenizer.nextToken();
271 if (token.Equals(host)) {
272 LOG(("nsChannelClassifier[%p]:StartInternal skipping %s (whitelisted)",
273 this, host.get()));
274 return true;
278 return false;
281 // Note in the cache entry that this URL was classified, so that future
282 // cached loads don't need to be checked.
283 void nsChannelClassifier::MarkEntryClassified(nsresult status) {
284 // Should only be called in the parent process.
285 MOZ_ASSERT(XRE_IsParentProcess());
287 // Don't cache tracking classifications because we support allowlisting.
288 if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status) ||
289 mIsAllowListed) {
290 return;
293 if (LOG_ENABLED()) {
294 nsAutoCString errorName;
295 GetErrorName(status, errorName);
296 nsCOMPtr<nsIURI> uri;
297 mChannel->GetURI(getter_AddRefs(uri));
298 nsAutoCString spec;
299 uri->GetAsciiSpec(spec);
300 spec.Truncate(std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
301 LOG(("nsChannelClassifier::MarkEntryClassified[%s] %s", errorName.get(),
302 spec.get()));
305 nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(mChannel);
306 if (!cachingChannel) {
307 return;
310 nsCOMPtr<nsISupports> cacheToken;
311 cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
312 if (!cacheToken) {
313 return;
316 nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken);
317 if (!cacheEntry) {
318 return;
321 cacheEntry->SetMetaDataElement("necko:classified",
322 NS_SUCCEEDED(status) ? "1" : nullptr);
325 bool nsChannelClassifier::HasBeenClassified(nsIChannel* aChannel) {
326 // Should only be called in the parent process.
327 MOZ_ASSERT(XRE_IsParentProcess());
329 nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aChannel);
330 if (!cachingChannel) {
331 return false;
334 // Only check the tag if we are loading from the cache without
335 // validation.
336 bool fromCache;
337 if (NS_FAILED(cachingChannel->IsFromCache(&fromCache)) || !fromCache) {
338 return false;
341 nsCOMPtr<nsISupports> cacheToken;
342 cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
343 if (!cacheToken) {
344 return false;
347 nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken);
348 if (!cacheEntry) {
349 return false;
352 nsCString tag;
353 cacheEntry->GetMetaDataElement("necko:classified", getter_Copies(tag));
354 return tag.EqualsLiteral("1");
357 /* static */
358 nsresult nsChannelClassifier::SendThreatHitReport(nsIChannel* aChannel,
359 const nsACString& aProvider,
360 const nsACString& aList,
361 const nsACString& aFullHash) {
362 NS_ENSURE_ARG_POINTER(aChannel);
364 nsAutoCString provider(aProvider);
365 nsPrintfCString reportEnablePref(
366 "browser.safebrowsing.provider.%s.dataSharing.enabled", provider.get());
367 if (!Preferences::GetBool(reportEnablePref.get(), false)) {
368 LOG((
369 "nsChannelClassifier::SendThreatHitReport data sharing disabled for %s",
370 provider.get()));
371 return NS_OK;
374 nsCOMPtr<nsIURIClassifier> uriClassifier =
375 components::UrlClassifierDB::Service();
376 if (!uriClassifier) {
377 return NS_ERROR_UNEXPECTED;
380 nsresult rv =
381 uriClassifier->SendThreatHitReport(aChannel, aProvider, aList, aFullHash);
382 NS_ENSURE_SUCCESS(rv, rv);
384 return NS_OK;
387 NS_IMETHODIMP
388 nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode,
389 const nsACString& aList,
390 const nsACString& aProvider,
391 const nsACString& aFullHash) {
392 // Should only be called in the parent process.
393 MOZ_ASSERT(XRE_IsParentProcess());
394 MOZ_ASSERT(
395 !UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
397 if (mSuspendedChannel) {
398 nsAutoCString errorName;
399 if (LOG_ENABLED() && NS_FAILED(aErrorCode)) {
400 GetErrorName(aErrorCode, errorName);
401 LOG(("nsChannelClassifier[%p]:OnClassifyComplete %s (suspended channel)",
402 this, errorName.get()));
404 MarkEntryClassified(aErrorCode);
406 if (NS_FAILED(aErrorCode)) {
407 if (LOG_ENABLED()) {
408 nsCOMPtr<nsIURI> uri;
409 mChannel->GetURI(getter_AddRefs(uri));
410 nsCString spec = uri->GetSpecOrDefault();
411 spec.Truncate(
412 std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
413 LOG(
414 ("nsChannelClassifier[%p]: cancelling channel %p for %s "
415 "with error code %s",
416 this, mChannel.get(), spec.get(), errorName.get()));
419 // Channel will be cancelled (page element blocked) due to Safe Browsing.
420 // Do update the security state of the document and fire a security
421 // change event.
422 UrlClassifierCommon::SetBlockedContent(mChannel, aErrorCode, aList,
423 aProvider, aFullHash);
425 if (aErrorCode == NS_ERROR_MALWARE_URI ||
426 aErrorCode == NS_ERROR_PHISHING_URI ||
427 aErrorCode == NS_ERROR_UNWANTED_URI ||
428 aErrorCode == NS_ERROR_HARMFUL_URI) {
429 SendThreatHitReport(mChannel, aProvider, aList, aFullHash);
432 mChannel->Cancel(aErrorCode);
434 LOG_DEBUG(
435 ("nsChannelClassifier[%p]: resuming channel[%p] from "
436 "OnClassifyComplete",
437 this, mChannel.get()));
438 mChannel->Resume();
441 mChannel = nullptr;
442 RemoveShutdownObserver();
444 return NS_OK;
447 void nsChannelClassifier::AddShutdownObserver() {
448 nsCOMPtr<nsIObserverService> observerService =
449 mozilla::services::GetObserverService();
450 if (observerService) {
451 observerService->AddObserver(this, "profile-change-net-teardown", false);
455 void nsChannelClassifier::RemoveShutdownObserver() {
456 nsCOMPtr<nsIObserverService> observerService =
457 mozilla::services::GetObserverService();
458 if (observerService) {
459 observerService->RemoveObserver(this, "profile-change-net-teardown");
463 ///////////////////////////////////////////////////////////////////////////////
464 // nsIObserver implementation
465 NS_IMETHODIMP
466 nsChannelClassifier::Observe(nsISupports* aSubject, const char* aTopic,
467 const char16_t* aData) {
468 if (!strcmp(aTopic, "profile-change-net-teardown")) {
469 // If we aren't getting a callback for any reason, make sure
470 // we resume the channel.
472 if (mChannel && mSuspendedChannel) {
473 mSuspendedChannel = false;
474 mChannel->Cancel(NS_ERROR_ABORT);
475 mChannel->Resume();
476 mChannel = nullptr;
479 RemoveShutdownObserver();
482 return NS_OK;
485 #undef LOG_ENABLED
487 } // namespace net
488 } // namespace mozilla