1 //* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "nsAutoPtr.h"
7 #include "nsCOMPtr.h"
8 #include "nsAppDirectoryServiceDefs.h"
9 #include "nsCRT.h"
10 #include "nsICryptoHash.h"
11 #include "nsICryptoHMAC.h"
12 #include "nsIDirectoryService.h"
13 #include "nsIKeyModule.h"
14 #include "nsIObserverService.h"
15 #include "nsIPermissionManager.h"
16 #include "nsIPrefBranch.h"
17 #include "nsIPrefService.h"
18 #include "nsIProperties.h"
19 #include "nsToolkitCompsCID.h"
20 #include "nsIUrlClassifierUtils.h"
21 #include "nsIXULRuntime.h"
22 #include "nsUrlClassifierDBService.h"
23 #include "nsUrlClassifierUtils.h"
24 #include "nsUrlClassifierProxies.h"
25 #include "nsURILoader.h"
26 #include "nsString.h"
27 #include "nsReadableUtils.h"
28 #include "nsTArray.h"
29 #include "nsNetCID.h"
30 #include "nsThreadUtils.h"
31 #include "nsXPCOMStrings.h"
32 #include "nsProxyRelease.h"
33 #include "nsString.h"
34 #include "mozilla/Atomics.h"
35 #include "mozilla/DebugOnly.h"
36 #include "mozilla/ErrorNames.h"
37 #include "mozilla/Mutex.h"
38 #include "mozilla/Preferences.h"
39 #include "mozilla/TimeStamp.h"
40 #include "mozilla/Telemetry.h"
41 #include "mozilla/Logging.h"
42 #include "prprf.h"
43 #include "prnetdb.h"
44 #include "Entries.h"
45 #include "HashStore.h"
46 #include "Classifier.h"
47 #include "ProtocolParser.h"
48 #include "mozilla/Attributes.h"
49 #include "nsIPrincipal.h"
50 #include "Classifier.h"
51 #include "ProtocolParser.h"
52 #include "nsContentUtils.h"
53
54 namespace mozilla {
55 namespace safebrowsing {
56
57 nsresult
TablesToResponse(const nsACString & tables)58 TablesToResponse(const nsACString& tables)
59 {
60 if (tables.IsEmpty()) {
61 return NS_OK;
62 }
63
64 // We don't check mCheckMalware and friends because BuildTables never
65 // includes a table that is not enabled.
66 if (FindInReadable(NS_LITERAL_CSTRING("-malware-"), tables)) {
67 return NS_ERROR_MALWARE_URI;
68 }
69 if (FindInReadable(NS_LITERAL_CSTRING("-phish-"), tables)) {
70 return NS_ERROR_PHISHING_URI;
71 }
72 if (FindInReadable(NS_LITERAL_CSTRING("-unwanted-"), tables)) {
73 return NS_ERROR_UNWANTED_URI;
74 }
75 if (FindInReadable(NS_LITERAL_CSTRING("-track-"), tables)) {
76 return NS_ERROR_TRACKING_URI;
77 }
78 if (FindInReadable(NS_LITERAL_CSTRING("-block-"), tables)) {
79 return NS_ERROR_BLOCKED_URI;
80 }
81 return NS_OK;
82 }
83
84 } // namespace safebrowsing
85 } // namespace mozilla
86
87 using namespace mozilla;
88 using namespace mozilla::safebrowsing;
89
90 // MOZ_LOG=UrlClassifierDbService:5
91 LazyLogModule gUrlClassifierDbServiceLog("UrlClassifierDbService");
92 #define LOG(args) MOZ_LOG(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug, args)
93 #define LOG_ENABLED() MOZ_LOG_TEST(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug)
94
95 // Prefs for implementing nsIURIClassifier to block page loads
96 #define CHECK_MALWARE_PREF "browser.safebrowsing.malware.enabled"
97 #define CHECK_MALWARE_DEFAULT false
98
99 #define CHECK_PHISHING_PREF "browser.safebrowsing.phishing.enabled"
100 #define CHECK_PHISHING_DEFAULT false
101
102 #define CHECK_TRACKING_PREF "privacy.trackingprotection.enabled"
103 #define CHECK_TRACKING_DEFAULT false
104
105 #define CHECK_TRACKING_PB_PREF "privacy.trackingprotection.pbmode.enabled"
106 #define CHECK_TRACKING_PB_DEFAULT false
107
108 #define CHECK_BLOCKED_PREF "browser.safebrowsing.blockedURIs.enabled"
109 #define CHECK_BLOCKED_DEFAULT false
110
111 #define GETHASH_NOISE_PREF "urlclassifier.gethashnoise"
112 #define GETHASH_NOISE_DEFAULT 4
113
114 // Comma-separated lists
115 #define MALWARE_TABLE_PREF "urlclassifier.malwareTable"
116 #define PHISH_TABLE_PREF "urlclassifier.phishTable"
117 #define TRACKING_TABLE_PREF "urlclassifier.trackingTable"
118 #define TRACKING_WHITELIST_TABLE_PREF "urlclassifier.trackingWhitelistTable"
119 #define BLOCKED_TABLE_PREF "urlclassifier.blockedTable"
120 #define DOWNLOAD_BLOCK_TABLE_PREF "urlclassifier.downloadBlockTable"
121 #define DOWNLOAD_ALLOW_TABLE_PREF "urlclassifier.downloadAllowTable"
122 #define DISALLOW_COMPLETION_TABLE_PREF "urlclassifier.disallow_completions"
123
124 #define CONFIRM_AGE_PREF "urlclassifier.max-complete-age"
125 #define CONFIRM_AGE_DEFAULT_SEC (45 * 60)
126
127 class nsUrlClassifierDBServiceWorker;
128
129 // Singleton instance.
130 static nsUrlClassifierDBService* sUrlClassifierDBService;
131
132 nsIThread* nsUrlClassifierDBService::gDbBackgroundThread = nullptr;
133
134 // Once we've committed to shutting down, don't do work in the background
135 // thread.
136 static bool gShuttingDownThread = false;
137
138 static mozilla::Atomic<int32_t> gFreshnessGuarantee(CONFIRM_AGE_DEFAULT_SEC);
139
NS_IMPL_ISUPPORTS(nsUrlClassifierDBServiceWorker,nsIUrlClassifierDBService)140 NS_IMPL_ISUPPORTS(nsUrlClassifierDBServiceWorker,
141 nsIUrlClassifierDBService)
142
143 nsUrlClassifierDBServiceWorker::nsUrlClassifierDBServiceWorker()
144 : mInStream(false)
145 , mGethashNoise(0)
146 , mPendingLookupLock("nsUrlClassifierDBServerWorker.mPendingLookupLock")
147 {
148 }
149
~nsUrlClassifierDBServiceWorker()150 nsUrlClassifierDBServiceWorker::~nsUrlClassifierDBServiceWorker()
151 {
152 NS_ASSERTION(!mClassifier,
153 "Db connection not closed, leaking memory! Call CloseDb "
154 "to close the connection.");
155 }
156
157 nsresult
Init(uint32_t aGethashNoise,nsCOMPtr<nsIFile> aCacheDir)158 nsUrlClassifierDBServiceWorker::Init(uint32_t aGethashNoise,
159 nsCOMPtr<nsIFile> aCacheDir)
160 {
161 mGethashNoise = aGethashNoise;
162 mCacheDir = aCacheDir;
163
164 ResetUpdate();
165
166 return NS_OK;
167 }
168
169 nsresult
QueueLookup(const nsACString & spec,const nsACString & tables,nsIUrlClassifierLookupCallback * callback)170 nsUrlClassifierDBServiceWorker::QueueLookup(const nsACString& spec,
171 const nsACString& tables,
172 nsIUrlClassifierLookupCallback* callback)
173 {
174 MutexAutoLock lock(mPendingLookupLock);
175
176 PendingLookup* lookup = mPendingLookups.AppendElement();
177 if (!lookup) return NS_ERROR_OUT_OF_MEMORY;
178
179 lookup->mStartTime = TimeStamp::Now();
180 lookup->mKey = spec;
181 lookup->mCallback = callback;
182 lookup->mTables = tables;
183
184 return NS_OK;
185 }
186
187 nsresult
DoLocalLookup(const nsACString & spec,const nsACString & tables,LookupResultArray * results)188 nsUrlClassifierDBServiceWorker::DoLocalLookup(const nsACString& spec,
189 const nsACString& tables,
190 LookupResultArray* results)
191 {
192 if (gShuttingDownThread) {
193 return NS_ERROR_ABORT;
194 }
195
196 MOZ_ASSERT(!NS_IsMainThread(), "DoLocalLookup must be on background thread");
197 if (!results) {
198 return NS_ERROR_FAILURE;
199 }
200 // Bail if we haven't been initialized on the background thread.
201 if (!mClassifier) {
202 return NS_ERROR_NOT_AVAILABLE;
203 }
204
205 // We ignore failures from Check because we'd rather return the
206 // results that were found than fail.
207 mClassifier->Check(spec, tables, gFreshnessGuarantee, *results);
208
209 LOG(("Found %d results.", results->Length()));
210 return NS_OK;
211 }
212
213 static nsCString
ProcessLookupResults(LookupResultArray * results)214 ProcessLookupResults(LookupResultArray* results)
215 {
216 // Build a stringified list of result tables.
217 nsTArray<nsCString> tables;
218 for (uint32_t i = 0; i < results->Length(); i++) {
219 LookupResult& result = results->ElementAt(i);
220 MOZ_ASSERT(!result.mNoise, "Lookup results should not have noise added");
221 LOG(("Found result from table %s", result.mTableName.get()));
222 if (tables.IndexOf(result.mTableName) == nsTArray<nsCString>::NoIndex) {
223 tables.AppendElement(result.mTableName);
224 }
225 }
226 nsAutoCString tableStr;
227 for (uint32_t i = 0; i < tables.Length(); i++) {
228 if (i != 0)
229 tableStr.Append(',');
230 tableStr.Append(tables[i]);
231 }
232 return tableStr;
233 }
234
235 /**
236 * Lookup up a key in the database is a two step process:
237 *
238 * a) First we look for any Entries in the database that might apply to this
239 * url. For each URL there are one or two possible domain names to check:
240 * the two-part domain name (example.com) and the three-part name
241 * (www.example.com). We check the database for both of these.
242 * b) If we find any entries, we check the list of fragments for that entry
243 * against the possible subfragments of the URL as described in the
244 * "Simplified Regular Expression Lookup" section of the protocol doc.
245 */
246 nsresult
DoLookup(const nsACString & spec,const nsACString & tables,nsIUrlClassifierLookupCallback * c)247 nsUrlClassifierDBServiceWorker::DoLookup(const nsACString& spec,
248 const nsACString& tables,
249 nsIUrlClassifierLookupCallback* c)
250 {
251 if (gShuttingDownThread) {
252 c->LookupComplete(nullptr);
253 return NS_ERROR_NOT_INITIALIZED;
254 }
255
256 PRIntervalTime clockStart = 0;
257 if (LOG_ENABLED()) {
258 clockStart = PR_IntervalNow();
259 }
260
261 nsAutoPtr<LookupResultArray> results(new LookupResultArray());
262 if (!results) {
263 c->LookupComplete(nullptr);
264 return NS_ERROR_OUT_OF_MEMORY;
265 }
266
267 nsresult rv = DoLocalLookup(spec, tables, results);
268 if (NS_FAILED(rv)) {
269 c->LookupComplete(nullptr);
270 return rv;
271 }
272
273 LOG(("Found %d results.", results->Length()));
274
275
276 if (LOG_ENABLED()) {
277 PRIntervalTime clockEnd = PR_IntervalNow();
278 LOG(("query took %dms\n",
279 PR_IntervalToMilliseconds(clockEnd - clockStart)));
280 }
281
282 nsAutoPtr<LookupResultArray> completes(new LookupResultArray());
283
284 for (uint32_t i = 0; i < results->Length(); i++) {
285 if (!mMissCache.Contains(results->ElementAt(i).hash.prefix)) {
286 completes->AppendElement(results->ElementAt(i));
287 }
288 }
289
290 for (uint32_t i = 0; i < completes->Length(); i++) {
291 if (!completes->ElementAt(i).Confirmed()) {
292 // We're going to be doing a gethash request, add some extra entries.
293 // Note that we cannot pass the first two by reference, because we
294 // add to completes, whicah can cause completes to reallocate and move.
295 AddNoise(completes->ElementAt(i).hash.prefix,
296 completes->ElementAt(i).mTableName,
297 mGethashNoise, *completes);
298 break;
299 }
300 }
301
302 // At this point ownership of 'results' is handed to the callback.
303 c->LookupComplete(completes.forget());
304
305 return NS_OK;
306 }
307
308 nsresult
HandlePendingLookups()309 nsUrlClassifierDBServiceWorker::HandlePendingLookups()
310 {
311 if (gShuttingDownThread) {
312 return NS_ERROR_ABORT;
313 }
314
315 MutexAutoLock lock(mPendingLookupLock);
316 while (mPendingLookups.Length() > 0) {
317 PendingLookup lookup = mPendingLookups[0];
318 mPendingLookups.RemoveElementAt(0);
319 {
320 MutexAutoUnlock unlock(mPendingLookupLock);
321 DoLookup(lookup.mKey, lookup.mTables, lookup.mCallback);
322 }
323 double lookupTime = (TimeStamp::Now() - lookup.mStartTime).ToMilliseconds();
324 Telemetry::Accumulate(Telemetry::URLCLASSIFIER_LOOKUP_TIME,
325 static_cast<uint32_t>(lookupTime));
326 }
327
328 return NS_OK;
329 }
330
331 nsresult
AddNoise(const Prefix aPrefix,const nsCString tableName,uint32_t aCount,LookupResultArray & results)332 nsUrlClassifierDBServiceWorker::AddNoise(const Prefix aPrefix,
333 const nsCString tableName,
334 uint32_t aCount,
335 LookupResultArray& results)
336 {
337 if (gShuttingDownThread) {
338 return NS_ERROR_ABORT;
339 }
340
341 if (aCount < 1) {
342 return NS_OK;
343 }
344
345 PrefixArray noiseEntries;
346 nsresult rv = mClassifier->ReadNoiseEntries(aPrefix, tableName,
347 aCount, &noiseEntries);
348 NS_ENSURE_SUCCESS(rv, rv);
349
350 for (uint32_t i = 0; i < noiseEntries.Length(); i++) {
351 LookupResult *result = results.AppendElement();
352 if (!result)
353 return NS_ERROR_OUT_OF_MEMORY;
354
355 result->hash.prefix = noiseEntries[i];
356 result->mNoise = true;
357
358 result->mTableName.Assign(tableName);
359 }
360
361 return NS_OK;
362 }
363
364 // Lookup a key in the db.
365 NS_IMETHODIMP
Lookup(nsIPrincipal * aPrincipal,const nsACString & aTables,nsIUrlClassifierCallback * c)366 nsUrlClassifierDBServiceWorker::Lookup(nsIPrincipal* aPrincipal,
367 const nsACString& aTables,
368 nsIUrlClassifierCallback* c)
369 {
370 if (gShuttingDownThread) {
371 return NS_ERROR_ABORT;
372 }
373
374 return HandlePendingLookups();
375 }
376
377 NS_IMETHODIMP
GetTables(nsIUrlClassifierCallback * c)378 nsUrlClassifierDBServiceWorker::GetTables(nsIUrlClassifierCallback* c)
379 {
380 if (gShuttingDownThread) {
381 return NS_ERROR_NOT_INITIALIZED;
382 }
383
384 nsresult rv = OpenDb();
385 if (NS_FAILED(rv)) {
386 NS_ERROR("Unable to open SafeBrowsing database");
387 return NS_ERROR_FAILURE;
388 }
389
390 NS_ENSURE_SUCCESS(rv, rv);
391
392 nsAutoCString response;
393 mClassifier->TableRequest(response);
394 LOG(("GetTables: %s", response.get()));
395 c->HandleEvent(response);
396
397 return rv;
398 }
399
400 void
ResetStream()401 nsUrlClassifierDBServiceWorker::ResetStream()
402 {
403 LOG(("ResetStream"));
404 mInStream = false;
405 mProtocolParser = nullptr;
406 }
407
408 void
ResetUpdate()409 nsUrlClassifierDBServiceWorker::ResetUpdate()
410 {
411 LOG(("ResetUpdate"));
412 mUpdateWaitSec = 0;
413 mUpdateStatus = NS_OK;
414 mUpdateObserver = nullptr;
415 }
416
417 NS_IMETHODIMP
SetHashCompleter(const nsACString & tableName,nsIUrlClassifierHashCompleter * completer)418 nsUrlClassifierDBServiceWorker::SetHashCompleter(const nsACString &tableName,
419 nsIUrlClassifierHashCompleter *completer)
420 {
421 return NS_ERROR_NOT_IMPLEMENTED;
422 }
423
424 NS_IMETHODIMP
BeginUpdate(nsIUrlClassifierUpdateObserver * observer,const nsACString & tables)425 nsUrlClassifierDBServiceWorker::BeginUpdate(nsIUrlClassifierUpdateObserver *observer,
426 const nsACString &tables)
427 {
428 LOG(("nsUrlClassifierDBServiceWorker::BeginUpdate [%s]", PromiseFlatCString(tables).get()));
429
430 if (gShuttingDownThread) {
431 return NS_ERROR_NOT_INITIALIZED;
432 }
433
434 NS_ENSURE_STATE(!mUpdateObserver);
435
436 nsresult rv = OpenDb();
437 if (NS_FAILED(rv)) {
438 NS_ERROR("Unable to open SafeBrowsing database");
439 return NS_ERROR_FAILURE;
440 }
441
442 mUpdateStatus = NS_OK;
443 mUpdateObserver = observer;
444 Classifier::SplitTables(tables, mUpdateTables);
445
446 return NS_OK;
447 }
448
449 // Called from the stream updater.
450 NS_IMETHODIMP
BeginStream(const nsACString & table)451 nsUrlClassifierDBServiceWorker::BeginStream(const nsACString &table)
452 {
453 LOG(("nsUrlClassifierDBServiceWorker::BeginStream"));
454 MOZ_ASSERT(!NS_IsMainThread(), "Streaming must be on the background thread");
455
456 if (gShuttingDownThread) {
457 return NS_ERROR_NOT_INITIALIZED;
458 }
459
460 NS_ENSURE_STATE(mUpdateObserver);
461 NS_ENSURE_STATE(!mInStream);
462
463 mInStream = true;
464
465 NS_ASSERTION(!mProtocolParser, "Should not have a protocol parser.");
466
467 // Check if we should use protobuf to parse the update.
468 bool useProtobuf = false;
469 for (size_t i = 0; i < mUpdateTables.Length(); i++) {
470 bool isCurProtobuf =
471 StringEndsWith(mUpdateTables[i], NS_LITERAL_CSTRING("-proto"));
472
473 if (0 == i) {
474 // Use the first table name to decice if all the subsequent tables
475 // should be '-proto'.
476 useProtobuf = isCurProtobuf;
477 continue;
478 }
479
480 if (useProtobuf != isCurProtobuf) {
481 NS_WARNING("Cannot mix 'proto' tables with other types "
482 "within the same provider.");
483 break;
484 }
485 }
486
487 mProtocolParser = (useProtobuf ? static_cast<ProtocolParser*>(new ProtocolParserProtobuf())
488 : static_cast<ProtocolParser*>(new ProtocolParserV2()));
489 if (!mProtocolParser)
490 return NS_ERROR_OUT_OF_MEMORY;
491
492 mProtocolParser->Init(mCryptoHash);
493
494 if (!table.IsEmpty()) {
495 mProtocolParser->SetCurrentTable(table);
496 }
497
498 mProtocolParser->SetRequestedTables(mUpdateTables);
499
500 return NS_OK;
501 }
502
503 /**
504 * Updating the database:
505 *
506 * The Update() method takes a series of chunks separated with control data,
507 * as described in
508 * http://code.google.com/p/google-safe-browsing/wiki/Protocolv2Spec
509 *
510 * It will iterate through the control data until it reaches a chunk. By
511 * the time it reaches a chunk, it should have received
512 * a) the table to which this chunk applies
513 * b) the type of chunk (add, delete, expire add, expire delete).
514 * c) the chunk ID
515 * d) the length of the chunk.
516 *
517 * For add and subtract chunks, it needs to read the chunk data (expires
518 * don't have any data). Chunk data is a list of URI fragments whose
519 * encoding depends on the type of table (which is indicated by the end
520 * of the table name):
521 * a) tables ending with -exp are a zlib-compressed list of URI fragments
522 * separated by newlines.
523 * b) tables ending with -sha128 have the form
524 * [domain][N][frag0]...[fragN]
525 * 16 1 16 16
526 * If N is 0, the domain is reused as a fragment.
527 * c) any other tables are assumed to be a plaintext list of URI fragments
528 * separated by newlines.
529 *
530 * Update() can be fed partial data; It will accumulate data until there is
531 * enough to act on. Finish() should be called when there will be no more
532 * data.
533 */
534 NS_IMETHODIMP
UpdateStream(const nsACString & chunk)535 nsUrlClassifierDBServiceWorker::UpdateStream(const nsACString& chunk)
536 {
537 if (gShuttingDownThread) {
538 return NS_ERROR_NOT_INITIALIZED;
539 }
540
541 NS_ENSURE_STATE(mInStream);
542
543 HandlePendingLookups();
544
545 // Feed the chunk to the parser.
546 return mProtocolParser->AppendStream(chunk);
547 }
548
549 NS_IMETHODIMP
FinishStream()550 nsUrlClassifierDBServiceWorker::FinishStream()
551 {
552 if (gShuttingDownThread) {
553 LOG(("shutting down"));
554 return NS_ERROR_NOT_INITIALIZED;
555 }
556
557 NS_ENSURE_STATE(mInStream);
558 NS_ENSURE_STATE(mUpdateObserver);
559
560 mInStream = false;
561
562 mProtocolParser->End();
563
564 if (NS_SUCCEEDED(mProtocolParser->Status())) {
565 if (mProtocolParser->UpdateWaitSec()) {
566 mUpdateWaitSec = mProtocolParser->UpdateWaitSec();
567 }
568 // XXX: Only allow forwards from the initial update?
569 const nsTArray<ProtocolParser::ForwardedUpdate> &forwards =
570 mProtocolParser->Forwards();
571 for (uint32_t i = 0; i < forwards.Length(); i++) {
572 const ProtocolParser::ForwardedUpdate &forward = forwards[i];
573 mUpdateObserver->UpdateUrlRequested(forward.url, forward.table);
574 }
575 // Hold on to any TableUpdate objects that were created by the
576 // parser.
577 mTableUpdates.AppendElements(mProtocolParser->GetTableUpdates());
578 mProtocolParser->ForgetTableUpdates();
579
580 #ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES
581 // The assignment involves no string copy since the source string is sharable.
582 mRawTableUpdates = mProtocolParser->GetRawTableUpdates();
583 #endif
584 } else {
585 LOG(("nsUrlClassifierDBService::FinishStream Failed to parse the stream "
586 "using mProtocolParser."));
587 mUpdateStatus = mProtocolParser->Status();
588 }
589 mUpdateObserver->StreamFinished(mProtocolParser->Status(), 0);
590
591 if (NS_SUCCEEDED(mUpdateStatus)) {
592 if (mProtocolParser->ResetRequested()) {
593 mClassifier->ResetTables(Classifier::Clear_All, mUpdateTables);
594 }
595 }
596
597 mProtocolParser = nullptr;
598
599 return NS_OK;
600 }
601
602 NS_IMETHODIMP
FinishUpdate()603 nsUrlClassifierDBServiceWorker::FinishUpdate()
604 {
605 if (gShuttingDownThread) {
606 return NS_ERROR_NOT_INITIALIZED;
607 }
608
609 NS_ENSURE_STATE(mUpdateObserver);
610
611 if (NS_SUCCEEDED(mUpdateStatus)) {
612 mUpdateStatus = ApplyUpdate();
613 } else {
614 LOG(("nsUrlClassifierDBServiceWorker::FinishUpdate() Not running "
615 "ApplyUpdate() since the update has already failed."));
616 }
617
618 mMissCache.Clear();
619
620 if (NS_SUCCEEDED(mUpdateStatus)) {
621 LOG(("Notifying success: %d", mUpdateWaitSec));
622 mUpdateObserver->UpdateSuccess(mUpdateWaitSec);
623 } else if (NS_ERROR_NOT_IMPLEMENTED == mUpdateStatus) {
624 LOG(("Treating NS_ERROR_NOT_IMPLEMENTED a successful update "
625 "but still mark it spoiled."));
626 mUpdateObserver->UpdateSuccess(0);
627 mClassifier->ResetTables(Classifier::Clear_Cache, mUpdateTables);
628 } else {
629 if (LOG_ENABLED()) {
630 nsAutoCString errorName;
631 mozilla::GetErrorName(mUpdateStatus, errorName);
632 LOG(("Notifying error: %s (%d)", errorName.get(), mUpdateStatus));
633 }
634
635 mUpdateObserver->UpdateError(mUpdateStatus);
636 /*
637 * mark the tables as spoiled(clear cache in LookupCache), we don't want to
638 * block hosts longer than normal because our update failed
639 */
640 mClassifier->ResetTables(Classifier::Clear_Cache, mUpdateTables);
641 }
642 mUpdateObserver = nullptr;
643
644 return NS_OK;
645 }
646
647 nsresult
ApplyUpdate()648 nsUrlClassifierDBServiceWorker::ApplyUpdate()
649 {
650 LOG(("nsUrlClassifierDBServiceWorker::ApplyUpdate()"));
651 nsresult rv = mClassifier->ApplyUpdates(&mTableUpdates);
652
653 #ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES
654 if (NS_FAILED(rv) && NS_ERROR_OUT_OF_MEMORY != rv) {
655 mClassifier->DumpRawTableUpdates(mRawTableUpdates);
656 }
657 // Invalidate the raw table updates.
658 mRawTableUpdates = EmptyCString();
659 #endif
660
661 return rv;
662 }
663
664 NS_IMETHODIMP
ResetDatabase()665 nsUrlClassifierDBServiceWorker::ResetDatabase()
666 {
667 nsresult rv = OpenDb();
668
669 if (NS_SUCCEEDED(rv)) {
670 mClassifier->Reset();
671 }
672
673 rv = CloseDb();
674 NS_ENSURE_SUCCESS(rv, rv);
675
676 return NS_OK;
677 }
678
679 NS_IMETHODIMP
ReloadDatabase()680 nsUrlClassifierDBServiceWorker::ReloadDatabase()
681 {
682 nsTArray<nsCString> tables;
683 nsTArray<int64_t> lastUpdateTimes;
684 nsresult rv = mClassifier->ActiveTables(tables);
685 NS_ENSURE_SUCCESS(rv, rv);
686
687 // We need to make sure lastupdatetime is set after reload database
688 // Otherwise request will be skipped if it is not confirmed.
689 for (uint32_t table = 0; table < tables.Length(); table++) {
690 lastUpdateTimes.AppendElement(mClassifier->GetLastUpdateTime(tables[table]));
691 }
692
693 // This will null out mClassifier
694 rv = CloseDb();
695 NS_ENSURE_SUCCESS(rv, rv);
696
697 // Create new mClassifier and load prefixset and completions from disk.
698 rv = OpenDb();
699 NS_ENSURE_SUCCESS(rv, rv);
700
701 for (uint32_t table = 0; table < tables.Length(); table++) {
702 int64_t time = lastUpdateTimes[table];
703 if (time) {
704 mClassifier->SetLastUpdateTime(tables[table], lastUpdateTimes[table]);
705 }
706 }
707
708 return NS_OK;
709 }
710
711 NS_IMETHODIMP
CancelUpdate()712 nsUrlClassifierDBServiceWorker::CancelUpdate()
713 {
714 LOG(("nsUrlClassifierDBServiceWorker::CancelUpdate"));
715
716 if (mUpdateObserver) {
717 LOG(("UpdateObserver exists, cancelling"));
718
719 mUpdateStatus = NS_BINDING_ABORTED;
720
721 mUpdateObserver->UpdateError(mUpdateStatus);
722
723 /*
724 * mark the tables as spoiled(clear cache in LookupCache), we don't want to
725 * block hosts longer than normal because our update failed
726 */
727 mClassifier->ResetTables(Classifier::Clear_Cache, mUpdateTables);
728
729 ResetStream();
730 ResetUpdate();
731 } else {
732 LOG(("No UpdateObserver, nothing to cancel"));
733 }
734
735 return NS_OK;
736 }
737
738 // Allows the main thread to delete the connection which may be in
739 // a background thread.
740 // XXX This could be turned into a single shutdown event so the logic
741 // is simpler in nsUrlClassifierDBService::Shutdown.
742 nsresult
CloseDb()743 nsUrlClassifierDBServiceWorker::CloseDb()
744 {
745 if (mClassifier) {
746 mClassifier->Close();
747 mClassifier = nullptr;
748 }
749
750 mCryptoHash = nullptr;
751 LOG(("urlclassifier db closed\n"));
752
753 return NS_OK;
754 }
755
756 nsresult
CacheCompletions(CacheResultArray * results)757 nsUrlClassifierDBServiceWorker::CacheCompletions(CacheResultArray *results)
758 {
759 if (gShuttingDownThread) {
760 return NS_ERROR_ABORT;
761 }
762
763 LOG(("nsUrlClassifierDBServiceWorker::CacheCompletions [%p]", this));
764 if (!mClassifier)
765 return NS_OK;
766
767 // Ownership is transferred in to us
768 nsAutoPtr<CacheResultArray> resultsPtr(results);
769
770 if (mLastResults == *resultsPtr) {
771 LOG(("Skipping completions that have just been cached already."));
772 return NS_OK;
773 }
774
775 nsAutoPtr<ProtocolParserV2> pParse(new ProtocolParserV2());
776 nsTArray<TableUpdate*> updates;
777
778 // Only cache results for tables that we have, don't take
779 // in tables we might accidentally have hit during a completion.
780 // This happens due to goog vs googpub lists existing.
781 nsTArray<nsCString> tables;
782 nsresult rv = mClassifier->ActiveTables(tables);
783 NS_ENSURE_SUCCESS(rv, rv);
784
785 for (uint32_t i = 0; i < resultsPtr->Length(); i++) {
786 bool activeTable = false;
787 for (uint32_t table = 0; table < tables.Length(); table++) {
788 if (tables[table].Equals(resultsPtr->ElementAt(i).table)) {
789 activeTable = true;
790 break;
791 }
792 }
793 if (activeTable) {
794 TableUpdateV2* tuV2 = TableUpdate::Cast<TableUpdateV2>(
795 pParse->GetTableUpdate(resultsPtr->ElementAt(i).table));
796
797 NS_ENSURE_TRUE(tuV2, NS_ERROR_FAILURE);
798
799 LOG(("CacheCompletion Addchunk %d hash %X", resultsPtr->ElementAt(i).entry.addChunk,
800 resultsPtr->ElementAt(i).entry.ToUint32()));
801 rv = tuV2->NewAddComplete(resultsPtr->ElementAt(i).entry.addChunk,
802 resultsPtr->ElementAt(i).entry.complete);
803 if (NS_FAILED(rv)) {
804 // We can bail without leaking here because ForgetTableUpdates
805 // hasn't been called yet.
806 return rv;
807 }
808 rv = tuV2->NewAddChunk(resultsPtr->ElementAt(i).entry.addChunk);
809 if (NS_FAILED(rv)) {
810 return rv;
811 }
812 updates.AppendElement(tuV2);
813 pParse->ForgetTableUpdates();
814 } else {
815 LOG(("Completion received, but table is not active, so not caching."));
816 }
817 }
818
819 mClassifier->ApplyFullHashes(&updates);
820 mLastResults = *resultsPtr;
821 return NS_OK;
822 }
823
824 nsresult
CacheMisses(PrefixArray * results)825 nsUrlClassifierDBServiceWorker::CacheMisses(PrefixArray *results)
826 {
827 LOG(("nsUrlClassifierDBServiceWorker::CacheMisses [%p] %d",
828 this, results->Length()));
829
830 // Ownership is transferred in to us
831 nsAutoPtr<PrefixArray> resultsPtr(results);
832
833 for (uint32_t i = 0; i < resultsPtr->Length(); i++) {
834 mMissCache.AppendElement(resultsPtr->ElementAt(i));
835 }
836 return NS_OK;
837 }
838
839 nsresult
OpenDb()840 nsUrlClassifierDBServiceWorker::OpenDb()
841 {
842 if (gShuttingDownThread) {
843 return NS_ERROR_ABORT;
844 }
845
846 MOZ_ASSERT(!NS_IsMainThread(), "Must initialize DB on background thread");
847 // Connection already open, don't do anything.
848 if (mClassifier) {
849 return NS_OK;
850 }
851
852 nsresult rv;
853 mCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
854 NS_ENSURE_SUCCESS(rv, rv);
855
856 nsAutoPtr<Classifier> classifier(new Classifier());
857 if (!classifier) {
858 return NS_ERROR_OUT_OF_MEMORY;
859 }
860
861 rv = classifier->Open(*mCacheDir);
862 NS_ENSURE_SUCCESS(rv, rv);
863
864 mClassifier = classifier;
865
866 return NS_OK;
867 }
868
869 nsresult
SetLastUpdateTime(const nsACString & table,uint64_t updateTime)870 nsUrlClassifierDBServiceWorker::SetLastUpdateTime(const nsACString &table,
871 uint64_t updateTime)
872 {
873 MOZ_ASSERT(!NS_IsMainThread(), "Must be on the background thread");
874 MOZ_ASSERT(mClassifier, "Classifier connection must be opened");
875
876 mClassifier->SetLastUpdateTime(table, updateTime);
877
878 return NS_OK;
879 }
880
881 NS_IMETHODIMP
ClearLastResults()882 nsUrlClassifierDBServiceWorker::ClearLastResults()
883 {
884 MOZ_ASSERT(!NS_IsMainThread(), "Must be on the background thread");
885 mLastResults.Clear();
886 return NS_OK;
887 }
888
889
890 // -------------------------------------------------------------------------
891 // nsUrlClassifierLookupCallback
892 //
893 // This class takes the results of a lookup found on the worker thread
894 // and handles any necessary partial hash expansions before calling
895 // the client callback.
896
897 class nsUrlClassifierLookupCallback final : public nsIUrlClassifierLookupCallback
898 , public nsIUrlClassifierHashCompleterCallback
899 {
900 public:
901 NS_DECL_THREADSAFE_ISUPPORTS
902 NS_DECL_NSIURLCLASSIFIERLOOKUPCALLBACK
903 NS_DECL_NSIURLCLASSIFIERHASHCOMPLETERCALLBACK
904
nsUrlClassifierLookupCallback(nsUrlClassifierDBService * dbservice,nsIUrlClassifierCallback * c)905 nsUrlClassifierLookupCallback(nsUrlClassifierDBService *dbservice,
906 nsIUrlClassifierCallback *c)
907 : mDBService(dbservice)
908 , mResults(nullptr)
909 , mPendingCompletions(0)
910 , mCallback(c)
911 {}
912
913 private:
914 ~nsUrlClassifierLookupCallback();
915
916 nsresult HandleResults();
917
918 RefPtr<nsUrlClassifierDBService> mDBService;
919 nsAutoPtr<LookupResultArray> mResults;
920
921 // Completed results to send back to the worker for caching.
922 nsAutoPtr<CacheResultArray> mCacheResults;
923
924 uint32_t mPendingCompletions;
925 nsCOMPtr<nsIUrlClassifierCallback> mCallback;
926 };
927
NS_IMPL_ISUPPORTS(nsUrlClassifierLookupCallback,nsIUrlClassifierLookupCallback,nsIUrlClassifierHashCompleterCallback)928 NS_IMPL_ISUPPORTS(nsUrlClassifierLookupCallback,
929 nsIUrlClassifierLookupCallback,
930 nsIUrlClassifierHashCompleterCallback)
931
932 nsUrlClassifierLookupCallback::~nsUrlClassifierLookupCallback()
933 {
934 if (mCallback) {
935 NS_ReleaseOnMainThread(mCallback.forget());
936 }
937 }
938
939 NS_IMETHODIMP
LookupComplete(nsTArray<LookupResult> * results)940 nsUrlClassifierLookupCallback::LookupComplete(nsTArray<LookupResult>* results)
941 {
942 NS_ASSERTION(mResults == nullptr,
943 "Should only get one set of results per nsUrlClassifierLookupCallback!");
944
945 if (!results) {
946 HandleResults();
947 return NS_OK;
948 }
949
950 mResults = results;
951
952 // Check the results entries that need to be completed.
953 for (uint32_t i = 0; i < results->Length(); i++) {
954 LookupResult& result = results->ElementAt(i);
955
956 // We will complete partial matches and matches that are stale.
957 if (!result.Confirmed()) {
958 nsCOMPtr<nsIUrlClassifierHashCompleter> completer;
959 nsCString gethashUrl;
960 nsresult rv;
961 nsCOMPtr<nsIUrlListManager> listManager = do_GetService(
962 "@mozilla.org/url-classifier/listmanager;1", &rv);
963 NS_ENSURE_SUCCESS(rv, rv);
964 rv = listManager->GetGethashUrl(result.mTableName, gethashUrl);
965 NS_ENSURE_SUCCESS(rv, rv);
966 LOG(("The match from %s needs to be completed at %s",
967 result.mTableName.get(), gethashUrl.get()));
968 // gethashUrls may be empty in 2 cases: test tables, and on startup where
969 // we may have found a prefix in an existing table before the listmanager
970 // has registered the table. In the second case we should not call
971 // complete.
972 if ((!gethashUrl.IsEmpty() ||
973 StringBeginsWith(result.mTableName, NS_LITERAL_CSTRING("test-"))) &&
974 mDBService->GetCompleter(result.mTableName,
975 getter_AddRefs(completer))) {
976 nsAutoCString partialHash;
977 partialHash.Assign(reinterpret_cast<char*>(&result.hash.prefix),
978 PREFIX_SIZE);
979
980 nsresult rv = completer->Complete(partialHash, gethashUrl, this);
981 if (NS_SUCCEEDED(rv)) {
982 mPendingCompletions++;
983 }
984 } else {
985 // For tables with no hash completer, a complete hash match is
986 // good enough, we'll consider it fresh, even if it hasn't been updated
987 // in 45 minutes.
988 if (result.Complete()) {
989 result.mFresh = true;
990 LOG(("Skipping completion in a table without a valid completer (%s).",
991 result.mTableName.get()));
992 } else {
993 NS_WARNING("Partial match in a table without a valid completer, ignoring partial match.");
994 }
995 }
996 }
997 }
998
999 LOG(("nsUrlClassifierLookupCallback::LookupComplete [%p] "
1000 "%u pending completions", this, mPendingCompletions));
1001 if (mPendingCompletions == 0) {
1002 // All results were complete, we're ready!
1003 HandleResults();
1004 }
1005
1006 return NS_OK;
1007 }
1008
1009 NS_IMETHODIMP
CompletionFinished(nsresult status)1010 nsUrlClassifierLookupCallback::CompletionFinished(nsresult status)
1011 {
1012 if (LOG_ENABLED()) {
1013 nsAutoCString errorName;
1014 mozilla::GetErrorName(status, errorName);
1015 LOG(("nsUrlClassifierLookupCallback::CompletionFinished [%p, %s]",
1016 this, errorName.get()));
1017 }
1018
1019 mPendingCompletions--;
1020 if (mPendingCompletions == 0) {
1021 HandleResults();
1022 }
1023
1024 return NS_OK;
1025 }
1026
1027 NS_IMETHODIMP
Completion(const nsACString & completeHash,const nsACString & tableName,uint32_t chunkId)1028 nsUrlClassifierLookupCallback::Completion(const nsACString& completeHash,
1029 const nsACString& tableName,
1030 uint32_t chunkId)
1031 {
1032 LOG(("nsUrlClassifierLookupCallback::Completion [%p, %s, %d]",
1033 this, PromiseFlatCString(tableName).get(), chunkId));
1034 mozilla::safebrowsing::Completion hash;
1035 hash.Assign(completeHash);
1036
1037 // Send this completion to the store for caching.
1038 if (!mCacheResults) {
1039 mCacheResults = new CacheResultArray();
1040 if (!mCacheResults)
1041 return NS_ERROR_OUT_OF_MEMORY;
1042 }
1043
1044 CacheResult result;
1045 result.entry.addChunk = chunkId;
1046 result.entry.complete = hash;
1047 result.table = tableName;
1048
1049 // OK if this fails, we just won't cache the item.
1050 mCacheResults->AppendElement(result);
1051
1052 // Check if this matched any of our results.
1053 for (uint32_t i = 0; i < mResults->Length(); i++) {
1054 LookupResult& result = mResults->ElementAt(i);
1055
1056 // Now, see if it verifies a lookup
1057 if (!result.mNoise
1058 && result.CompleteHash() == hash
1059 && result.mTableName.Equals(tableName)) {
1060 result.mProtocolConfirmed = true;
1061 }
1062 }
1063
1064 return NS_OK;
1065 }
1066
1067 nsresult
HandleResults()1068 nsUrlClassifierLookupCallback::HandleResults()
1069 {
1070 if (!mResults) {
1071 // No results, this URI is clean.
1072 LOG(("nsUrlClassifierLookupCallback::HandleResults [%p, no results]", this));
1073 return mCallback->HandleEvent(NS_LITERAL_CSTRING(""));
1074 }
1075 MOZ_ASSERT(mPendingCompletions == 0, "HandleResults() should never be "
1076 "called while there are pending completions");
1077
1078 LOG(("nsUrlClassifierLookupCallback::HandleResults [%p, %u results]",
1079 this, mResults->Length()));
1080
1081 nsTArray<nsCString> tables;
1082 // Build a stringified list of result tables.
1083 for (uint32_t i = 0; i < mResults->Length(); i++) {
1084 LookupResult& result = mResults->ElementAt(i);
1085
1086 // Leave out results that weren't confirmed, as their existence on
1087 // the list can't be verified. Also leave out randomly-generated
1088 // noise.
1089 if (result.mNoise) {
1090 LOG(("Skipping result %X from table %s (noise)",
1091 result.hash.prefix.ToUint32(), result.mTableName.get()));
1092 continue;
1093 } else if (!result.Confirmed()) {
1094 LOG(("Skipping result %X from table %s (not confirmed)",
1095 result.hash.prefix.ToUint32(), result.mTableName.get()));
1096 continue;
1097 }
1098
1099 LOG(("Confirmed result %X from table %s",
1100 result.hash.prefix.ToUint32(), result.mTableName.get()));
1101
1102 if (tables.IndexOf(result.mTableName) == nsTArray<nsCString>::NoIndex) {
1103 tables.AppendElement(result.mTableName);
1104 }
1105 }
1106
1107 // Some parts of this gethash request generated no hits at all.
1108 // Prefixes must have been removed from the database since our last update.
1109 // Save the prefixes we checked to prevent repeated requests
1110 // until the next update.
1111 nsAutoPtr<PrefixArray> cacheMisses(new PrefixArray());
1112 if (cacheMisses) {
1113 for (uint32_t i = 0; i < mResults->Length(); i++) {
1114 LookupResult &result = mResults->ElementAt(i);
1115 if (!result.Confirmed() && !result.mNoise) {
1116 cacheMisses->AppendElement(result.PrefixHash());
1117 }
1118 }
1119 // Hands ownership of the miss array back to the worker thread.
1120 mDBService->CacheMisses(cacheMisses.forget());
1121 }
1122
1123 if (mCacheResults) {
1124 // This hands ownership of the cache results array back to the worker
1125 // thread.
1126 mDBService->CacheCompletions(mCacheResults.forget());
1127 }
1128
1129 nsAutoCString tableStr;
1130 for (uint32_t i = 0; i < tables.Length(); i++) {
1131 if (i != 0)
1132 tableStr.Append(',');
1133 tableStr.Append(tables[i]);
1134 }
1135
1136 return mCallback->HandleEvent(tableStr);
1137 }
1138
1139
1140 // -------------------------------------------------------------------------
1141 // Helper class for nsIURIClassifier implementation, translates table names
1142 // to nsIURIClassifier enums.
1143
1144 class nsUrlClassifierClassifyCallback final : public nsIUrlClassifierCallback
1145 {
1146 public:
1147 NS_DECL_THREADSAFE_ISUPPORTS
1148 NS_DECL_NSIURLCLASSIFIERCALLBACK
1149
nsUrlClassifierClassifyCallback(nsIURIClassifierCallback * c)1150 explicit nsUrlClassifierClassifyCallback(nsIURIClassifierCallback *c)
1151 : mCallback(c)
1152 {}
1153
1154 private:
~nsUrlClassifierClassifyCallback()1155 ~nsUrlClassifierClassifyCallback() {}
1156
1157 nsCOMPtr<nsIURIClassifierCallback> mCallback;
1158 };
1159
NS_IMPL_ISUPPORTS(nsUrlClassifierClassifyCallback,nsIUrlClassifierCallback)1160 NS_IMPL_ISUPPORTS(nsUrlClassifierClassifyCallback,
1161 nsIUrlClassifierCallback)
1162
1163 NS_IMETHODIMP
1164 nsUrlClassifierClassifyCallback::HandleEvent(const nsACString& tables)
1165 {
1166 nsresult response = TablesToResponse(tables);
1167 mCallback->OnClassifyComplete(response);
1168 return NS_OK;
1169 }
1170
1171
1172 // -------------------------------------------------------------------------
1173 // Proxy class implementation
1174
NS_IMPL_ISUPPORTS(nsUrlClassifierDBService,nsIUrlClassifierDBService,nsIURIClassifier,nsIObserver)1175 NS_IMPL_ISUPPORTS(nsUrlClassifierDBService,
1176 nsIUrlClassifierDBService,
1177 nsIURIClassifier,
1178 nsIObserver)
1179
1180 /* static */ nsUrlClassifierDBService*
1181 nsUrlClassifierDBService::GetInstance(nsresult *result)
1182 {
1183 *result = NS_OK;
1184 if (!sUrlClassifierDBService) {
1185 sUrlClassifierDBService = new nsUrlClassifierDBService();
1186 if (!sUrlClassifierDBService) {
1187 *result = NS_ERROR_OUT_OF_MEMORY;
1188 return nullptr;
1189 }
1190
1191 NS_ADDREF(sUrlClassifierDBService); // addref the global
1192
1193 *result = sUrlClassifierDBService->Init();
1194 if (NS_FAILED(*result)) {
1195 NS_RELEASE(sUrlClassifierDBService);
1196 return nullptr;
1197 }
1198 } else {
1199 // Already exists, just add a ref
1200 NS_ADDREF(sUrlClassifierDBService); // addref the return result
1201 }
1202 return sUrlClassifierDBService;
1203 }
1204
1205
nsUrlClassifierDBService()1206 nsUrlClassifierDBService::nsUrlClassifierDBService()
1207 : mCheckMalware(CHECK_MALWARE_DEFAULT)
1208 , mCheckPhishing(CHECK_PHISHING_DEFAULT)
1209 , mCheckTracking(CHECK_TRACKING_DEFAULT)
1210 , mCheckBlockedURIs(CHECK_BLOCKED_DEFAULT)
1211 , mInUpdate(false)
1212 {
1213 }
1214
~nsUrlClassifierDBService()1215 nsUrlClassifierDBService::~nsUrlClassifierDBService()
1216 {
1217 sUrlClassifierDBService = nullptr;
1218 }
1219
1220 nsresult
ReadTablesFromPrefs()1221 nsUrlClassifierDBService::ReadTablesFromPrefs()
1222 {
1223 nsCString allTables;
1224 nsCString tables;
1225 Preferences::GetCString(PHISH_TABLE_PREF, &allTables);
1226
1227 Preferences::GetCString(MALWARE_TABLE_PREF, &tables);
1228 if (!tables.IsEmpty()) {
1229 allTables.Append(',');
1230 allTables.Append(tables);
1231 }
1232
1233 Preferences::GetCString(DOWNLOAD_BLOCK_TABLE_PREF, &tables);
1234 if (!tables.IsEmpty()) {
1235 allTables.Append(',');
1236 allTables.Append(tables);
1237 }
1238
1239 Preferences::GetCString(DOWNLOAD_ALLOW_TABLE_PREF, &tables);
1240 if (!tables.IsEmpty()) {
1241 allTables.Append(',');
1242 allTables.Append(tables);
1243 }
1244
1245 Preferences::GetCString(TRACKING_TABLE_PREF, &tables);
1246 if (!tables.IsEmpty()) {
1247 allTables.Append(',');
1248 allTables.Append(tables);
1249 }
1250
1251 Preferences::GetCString(TRACKING_WHITELIST_TABLE_PREF, &tables);
1252 if (!tables.IsEmpty()) {
1253 allTables.Append(',');
1254 allTables.Append(tables);
1255 }
1256
1257 Preferences::GetCString(BLOCKED_TABLE_PREF, &tables);
1258 if (!tables.IsEmpty()) {
1259 allTables.Append(',');
1260 allTables.Append(tables);
1261 }
1262
1263 Classifier::SplitTables(allTables, mGethashTables);
1264
1265 Preferences::GetCString(DISALLOW_COMPLETION_TABLE_PREF, &tables);
1266 Classifier::SplitTables(tables, mDisallowCompletionsTables);
1267
1268 return NS_OK;
1269 }
1270
1271 nsresult
Init()1272 nsUrlClassifierDBService::Init()
1273 {
1274 MOZ_ASSERT(NS_IsMainThread(), "Must initialize DB service on main thread");
1275 nsCOMPtr<nsIXULRuntime> appInfo = do_GetService("@mozilla.org/xre/app-info;1");
1276 if (appInfo) {
1277 bool inSafeMode = false;
1278 appInfo->GetInSafeMode(&inSafeMode);
1279 if (inSafeMode) {
1280 return NS_ERROR_NOT_AVAILABLE;
1281 }
1282 }
1283
1284 // Retrieve all the preferences.
1285 mCheckMalware = Preferences::GetBool(CHECK_MALWARE_PREF,
1286 CHECK_MALWARE_DEFAULT);
1287 mCheckPhishing = Preferences::GetBool(CHECK_PHISHING_PREF,
1288 CHECK_PHISHING_DEFAULT);
1289 mCheckTracking =
1290 Preferences::GetBool(CHECK_TRACKING_PREF, CHECK_TRACKING_DEFAULT) ||
1291 Preferences::GetBool(CHECK_TRACKING_PB_PREF, CHECK_TRACKING_PB_DEFAULT);
1292 mCheckBlockedURIs = Preferences::GetBool(CHECK_BLOCKED_PREF,
1293 CHECK_BLOCKED_DEFAULT);
1294 uint32_t gethashNoise = Preferences::GetUint(GETHASH_NOISE_PREF,
1295 GETHASH_NOISE_DEFAULT);
1296 gFreshnessGuarantee = Preferences::GetInt(CONFIRM_AGE_PREF,
1297 CONFIRM_AGE_DEFAULT_SEC);
1298 ReadTablesFromPrefs();
1299
1300 nsresult rv;
1301
1302 {
1303 // Force PSM loading on main thread
1304 nsCOMPtr<nsICryptoHash> dummy = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
1305 NS_ENSURE_SUCCESS(rv, rv);
1306 }
1307
1308 {
1309 // Force nsIUrlClassifierUtils loading on main thread.
1310 nsCOMPtr<nsIUrlClassifierUtils> dummy =
1311 do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID, &rv);
1312 NS_ENSURE_SUCCESS(rv, rv);
1313 }
1314
1315 // Directory providers must also be accessed on the main thread.
1316 nsCOMPtr<nsIFile> cacheDir;
1317 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
1318 getter_AddRefs(cacheDir));
1319 if (NS_FAILED(rv)) {
1320 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1321 getter_AddRefs(cacheDir));
1322 if (NS_FAILED(rv)) {
1323 return rv;
1324 }
1325 }
1326
1327 // Start the background thread.
1328 rv = NS_NewNamedThread("URL Classifier", &gDbBackgroundThread);
1329 if (NS_FAILED(rv))
1330 return rv;
1331
1332 mWorker = new nsUrlClassifierDBServiceWorker();
1333 if (!mWorker)
1334 return NS_ERROR_OUT_OF_MEMORY;
1335
1336 rv = mWorker->Init(gethashNoise, cacheDir);
1337 if (NS_FAILED(rv)) {
1338 mWorker = nullptr;
1339 return rv;
1340 }
1341
1342 // Proxy for calling the worker on the background thread
1343 mWorkerProxy = new UrlClassifierDBServiceWorkerProxy(mWorker);
1344 rv = mWorkerProxy->OpenDb();
1345 if (NS_FAILED(rv)) {
1346 return rv;
1347 }
1348
1349 // Add an observer for shutdown
1350 nsCOMPtr<nsIObserverService> observerService =
1351 mozilla::services::GetObserverService();
1352 if (!observerService)
1353 return NS_ERROR_FAILURE;
1354
1355 // The application is about to quit
1356 observerService->AddObserver(this, "quit-application", false);
1357 observerService->AddObserver(this, "profile-before-change", false);
1358
1359 // XXX: Do we *really* need to be able to change all of these at runtime?
1360 // Note: These observers should only be added when everything else above has
1361 // succeeded. Failing to do so can cause long shutdown times in certain
1362 // situations. See Bug 1247798 and Bug 1244803.
1363 Preferences::AddStrongObserver(this, CHECK_MALWARE_PREF);
1364 Preferences::AddStrongObserver(this, CHECK_PHISHING_PREF);
1365 Preferences::AddStrongObserver(this, CHECK_TRACKING_PREF);
1366 Preferences::AddStrongObserver(this, CHECK_TRACKING_PB_PREF);
1367 Preferences::AddStrongObserver(this, CHECK_BLOCKED_PREF);
1368 Preferences::AddStrongObserver(this, GETHASH_NOISE_PREF);
1369 Preferences::AddStrongObserver(this, CONFIRM_AGE_PREF);
1370 Preferences::AddStrongObserver(this, PHISH_TABLE_PREF);
1371 Preferences::AddStrongObserver(this, MALWARE_TABLE_PREF);
1372 Preferences::AddStrongObserver(this, TRACKING_TABLE_PREF);
1373 Preferences::AddStrongObserver(this, TRACKING_WHITELIST_TABLE_PREF);
1374 Preferences::AddStrongObserver(this, BLOCKED_TABLE_PREF);
1375 Preferences::AddStrongObserver(this, DOWNLOAD_BLOCK_TABLE_PREF);
1376 Preferences::AddStrongObserver(this, DOWNLOAD_ALLOW_TABLE_PREF);
1377 Preferences::AddStrongObserver(this, DISALLOW_COMPLETION_TABLE_PREF);
1378
1379 return NS_OK;
1380 }
1381
1382 void
BuildTables(bool aTrackingProtectionEnabled,nsCString & tables)1383 nsUrlClassifierDBService::BuildTables(bool aTrackingProtectionEnabled,
1384 nsCString &tables)
1385 {
1386 nsAutoCString malware;
1387 // LookupURI takes a comma-separated list already.
1388 Preferences::GetCString(MALWARE_TABLE_PREF, &malware);
1389 if (mCheckMalware && !malware.IsEmpty()) {
1390 tables.Append(malware);
1391 }
1392 nsAutoCString phishing;
1393 Preferences::GetCString(PHISH_TABLE_PREF, &phishing);
1394 if (mCheckPhishing && !phishing.IsEmpty()) {
1395 tables.Append(',');
1396 tables.Append(phishing);
1397 }
1398 if (aTrackingProtectionEnabled) {
1399 nsAutoCString tracking, trackingWhitelist;
1400 Preferences::GetCString(TRACKING_TABLE_PREF, &tracking);
1401 if (!tracking.IsEmpty()) {
1402 tables.Append(',');
1403 tables.Append(tracking);
1404 }
1405 Preferences::GetCString(TRACKING_WHITELIST_TABLE_PREF, &trackingWhitelist);
1406 if (!trackingWhitelist.IsEmpty()) {
1407 tables.Append(',');
1408 tables.Append(trackingWhitelist);
1409 }
1410 }
1411 nsAutoCString blocked;
1412 Preferences::GetCString(BLOCKED_TABLE_PREF, &blocked);
1413 if (mCheckBlockedURIs && !blocked.IsEmpty()) {
1414 tables.Append(',');
1415 tables.Append(blocked);
1416 }
1417
1418 if (StringBeginsWith(tables, NS_LITERAL_CSTRING(","))) {
1419 tables.Cut(0, 1);
1420 }
1421 }
1422
1423 // nsChannelClassifier is the only consumer of this interface.
1424 NS_IMETHODIMP
Classify(nsIPrincipal * aPrincipal,bool aTrackingProtectionEnabled,nsIURIClassifierCallback * c,bool * result)1425 nsUrlClassifierDBService::Classify(nsIPrincipal* aPrincipal,
1426 bool aTrackingProtectionEnabled,
1427 nsIURIClassifierCallback* c,
1428 bool* result)
1429 {
1430 NS_ENSURE_ARG(aPrincipal);
1431 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1432
1433 if (!(mCheckMalware || mCheckPhishing || aTrackingProtectionEnabled ||
1434 mCheckBlockedURIs)) {
1435 *result = false;
1436 return NS_OK;
1437 }
1438
1439 RefPtr<nsUrlClassifierClassifyCallback> callback =
1440 new nsUrlClassifierClassifyCallback(c);
1441 if (!callback) return NS_ERROR_OUT_OF_MEMORY;
1442
1443 nsAutoCString tables;
1444 BuildTables(aTrackingProtectionEnabled, tables);
1445
1446 nsresult rv = LookupURI(aPrincipal, tables, callback, false, result);
1447 if (rv == NS_ERROR_MALFORMED_URI) {
1448 *result = false;
1449 // The URI had no hostname, don't try to classify it.
1450 return NS_OK;
1451 }
1452 NS_ENSURE_SUCCESS(rv, rv);
1453
1454 return NS_OK;
1455 }
1456
1457 NS_IMETHODIMP
ClassifyLocalWithTables(nsIURI * aURI,const nsACString & aTables,nsACString & aTableResults)1458 nsUrlClassifierDBService::ClassifyLocalWithTables(nsIURI *aURI,
1459 const nsACString & aTables,
1460 nsACString & aTableResults)
1461 {
1462 if (gShuttingDownThread) {
1463 return NS_ERROR_ABORT;
1464 }
1465
1466 PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
1467 MOZ_ASSERT(NS_IsMainThread(), "ClassifyLocalWithTables must be on main thread");
1468
1469 nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI);
1470 NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
1471
1472 nsAutoCString key;
1473 // Canonicalize the url
1474 nsCOMPtr<nsIUrlClassifierUtils> utilsService =
1475 do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
1476 nsresult rv = utilsService->GetKeyForURI(uri, key);
1477 NS_ENSURE_SUCCESS(rv, rv);
1478
1479 nsAutoPtr<LookupResultArray> results(new LookupResultArray());
1480 if (!results) {
1481 return NS_ERROR_OUT_OF_MEMORY;
1482 }
1483
1484 // In unittests, we may not have been initalized, so don't crash.
1485 rv = mWorkerProxy->DoLocalLookup(key, aTables, results);
1486 if (NS_SUCCEEDED(rv)) {
1487 aTableResults = ProcessLookupResults(results);
1488 }
1489 return NS_OK;
1490 }
1491
1492 NS_IMETHODIMP
Lookup(nsIPrincipal * aPrincipal,const nsACString & tables,nsIUrlClassifierCallback * c)1493 nsUrlClassifierDBService::Lookup(nsIPrincipal* aPrincipal,
1494 const nsACString& tables,
1495 nsIUrlClassifierCallback* c)
1496 {
1497 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1498
1499 bool dummy;
1500 return LookupURI(aPrincipal, tables, c, true, &dummy);
1501 }
1502
1503 nsresult
LookupURI(nsIPrincipal * aPrincipal,const nsACString & tables,nsIUrlClassifierCallback * c,bool forceLookup,bool * didLookup)1504 nsUrlClassifierDBService::LookupURI(nsIPrincipal* aPrincipal,
1505 const nsACString& tables,
1506 nsIUrlClassifierCallback* c,
1507 bool forceLookup,
1508 bool *didLookup)
1509 {
1510 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1511 NS_ENSURE_ARG(aPrincipal);
1512
1513 if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
1514 *didLookup = false;
1515 return NS_OK;
1516 }
1517
1518 if (gShuttingDownThread) {
1519 *didLookup = false;
1520 return NS_ERROR_ABORT;
1521 }
1522
1523 nsCOMPtr<nsIURI> uri;
1524 nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
1525 NS_ENSURE_SUCCESS(rv, rv);
1526 NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
1527
1528 uri = NS_GetInnermostURI(uri);
1529 NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
1530
1531 nsAutoCString key;
1532 // Canonicalize the url
1533 nsCOMPtr<nsIUrlClassifierUtils> utilsService =
1534 do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
1535 rv = utilsService->GetKeyForURI(uri, key);
1536 if (NS_FAILED(rv))
1537 return rv;
1538
1539 if (forceLookup) {
1540 *didLookup = true;
1541 } else {
1542 bool clean = false;
1543
1544 if (!clean) {
1545 nsCOMPtr<nsIPermissionManager> permissionManager =
1546 services::GetPermissionManager();
1547
1548 if (permissionManager) {
1549 uint32_t perm;
1550 rv = permissionManager->TestPermissionFromPrincipal(aPrincipal,
1551 "safe-browsing", &perm);
1552 NS_ENSURE_SUCCESS(rv, rv);
1553
1554 clean |= (perm == nsIPermissionManager::ALLOW_ACTION);
1555 }
1556 }
1557
1558 *didLookup = !clean;
1559 if (clean) {
1560 return NS_OK;
1561 }
1562 }
1563
1564 // Create an nsUrlClassifierLookupCallback object. This object will
1565 // take care of confirming partial hash matches if necessary before
1566 // calling the client's callback.
1567 nsCOMPtr<nsIUrlClassifierLookupCallback> callback =
1568 new nsUrlClassifierLookupCallback(this, c);
1569 if (!callback)
1570 return NS_ERROR_OUT_OF_MEMORY;
1571
1572 nsCOMPtr<nsIUrlClassifierLookupCallback> proxyCallback =
1573 new UrlClassifierLookupCallbackProxy(callback);
1574
1575 // Queue this lookup and call the lookup function to flush the queue if
1576 // necessary.
1577 rv = mWorker->QueueLookup(key, tables, proxyCallback);
1578 NS_ENSURE_SUCCESS(rv, rv);
1579
1580 // This seems to just call HandlePendingLookups.
1581 nsAutoCString dummy;
1582 return mWorkerProxy->Lookup(nullptr, dummy, nullptr);
1583 }
1584
1585 NS_IMETHODIMP
GetTables(nsIUrlClassifierCallback * c)1586 nsUrlClassifierDBService::GetTables(nsIUrlClassifierCallback* c)
1587 {
1588 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1589
1590 // The proxy callback uses the current thread.
1591 nsCOMPtr<nsIUrlClassifierCallback> proxyCallback =
1592 new UrlClassifierCallbackProxy(c);
1593
1594 return mWorkerProxy->GetTables(proxyCallback);
1595 }
1596
1597 NS_IMETHODIMP
SetHashCompleter(const nsACString & tableName,nsIUrlClassifierHashCompleter * completer)1598 nsUrlClassifierDBService::SetHashCompleter(const nsACString &tableName,
1599 nsIUrlClassifierHashCompleter *completer)
1600 {
1601 if (completer) {
1602 mCompleters.Put(tableName, completer);
1603 } else {
1604 mCompleters.Remove(tableName);
1605 }
1606 ClearLastResults();
1607 return NS_OK;
1608 }
1609
1610 NS_IMETHODIMP
SetLastUpdateTime(const nsACString & tableName,uint64_t lastUpdateTime)1611 nsUrlClassifierDBService::SetLastUpdateTime(const nsACString &tableName,
1612 uint64_t lastUpdateTime)
1613 {
1614 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1615
1616 return mWorkerProxy->SetLastUpdateTime(tableName, lastUpdateTime);
1617 }
1618
1619 NS_IMETHODIMP
ClearLastResults()1620 nsUrlClassifierDBService::ClearLastResults()
1621 {
1622 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1623
1624 return mWorkerProxy->ClearLastResults();
1625 }
1626
1627 NS_IMETHODIMP
BeginUpdate(nsIUrlClassifierUpdateObserver * observer,const nsACString & updateTables)1628 nsUrlClassifierDBService::BeginUpdate(nsIUrlClassifierUpdateObserver *observer,
1629 const nsACString &updateTables)
1630 {
1631 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1632
1633 if (mInUpdate) {
1634 LOG(("Already updating, not available"));
1635 return NS_ERROR_NOT_AVAILABLE;
1636 }
1637
1638 mInUpdate = true;
1639
1640 // The proxy observer uses the current thread
1641 nsCOMPtr<nsIUrlClassifierUpdateObserver> proxyObserver =
1642 new UrlClassifierUpdateObserverProxy(observer);
1643
1644 return mWorkerProxy->BeginUpdate(proxyObserver, updateTables);
1645 }
1646
1647 NS_IMETHODIMP
BeginStream(const nsACString & table)1648 nsUrlClassifierDBService::BeginStream(const nsACString &table)
1649 {
1650 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1651
1652 return mWorkerProxy->BeginStream(table);
1653 }
1654
1655 NS_IMETHODIMP
UpdateStream(const nsACString & aUpdateChunk)1656 nsUrlClassifierDBService::UpdateStream(const nsACString& aUpdateChunk)
1657 {
1658 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1659
1660 return mWorkerProxy->UpdateStream(aUpdateChunk);
1661 }
1662
1663 NS_IMETHODIMP
FinishStream()1664 nsUrlClassifierDBService::FinishStream()
1665 {
1666 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1667
1668 return mWorkerProxy->FinishStream();
1669 }
1670
1671 NS_IMETHODIMP
FinishUpdate()1672 nsUrlClassifierDBService::FinishUpdate()
1673 {
1674 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1675
1676 mInUpdate = false;
1677
1678 return mWorkerProxy->FinishUpdate();
1679 }
1680
1681
1682 NS_IMETHODIMP
CancelUpdate()1683 nsUrlClassifierDBService::CancelUpdate()
1684 {
1685 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1686
1687 mInUpdate = false;
1688
1689 return mWorkerProxy->CancelUpdate();
1690 }
1691
1692 NS_IMETHODIMP
ResetDatabase()1693 nsUrlClassifierDBService::ResetDatabase()
1694 {
1695 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1696
1697 return mWorkerProxy->ResetDatabase();
1698 }
1699
1700 NS_IMETHODIMP
ReloadDatabase()1701 nsUrlClassifierDBService::ReloadDatabase()
1702 {
1703 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1704
1705 return mWorkerProxy->ReloadDatabase();
1706 }
1707
1708 nsresult
CacheCompletions(CacheResultArray * results)1709 nsUrlClassifierDBService::CacheCompletions(CacheResultArray *results)
1710 {
1711 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1712
1713 return mWorkerProxy->CacheCompletions(results);
1714 }
1715
1716 nsresult
CacheMisses(PrefixArray * results)1717 nsUrlClassifierDBService::CacheMisses(PrefixArray *results)
1718 {
1719 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1720
1721 return mWorkerProxy->CacheMisses(results);
1722 }
1723
1724 bool
GetCompleter(const nsACString & tableName,nsIUrlClassifierHashCompleter ** completer)1725 nsUrlClassifierDBService::GetCompleter(const nsACString &tableName,
1726 nsIUrlClassifierHashCompleter **completer)
1727 {
1728 // If we have specified a completer, go ahead and query it. This is only
1729 // used by tests.
1730 if (mCompleters.Get(tableName, completer)) {
1731 return true;
1732 }
1733
1734 // If we don't know about this table at all, or are disallowing completions
1735 // for it, skip completion checks.
1736 if (!mGethashTables.Contains(tableName) ||
1737 mDisallowCompletionsTables.Contains(tableName)) {
1738 return false;
1739 }
1740
1741 // Otherwise, call gethash to find the hash completions.
1742 return NS_SUCCEEDED(CallGetService(NS_URLCLASSIFIERHASHCOMPLETER_CONTRACTID,
1743 completer));
1744 }
1745
1746 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)1747 nsUrlClassifierDBService::Observe(nsISupports *aSubject, const char *aTopic,
1748 const char16_t *aData)
1749 {
1750 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
1751 nsresult rv;
1752 nsCOMPtr<nsIPrefBranch> prefs(do_QueryInterface(aSubject, &rv));
1753 NS_ENSURE_SUCCESS(rv, rv);
1754 Unused << prefs;
1755
1756 if (NS_LITERAL_STRING(CHECK_MALWARE_PREF).Equals(aData)) {
1757 mCheckMalware = Preferences::GetBool(CHECK_MALWARE_PREF,
1758 CHECK_MALWARE_DEFAULT);
1759 } else if (NS_LITERAL_STRING(CHECK_PHISHING_PREF).Equals(aData)) {
1760 mCheckPhishing = Preferences::GetBool(CHECK_PHISHING_PREF,
1761 CHECK_PHISHING_DEFAULT);
1762 } else if (NS_LITERAL_STRING(CHECK_TRACKING_PREF).Equals(aData) ||
1763 NS_LITERAL_STRING(CHECK_TRACKING_PB_PREF).Equals(aData)) {
1764 mCheckTracking =
1765 Preferences::GetBool(CHECK_TRACKING_PREF, CHECK_TRACKING_DEFAULT) ||
1766 Preferences::GetBool(CHECK_TRACKING_PB_PREF, CHECK_TRACKING_PB_DEFAULT);
1767 } else if (NS_LITERAL_STRING(CHECK_BLOCKED_PREF).Equals(aData)) {
1768 mCheckBlockedURIs = Preferences::GetBool(CHECK_BLOCKED_PREF,
1769 CHECK_BLOCKED_DEFAULT);
1770 } else if (
1771 NS_LITERAL_STRING(PHISH_TABLE_PREF).Equals(aData) ||
1772 NS_LITERAL_STRING(MALWARE_TABLE_PREF).Equals(aData) ||
1773 NS_LITERAL_STRING(TRACKING_TABLE_PREF).Equals(aData) ||
1774 NS_LITERAL_STRING(TRACKING_WHITELIST_TABLE_PREF).Equals(aData) ||
1775 NS_LITERAL_STRING(BLOCKED_TABLE_PREF).Equals(aData) ||
1776 NS_LITERAL_STRING(DOWNLOAD_BLOCK_TABLE_PREF).Equals(aData) ||
1777 NS_LITERAL_STRING(DOWNLOAD_ALLOW_TABLE_PREF).Equals(aData) ||
1778 NS_LITERAL_STRING(DISALLOW_COMPLETION_TABLE_PREF).Equals(aData)) {
1779 // Just read everything again.
1780 ReadTablesFromPrefs();
1781 } else if (NS_LITERAL_STRING(CONFIRM_AGE_PREF).Equals(aData)) {
1782 gFreshnessGuarantee = Preferences::GetInt(CONFIRM_AGE_PREF,
1783 CONFIRM_AGE_DEFAULT_SEC);
1784 }
1785 } else if (!strcmp(aTopic, "quit-application")) {
1786 Shutdown();
1787 } else if (!strcmp(aTopic, "profile-before-change")) {
1788 // Unit test does not receive "quit-application",
1789 // need call shutdown in this case
1790 Shutdown();
1791 LOG(("joining background thread"));
1792 mWorkerProxy = nullptr;
1793
1794 if (!gDbBackgroundThread) {
1795 return NS_OK;
1796 }
1797
1798 nsIThread *backgroundThread = gDbBackgroundThread;
1799 gDbBackgroundThread = nullptr;
1800 backgroundThread->Shutdown();
1801 NS_RELEASE(backgroundThread);
1802 } else {
1803 return NS_ERROR_UNEXPECTED;
1804 }
1805
1806 return NS_OK;
1807 }
1808
1809 // Join the background thread if it exists.
1810 nsresult
Shutdown()1811 nsUrlClassifierDBService::Shutdown()
1812 {
1813 LOG(("shutting down db service\n"));
1814
1815 if (!gDbBackgroundThread || gShuttingDownThread) {
1816 return NS_OK;
1817 }
1818
1819 gShuttingDownThread = true;
1820
1821 Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_SHUTDOWN_TIME> timer;
1822
1823 mCompleters.Clear();
1824
1825 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
1826 if (prefs) {
1827 prefs->RemoveObserver(CHECK_MALWARE_PREF, this);
1828 prefs->RemoveObserver(CHECK_PHISHING_PREF, this);
1829 prefs->RemoveObserver(CHECK_TRACKING_PREF, this);
1830 prefs->RemoveObserver(CHECK_TRACKING_PB_PREF, this);
1831 prefs->RemoveObserver(CHECK_BLOCKED_PREF, this);
1832 prefs->RemoveObserver(PHISH_TABLE_PREF, this);
1833 prefs->RemoveObserver(MALWARE_TABLE_PREF, this);
1834 prefs->RemoveObserver(TRACKING_TABLE_PREF, this);
1835 prefs->RemoveObserver(TRACKING_WHITELIST_TABLE_PREF, this);
1836 prefs->RemoveObserver(BLOCKED_TABLE_PREF, this);
1837 prefs->RemoveObserver(DOWNLOAD_BLOCK_TABLE_PREF, this);
1838 prefs->RemoveObserver(DOWNLOAD_ALLOW_TABLE_PREF, this);
1839 prefs->RemoveObserver(DISALLOW_COMPLETION_TABLE_PREF, this);
1840 prefs->RemoveObserver(CONFIRM_AGE_PREF, this);
1841 }
1842
1843 DebugOnly<nsresult> rv;
1844 // First close the db connection.
1845 if (mWorker) {
1846 rv = mWorkerProxy->CancelUpdate();
1847 NS_ASSERTION(NS_SUCCEEDED(rv), "failed to post cancel update event");
1848
1849 rv = mWorkerProxy->CloseDb();
1850 NS_ASSERTION(NS_SUCCEEDED(rv), "failed to post close db event");
1851 }
1852 return NS_OK;
1853 }
1854
1855 nsIThread*
BackgroundThread()1856 nsUrlClassifierDBService::BackgroundThread()
1857 {
1858 return gDbBackgroundThread;
1859 }
1860
1861 // static
1862 bool
ShutdownHasStarted()1863 nsUrlClassifierDBService::ShutdownHasStarted()
1864 {
1865 return gShuttingDownThread;
1866 }
1867