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