Bug 1874684 - Part 13: Pass DateDuration to AddDate/CalendarDateAdd. r=sfink
[gecko.git] / netwerk / url-classifier / nsChannelClassifier.cpp
blobb9bdb3a050d4369d9ee0f35f9f70c2bdf2785420
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 "nsIObserverService.h"
14 #include "nsIProtocolHandler.h"
15 #include "nsIScriptSecurityManager.h"
16 #include "nsNetUtil.h"
17 #include "nsXULAppAPI.h"
18 #include "nsQueryObject.h"
19 #include "nsPrintfCString.h"
21 #include "mozilla/Components.h"
22 #include "mozilla/ErrorNames.h"
23 #include "mozilla/Logging.h"
24 #include "mozilla/Preferences.h"
25 #include "mozilla/net/UrlClassifierCommon.h"
26 #include "mozilla/net/UrlClassifierFeatureFactory.h"
27 #include "mozilla/ClearOnShutdown.h"
28 #include "mozilla/Services.h"
30 namespace mozilla {
31 namespace net {
33 #define URLCLASSIFIER_EXCEPTION_HOSTNAMES "urlclassifier.skipHostnames"
35 // Put CachedPrefs in anonymous namespace to avoid any collision from outside of
36 // this file.
37 namespace {
39 /**
40 * It is not recommended to read from Preference everytime a channel is
41 * connected.
42 * That is not fast and we should cache preference values and reuse them
44 class CachedPrefs final {
45 public:
46 static CachedPrefs* GetInstance();
48 void Init();
50 nsCString GetExceptionHostnames() const { return mExceptionHostnames; }
51 void SetExceptionHostnames(const nsACString& aHostnames) {
52 mExceptionHostnames = aHostnames;
55 private:
56 friend class StaticAutoPtr<CachedPrefs>;
57 CachedPrefs();
58 ~CachedPrefs();
60 static void OnPrefsChange(const char* aPrefName, void*);
62 nsCString mExceptionHostnames;
64 static StaticAutoPtr<CachedPrefs> sInstance;
67 StaticAutoPtr<CachedPrefs> CachedPrefs::sInstance;
69 // static
70 void CachedPrefs::OnPrefsChange(const char* aPref, void* aPrefs) {
71 auto* prefs = static_cast<CachedPrefs*>(aPrefs);
73 if (!strcmp(aPref, URLCLASSIFIER_EXCEPTION_HOSTNAMES)) {
74 nsCString exceptionHostnames;
75 Preferences::GetCString(URLCLASSIFIER_EXCEPTION_HOSTNAMES,
76 exceptionHostnames);
77 ToLowerCase(exceptionHostnames);
78 prefs->SetExceptionHostnames(exceptionHostnames);
82 void CachedPrefs::Init() {
83 Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange,
84 URLCLASSIFIER_EXCEPTION_HOSTNAMES, this);
87 // static
88 CachedPrefs* CachedPrefs::GetInstance() {
89 if (!sInstance) {
90 sInstance = new CachedPrefs();
91 sInstance->Init();
92 ClearOnShutdown(&sInstance);
94 MOZ_ASSERT(sInstance);
95 return sInstance;
98 CachedPrefs::CachedPrefs() { MOZ_COUNT_CTOR(CachedPrefs); }
100 CachedPrefs::~CachedPrefs() {
101 MOZ_COUNT_DTOR(CachedPrefs);
103 Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange,
104 URLCLASSIFIER_EXCEPTION_HOSTNAMES, this);
107 } // anonymous namespace
109 NS_IMPL_ISUPPORTS(nsChannelClassifier, nsIURIClassifierCallback, nsIObserver)
111 nsChannelClassifier::nsChannelClassifier(nsIChannel* aChannel)
112 : mIsAllowListed(false), mSuspendedChannel(false), mChannel(aChannel) {
113 UC_LOG_LEAK(("nsChannelClassifier::nsChannelClassifier [this=%p]", this));
114 MOZ_ASSERT(mChannel);
117 nsChannelClassifier::~nsChannelClassifier() {
118 UC_LOG_LEAK(("nsChannelClassifier::~nsChannelClassifier [this=%p]", this));
121 void nsChannelClassifier::Start() {
122 nsresult rv = StartInternal();
123 if (NS_FAILED(rv)) {
124 // If we aren't getting a callback for any reason, assume a good verdict and
125 // make sure we resume the channel if necessary.
126 OnClassifyComplete(NS_OK, ""_ns, ""_ns, ""_ns);
130 nsresult nsChannelClassifier::StartInternal() {
131 // Should only be called in the parent process.
132 MOZ_ASSERT(XRE_IsParentProcess());
134 // Don't bother to run the classifier on a load that has already failed.
135 // (this might happen after a redirect)
136 nsresult status;
137 mChannel->GetStatus(&status);
138 if (NS_FAILED(status)) return status;
140 // Don't bother to run the classifier on a cached load that was
141 // previously classified as good.
142 if (HasBeenClassified(mChannel)) {
143 return NS_ERROR_UNEXPECTED;
146 nsCOMPtr<nsIURI> uri;
147 nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
148 NS_ENSURE_SUCCESS(rv, rv);
150 // Don't bother checking certain types of URIs.
151 if (uri->SchemeIs("about")) {
152 return NS_ERROR_UNEXPECTED;
155 bool hasFlags;
156 rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
157 &hasFlags);
158 NS_ENSURE_SUCCESS(rv, rv);
159 if (hasFlags) return NS_ERROR_UNEXPECTED;
161 rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_FILE,
162 &hasFlags);
163 NS_ENSURE_SUCCESS(rv, rv);
164 if (hasFlags) return NS_ERROR_UNEXPECTED;
166 rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
167 &hasFlags);
168 NS_ENSURE_SUCCESS(rv, rv);
169 if (hasFlags) return NS_ERROR_UNEXPECTED;
171 rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
172 &hasFlags);
173 NS_ENSURE_SUCCESS(rv, rv);
174 if (hasFlags) return NS_ERROR_UNEXPECTED;
176 nsCString exceptionHostnames =
177 CachedPrefs::GetInstance()->GetExceptionHostnames();
178 if (!exceptionHostnames.IsEmpty()) {
179 UC_LOG(
180 ("nsChannelClassifier::StartInternal - entitylisted hostnames = %s "
181 "[this=%p]",
182 exceptionHostnames.get(), this));
183 if (IsHostnameEntitylisted(uri, exceptionHostnames)) {
184 return NS_ERROR_UNEXPECTED;
188 nsCOMPtr<nsIURIClassifier> uriClassifier =
189 do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
190 if (rv == NS_ERROR_FACTORY_NOT_REGISTERED || rv == NS_ERROR_NOT_AVAILABLE) {
191 // no URI classifier, ignore this failure.
192 return NS_ERROR_NOT_AVAILABLE;
194 NS_ENSURE_SUCCESS(rv, rv);
196 nsCOMPtr<nsIScriptSecurityManager> securityManager =
197 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
198 NS_ENSURE_SUCCESS(rv, rv);
200 nsCOMPtr<nsIPrincipal> principal;
201 rv = securityManager->GetChannelURIPrincipal(mChannel,
202 getter_AddRefs(principal));
203 NS_ENSURE_SUCCESS(rv, rv);
205 bool expectCallback;
206 if (UC_LOG_ENABLED()) {
207 nsCOMPtr<nsIURI> principalURI;
208 nsCString spec;
209 principal->GetAsciiSpec(spec);
210 spec.Truncate(std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
211 UC_LOG(
212 ("nsChannelClassifier::StartInternal - classifying principal %s on "
213 "channel %p [this=%p]",
214 spec.get(), mChannel.get(), this));
216 // The classify is running in parent process, no need to give a valid event
217 // target
218 rv = uriClassifier->Classify(principal, this, &expectCallback);
219 if (NS_FAILED(rv)) {
220 return rv;
223 if (expectCallback) {
224 // Suspend the channel, it will be resumed when we get the classifier
225 // callback.
226 rv = mChannel->Suspend();
227 if (NS_FAILED(rv)) {
228 // Some channels (including nsJSChannel) fail on Suspend. This
229 // shouldn't be fatal, but will prevent malware from being
230 // blocked on these channels.
231 UC_LOG_WARN(
232 ("nsChannelClassifier::StartInternal - couldn't suspend channel "
233 "[this=%p]",
234 this));
235 return rv;
238 mSuspendedChannel = true;
239 UC_LOG(
240 ("nsChannelClassifier::StartInternal - suspended channel %p [this=%p]",
241 mChannel.get(), this));
242 } else {
243 UC_LOG_WARN((
244 "nsChannelClassifier::StartInternal - not expecting callback [this=%p]",
245 this));
246 return NS_ERROR_FAILURE;
249 // Add an observer for shutdown
250 AddShutdownObserver();
251 return NS_OK;
254 bool nsChannelClassifier::IsHostnameEntitylisted(
255 nsIURI* aUri, const nsACString& aEntitylisted) {
256 nsAutoCString host;
257 nsresult rv = aUri->GetHost(host);
258 if (NS_FAILED(rv) || host.IsEmpty()) {
259 return false;
261 ToLowerCase(host);
263 for (const nsACString& token :
264 nsCCharSeparatedTokenizer(aEntitylisted, ',').ToRange()) {
265 if (token.Equals(host)) {
266 UC_LOG(
267 ("nsChannelClassifier::StartInternal - skipping %s (entitylisted) "
268 "[this=%p]",
269 host.get(), this));
270 return true;
274 return false;
277 // Note in the cache entry that this URL was classified, so that future
278 // cached loads don't need to be checked.
279 void nsChannelClassifier::MarkEntryClassified(nsresult status) {
280 // Should only be called in the parent process.
281 MOZ_ASSERT(XRE_IsParentProcess());
283 // Don't cache tracking classifications because we support allowlisting.
284 if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status) ||
285 mIsAllowListed) {
286 return;
289 if (UC_LOG_ENABLED()) {
290 nsAutoCString errorName;
291 GetErrorName(status, errorName);
292 nsCOMPtr<nsIURI> uri;
293 mChannel->GetURI(getter_AddRefs(uri));
294 nsAutoCString spec;
295 uri->GetAsciiSpec(spec);
296 spec.Truncate(std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
297 UC_LOG(
298 ("nsChannelClassifier::MarkEntryClassified - result is %s "
299 "for uri %s [this=%p, channel=%p]",
300 errorName.get(), spec.get(), this, mChannel.get()));
303 nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(mChannel);
304 if (!cachingChannel) {
305 return;
308 nsCOMPtr<nsISupports> cacheToken;
309 cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
310 if (!cacheToken) {
311 return;
314 nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken);
315 if (!cacheEntry) {
316 return;
319 cacheEntry->SetMetaDataElement("necko:classified",
320 NS_SUCCEEDED(status) ? "1" : nullptr);
323 bool nsChannelClassifier::HasBeenClassified(nsIChannel* aChannel) {
324 // Should only be called in the parent process.
325 MOZ_ASSERT(XRE_IsParentProcess());
327 nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aChannel);
328 if (!cachingChannel) {
329 return false;
332 // Only check the tag if we are loading from the cache without
333 // validation.
334 bool fromCache;
335 if (NS_FAILED(cachingChannel->IsFromCache(&fromCache)) || !fromCache) {
336 return false;
339 nsCOMPtr<nsISupports> cacheToken;
340 cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
341 if (!cacheToken) {
342 return false;
345 nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken);
346 if (!cacheEntry) {
347 return false;
350 nsCString tag;
351 cacheEntry->GetMetaDataElement("necko:classified", getter_Copies(tag));
352 return tag.EqualsLiteral("1");
355 /* static */
356 nsresult nsChannelClassifier::SendThreatHitReport(nsIChannel* aChannel,
357 const nsACString& aProvider,
358 const nsACString& aList,
359 const nsACString& aFullHash) {
360 NS_ENSURE_ARG_POINTER(aChannel);
362 nsAutoCString provider(aProvider);
363 nsPrintfCString reportEnablePref(
364 "browser.safebrowsing.provider.%s.dataSharing.enabled", provider.get());
365 if (!Preferences::GetBool(reportEnablePref.get(), false)) {
366 UC_LOG(
367 ("nsChannelClassifier::SendThreatHitReport - data sharing disabled for "
368 "%s",
369 provider.get()));
370 return NS_OK;
373 nsCOMPtr<nsIURIClassifier> uriClassifier =
374 components::UrlClassifierDB::Service();
375 if (!uriClassifier) {
376 return NS_ERROR_UNEXPECTED;
379 nsresult rv =
380 uriClassifier->SendThreatHitReport(aChannel, aProvider, aList, aFullHash);
381 NS_ENSURE_SUCCESS(rv, rv);
383 return NS_OK;
386 NS_IMETHODIMP
387 nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode,
388 const nsACString& aList,
389 const nsACString& aProvider,
390 const nsACString& aFullHash) {
391 // Should only be called in the parent process.
392 MOZ_ASSERT(XRE_IsParentProcess());
393 MOZ_ASSERT(
394 !UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
396 if (mSuspendedChannel) {
397 MarkEntryClassified(aErrorCode);
399 if (NS_FAILED(aErrorCode)) {
400 if (UC_LOG_ENABLED()) {
401 nsAutoCString errorName;
402 GetErrorName(aErrorCode, errorName);
404 nsCOMPtr<nsIURI> uri;
405 mChannel->GetURI(getter_AddRefs(uri));
406 nsCString spec = uri->GetSpecOrDefault();
407 spec.Truncate(
408 std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
409 UC_LOG(
410 ("nsChannelClassifier::OnClassifyComplete - cancelling channel %p "
411 "for %s "
412 "with error code %s [this=%p]",
413 mChannel.get(), spec.get(), errorName.get(), this));
416 // Channel will be cancelled (page element blocked) due to Safe Browsing.
417 // Do update the security state of the document and fire a security
418 // change event.
419 UrlClassifierCommon::SetBlockedContent(mChannel, aErrorCode, aList,
420 aProvider, aFullHash);
422 if (aErrorCode == NS_ERROR_MALWARE_URI ||
423 aErrorCode == NS_ERROR_PHISHING_URI ||
424 aErrorCode == NS_ERROR_UNWANTED_URI ||
425 aErrorCode == NS_ERROR_HARMFUL_URI) {
426 SendThreatHitReport(mChannel, aProvider, aList, aFullHash);
429 mChannel->Cancel(aErrorCode);
431 UC_LOG(
432 ("nsChannelClassifier::OnClassifyComplete - resuming channel %p "
433 "[this=%p]",
434 mChannel.get(), this));
435 mChannel->Resume();
438 mChannel = nullptr;
439 RemoveShutdownObserver();
441 return NS_OK;
444 void nsChannelClassifier::AddShutdownObserver() {
445 nsCOMPtr<nsIObserverService> observerService =
446 mozilla::services::GetObserverService();
447 if (observerService) {
448 observerService->AddObserver(this, "profile-change-net-teardown", false);
452 void nsChannelClassifier::RemoveShutdownObserver() {
453 nsCOMPtr<nsIObserverService> observerService =
454 mozilla::services::GetObserverService();
455 if (observerService) {
456 observerService->RemoveObserver(this, "profile-change-net-teardown");
460 ///////////////////////////////////////////////////////////////////////////////
461 // nsIObserver implementation
462 NS_IMETHODIMP
463 nsChannelClassifier::Observe(nsISupports* aSubject, const char* aTopic,
464 const char16_t* aData) {
465 if (!strcmp(aTopic, "profile-change-net-teardown")) {
466 // If we aren't getting a callback for any reason, make sure
467 // we resume the channel.
469 if (mChannel && mSuspendedChannel) {
470 mSuspendedChannel = false;
471 mChannel->Cancel(NS_ERROR_ABORT);
472 mChannel->Resume();
473 mChannel = nullptr;
476 RemoveShutdownObserver();
479 return NS_OK;
482 } // namespace net
483 } // namespace mozilla