Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
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/. */
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 "nsUrlClassifierDBService.h"
22 #include "nsUrlClassifierUtils.h"
23 #include "nsUrlClassifierProxies.h"
24 #include "nsURILoader.h"
25 #include "nsString.h"
26 #include "nsReadableUtils.h"
27 #include "nsTArray.h"
28 #include "nsNetUtil.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/Mutex.h"
37 #include "mozilla/Preferences.h"
38 #include "mozilla/TimeStamp.h"
39 #include "mozilla/Telemetry.h"
40 #include "prlog.h"
41 #include "prprf.h"
42 #include "prnetdb.h"
43 #include "Entries.h"
44 #include "mozilla/Attributes.h"
45 #include "nsIPrincipal.h"
46 #include "Classifier.h"
47 #include "ProtocolParser.h"
48 #include "nsContentUtils.h"
50 using namespace mozilla;
51 using namespace mozilla::safebrowsing;
53 // NSPR_LOG_MODULES=UrlClassifierDbService:5
54 #if defined(PR_LOGGING)
55 PRLogModuleInfo *gUrlClassifierDbServiceLog = nullptr;
56 #define LOG(args) PR_LOG(gUrlClassifierDbServiceLog, PR_LOG_DEBUG, args)
57 #define LOG_ENABLED() PR_LOG_TEST(gUrlClassifierDbServiceLog, 4)
58 #else
59 #define LOG(args)
60 #define LOG_ENABLED() (false)
61 #endif
63 // Prefs for implementing nsIURIClassifier to block page loads
64 #define CHECK_MALWARE_PREF "browser.safebrowsing.malware.enabled"
65 #define CHECK_MALWARE_DEFAULT false
67 #define CHECK_PHISHING_PREF "browser.safebrowsing.enabled"
68 #define CHECK_PHISHING_DEFAULT false
70 #define GETHASH_NOISE_PREF "urlclassifier.gethashnoise"
71 #define GETHASH_NOISE_DEFAULT 4
73 // Comma-separated lists
74 #define MALWARE_TABLE_PREF "urlclassifier.malware_table"
75 #define PHISH_TABLE_PREF "urlclassifier.phish_table"
76 #define DOWNLOAD_BLOCK_TABLE_PREF "urlclassifier.downloadBlockTable"
77 #define DOWNLOAD_ALLOW_TABLE_PREF "urlclassifier.downloadAllowTable"
78 #define DISALLOW_COMPLETION_TABLE_PREF "urlclassifier.disallow_completions"
80 #define CONFIRM_AGE_PREF "urlclassifier.max-complete-age"
81 #define CONFIRM_AGE_DEFAULT_SEC (45 * 60)
83 class nsUrlClassifierDBServiceWorker;
85 // Singleton instance.
86 static nsUrlClassifierDBService* sUrlClassifierDBService;
88 nsIThread* nsUrlClassifierDBService::gDbBackgroundThread = nullptr;
90 // Once we've committed to shutting down, don't do work in the background
91 // thread.
92 static bool gShuttingDownThread = false;
94 static mozilla::Atomic<int32_t> gFreshnessGuarantee(CONFIRM_AGE_DEFAULT_SEC);
96 // -------------------------------------------------------------------------
97 // Actual worker implemenatation
98 class nsUrlClassifierDBServiceWorker MOZ_FINAL :
99 public nsIUrlClassifierDBServiceWorker
100 {
101 public:
102 nsUrlClassifierDBServiceWorker();
104 NS_DECL_THREADSAFE_ISUPPORTS
105 NS_DECL_NSIURLCLASSIFIERDBSERVICE
106 NS_DECL_NSIURLCLASSIFIERDBSERVICEWORKER
108 nsresult Init(uint32_t aGethashNoise, nsCOMPtr<nsIFile> aCacheDir);
110 // Queue a lookup for the worker to perform, called in the main thread.
111 // tables is a comma-separated list of tables to query
112 nsresult QueueLookup(const nsACString& lookupKey,
113 const nsACString& tables,
114 nsIUrlClassifierLookupCallback* callback);
116 // Handle any queued-up lookups. We call this function during long-running
117 // update operations to prevent lookups from blocking for too long.
118 nsresult HandlePendingLookups();
120 private:
121 // No subclassing
122 ~nsUrlClassifierDBServiceWorker();
124 // Disallow copy constructor
125 nsUrlClassifierDBServiceWorker(nsUrlClassifierDBServiceWorker&);
127 nsresult OpenDb();
129 // Applies the current transaction and resets the update/working times.
130 nsresult ApplyUpdate();
132 // Reset the in-progress update stream
133 void ResetStream();
135 // Reset the in-progress update
136 void ResetUpdate();
138 // Perform a classifier lookup for a given url.
139 nsresult DoLookup(const nsACString& spec,
140 const nsACString& tables,
141 nsIUrlClassifierLookupCallback* c);
143 nsresult AddNoise(const Prefix aPrefix,
144 const nsCString tableName,
145 uint32_t aCount,
146 LookupResultArray& results);
148 nsCOMPtr<nsICryptoHash> mCryptoHash;
150 nsAutoPtr<Classifier> mClassifier;
151 // The class that actually parses the update chunks.
152 nsAutoPtr<ProtocolParser> mProtocolParser;
154 // Directory where to store the SB databases.
155 nsCOMPtr<nsIFile> mCacheDir;
157 // XXX: maybe an array of autoptrs. Or maybe a class specifically
158 // storing a series of updates.
159 nsTArray<TableUpdate*> mTableUpdates;
161 int32_t mUpdateWait;
163 // Entries that cannot be completed. We expect them to die at
164 // the next update
165 PrefixArray mMissCache;
167 nsresult mUpdateStatus;
168 nsTArray<nsCString> mUpdateTables;
170 nsCOMPtr<nsIUrlClassifierUpdateObserver> mUpdateObserver;
171 bool mInStream;
173 // The number of noise entries to add to the set of lookup results.
174 uint32_t mGethashNoise;
176 // Pending lookups are stored in a queue for processing. The queue
177 // is protected by mPendingLookupLock.
178 Mutex mPendingLookupLock;
180 class PendingLookup {
181 public:
182 TimeStamp mStartTime;
183 nsCString mKey;
184 nsCString mTables;
185 nsCOMPtr<nsIUrlClassifierLookupCallback> mCallback;
186 };
188 // list of pending lookups
189 nsTArray<PendingLookup> mPendingLookups;
190 };
192 NS_IMPL_ISUPPORTS(nsUrlClassifierDBServiceWorker,
193 nsIUrlClassifierDBServiceWorker,
194 nsIUrlClassifierDBService)
196 nsUrlClassifierDBServiceWorker::nsUrlClassifierDBServiceWorker()
197 : mInStream(false)
198 , mGethashNoise(0)
199 , mPendingLookupLock("nsUrlClassifierDBServerWorker.mPendingLookupLock")
200 {
201 }
203 nsUrlClassifierDBServiceWorker::~nsUrlClassifierDBServiceWorker()
204 {
205 NS_ASSERTION(!mClassifier,
206 "Db connection not closed, leaking memory! Call CloseDb "
207 "to close the connection.");
208 }
210 nsresult
211 nsUrlClassifierDBServiceWorker::Init(uint32_t aGethashNoise,
212 nsCOMPtr<nsIFile> aCacheDir)
213 {
214 mGethashNoise = aGethashNoise;
215 mCacheDir = aCacheDir;
217 ResetUpdate();
219 return NS_OK;
220 }
222 nsresult
223 nsUrlClassifierDBServiceWorker::QueueLookup(const nsACString& spec,
224 const nsACString& tables,
225 nsIUrlClassifierLookupCallback* callback)
226 {
227 MutexAutoLock lock(mPendingLookupLock);
229 PendingLookup* lookup = mPendingLookups.AppendElement();
230 if (!lookup) return NS_ERROR_OUT_OF_MEMORY;
232 lookup->mStartTime = TimeStamp::Now();
233 lookup->mKey = spec;
234 lookup->mCallback = callback;
235 lookup->mTables = tables;
237 return NS_OK;
238 }
240 /**
241 * Lookup up a key in the database is a two step process:
242 *
243 * a) First we look for any Entries in the database that might apply to this
244 * url. For each URL there are one or two possible domain names to check:
245 * the two-part domain name (example.com) and the three-part name
246 * (www.example.com). We check the database for both of these.
247 * b) If we find any entries, we check the list of fragments for that entry
248 * against the possible subfragments of the URL as described in the
249 * "Simplified Regular Expression Lookup" section of the protocol doc.
250 */
251 nsresult
252 nsUrlClassifierDBServiceWorker::DoLookup(const nsACString& spec,
253 const nsACString& tables,
254 nsIUrlClassifierLookupCallback* c)
255 {
256 if (gShuttingDownThread) {
257 c->LookupComplete(nullptr);
258 return NS_ERROR_NOT_INITIALIZED;
259 }
261 nsresult rv = OpenDb();
262 if (NS_FAILED(rv)) {
263 c->LookupComplete(nullptr);
264 NS_ERROR("Unable to open SafeBrowsing database.");
265 return NS_ERROR_FAILURE;
266 }
268 #if defined(PR_LOGGING)
269 PRIntervalTime clockStart = 0;
270 if (LOG_ENABLED()) {
271 clockStart = PR_IntervalNow();
272 }
273 #endif
275 nsAutoPtr<LookupResultArray> results(new LookupResultArray());
276 if (!results) {
277 c->LookupComplete(nullptr);
278 return NS_ERROR_OUT_OF_MEMORY;
279 }
281 // we ignore failures from Check because we'd rather return the
282 // results that were found than fail.
283 mClassifier->SetFreshTime(gFreshnessGuarantee);
284 mClassifier->Check(spec, tables, *results);
286 LOG(("Found %d results.", results->Length()));
289 #if defined(PR_LOGGING)
290 if (LOG_ENABLED()) {
291 PRIntervalTime clockEnd = PR_IntervalNow();
292 LOG(("query took %dms\n",
293 PR_IntervalToMilliseconds(clockEnd - clockStart)));
294 }
295 #endif
297 nsAutoPtr<LookupResultArray> completes(new LookupResultArray());
299 for (uint32_t i = 0; i < results->Length(); i++) {
300 if (!mMissCache.Contains(results->ElementAt(i).hash.prefix)) {
301 completes->AppendElement(results->ElementAt(i));
302 }
303 }
305 for (uint32_t i = 0; i < completes->Length(); i++) {
306 if (!completes->ElementAt(i).Confirmed()) {
307 // We're going to be doing a gethash request, add some extra entries.
308 // Note that we cannot pass the first two by reference, because we
309 // add to completes, whicah can cause completes to reallocate and move.
310 AddNoise(completes->ElementAt(i).hash.prefix,
311 completes->ElementAt(i).mTableName,
312 mGethashNoise, *completes);
313 break;
314 }
315 }
317 // At this point ownership of 'results' is handed to the callback.
318 c->LookupComplete(completes.forget());
320 return NS_OK;
321 }
323 nsresult
324 nsUrlClassifierDBServiceWorker::HandlePendingLookups()
325 {
326 MutexAutoLock lock(mPendingLookupLock);
327 while (mPendingLookups.Length() > 0) {
328 PendingLookup lookup = mPendingLookups[0];
329 mPendingLookups.RemoveElementAt(0);
330 {
331 MutexAutoUnlock unlock(mPendingLookupLock);
332 DoLookup(lookup.mKey, lookup.mTables, lookup.mCallback);
333 }
334 double lookupTime = (TimeStamp::Now() - lookup.mStartTime).ToMilliseconds();
335 Telemetry::Accumulate(Telemetry::URLCLASSIFIER_LOOKUP_TIME,
336 static_cast<uint32_t>(lookupTime));
337 }
339 return NS_OK;
340 }
342 nsresult
343 nsUrlClassifierDBServiceWorker::AddNoise(const Prefix aPrefix,
344 const nsCString tableName,
345 uint32_t aCount,
346 LookupResultArray& results)
347 {
348 if (aCount < 1) {
349 return NS_OK;
350 }
352 PrefixArray noiseEntries;
353 nsresult rv = mClassifier->ReadNoiseEntries(aPrefix, tableName,
354 aCount, &noiseEntries);
355 NS_ENSURE_SUCCESS(rv, rv);
357 for (uint32_t i = 0; i < noiseEntries.Length(); i++) {
358 LookupResult *result = results.AppendElement();
359 if (!result)
360 return NS_ERROR_OUT_OF_MEMORY;
362 result->hash.prefix = noiseEntries[i];
363 result->mNoise = true;
365 result->mTableName.Assign(tableName);
366 }
368 return NS_OK;
369 }
371 // Lookup a key in the db.
372 NS_IMETHODIMP
373 nsUrlClassifierDBServiceWorker::Lookup(nsIPrincipal* aPrincipal,
374 const nsACString& aTables,
375 nsIUrlClassifierCallback* c)
376 {
377 return HandlePendingLookups();
378 }
380 NS_IMETHODIMP
381 nsUrlClassifierDBServiceWorker::GetTables(nsIUrlClassifierCallback* c)
382 {
383 if (gShuttingDownThread)
384 return NS_ERROR_NOT_INITIALIZED;
386 nsresult rv = OpenDb();
387 if (NS_FAILED(rv)) {
388 NS_ERROR("Unable to open SafeBrowsing database");
389 return NS_ERROR_FAILURE;
390 }
392 NS_ENSURE_SUCCESS(rv, rv);
394 nsAutoCString response;
395 mClassifier->TableRequest(response);
396 c->HandleEvent(response);
398 return rv;
399 }
401 void
402 nsUrlClassifierDBServiceWorker::ResetStream()
403 {
404 LOG(("ResetStream"));
405 mInStream = false;
406 mProtocolParser = nullptr;
407 }
409 void
410 nsUrlClassifierDBServiceWorker::ResetUpdate()
411 {
412 LOG(("ResetUpdate"));
413 mUpdateWait = 0;
414 mUpdateStatus = NS_OK;
415 mUpdateObserver = nullptr;
416 }
418 NS_IMETHODIMP
419 nsUrlClassifierDBServiceWorker::SetHashCompleter(const nsACString &tableName,
420 nsIUrlClassifierHashCompleter *completer)
421 {
422 return NS_ERROR_NOT_IMPLEMENTED;
423 }
425 NS_IMETHODIMP
426 nsUrlClassifierDBServiceWorker::BeginUpdate(nsIUrlClassifierUpdateObserver *observer,
427 const nsACString &tables)
428 {
429 LOG(("nsUrlClassifierDBServiceWorker::BeginUpdate [%s]", PromiseFlatCString(tables).get()));
431 if (gShuttingDownThread)
432 return NS_ERROR_NOT_INITIALIZED;
434 NS_ENSURE_STATE(!mUpdateObserver);
436 nsresult rv = OpenDb();
437 if (NS_FAILED(rv)) {
438 NS_ERROR("Unable to open SafeBrowsing database");
439 return NS_ERROR_FAILURE;
440 }
442 mUpdateStatus = NS_OK;
443 mUpdateObserver = observer;
444 Classifier::SplitTables(tables, mUpdateTables);
446 return NS_OK;
447 }
449 // Called from the stream updater.
450 NS_IMETHODIMP
451 nsUrlClassifierDBServiceWorker::BeginStream(const nsACString &table)
452 {
453 LOG(("nsUrlClassifierDBServiceWorker::BeginStream"));
455 if (gShuttingDownThread)
456 return NS_ERROR_NOT_INITIALIZED;
458 NS_ENSURE_STATE(mUpdateObserver);
459 NS_ENSURE_STATE(!mInStream);
461 mInStream = true;
463 NS_ASSERTION(!mProtocolParser, "Should not have a protocol parser.");
465 mProtocolParser = new ProtocolParser();
466 if (!mProtocolParser)
467 return NS_ERROR_OUT_OF_MEMORY;
469 mProtocolParser->Init(mCryptoHash);
471 if (!table.IsEmpty()) {
472 mProtocolParser->SetCurrentTable(table);
473 }
475 return NS_OK;
476 }
478 /**
479 * Updating the database:
480 *
481 * The Update() method takes a series of chunks separated with control data,
482 * as described in
483 * http://code.google.com/p/google-safe-browsing/wiki/Protocolv2Spec
484 *
485 * It will iterate through the control data until it reaches a chunk. By
486 * the time it reaches a chunk, it should have received
487 * a) the table to which this chunk applies
488 * b) the type of chunk (add, delete, expire add, expire delete).
489 * c) the chunk ID
490 * d) the length of the chunk.
491 *
492 * For add and subtract chunks, it needs to read the chunk data (expires
493 * don't have any data). Chunk data is a list of URI fragments whose
494 * encoding depends on the type of table (which is indicated by the end
495 * of the table name):
496 * a) tables ending with -exp are a zlib-compressed list of URI fragments
497 * separated by newlines.
498 * b) tables ending with -sha128 have the form
499 * [domain][N][frag0]...[fragN]
500 * 16 1 16 16
501 * If N is 0, the domain is reused as a fragment.
502 * c) any other tables are assumed to be a plaintext list of URI fragments
503 * separated by newlines.
504 *
505 * Update() can be fed partial data; It will accumulate data until there is
506 * enough to act on. Finish() should be called when there will be no more
507 * data.
508 */
509 NS_IMETHODIMP
510 nsUrlClassifierDBServiceWorker::UpdateStream(const nsACString& chunk)
511 {
512 if (gShuttingDownThread)
513 return NS_ERROR_NOT_INITIALIZED;
515 NS_ENSURE_STATE(mInStream);
517 HandlePendingLookups();
519 // Feed the chunk to the parser.
520 return mProtocolParser->AppendStream(chunk);
521 }
523 NS_IMETHODIMP
524 nsUrlClassifierDBServiceWorker::FinishStream()
525 {
526 if (gShuttingDownThread)
527 return NS_ERROR_NOT_INITIALIZED;
529 NS_ENSURE_STATE(mInStream);
530 NS_ENSURE_STATE(mUpdateObserver);
532 mInStream = false;
534 if (NS_SUCCEEDED(mProtocolParser->Status())) {
535 if (mProtocolParser->UpdateWait()) {
536 mUpdateWait = mProtocolParser->UpdateWait();
537 }
538 // XXX: Only allow forwards from the initial update?
539 const nsTArray<ProtocolParser::ForwardedUpdate> &forwards =
540 mProtocolParser->Forwards();
541 for (uint32_t i = 0; i < forwards.Length(); i++) {
542 const ProtocolParser::ForwardedUpdate &forward = forwards[i];
543 mUpdateObserver->UpdateUrlRequested(forward.url, forward.table);
544 }
545 // Hold on to any TableUpdate objects that were created by the
546 // parser.
547 mTableUpdates.AppendElements(mProtocolParser->GetTableUpdates());
548 mProtocolParser->ForgetTableUpdates();
549 } else {
550 mUpdateStatus = mProtocolParser->Status();
551 }
552 mUpdateObserver->StreamFinished(mProtocolParser->Status(), 0);
554 if (NS_SUCCEEDED(mUpdateStatus)) {
555 if (mProtocolParser->ResetRequested()) {
556 mClassifier->Reset();
557 }
558 }
560 mProtocolParser = nullptr;
562 return NS_OK;
563 }
565 NS_IMETHODIMP
566 nsUrlClassifierDBServiceWorker::FinishUpdate()
567 {
568 if (gShuttingDownThread)
569 return NS_ERROR_NOT_INITIALIZED;
570 NS_ENSURE_STATE(mUpdateObserver);
572 if (NS_SUCCEEDED(mUpdateStatus)) {
573 mUpdateStatus = ApplyUpdate();
574 }
576 mMissCache.Clear();
578 if (NS_SUCCEEDED(mUpdateStatus)) {
579 LOG(("Notifying success: %d", mUpdateWait));
580 mUpdateObserver->UpdateSuccess(mUpdateWait);
581 } else {
582 LOG(("Notifying error: %d", mUpdateStatus));
583 mUpdateObserver->UpdateError(mUpdateStatus);
584 /*
585 * mark the tables as spoiled, we don't want to block hosts
586 * longer than normal because our update failed
587 */
588 mClassifier->MarkSpoiled(mUpdateTables);
589 }
590 mUpdateObserver = nullptr;
592 return NS_OK;
593 }
595 nsresult
596 nsUrlClassifierDBServiceWorker::ApplyUpdate()
597 {
598 LOG(("nsUrlClassifierDBServiceWorker::ApplyUpdate()"));
599 return mClassifier->ApplyUpdates(&mTableUpdates);
600 }
602 NS_IMETHODIMP
603 nsUrlClassifierDBServiceWorker::ResetDatabase()
604 {
605 nsresult rv = OpenDb();
607 if (NS_SUCCEEDED(rv)) {
608 mClassifier->Reset();
609 }
611 rv = CloseDb();
612 NS_ENSURE_SUCCESS(rv, rv);
614 return NS_OK;
615 }
617 NS_IMETHODIMP
618 nsUrlClassifierDBServiceWorker::CancelUpdate()
619 {
620 LOG(("nsUrlClassifierDBServiceWorker::CancelUpdate"));
622 if (mUpdateObserver) {
623 LOG(("UpdateObserver exists, cancelling"));
625 mUpdateStatus = NS_BINDING_ABORTED;
627 mUpdateObserver->UpdateError(mUpdateStatus);
629 /*
630 * mark the tables as spoiled, we don't want to block hosts
631 * longer than normal because our update failed
632 */
633 mClassifier->MarkSpoiled(mUpdateTables);
635 ResetStream();
636 ResetUpdate();
637 } else {
638 LOG(("No UpdateObserver, nothing to cancel"));
639 }
641 return NS_OK;
642 }
644 // Allows the main thread to delete the connection which may be in
645 // a background thread.
646 // XXX This could be turned into a single shutdown event so the logic
647 // is simpler in nsUrlClassifierDBService::Shutdown.
648 NS_IMETHODIMP
649 nsUrlClassifierDBServiceWorker::CloseDb()
650 {
651 if (mClassifier) {
652 mClassifier->Close();
653 mClassifier = nullptr;
654 }
656 mCryptoHash = nullptr;
657 LOG(("urlclassifier db closed\n"));
659 return NS_OK;
660 }
662 NS_IMETHODIMP
663 nsUrlClassifierDBServiceWorker::CacheCompletions(CacheResultArray *results)
664 {
665 LOG(("nsUrlClassifierDBServiceWorker::CacheCompletions [%p]", this));
666 if (!mClassifier)
667 return NS_OK;
669 // Ownership is transferred in to us
670 nsAutoPtr<CacheResultArray> resultsPtr(results);
672 nsAutoPtr<ProtocolParser> pParse(new ProtocolParser());
673 nsTArray<TableUpdate*> updates;
675 // Only cache results for tables that we have, don't take
676 // in tables we might accidentally have hit during a completion.
677 // This happens due to goog vs googpub lists existing.
678 nsTArray<nsCString> tables;
679 nsresult rv = mClassifier->ActiveTables(tables);
680 NS_ENSURE_SUCCESS(rv, rv);
682 for (uint32_t i = 0; i < resultsPtr->Length(); i++) {
683 bool activeTable = false;
684 for (uint32_t table = 0; table < tables.Length(); table++) {
685 if (tables[table].Equals(resultsPtr->ElementAt(i).table)) {
686 activeTable = true;
687 break;
688 }
689 }
690 if (activeTable) {
691 TableUpdate * tu = pParse->GetTableUpdate(resultsPtr->ElementAt(i).table);
692 LOG(("CacheCompletion Addchunk %d hash %X", resultsPtr->ElementAt(i).entry.addChunk,
693 resultsPtr->ElementAt(i).entry.ToUint32()));
694 tu->NewAddComplete(resultsPtr->ElementAt(i).entry.addChunk,
695 resultsPtr->ElementAt(i).entry.complete);
696 tu->NewAddChunk(resultsPtr->ElementAt(i).entry.addChunk);
697 tu->SetLocalUpdate();
698 updates.AppendElement(tu);
699 pParse->ForgetTableUpdates();
700 } else {
701 LOG(("Completion received, but table is not active, so not caching."));
702 }
703 }
705 mClassifier->ApplyUpdates(&updates);
706 return NS_OK;
707 }
709 NS_IMETHODIMP
710 nsUrlClassifierDBServiceWorker::CacheMisses(PrefixArray *results)
711 {
712 LOG(("nsUrlClassifierDBServiceWorker::CacheMisses [%p] %d",
713 this, results->Length()));
715 // Ownership is transferred in to us
716 nsAutoPtr<PrefixArray> resultsPtr(results);
718 for (uint32_t i = 0; i < resultsPtr->Length(); i++) {
719 mMissCache.AppendElement(resultsPtr->ElementAt(i));
720 }
721 return NS_OK;
722 }
724 nsresult
725 nsUrlClassifierDBServiceWorker::OpenDb()
726 {
727 // Connection already open, don't do anything.
728 if (mClassifier) {
729 return NS_OK;
730 }
732 LOG(("Opening db"));
734 nsresult rv;
735 mCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
736 NS_ENSURE_SUCCESS(rv, rv);
738 nsAutoPtr<Classifier> classifier(new Classifier());
739 if (!classifier) {
740 return NS_ERROR_OUT_OF_MEMORY;
741 }
743 classifier->SetFreshTime(gFreshnessGuarantee);
745 rv = classifier->Open(*mCacheDir);
746 NS_ENSURE_SUCCESS(rv, rv);
748 mClassifier = classifier;
750 return NS_OK;
751 }
753 // -------------------------------------------------------------------------
754 // nsUrlClassifierLookupCallback
755 //
756 // This class takes the results of a lookup found on the worker thread
757 // and handles any necessary partial hash expansions before calling
758 // the client callback.
760 class nsUrlClassifierLookupCallback MOZ_FINAL : public nsIUrlClassifierLookupCallback
761 , public nsIUrlClassifierHashCompleterCallback
762 {
763 public:
764 NS_DECL_THREADSAFE_ISUPPORTS
765 NS_DECL_NSIURLCLASSIFIERLOOKUPCALLBACK
766 NS_DECL_NSIURLCLASSIFIERHASHCOMPLETERCALLBACK
768 nsUrlClassifierLookupCallback(nsUrlClassifierDBService *dbservice,
769 nsIUrlClassifierCallback *c)
770 : mDBService(dbservice)
771 , mResults(nullptr)
772 , mPendingCompletions(0)
773 , mCallback(c)
774 {}
776 ~nsUrlClassifierLookupCallback();
778 private:
779 nsresult HandleResults();
781 nsRefPtr<nsUrlClassifierDBService> mDBService;
782 nsAutoPtr<LookupResultArray> mResults;
784 // Completed results to send back to the worker for caching.
785 nsAutoPtr<CacheResultArray> mCacheResults;
787 uint32_t mPendingCompletions;
788 nsCOMPtr<nsIUrlClassifierCallback> mCallback;
789 };
791 NS_IMPL_ISUPPORTS(nsUrlClassifierLookupCallback,
792 nsIUrlClassifierLookupCallback,
793 nsIUrlClassifierHashCompleterCallback)
795 nsUrlClassifierLookupCallback::~nsUrlClassifierLookupCallback()
796 {
797 nsCOMPtr<nsIThread> thread;
798 (void)NS_GetMainThread(getter_AddRefs(thread));
800 if (mCallback) {
801 (void)NS_ProxyRelease(thread, mCallback, false);
802 }
803 }
805 NS_IMETHODIMP
806 nsUrlClassifierLookupCallback::LookupComplete(nsTArray<LookupResult>* results)
807 {
808 NS_ASSERTION(mResults == nullptr,
809 "Should only get one set of results per nsUrlClassifierLookupCallback!");
811 if (!results) {
812 HandleResults();
813 return NS_OK;
814 }
816 mResults = results;
818 // Check the results entries that need to be completed.
819 for (uint32_t i = 0; i < results->Length(); i++) {
820 LookupResult& result = results->ElementAt(i);
822 // We will complete partial matches and matches that are stale.
823 if (!result.Confirmed()) {
824 nsCOMPtr<nsIUrlClassifierHashCompleter> completer;
825 if (mDBService->GetCompleter(result.mTableName,
826 getter_AddRefs(completer))) {
827 nsAutoCString partialHash;
828 partialHash.Assign(reinterpret_cast<char*>(&result.hash.prefix),
829 PREFIX_SIZE);
831 nsresult rv = completer->Complete(partialHash, this);
832 if (NS_SUCCEEDED(rv)) {
833 mPendingCompletions++;
834 }
835 } else {
836 // For tables with no hash completer, a complete hash match is
837 // good enough, we'll consider it fresh.
838 if (result.Complete()) {
839 result.mFresh = true;
840 } else {
841 NS_WARNING("Partial match in a table without a valid completer, ignoring partial match.");
842 }
843 }
844 }
845 }
847 if (mPendingCompletions == 0) {
848 // All results were complete, we're ready!
849 HandleResults();
850 }
852 return NS_OK;
853 }
855 NS_IMETHODIMP
856 nsUrlClassifierLookupCallback::CompletionFinished(nsresult status)
857 {
858 LOG(("nsUrlClassifierLookupCallback::CompletionFinished [%p, %08x]",
859 this, status));
860 if (NS_FAILED(status)) {
861 NS_WARNING("gethash response failed.");
862 }
864 mPendingCompletions--;
865 if (mPendingCompletions == 0) {
866 HandleResults();
867 }
869 return NS_OK;
870 }
872 NS_IMETHODIMP
873 nsUrlClassifierLookupCallback::Completion(const nsACString& completeHash,
874 const nsACString& tableName,
875 uint32_t chunkId)
876 {
877 LOG(("nsUrlClassifierLookupCallback::Completion [%p, %s, %d]",
878 this, PromiseFlatCString(tableName).get(), chunkId));
879 mozilla::safebrowsing::Completion hash;
880 hash.Assign(completeHash);
882 // Send this completion to the store for caching.
883 if (!mCacheResults) {
884 mCacheResults = new CacheResultArray();
885 if (!mCacheResults)
886 return NS_ERROR_OUT_OF_MEMORY;
887 }
889 CacheResult result;
890 result.entry.addChunk = chunkId;
891 result.entry.complete = hash;
892 result.table = tableName;
894 // OK if this fails, we just won't cache the item.
895 mCacheResults->AppendElement(result);
897 // Check if this matched any of our results.
898 for (uint32_t i = 0; i < mResults->Length(); i++) {
899 LookupResult& result = mResults->ElementAt(i);
901 // Now, see if it verifies a lookup
902 if (result.CompleteHash() == hash && result.mTableName.Equals(tableName)) {
903 result.mProtocolConfirmed = true;
904 }
905 }
907 return NS_OK;
908 }
910 nsresult
911 nsUrlClassifierLookupCallback::HandleResults()
912 {
913 if (!mResults) {
914 // No results, this URI is clean.
915 return mCallback->HandleEvent(NS_LITERAL_CSTRING(""));
916 }
918 nsTArray<nsCString> tables;
919 // Build a stringified list of result tables.
920 for (uint32_t i = 0; i < mResults->Length(); i++) {
921 LookupResult& result = mResults->ElementAt(i);
923 // Leave out results that weren't confirmed, as their existence on
924 // the list can't be verified. Also leave out randomly-generated
925 // noise.
926 if (!result.Confirmed() || result.mNoise) {
927 LOG(("Skipping result from table %s", result.mTableName.get()));
928 continue;
929 }
931 LOG(("Confirmed result from table %s", result.mTableName.get()));
933 if (tables.IndexOf(result.mTableName) == nsTArray<nsCString>::NoIndex) {
934 tables.AppendElement(result.mTableName);
935 }
936 }
938 // Some parts of this gethash request generated no hits at all.
939 // Prefixes must have been removed from the database since our last update.
940 // Save the prefixes we checked to prevent repeated requests
941 // until the next update.
942 nsAutoPtr<PrefixArray> cacheMisses(new PrefixArray());
943 if (cacheMisses) {
944 for (uint32_t i = 0; i < mResults->Length(); i++) {
945 LookupResult &result = mResults->ElementAt(i);
946 if (!result.Confirmed() && !result.mNoise) {
947 cacheMisses->AppendElement(result.PrefixHash());
948 }
949 }
950 // Hands ownership of the miss array back to the worker thread.
951 mDBService->CacheMisses(cacheMisses.forget());
952 }
954 if (mCacheResults) {
955 // This hands ownership of the cache results array back to the worker
956 // thread.
957 mDBService->CacheCompletions(mCacheResults.forget());
958 }
960 nsAutoCString tableStr;
961 for (uint32_t i = 0; i < tables.Length(); i++) {
962 if (i != 0)
963 tableStr.Append(',');
964 tableStr.Append(tables[i]);
965 }
967 return mCallback->HandleEvent(tableStr);
968 }
971 // -------------------------------------------------------------------------
972 // Helper class for nsIURIClassifier implementation, translates table names
973 // to nsIURIClassifier enums.
975 class nsUrlClassifierClassifyCallback MOZ_FINAL : public nsIUrlClassifierCallback
976 {
977 public:
978 NS_DECL_THREADSAFE_ISUPPORTS
979 NS_DECL_NSIURLCLASSIFIERCALLBACK
981 nsUrlClassifierClassifyCallback(nsIURIClassifierCallback *c,
982 bool checkMalware,
983 bool checkPhishing)
984 : mCallback(c)
985 , mCheckMalware(checkMalware)
986 , mCheckPhishing(checkPhishing)
987 {}
989 private:
990 nsCOMPtr<nsIURIClassifierCallback> mCallback;
991 bool mCheckMalware;
992 bool mCheckPhishing;
993 };
995 NS_IMPL_ISUPPORTS(nsUrlClassifierClassifyCallback,
996 nsIUrlClassifierCallback)
998 NS_IMETHODIMP
999 nsUrlClassifierClassifyCallback::HandleEvent(const nsACString& tables)
1000 {
1001 // XXX: we should probably have the wardens tell the service which table
1002 // names match with which classification. For now the table names give
1003 // enough information.
1004 nsresult response = NS_OK;
1006 nsACString::const_iterator begin, end;
1008 tables.BeginReading(begin);
1009 tables.EndReading(end);
1010 if (mCheckMalware &&
1011 FindInReadable(NS_LITERAL_CSTRING("-malware-"), begin, end)) {
1012 response = NS_ERROR_MALWARE_URI;
1013 } else {
1014 // Reset begin before checking phishing table
1015 tables.BeginReading(begin);
1017 if (mCheckPhishing &&
1018 FindInReadable(NS_LITERAL_CSTRING("-phish-"), begin, end)) {
1019 response = NS_ERROR_PHISHING_URI;
1020 }
1021 }
1023 mCallback->OnClassifyComplete(response);
1025 return NS_OK;
1026 }
1029 // -------------------------------------------------------------------------
1030 // Proxy class implementation
1032 NS_IMPL_ISUPPORTS(nsUrlClassifierDBService,
1033 nsIUrlClassifierDBService,
1034 nsIURIClassifier,
1035 nsIObserver)
1037 /* static */ nsUrlClassifierDBService*
1038 nsUrlClassifierDBService::GetInstance(nsresult *result)
1039 {
1040 *result = NS_OK;
1041 if (!sUrlClassifierDBService) {
1042 sUrlClassifierDBService = new nsUrlClassifierDBService();
1043 if (!sUrlClassifierDBService) {
1044 *result = NS_ERROR_OUT_OF_MEMORY;
1045 return nullptr;
1046 }
1048 NS_ADDREF(sUrlClassifierDBService); // addref the global
1050 *result = sUrlClassifierDBService->Init();
1051 if (NS_FAILED(*result)) {
1052 NS_RELEASE(sUrlClassifierDBService);
1053 return nullptr;
1054 }
1055 } else {
1056 // Already exists, just add a ref
1057 NS_ADDREF(sUrlClassifierDBService); // addref the return result
1058 }
1059 return sUrlClassifierDBService;
1060 }
1063 nsUrlClassifierDBService::nsUrlClassifierDBService()
1064 : mCheckMalware(CHECK_MALWARE_DEFAULT)
1065 , mCheckPhishing(CHECK_PHISHING_DEFAULT)
1066 , mInUpdate(false)
1067 {
1068 }
1070 nsUrlClassifierDBService::~nsUrlClassifierDBService()
1071 {
1072 sUrlClassifierDBService = nullptr;
1073 }
1075 nsresult
1076 nsUrlClassifierDBService::ReadTablesFromPrefs()
1077 {
1078 nsCString allTables;
1079 nsCString tables;
1080 Preferences::GetCString(PHISH_TABLE_PREF, &allTables);
1082 Preferences::GetCString(MALWARE_TABLE_PREF, &tables);
1083 if (!tables.IsEmpty()) {
1084 allTables.Append(',');
1085 allTables.Append(tables);
1086 }
1088 Preferences::GetCString(DOWNLOAD_BLOCK_TABLE_PREF, &tables);
1089 if (!tables.IsEmpty()) {
1090 allTables.Append(',');
1091 allTables.Append(tables);
1092 }
1094 Preferences::GetCString(DOWNLOAD_ALLOW_TABLE_PREF, &tables);
1095 if (!tables.IsEmpty()) {
1096 allTables.Append(',');
1097 allTables.Append(tables);
1098 }
1100 Classifier::SplitTables(allTables, mGethashTables);
1102 Preferences::GetCString(DISALLOW_COMPLETION_TABLE_PREF, &tables);
1103 Classifier::SplitTables(tables, mDisallowCompletionsTables);
1105 return NS_OK;
1106 }
1108 nsresult
1109 nsUrlClassifierDBService::Init()
1110 {
1111 #if defined(PR_LOGGING)
1112 if (!gUrlClassifierDbServiceLog)
1113 gUrlClassifierDbServiceLog = PR_NewLogModule("UrlClassifierDbService");
1114 #endif
1116 // Retrieve all the preferences.
1117 mCheckMalware = Preferences::GetBool(CHECK_MALWARE_PREF,
1118 CHECK_MALWARE_DEFAULT);
1119 mCheckPhishing = Preferences::GetBool(CHECK_PHISHING_PREF,
1120 CHECK_PHISHING_DEFAULT);
1121 uint32_t gethashNoise = Preferences::GetUint(GETHASH_NOISE_PREF,
1122 GETHASH_NOISE_DEFAULT);
1123 gFreshnessGuarantee = Preferences::GetInt(CONFIRM_AGE_PREF,
1124 CONFIRM_AGE_DEFAULT_SEC);
1125 ReadTablesFromPrefs();
1127 // Do we *really* need to be able to change all of these at runtime?
1128 Preferences::AddStrongObserver(this, CHECK_MALWARE_PREF);
1129 Preferences::AddStrongObserver(this, CHECK_PHISHING_PREF);
1130 Preferences::AddStrongObserver(this, GETHASH_NOISE_PREF);
1131 Preferences::AddStrongObserver(this, CONFIRM_AGE_PREF);
1132 Preferences::AddStrongObserver(this, PHISH_TABLE_PREF);
1133 Preferences::AddStrongObserver(this, MALWARE_TABLE_PREF);
1134 Preferences::AddStrongObserver(this, DOWNLOAD_BLOCK_TABLE_PREF);
1135 Preferences::AddStrongObserver(this, DOWNLOAD_ALLOW_TABLE_PREF);
1136 Preferences::AddStrongObserver(this, DISALLOW_COMPLETION_TABLE_PREF);
1138 // Force PSM loading on main thread
1139 nsresult rv;
1140 nsCOMPtr<nsICryptoHash> acryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
1141 NS_ENSURE_SUCCESS(rv, rv);
1143 // Directory providers must also be accessed on the main thread.
1144 nsCOMPtr<nsIFile> cacheDir;
1145 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
1146 getter_AddRefs(cacheDir));
1147 if (NS_FAILED(rv)) {
1148 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1149 getter_AddRefs(cacheDir));
1150 }
1152 // Start the background thread.
1153 rv = NS_NewNamedThread("URL Classifier", &gDbBackgroundThread);
1154 if (NS_FAILED(rv))
1155 return rv;
1157 mWorker = new nsUrlClassifierDBServiceWorker();
1158 if (!mWorker)
1159 return NS_ERROR_OUT_OF_MEMORY;
1161 rv = mWorker->Init(gethashNoise, cacheDir);
1162 if (NS_FAILED(rv)) {
1163 mWorker = nullptr;
1164 return rv;
1165 }
1167 // Proxy for calling the worker on the background thread
1168 mWorkerProxy = new UrlClassifierDBServiceWorkerProxy(mWorker);
1170 // Add an observer for shutdown
1171 nsCOMPtr<nsIObserverService> observerService =
1172 mozilla::services::GetObserverService();
1173 if (!observerService)
1174 return NS_ERROR_FAILURE;
1176 observerService->AddObserver(this, "profile-before-change", false);
1177 observerService->AddObserver(this, "xpcom-shutdown-threads", false);
1179 return NS_OK;
1180 }
1182 // nsChannelClassifier is the only consumer of this interface.
1183 NS_IMETHODIMP
1184 nsUrlClassifierDBService::Classify(nsIPrincipal* aPrincipal,
1185 nsIURIClassifierCallback* c,
1186 bool* result)
1187 {
1188 NS_ENSURE_ARG(aPrincipal);
1189 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1191 if (!(mCheckMalware || mCheckPhishing)) {
1192 *result = false;
1193 return NS_OK;
1194 }
1196 nsRefPtr<nsUrlClassifierClassifyCallback> callback =
1197 new nsUrlClassifierClassifyCallback(c, mCheckMalware, mCheckPhishing);
1198 if (!callback) return NS_ERROR_OUT_OF_MEMORY;
1200 nsAutoCString tables;
1201 nsAutoCString malware;
1202 // LookupURI takes a comma-separated list already.
1203 Preferences::GetCString(MALWARE_TABLE_PREF, &malware);
1204 if (!malware.IsEmpty()) {
1205 tables.Append(malware);
1206 }
1207 nsAutoCString phishing;
1208 Preferences::GetCString(PHISH_TABLE_PREF, &phishing);
1209 if (!phishing.IsEmpty()) {
1210 tables.Append(",");
1211 tables.Append(phishing);
1212 }
1213 nsresult rv = LookupURI(aPrincipal, tables, callback, false, result);
1214 if (rv == NS_ERROR_MALFORMED_URI) {
1215 *result = false;
1216 // The URI had no hostname, don't try to classify it.
1217 return NS_OK;
1218 }
1219 NS_ENSURE_SUCCESS(rv, rv);
1221 return NS_OK;
1222 }
1224 NS_IMETHODIMP
1225 nsUrlClassifierDBService::Lookup(nsIPrincipal* aPrincipal,
1226 const nsACString& tables,
1227 nsIUrlClassifierCallback* c)
1228 {
1229 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1231 bool dummy;
1232 return LookupURI(aPrincipal, tables, c, true, &dummy);
1233 }
1235 nsresult
1236 nsUrlClassifierDBService::LookupURI(nsIPrincipal* aPrincipal,
1237 const nsACString& tables,
1238 nsIUrlClassifierCallback* c,
1239 bool forceLookup,
1240 bool *didLookup)
1241 {
1242 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1243 NS_ENSURE_ARG(aPrincipal);
1245 if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
1246 *didLookup = false;
1247 return NS_OK;
1248 }
1250 nsCOMPtr<nsIURI> uri;
1251 nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
1252 NS_ENSURE_SUCCESS(rv, rv);
1253 NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
1255 uri = NS_GetInnermostURI(uri);
1256 NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
1258 nsAutoCString key;
1259 // Canonicalize the url
1260 nsCOMPtr<nsIUrlClassifierUtils> utilsService =
1261 do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
1262 rv = utilsService->GetKeyForURI(uri, key);
1263 if (NS_FAILED(rv))
1264 return rv;
1266 if (forceLookup) {
1267 *didLookup = true;
1268 } else {
1269 bool clean = false;
1271 if (!clean) {
1272 nsCOMPtr<nsIPermissionManager> permissionManager =
1273 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
1275 if (permissionManager) {
1276 uint32_t perm;
1277 rv = permissionManager->TestPermissionFromPrincipal(aPrincipal,
1278 "safe-browsing", &perm);
1279 NS_ENSURE_SUCCESS(rv, rv);
1281 clean |= (perm == nsIPermissionManager::ALLOW_ACTION);
1282 }
1283 }
1285 *didLookup = !clean;
1286 if (clean) {
1287 return NS_OK;
1288 }
1289 }
1291 // Create an nsUrlClassifierLookupCallback object. This object will
1292 // take care of confirming partial hash matches if necessary before
1293 // calling the client's callback.
1294 nsCOMPtr<nsIUrlClassifierLookupCallback> callback =
1295 new nsUrlClassifierLookupCallback(this, c);
1296 if (!callback)
1297 return NS_ERROR_OUT_OF_MEMORY;
1299 nsCOMPtr<nsIUrlClassifierLookupCallback> proxyCallback =
1300 new UrlClassifierLookupCallbackProxy(callback);
1302 // Queue this lookup and call the lookup function to flush the queue if
1303 // necessary.
1304 rv = mWorker->QueueLookup(key, tables, proxyCallback);
1305 NS_ENSURE_SUCCESS(rv, rv);
1307 // This seems to just call HandlePendingLookups.
1308 nsAutoCString dummy;
1309 return mWorkerProxy->Lookup(nullptr, dummy, nullptr);
1310 }
1312 NS_IMETHODIMP
1313 nsUrlClassifierDBService::GetTables(nsIUrlClassifierCallback* c)
1314 {
1315 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1317 // The proxy callback uses the current thread.
1318 nsCOMPtr<nsIUrlClassifierCallback> proxyCallback =
1319 new UrlClassifierCallbackProxy(c);
1321 return mWorkerProxy->GetTables(proxyCallback);
1322 }
1324 NS_IMETHODIMP
1325 nsUrlClassifierDBService::SetHashCompleter(const nsACString &tableName,
1326 nsIUrlClassifierHashCompleter *completer)
1327 {
1328 if (completer) {
1329 mCompleters.Put(tableName, completer);
1330 } else {
1331 mCompleters.Remove(tableName);
1332 }
1334 return NS_OK;
1335 }
1337 NS_IMETHODIMP
1338 nsUrlClassifierDBService::BeginUpdate(nsIUrlClassifierUpdateObserver *observer,
1339 const nsACString &updateTables)
1340 {
1341 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1343 if (mInUpdate)
1344 return NS_ERROR_NOT_AVAILABLE;
1346 mInUpdate = true;
1348 // The proxy observer uses the current thread
1349 nsCOMPtr<nsIUrlClassifierUpdateObserver> proxyObserver =
1350 new UrlClassifierUpdateObserverProxy(observer);
1352 return mWorkerProxy->BeginUpdate(proxyObserver, updateTables);
1353 }
1355 NS_IMETHODIMP
1356 nsUrlClassifierDBService::BeginStream(const nsACString &table)
1357 {
1358 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1360 return mWorkerProxy->BeginStream(table);
1361 }
1363 NS_IMETHODIMP
1364 nsUrlClassifierDBService::UpdateStream(const nsACString& aUpdateChunk)
1365 {
1366 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1368 return mWorkerProxy->UpdateStream(aUpdateChunk);
1369 }
1371 NS_IMETHODIMP
1372 nsUrlClassifierDBService::FinishStream()
1373 {
1374 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1376 return mWorkerProxy->FinishStream();
1377 }
1379 NS_IMETHODIMP
1380 nsUrlClassifierDBService::FinishUpdate()
1381 {
1382 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1384 mInUpdate = false;
1386 return mWorkerProxy->FinishUpdate();
1387 }
1390 NS_IMETHODIMP
1391 nsUrlClassifierDBService::CancelUpdate()
1392 {
1393 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1395 mInUpdate = false;
1397 return mWorkerProxy->CancelUpdate();
1398 }
1400 NS_IMETHODIMP
1401 nsUrlClassifierDBService::ResetDatabase()
1402 {
1403 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1405 return mWorkerProxy->ResetDatabase();
1406 }
1408 nsresult
1409 nsUrlClassifierDBService::CacheCompletions(CacheResultArray *results)
1410 {
1411 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1413 return mWorkerProxy->CacheCompletions(results);
1414 }
1416 nsresult
1417 nsUrlClassifierDBService::CacheMisses(PrefixArray *results)
1418 {
1419 NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1421 return mWorkerProxy->CacheMisses(results);
1422 }
1424 bool
1425 nsUrlClassifierDBService::GetCompleter(const nsACString &tableName,
1426 nsIUrlClassifierHashCompleter **completer)
1427 {
1428 // If we have specified a completer, go ahead and query it. This is only
1429 // used by tests.
1430 if (mCompleters.Get(tableName, completer)) {
1431 return true;
1432 }
1434 // If we don't know about this table at all, or are disallowing completions
1435 // for it, skip completion checks.
1436 if (!mGethashTables.Contains(tableName) ||
1437 mDisallowCompletionsTables.Contains(tableName)) {
1438 return false;
1439 }
1441 MOZ_ASSERT(!StringBeginsWith(tableName, NS_LITERAL_CSTRING("test-")),
1442 "We should never fetch hash completions for test tables");
1444 // Otherwise, call gethash to find the hash completions.
1445 return NS_SUCCEEDED(CallGetService(NS_URLCLASSIFIERHASHCOMPLETER_CONTRACTID,
1446 completer));
1447 }
1449 NS_IMETHODIMP
1450 nsUrlClassifierDBService::Observe(nsISupports *aSubject, const char *aTopic,
1451 const char16_t *aData)
1452 {
1453 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
1454 nsresult rv;
1455 nsCOMPtr<nsIPrefBranch> prefs(do_QueryInterface(aSubject, &rv));
1456 NS_ENSURE_SUCCESS(rv, rv);
1457 if (NS_LITERAL_STRING(CHECK_MALWARE_PREF).Equals(aData)) {
1458 mCheckMalware = Preferences::GetBool(CHECK_MALWARE_PREF,
1459 CHECK_MALWARE_DEFAULT);
1460 } else if (NS_LITERAL_STRING(CHECK_PHISHING_PREF).Equals(aData)) {
1461 mCheckPhishing = Preferences::GetBool(CHECK_PHISHING_PREF,
1462 CHECK_PHISHING_DEFAULT);
1463 } else if (
1464 NS_LITERAL_STRING(PHISH_TABLE_PREF).Equals(aData) ||
1465 NS_LITERAL_STRING(MALWARE_TABLE_PREF).Equals(aData) ||
1466 NS_LITERAL_STRING(DOWNLOAD_BLOCK_TABLE_PREF).Equals(aData) ||
1467 NS_LITERAL_STRING(DOWNLOAD_ALLOW_TABLE_PREF).Equals(aData) ||
1468 NS_LITERAL_STRING(DISALLOW_COMPLETION_TABLE_PREF).Equals(aData)) {
1469 // Just read everything again.
1470 ReadTablesFromPrefs();
1471 } else if (NS_LITERAL_STRING(CONFIRM_AGE_PREF).Equals(aData)) {
1472 gFreshnessGuarantee = Preferences::GetInt(CONFIRM_AGE_PREF,
1473 CONFIRM_AGE_DEFAULT_SEC);
1474 }
1475 } else if (!strcmp(aTopic, "profile-before-change") ||
1476 !strcmp(aTopic, "xpcom-shutdown-threads")) {
1477 Shutdown();
1478 } else {
1479 return NS_ERROR_UNEXPECTED;
1480 }
1482 return NS_OK;
1483 }
1485 // Join the background thread if it exists.
1486 nsresult
1487 nsUrlClassifierDBService::Shutdown()
1488 {
1489 LOG(("shutting down db service\n"));
1491 if (!gDbBackgroundThread)
1492 return NS_OK;
1494 mCompleters.Clear();
1496 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
1497 if (prefs) {
1498 prefs->RemoveObserver(CHECK_MALWARE_PREF, this);
1499 prefs->RemoveObserver(CHECK_PHISHING_PREF, this);
1500 prefs->RemoveObserver(PHISH_TABLE_PREF, this);
1501 prefs->RemoveObserver(MALWARE_TABLE_PREF, this);
1502 prefs->RemoveObserver(DOWNLOAD_BLOCK_TABLE_PREF, this);
1503 prefs->RemoveObserver(DOWNLOAD_ALLOW_TABLE_PREF, this);
1504 prefs->RemoveObserver(DISALLOW_COMPLETION_TABLE_PREF, this);
1505 prefs->RemoveObserver(CONFIRM_AGE_PREF, this);
1506 }
1508 DebugOnly<nsresult> rv;
1509 // First close the db connection.
1510 if (mWorker) {
1511 rv = mWorkerProxy->CancelUpdate();
1512 NS_ASSERTION(NS_SUCCEEDED(rv), "failed to post cancel update event");
1514 rv = mWorkerProxy->CloseDb();
1515 NS_ASSERTION(NS_SUCCEEDED(rv), "failed to post close db event");
1516 }
1518 mWorkerProxy = nullptr;
1520 LOG(("joining background thread"));
1522 gShuttingDownThread = true;
1524 nsIThread *backgroundThread = gDbBackgroundThread;
1525 gDbBackgroundThread = nullptr;
1526 backgroundThread->Shutdown();
1527 NS_RELEASE(backgroundThread);
1529 return NS_OK;
1530 }
1532 nsIThread*
1533 nsUrlClassifierDBService::BackgroundThread()
1534 {
1535 return gDbBackgroundThread;
1536 }