Line data Source 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/. */
5 :
6 : #include "nsAutoPtr.h"
7 : #include "nsCOMPtr.h"
8 : #include "nsAppDirectoryServiceDefs.h"
9 : #include "nsArrayUtils.h"
10 : #include "nsCRT.h"
11 : #include "nsIDirectoryService.h"
12 : #include "nsIKeyModule.h"
13 : #include "nsIObserverService.h"
14 : #include "nsIPermissionManager.h"
15 : #include "nsIPrefBranch.h"
16 : #include "nsIPrefService.h"
17 : #include "nsIProperties.h"
18 : #include "nsToolkitCompsCID.h"
19 : #include "nsIXULRuntime.h"
20 : #include "nsUrlClassifierDBService.h"
21 : #include "nsUrlClassifierUtils.h"
22 : #include "nsUrlClassifierProxies.h"
23 : #include "nsURILoader.h"
24 : #include "nsString.h"
25 : #include "nsReadableUtils.h"
26 : #include "nsTArray.h"
27 : #include "nsNetCID.h"
28 : #include "nsThreadUtils.h"
29 : #include "nsProxyRelease.h"
30 : #include "nsString.h"
31 : #include "mozilla/Atomics.h"
32 : #include "mozilla/DebugOnly.h"
33 : #include "mozilla/ErrorNames.h"
34 : #include "mozilla/Mutex.h"
35 : #include "mozilla/Preferences.h"
36 : #include "mozilla/TimeStamp.h"
37 : #include "mozilla/Telemetry.h"
38 : #include "mozilla/Logging.h"
39 : #include "prnetdb.h"
40 : #include "Entries.h"
41 : #include "Classifier.h"
42 : #include "ProtocolParser.h"
43 : #include "mozilla/Attributes.h"
44 : #include "nsIPrincipal.h"
45 : #include "Classifier.h"
46 : #include "ProtocolParser.h"
47 : #include "nsContentUtils.h"
48 : #include "mozilla/dom/ContentChild.h"
49 : #include "mozilla/dom/PermissionMessageUtils.h"
50 : #include "mozilla/dom/URLClassifierChild.h"
51 : #include "mozilla/ipc/URIUtils.h"
52 : #include "nsProxyRelease.h"
53 : #include "UrlClassifierTelemetryUtils.h"
54 : #include "nsIURLFormatter.h"
55 : #include "nsIUploadChannel.h"
56 : #include "nsStringStream.h"
57 : #include "nsNetUtil.h"
58 : #include "nsToolkitCompsCID.h"
59 : #include "nsIClassifiedChannel.h"
60 :
61 : namespace mozilla {
62 : namespace safebrowsing {
63 :
64 : nsresult
65 0 : TablesToResponse(const nsACString& tables)
66 : {
67 0 : if (tables.IsEmpty()) {
68 : return NS_OK;
69 : }
70 :
71 : // We don't check mCheckMalware and friends because disabled tables are
72 : // never included
73 0 : if (FindInReadable(NS_LITERAL_CSTRING("-malware-"), tables)) {
74 : return NS_ERROR_MALWARE_URI;
75 : }
76 0 : if (FindInReadable(NS_LITERAL_CSTRING("-phish-"), tables)) {
77 : return NS_ERROR_PHISHING_URI;
78 : }
79 0 : if (FindInReadable(NS_LITERAL_CSTRING("-unwanted-"), tables)) {
80 : return NS_ERROR_UNWANTED_URI;
81 : }
82 0 : if (FindInReadable(NS_LITERAL_CSTRING("-track-"), tables)) {
83 : return NS_ERROR_TRACKING_URI;
84 : }
85 0 : if (FindInReadable(NS_LITERAL_CSTRING("-block-"), tables)) {
86 : return NS_ERROR_BLOCKED_URI;
87 : }
88 0 : if (FindInReadable(NS_LITERAL_CSTRING("-harmful-"), tables)) {
89 : return NS_ERROR_HARMFUL_URI;
90 : }
91 0 : return NS_OK;
92 : }
93 :
94 : } // namespace safebrowsing
95 : } // namespace mozilla
96 :
97 : using namespace mozilla;
98 : using namespace mozilla::safebrowsing;
99 :
100 : // MOZ_LOG=UrlClassifierDbService:5
101 : LazyLogModule gUrlClassifierDbServiceLog("UrlClassifierDbService");
102 : #define LOG(args) MOZ_LOG(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug, args)
103 : #define LOG_ENABLED() MOZ_LOG_TEST(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug)
104 :
105 : #define GETHASH_NOISE_PREF "urlclassifier.gethashnoise"
106 : #define GETHASH_NOISE_DEFAULT 4
107 :
108 : // 30 minutes as the maximum negative cache duration.
109 : #define MAXIMUM_NEGATIVE_CACHE_DURATION_SEC (30 * 60 * 1000)
110 :
111 : class nsUrlClassifierDBServiceWorker;
112 :
113 : // Singleton instance.
114 : static nsUrlClassifierDBService* sUrlClassifierDBService;
115 :
116 : nsIThread* nsUrlClassifierDBService::gDbBackgroundThread = nullptr;
117 :
118 : // Once we've committed to shutting down, don't do work in the background
119 : // thread.
120 : static bool gShuttingDownThread = false;
121 :
122 : static uint32_t sGethashNoise = GETHASH_NOISE_DEFAULT;
123 :
124 8 : NS_IMPL_ISUPPORTS(nsUrlClassifierDBServiceWorker,
125 : nsIUrlClassifierDBService)
126 :
127 1 : nsUrlClassifierDBServiceWorker::nsUrlClassifierDBServiceWorker()
128 : : mInStream(false)
129 : , mGethashNoise(0)
130 12 : , mPendingLookupLock("nsUrlClassifierDBServerWorker.mPendingLookupLock")
131 : {
132 1 : }
133 :
134 0 : nsUrlClassifierDBServiceWorker::~nsUrlClassifierDBServiceWorker()
135 : {
136 0 : NS_ASSERTION(!mClassifier,
137 : "Db connection not closed, leaking memory! Call CloseDb "
138 : "to close the connection.");
139 0 : }
140 :
141 : nsresult
142 1 : nsUrlClassifierDBServiceWorker::Init(uint32_t aGethashNoise,
143 : nsCOMPtr<nsIFile> aCacheDir,
144 : nsUrlClassifierDBService *aDBService)
145 : {
146 1 : mGethashNoise = aGethashNoise;
147 0 : mCacheDir = aCacheDir;
148 0 : mDBService = aDBService;
149 :
150 1 : ResetUpdate();
151 :
152 1 : return NS_OK;
153 : }
154 :
155 : nsresult
156 0 : nsUrlClassifierDBServiceWorker::QueueLookup(const nsACString& spec,
157 : const nsACString& tables,
158 : nsIUrlClassifierLookupCallback* callback)
159 : {
160 0 : MutexAutoLock lock(mPendingLookupLock);
161 0 : if (gShuttingDownThread) {
162 : return NS_ERROR_ABORT;
163 : }
164 :
165 0 : PendingLookup* lookup = mPendingLookups.AppendElement(fallible);
166 0 : if (!lookup) return NS_ERROR_OUT_OF_MEMORY;
167 :
168 0 : lookup->mStartTime = TimeStamp::Now();
169 0 : lookup->mKey = spec;
170 0 : lookup->mCallback = callback;
171 0 : lookup->mTables = tables;
172 :
173 0 : return NS_OK;
174 : }
175 :
176 : nsresult
177 0 : nsUrlClassifierDBServiceWorker::DoLocalLookup(const nsACString& spec,
178 : const nsACString& tables,
179 : LookupResultArray& results)
180 : {
181 0 : if (gShuttingDownThread) {
182 : return NS_ERROR_ABORT;
183 : }
184 :
185 0 : MOZ_ASSERT(!NS_IsMainThread(), "DoLocalLookup must be on background thread");
186 :
187 : // Bail if we haven't been initialized on the background thread.
188 0 : if (!mClassifier) {
189 : return NS_ERROR_NOT_AVAILABLE;
190 : }
191 :
192 : // We ignore failures from Check because we'd rather return the
193 : // results that were found than fail.
194 0 : mClassifier->Check(spec, tables, results);
195 :
196 0 : LOG(("Found %zu results.", results.Length()));
197 : return NS_OK;
198 : }
199 :
200 : static nsresult
201 0 : ProcessLookupResults(const LookupResultArray& aResults, nsTArray<nsCString>& aTables)
202 : {
203 : // Build the result array.
204 0 : for (const RefPtr<const LookupResult> result : aResults) {
205 0 : MOZ_ASSERT(!result->mNoise, "Lookup results should not have noise added");
206 0 : LOG(("Found result from table %s", result->mTableName.get()));
207 0 : if (aTables.IndexOf(result->mTableName) == nsTArray<nsCString>::NoIndex) {
208 0 : aTables.AppendElement(result->mTableName);
209 : }
210 : }
211 0 : return NS_OK;
212 : }
213 :
214 : /**
215 : * Lookup up a key in the database is a two step process:
216 : *
217 : * a) First we look for any Entries in the database that might apply to this
218 : * url. For each URL there are one or two possible domain names to check:
219 : * the two-part domain name (example.com) and the three-part name
220 : * (www.example.com). We check the database for both of these.
221 : * b) If we find any entries, we check the list of fragments for that entry
222 : * against the possible subfragments of the URL as described in the
223 : * "Simplified Regular Expression Lookup" section of the protocol doc.
224 : */
225 : nsresult
226 0 : nsUrlClassifierDBServiceWorker::DoLookup(const nsACString& spec,
227 : const nsACString& tables,
228 : nsIUrlClassifierLookupCallback* c)
229 : {
230 0 : if (gShuttingDownThread) {
231 0 : c->LookupComplete(nullptr);
232 0 : return NS_ERROR_NOT_INITIALIZED;
233 : }
234 :
235 0 : PRIntervalTime clockStart = 0;
236 0 : if (LOG_ENABLED()) {
237 0 : clockStart = PR_IntervalNow();
238 : }
239 :
240 0 : UniquePtr<LookupResultArray> results = MakeUnique<LookupResultArray>();
241 0 : if (!results) {
242 0 : c->LookupComplete(nullptr);
243 0 : return NS_ERROR_OUT_OF_MEMORY;
244 : }
245 :
246 0 : nsresult rv = DoLocalLookup(spec, tables, *results);
247 0 : if (NS_FAILED(rv)) {
248 0 : MOZ_ASSERT(results->IsEmpty(),
249 : "DoLocalLookup() should not return any results if it fails.");
250 0 : c->LookupComplete(nullptr);
251 0 : return rv;
252 : }
253 :
254 0 : LOG(("Found %zu results.", results->Length()));
255 :
256 :
257 0 : if (LOG_ENABLED()) {
258 0 : PRIntervalTime clockEnd = PR_IntervalNow();
259 0 : LOG(("query took %dms\n",
260 : PR_IntervalToMilliseconds(clockEnd - clockStart)));
261 : }
262 :
263 0 : for (const RefPtr<const LookupResult> lookupResult : *results) {
264 0 : if (!lookupResult->Confirmed() &&
265 0 : mDBService->CanComplete(lookupResult->mTableName)) {
266 :
267 : // We're going to be doing a gethash request, add some extra entries.
268 : // Note that we cannot pass the first two by reference, because we
269 : // add to completes, which can cause completes to reallocate and move.
270 0 : AddNoise(lookupResult->hash.fixedLengthPrefix,
271 0 : lookupResult->mTableName,
272 0 : mGethashNoise, *results);
273 0 : break;
274 : }
275 : }
276 :
277 : // At this point ownership of 'results' is handed to the callback.
278 0 : c->LookupComplete(std::move(results));
279 :
280 0 : return NS_OK;
281 : }
282 :
283 : nsresult
284 0 : nsUrlClassifierDBServiceWorker::HandlePendingLookups()
285 : {
286 0 : if (gShuttingDownThread) {
287 : return NS_ERROR_ABORT;
288 : }
289 :
290 0 : MutexAutoLock lock(mPendingLookupLock);
291 0 : while (mPendingLookups.Length() > 0) {
292 0 : PendingLookup lookup = mPendingLookups[0];
293 0 : mPendingLookups.RemoveElementAt(0);
294 : {
295 0 : MutexAutoUnlock unlock(mPendingLookupLock);
296 0 : DoLookup(lookup.mKey, lookup.mTables, lookup.mCallback);
297 : }
298 0 : double lookupTime = (TimeStamp::Now() - lookup.mStartTime).ToMilliseconds();
299 0 : Telemetry::Accumulate(Telemetry::URLCLASSIFIER_LOOKUP_TIME_2,
300 0 : static_cast<uint32_t>(lookupTime));
301 : }
302 :
303 : return NS_OK;
304 : }
305 :
306 : nsresult
307 0 : nsUrlClassifierDBServiceWorker::AddNoise(const Prefix aPrefix,
308 : const nsCString tableName,
309 : uint32_t aCount,
310 : LookupResultArray& results)
311 : {
312 0 : if (gShuttingDownThread) {
313 : return NS_ERROR_ABORT;
314 : }
315 :
316 0 : if (aCount < 1) {
317 : return NS_OK;
318 : }
319 :
320 0 : PrefixArray noiseEntries;
321 0 : nsresult rv = mClassifier->ReadNoiseEntries(aPrefix, tableName,
322 0 : aCount, noiseEntries);
323 0 : NS_ENSURE_SUCCESS(rv, rv);
324 :
325 0 : for (const auto noiseEntry : noiseEntries) {
326 0 : RefPtr<LookupResult> result = new LookupResult;
327 0 : results.AppendElement(result);
328 :
329 0 : result->hash.fixedLengthPrefix = noiseEntry;
330 0 : result->mNoise = true;
331 0 : result->mPartialHashLength = PREFIX_SIZE; // Noise is always 4-byte,
332 0 : result->mTableName.Assign(tableName);
333 : }
334 :
335 0 : return NS_OK;
336 : }
337 :
338 : // Lookup a key in the db.
339 : NS_IMETHODIMP
340 0 : nsUrlClassifierDBServiceWorker::Lookup(nsIPrincipal* aPrincipal,
341 : const nsACString& aTables,
342 : nsIUrlClassifierCallback* c)
343 : {
344 0 : if (gShuttingDownThread) {
345 : return NS_ERROR_ABORT;
346 : }
347 :
348 0 : return HandlePendingLookups();
349 : }
350 :
351 : NS_IMETHODIMP
352 0 : nsUrlClassifierDBServiceWorker::GetTables(nsIUrlClassifierCallback* c)
353 : {
354 0 : if (gShuttingDownThread) {
355 : return NS_ERROR_NOT_INITIALIZED;
356 : }
357 :
358 0 : nsresult rv = OpenDb();
359 0 : if (NS_FAILED(rv)) {
360 0 : NS_ERROR("Unable to open SafeBrowsing database");
361 0 : return NS_ERROR_FAILURE;
362 : }
363 :
364 0 : NS_ENSURE_SUCCESS(rv, rv);
365 :
366 0 : nsAutoCString response;
367 0 : mClassifier->TableRequest(response);
368 0 : LOG(("GetTables: %s", response.get()));
369 0 : c->HandleEvent(response);
370 :
371 0 : return rv;
372 : }
373 :
374 : void
375 0 : nsUrlClassifierDBServiceWorker::ResetStream()
376 : {
377 0 : LOG(("ResetStream"));
378 0 : mInStream = false;
379 0 : mProtocolParser = nullptr;
380 0 : }
381 :
382 : void
383 0 : nsUrlClassifierDBServiceWorker::ResetUpdate()
384 : {
385 0 : LOG(("ResetUpdate"));
386 0 : mUpdateWaitSec = 0;
387 1 : mUpdateStatus = NS_OK;
388 1 : mUpdateObserver = nullptr;
389 0 : }
390 :
391 : NS_IMETHODIMP
392 0 : nsUrlClassifierDBServiceWorker::SetHashCompleter(const nsACString &tableName,
393 : nsIUrlClassifierHashCompleter *completer)
394 : {
395 0 : return NS_ERROR_NOT_IMPLEMENTED;
396 : }
397 :
398 : NS_IMETHODIMP
399 0 : nsUrlClassifierDBServiceWorker::BeginUpdate(nsIUrlClassifierUpdateObserver *observer,
400 : const nsACString &tables)
401 : {
402 0 : LOG(("nsUrlClassifierDBServiceWorker::BeginUpdate [%s]", PromiseFlatCString(tables).get()));
403 :
404 0 : if (gShuttingDownThread) {
405 : return NS_ERROR_NOT_INITIALIZED;
406 : }
407 :
408 0 : NS_ENSURE_STATE(!mUpdateObserver);
409 :
410 0 : nsresult rv = OpenDb();
411 0 : if (NS_FAILED(rv)) {
412 0 : NS_ERROR("Unable to open SafeBrowsing database");
413 0 : return NS_ERROR_FAILURE;
414 : }
415 :
416 0 : mUpdateStatus = NS_OK;
417 0 : MOZ_ASSERT(mTableUpdates.IsEmpty(),
418 : "mTableUpdates should have been cleared in FinishUpdate()");
419 0 : mUpdateObserver = observer;
420 0 : Classifier::SplitTables(tables, mUpdateTables);
421 :
422 0 : return NS_OK;
423 : }
424 :
425 : // Called from the stream updater.
426 : NS_IMETHODIMP
427 0 : nsUrlClassifierDBServiceWorker::BeginStream(const nsACString &table)
428 : {
429 0 : LOG(("nsUrlClassifierDBServiceWorker::BeginStream"));
430 0 : MOZ_ASSERT(!NS_IsMainThread(), "Streaming must be on the background thread");
431 :
432 0 : if (gShuttingDownThread) {
433 : return NS_ERROR_NOT_INITIALIZED;
434 : }
435 :
436 0 : NS_ENSURE_STATE(mUpdateObserver);
437 0 : NS_ENSURE_STATE(!mInStream);
438 :
439 0 : mInStream = true;
440 :
441 0 : NS_ASSERTION(!mProtocolParser, "Should not have a protocol parser.");
442 :
443 : // Check if we should use protobuf to parse the update.
444 : bool useProtobuf = false;
445 0 : for (size_t i = 0; i < mUpdateTables.Length(); i++) {
446 : bool isCurProtobuf =
447 0 : StringEndsWith(mUpdateTables[i], NS_LITERAL_CSTRING("-proto"));
448 :
449 0 : if (0 == i) {
450 : // Use the first table name to decice if all the subsequent tables
451 : // should be '-proto'.
452 : useProtobuf = isCurProtobuf;
453 : continue;
454 : }
455 :
456 0 : if (useProtobuf != isCurProtobuf) {
457 : NS_WARNING("Cannot mix 'proto' tables with other types "
458 0 : "within the same provider.");
459 0 : break;
460 : }
461 : }
462 :
463 : mProtocolParser = (useProtobuf ? static_cast<ProtocolParser*>(new (fallible)
464 0 : ProtocolParserProtobuf())
465 : : static_cast<ProtocolParser*>(new (fallible)
466 0 : ProtocolParserV2()));
467 0 : if (!mProtocolParser) {
468 : return NS_ERROR_OUT_OF_MEMORY;
469 : }
470 :
471 0 : return mProtocolParser->Begin(table, mUpdateTables);
472 : }
473 :
474 : /**
475 : * Updating the database:
476 : *
477 : * The Update() method takes a series of chunks separated with control data,
478 : * as described in
479 : * http://code.google.com/p/google-safe-browsing/wiki/Protocolv2Spec
480 : *
481 : * It will iterate through the control data until it reaches a chunk. By
482 : * the time it reaches a chunk, it should have received
483 : * a) the table to which this chunk applies
484 : * b) the type of chunk (add, delete, expire add, expire delete).
485 : * c) the chunk ID
486 : * d) the length of the chunk.
487 : *
488 : * For add and subtract chunks, it needs to read the chunk data (expires
489 : * don't have any data). Chunk data is a list of URI fragments whose
490 : * encoding depends on the type of table (which is indicated by the end
491 : * of the table name):
492 : * a) tables ending with -exp are a zlib-compressed list of URI fragments
493 : * separated by newlines.
494 : * b) tables ending with -sha128 have the form
495 : * [domain][N][frag0]...[fragN]
496 : * 16 1 16 16
497 : * If N is 0, the domain is reused as a fragment.
498 : * c) any other tables are assumed to be a plaintext list of URI fragments
499 : * separated by newlines.
500 : *
501 : * Update() can be fed partial data; It will accumulate data until there is
502 : * enough to act on. Finish() should be called when there will be no more
503 : * data.
504 : */
505 : NS_IMETHODIMP
506 0 : nsUrlClassifierDBServiceWorker::UpdateStream(const nsACString& chunk)
507 : {
508 0 : if (gShuttingDownThread) {
509 : return NS_ERROR_NOT_INITIALIZED;
510 : }
511 :
512 0 : MOZ_ASSERT(mProtocolParser);
513 :
514 0 : NS_ENSURE_STATE(mInStream);
515 :
516 0 : HandlePendingLookups();
517 :
518 : // Feed the chunk to the parser.
519 0 : return mProtocolParser->AppendStream(chunk);
520 : }
521 :
522 : NS_IMETHODIMP
523 0 : nsUrlClassifierDBServiceWorker::FinishStream()
524 : {
525 0 : if (gShuttingDownThread) {
526 0 : LOG(("shutting down"));
527 : return NS_ERROR_NOT_INITIALIZED;
528 : }
529 :
530 0 : MOZ_ASSERT(mProtocolParser);
531 :
532 0 : NS_ENSURE_STATE(mInStream);
533 0 : NS_ENSURE_STATE(mUpdateObserver);
534 :
535 0 : mInStream = false;
536 :
537 0 : mProtocolParser->End();
538 :
539 0 : if (NS_SUCCEEDED(mProtocolParser->Status())) {
540 0 : if (mProtocolParser->UpdateWaitSec()) {
541 0 : mUpdateWaitSec = mProtocolParser->UpdateWaitSec();
542 : }
543 : // XXX: Only allow forwards from the initial update?
544 : const nsTArray<ProtocolParser::ForwardedUpdate> &forwards =
545 0 : mProtocolParser->Forwards();
546 0 : for (uint32_t i = 0; i < forwards.Length(); i++) {
547 0 : const ProtocolParser::ForwardedUpdate &forward = forwards[i];
548 0 : mUpdateObserver->UpdateUrlRequested(forward.url, forward.table);
549 : }
550 : // Hold on to any TableUpdate objects that were created by the
551 : // parser.
552 0 : mTableUpdates.AppendElements(mProtocolParser->GetTableUpdates());
553 0 : mProtocolParser->ForgetTableUpdates();
554 :
555 : #ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES
556 : // The assignment involves no string copy since the source string is sharable.
557 0 : mRawTableUpdates = mProtocolParser->GetRawTableUpdates();
558 : #endif
559 : } else {
560 0 : LOG(("nsUrlClassifierDBService::FinishStream Failed to parse the stream "
561 : "using mProtocolParser."));
562 0 : mUpdateStatus = mProtocolParser->Status();
563 : }
564 0 : mUpdateObserver->StreamFinished(mProtocolParser->Status(), 0);
565 :
566 0 : if (NS_SUCCEEDED(mUpdateStatus)) {
567 0 : if (mProtocolParser->ResetRequested()) {
568 0 : mClassifier->ResetTables(Classifier::Clear_All,
569 0 : mProtocolParser->TablesToReset());
570 : }
571 : }
572 :
573 0 : mProtocolParser = nullptr;
574 :
575 0 : return mUpdateStatus;
576 : }
577 :
578 : NS_IMETHODIMP
579 0 : nsUrlClassifierDBServiceWorker::FinishUpdate()
580 : {
581 0 : LOG(("nsUrlClassifierDBServiceWorker::FinishUpdate"));
582 :
583 0 : MOZ_ASSERT(!NS_IsMainThread(), "nsUrlClassifierDBServiceWorker::FinishUpdate "
584 : "NUST NOT be on the main thread.");
585 :
586 0 : if (gShuttingDownThread) {
587 : return NS_ERROR_NOT_INITIALIZED;
588 : }
589 :
590 0 : MOZ_ASSERT(!mProtocolParser, "Should have been nulled out in FinishStream() "
591 : "or never created.");
592 :
593 0 : NS_ENSURE_STATE(mUpdateObserver);
594 :
595 0 : if (NS_FAILED(mUpdateStatus)) {
596 0 : LOG(("nsUrlClassifierDBServiceWorker::FinishUpdate() Not running "
597 : "ApplyUpdate() since the update has already failed."));
598 0 : mTableUpdates.Clear();
599 0 : return NotifyUpdateObserver(mUpdateStatus);
600 : }
601 :
602 0 : if (mTableUpdates.IsEmpty()) {
603 0 : LOG(("Nothing to update. Just notify update observer."));
604 0 : return NotifyUpdateObserver(NS_OK);
605 : }
606 :
607 0 : RefPtr<nsUrlClassifierDBServiceWorker> self = this;
608 0 : nsresult rv = mClassifier->AsyncApplyUpdates(mTableUpdates,
609 0 : [=] (nsresult aRv) -> void {
610 : #ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES
611 0 : if (NS_FAILED(aRv) &&
612 0 : NS_ERROR_OUT_OF_MEMORY != aRv &&
613 0 : NS_ERROR_UC_UPDATE_SHUTDOWNING != aRv) {
614 0 : self->mClassifier->DumpRawTableUpdates(mRawTableUpdates);
615 : }
616 : // Invalidate the raw table updates.
617 0 : self->mRawTableUpdates = EmptyCString();
618 : #endif
619 :
620 0 : self->NotifyUpdateObserver(aRv);
621 0 : });
622 0 : mTableUpdates.Clear(); // Classifier is working on its copy.
623 :
624 0 : if (NS_FAILED(rv)) {
625 0 : LOG(("Failed to start async update. Notify immediately."));
626 0 : NotifyUpdateObserver(rv);
627 : }
628 :
629 : return rv;
630 : }
631 :
632 : nsresult
633 0 : nsUrlClassifierDBServiceWorker::NotifyUpdateObserver(nsresult aUpdateStatus)
634 : {
635 0 : MOZ_ASSERT(!NS_IsMainThread(), "nsUrlClassifierDBServiceWorker::NotifyUpdateObserver "
636 : "NUST NOT be on the main thread.");
637 :
638 0 : LOG(("nsUrlClassifierDBServiceWorker::NotifyUpdateObserver"));
639 :
640 : // We've either
641 : // 1) failed starting a download stream
642 : // 2) succeeded in starting a download stream but failed to obtain
643 : // table updates
644 : // 3) succeeded in obtaining table updates but failed to build new
645 : // tables.
646 : // 4) succeeded in building new tables but failed to take them.
647 : // 5) succeeded in taking new tables.
648 :
649 0 : mUpdateStatus = aUpdateStatus;
650 :
651 : nsCOMPtr<nsIUrlClassifierUtils> urlUtil =
652 0 : do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
653 :
654 0 : nsCString provider;
655 : // Assume that all the tables in update should have the same provider.
656 0 : urlUtil->GetTelemetryProvider(mUpdateTables.SafeElementAt(0, EmptyCString()), provider);
657 :
658 0 : nsresult updateStatus = mUpdateStatus;
659 0 : if (NS_FAILED(mUpdateStatus)) {
660 0 : updateStatus = NS_ERROR_GET_MODULE(mUpdateStatus) == NS_ERROR_MODULE_URL_CLASSIFIER ?
661 : mUpdateStatus : NS_ERROR_UC_UPDATE_UNKNOWN;
662 : }
663 :
664 : // Do not record telemetry for testing tables.
665 0 : if (!provider.EqualsLiteral(TESTING_TABLE_PROVIDER_NAME)) {
666 : Telemetry::Accumulate(Telemetry::URLCLASSIFIER_UPDATE_ERROR, provider,
667 0 : NS_ERROR_GET_CODE(updateStatus));
668 : }
669 :
670 0 : if (!mUpdateObserver) {
671 : // In the normal shutdown process, CancelUpdate() would NOT be
672 : // called prior to NotifyUpdateObserver(). However, CancelUpdate()
673 : // is a public API which can be called in the test case at any point.
674 : // If the call sequence is FinishUpdate() then CancelUpdate(), the later
675 : // might be executed before NotifyUpdateObserver() which is triggered
676 : // by the update thread. In this case, we will get null mUpdateObserver.
677 : NS_WARNING("CancelUpdate() is called before we asynchronously call "
678 0 : "NotifyUpdateObserver() in FinishUpdate().");
679 :
680 : // The DB cleanup will be done in CancelUpdate() so we can just return.
681 0 : return NS_OK;
682 : }
683 :
684 : // Null out mUpdateObserver before notifying so that BeginUpdate()
685 : // becomes available prior to callback.
686 0 : nsCOMPtr<nsIUrlClassifierUpdateObserver> updateObserver = nullptr;
687 0 : updateObserver.swap(mUpdateObserver);
688 :
689 0 : if (NS_SUCCEEDED(mUpdateStatus)) {
690 0 : LOG(("Notifying success: %d", mUpdateWaitSec));
691 0 : updateObserver->UpdateSuccess(mUpdateWaitSec);
692 : } else {
693 0 : if (LOG_ENABLED()) {
694 0 : nsAutoCString errorName;
695 0 : mozilla::GetErrorName(mUpdateStatus, errorName);
696 0 : LOG(("Notifying error: %s (%" PRIu32 ")", errorName.get(),
697 : static_cast<uint32_t>(mUpdateStatus)));
698 : }
699 :
700 0 : updateObserver->UpdateError(mUpdateStatus);
701 : /*
702 : * mark the tables as spoiled(clear cache in LookupCache), we don't want to
703 : * block hosts longer than normal because our update failed
704 : */
705 0 : mClassifier->ResetTables(Classifier::Clear_Cache, mUpdateTables);
706 : }
707 :
708 : return NS_OK;
709 : }
710 :
711 : NS_IMETHODIMP
712 0 : nsUrlClassifierDBServiceWorker::ResetDatabase()
713 : {
714 0 : nsresult rv = OpenDb();
715 :
716 0 : if (NS_SUCCEEDED(rv)) {
717 0 : mClassifier->Reset();
718 : }
719 :
720 0 : rv = CloseDb();
721 0 : NS_ENSURE_SUCCESS(rv, rv);
722 :
723 : return NS_OK;
724 : }
725 :
726 : NS_IMETHODIMP
727 0 : nsUrlClassifierDBServiceWorker::ReloadDatabase()
728 : {
729 0 : nsTArray<nsCString> tables;
730 0 : nsresult rv = mClassifier->ActiveTables(tables);
731 0 : NS_ENSURE_SUCCESS(rv, rv);
732 :
733 : // This will null out mClassifier
734 0 : rv = CloseDb();
735 0 : NS_ENSURE_SUCCESS(rv, rv);
736 :
737 : // Create new mClassifier and load prefixset and completions from disk.
738 0 : rv = OpenDb();
739 0 : NS_ENSURE_SUCCESS(rv, rv);
740 :
741 : return NS_OK;
742 : }
743 :
744 : NS_IMETHODIMP
745 0 : nsUrlClassifierDBServiceWorker::ClearCache()
746 : {
747 0 : nsTArray<nsCString> tables;
748 0 : nsresult rv = mClassifier->ActiveTables(tables);
749 0 : NS_ENSURE_SUCCESS(rv, rv);
750 :
751 0 : mClassifier->ResetTables(Classifier::Clear_Cache, tables);
752 :
753 : return NS_OK;
754 : }
755 :
756 : NS_IMETHODIMP
757 0 : nsUrlClassifierDBServiceWorker::CancelUpdate()
758 : {
759 0 : LOG(("nsUrlClassifierDBServiceWorker::CancelUpdate"));
760 :
761 0 : if (mUpdateObserver) {
762 0 : LOG(("UpdateObserver exists, cancelling"));
763 :
764 0 : mUpdateStatus = NS_BINDING_ABORTED;
765 :
766 0 : mUpdateObserver->UpdateError(mUpdateStatus);
767 :
768 : /*
769 : * mark the tables as spoiled(clear cache in LookupCache), we don't want to
770 : * block hosts longer than normal because our update failed
771 : */
772 0 : mClassifier->ResetTables(Classifier::Clear_Cache, mUpdateTables);
773 :
774 0 : ResetStream();
775 0 : ResetUpdate();
776 : } else {
777 0 : LOG(("No UpdateObserver, nothing to cancel"));
778 : }
779 :
780 0 : return NS_OK;
781 : }
782 :
783 : void
784 0 : nsUrlClassifierDBServiceWorker::FlushAndDisableAsyncUpdate()
785 : {
786 0 : LOG(("nsUrlClassifierDBServiceWorker::FlushAndDisableAsyncUpdate()"));
787 :
788 0 : if (mClassifier) {
789 0 : mClassifier->FlushAndDisableAsyncUpdate();
790 : }
791 0 : }
792 :
793 : // Allows the main thread to delete the connection which may be in
794 : // a background thread.
795 : // XXX This could be turned into a single shutdown event so the logic
796 : // is simpler in nsUrlClassifierDBService::Shutdown.
797 : nsresult
798 0 : nsUrlClassifierDBServiceWorker::CloseDb()
799 : {
800 0 : if (mClassifier) {
801 0 : mClassifier->Close();
802 0 : mClassifier = nullptr;
803 : }
804 :
805 : // Clear last completion result when close db so we will still cache completion
806 : // result next time we re-open it.
807 0 : mLastResults.Clear();
808 :
809 0 : LOG(("urlclassifier db closed\n"));
810 :
811 0 : return NS_OK;
812 : }
813 :
814 : nsresult
815 0 : nsUrlClassifierDBServiceWorker::PreShutdown()
816 : {
817 0 : if (mClassifier) {
818 : // Classifier close will release all lookup caches which may be a time-consuming job.
819 : // See Bug 1408631.
820 0 : mClassifier->Close();
821 : }
822 :
823 : // WARNING: nothing we put here should affect an ongoing update thread. When in doubt,
824 : // put things in Shutdown() instead.
825 0 : return NS_OK;
826 : }
827 :
828 : nsresult
829 0 : nsUrlClassifierDBServiceWorker::CacheCompletions(const ConstCacheResultArray& aResults)
830 : {
831 0 : if (gShuttingDownThread) {
832 : return NS_ERROR_ABORT;
833 : }
834 :
835 0 : LOG(("nsUrlClassifierDBServiceWorker::CacheCompletions [%p]", this));
836 0 : if (!mClassifier) {
837 : return NS_OK;
838 : }
839 :
840 0 : if (aResults.Length() == 0) {
841 : return NS_OK;
842 : }
843 :
844 0 : if (IsSameAsLastResults(aResults)) {
845 0 : LOG(("Skipping completions that have just been cached already."));
846 : return NS_OK;
847 : }
848 :
849 : // Only cache results for tables that we have, don't take
850 : // in tables we might accidentally have hit during a completion.
851 : // This happens due to goog vs googpub lists existing.
852 0 : nsTArray<nsCString> tables;
853 0 : nsresult rv = mClassifier->ActiveTables(tables);
854 0 : NS_ENSURE_SUCCESS(rv, rv);
855 0 : if (LOG_ENABLED()) {
856 0 : nsCString s;
857 0 : for (size_t i=0; i < tables.Length(); i++) {
858 0 : if (!s.IsEmpty()) {
859 0 : s += ",";
860 : }
861 0 : s += tables[i];
862 : }
863 0 : LOG(("Active tables: %s", s.get()));
864 : }
865 :
866 0 : ConstTableUpdateArray updates;
867 :
868 0 : for (const auto& result : aResults) {
869 0 : bool activeTable = false;
870 :
871 0 : for (uint32_t table = 0; table < tables.Length(); table++) {
872 0 : if (tables[table].Equals(result->table)) {
873 : activeTable = true;
874 : break;
875 : }
876 : }
877 0 : if (activeTable) {
878 0 : nsAutoPtr<ProtocolParser> pParse;
879 0 : pParse = result->Ver() == CacheResult::V2 ?
880 0 : static_cast<ProtocolParser*>(new ProtocolParserV2()) :
881 0 : static_cast<ProtocolParser*>(new ProtocolParserProtobuf());
882 :
883 0 : RefPtr<TableUpdate> tu = pParse->GetTableUpdate(result->table);
884 :
885 0 : rv = CacheResultToTableUpdate(result, tu);
886 0 : if (NS_FAILED(rv)) {
887 : // We can bail without leaking here because ForgetTableUpdates
888 : // hasn't been called yet.
889 0 : return rv;
890 : }
891 0 : updates.AppendElement(tu);
892 0 : pParse->ForgetTableUpdates();
893 : } else {
894 0 : LOG(("Completion received, but table %s is not active, so not caching.",
895 : result->table.get()));
896 : }
897 : }
898 :
899 0 : rv = mClassifier->ApplyFullHashes(updates);
900 0 : if (NS_SUCCEEDED(rv)) {
901 0 : mLastResults = aResults;
902 : }
903 : return rv;
904 : }
905 :
906 : nsresult
907 0 : nsUrlClassifierDBServiceWorker::CacheResultToTableUpdate(RefPtr<const CacheResult> aCacheResult,
908 : RefPtr<TableUpdate> aUpdate)
909 : {
910 0 : RefPtr<TableUpdateV2> tuV2 = TableUpdate::Cast<TableUpdateV2>(aUpdate);
911 0 : if (tuV2) {
912 0 : RefPtr<const CacheResultV2> result = CacheResult::Cast<const CacheResultV2>(aCacheResult);
913 0 : MOZ_ASSERT(result);
914 :
915 0 : if (result->miss) {
916 0 : return tuV2->NewMissPrefix(result->prefix);
917 : } else {
918 0 : LOG(("CacheCompletion hash %X, Addchunk %d", result->completion.ToUint32(),
919 : result->addChunk));
920 :
921 0 : nsresult rv = tuV2->NewAddComplete(result->addChunk, result->completion);
922 0 : if (NS_FAILED(rv)) {
923 : return rv;
924 : }
925 0 : return tuV2->NewAddChunk(result->addChunk);
926 : }
927 : }
928 :
929 0 : RefPtr<TableUpdateV4> tuV4 = TableUpdate::Cast<TableUpdateV4>(aUpdate);
930 0 : if (tuV4) {
931 0 : RefPtr<const CacheResultV4> result = CacheResult::Cast<const CacheResultV4>(aCacheResult);
932 0 : MOZ_ASSERT(result);
933 :
934 0 : if (LOG_ENABLED()) {
935 0 : const FullHashExpiryCache& fullHashes = result->response.fullHashes;
936 0 : for (auto iter = fullHashes.ConstIter(); !iter.Done(); iter.Next()) {
937 : Completion completion;
938 0 : completion.Assign(iter.Key());
939 0 : LOG(("CacheCompletion(v4) hash %X, CacheExpireTime %" PRId64,
940 : completion.ToUint32(), iter.Data()));
941 : }
942 : }
943 :
944 0 : tuV4->NewFullHashResponse(result->prefix, result->response);
945 : return NS_OK;
946 : }
947 :
948 : // tableUpdate object should be either V2 or V4.
949 : return NS_ERROR_FAILURE;
950 : }
951 :
952 : nsresult
953 1 : nsUrlClassifierDBServiceWorker::OpenDb()
954 : {
955 1 : if (gShuttingDownThread) {
956 : return NS_ERROR_ABORT;
957 : }
958 :
959 0 : MOZ_ASSERT(!NS_IsMainThread(), "Must initialize DB on background thread");
960 : // Connection already open, don't do anything.
961 0 : if (mClassifier) {
962 : return NS_OK;
963 : }
964 :
965 : nsresult rv;
966 4 : nsAutoPtr<Classifier> classifier(new (fallible) Classifier());
967 0 : if (!classifier) {
968 : return NS_ERROR_OUT_OF_MEMORY;
969 : }
970 :
971 1 : rv = classifier->Open(*mCacheDir);
972 0 : NS_ENSURE_SUCCESS(rv, rv);
973 :
974 1 : mClassifier = classifier;
975 :
976 1 : return NS_OK;
977 : }
978 :
979 : NS_IMETHODIMP
980 0 : nsUrlClassifierDBServiceWorker::ClearLastResults()
981 : {
982 0 : MOZ_ASSERT(!NS_IsMainThread(), "Must be on the background thread");
983 0 : mLastResults.Clear();
984 0 : return NS_OK;
985 : }
986 :
987 : nsresult
988 0 : nsUrlClassifierDBServiceWorker::GetCacheInfo(const nsACString& aTable,
989 : nsIUrlClassifierCacheInfo** aCache)
990 : {
991 0 : MOZ_ASSERT(!NS_IsMainThread(), "Must be on the background thread");
992 0 : if (!mClassifier) {
993 : return NS_ERROR_NOT_AVAILABLE;
994 : }
995 :
996 0 : mClassifier->GetCacheInfo(aTable, aCache);
997 0 : return NS_OK;
998 : }
999 :
1000 : bool
1001 0 : nsUrlClassifierDBServiceWorker::IsSameAsLastResults(const ConstCacheResultArray& aResult) const
1002 : {
1003 0 : if (mLastResults.Length() != aResult.Length()) {
1004 : return false;
1005 : }
1006 :
1007 : bool equal = true;
1008 0 : for (uint32_t i = 0; i < mLastResults.Length() && equal; i++) {
1009 0 : RefPtr<const CacheResult> lhs = mLastResults[i];
1010 0 : RefPtr<const CacheResult> rhs = aResult[i];
1011 :
1012 0 : if (lhs->Ver() != rhs->Ver()) {
1013 0 : return false;
1014 : }
1015 :
1016 0 : if (lhs->Ver() == CacheResult::V2) {
1017 0 : equal = *(CacheResult::Cast<const CacheResultV2>(lhs)) ==
1018 0 : *(CacheResult::Cast<const CacheResultV2>(rhs));
1019 0 : } else if (lhs->Ver() == CacheResult::V4) {
1020 0 : equal = *(CacheResult::Cast<const CacheResultV4>(lhs)) ==
1021 0 : *(CacheResult::Cast<const CacheResultV4>(rhs));
1022 : }
1023 : }
1024 :
1025 : return equal;
1026 : }
1027 :
1028 : // -------------------------------------------------------------------------
1029 : // nsUrlClassifierLookupCallback
1030 : //
1031 : // This class takes the results of a lookup found on the worker thread
1032 : // and handles any necessary partial hash expansions before calling
1033 : // the client callback.
1034 :
1035 : class nsUrlClassifierLookupCallback final : public nsIUrlClassifierLookupCallback
1036 : , public nsIUrlClassifierHashCompleterCallback
1037 : {
1038 : public:
1039 : NS_DECL_THREADSAFE_ISUPPORTS
1040 : NS_DECL_NSIURLCLASSIFIERLOOKUPCALLBACK
1041 : NS_DECL_NSIURLCLASSIFIERHASHCOMPLETERCALLBACK
1042 :
1043 0 : nsUrlClassifierLookupCallback(nsUrlClassifierDBService *dbservice,
1044 : nsIUrlClassifierCallback *c)
1045 0 : : mDBService(dbservice)
1046 : , mResults(nullptr)
1047 : , mPendingCompletions(0)
1048 0 : , mCallback(c)
1049 0 : {}
1050 :
1051 : private:
1052 : ~nsUrlClassifierLookupCallback();
1053 :
1054 : nsresult HandleResults();
1055 : nsresult ProcessComplete(RefPtr<CacheResult> aCacheResult);
1056 : nsresult CacheMisses();
1057 :
1058 : RefPtr<nsUrlClassifierDBService> mDBService;
1059 : UniquePtr<LookupResultArray> mResults;
1060 :
1061 : // Completed results to send back to the worker for caching.
1062 : ConstCacheResultArray mCacheResults;
1063 :
1064 : uint32_t mPendingCompletions;
1065 : nsCOMPtr<nsIUrlClassifierCallback> mCallback;
1066 : };
1067 :
1068 0 : NS_IMPL_ISUPPORTS(nsUrlClassifierLookupCallback,
1069 : nsIUrlClassifierLookupCallback,
1070 : nsIUrlClassifierHashCompleterCallback)
1071 :
1072 0 : nsUrlClassifierLookupCallback::~nsUrlClassifierLookupCallback()
1073 : {
1074 0 : if (mCallback) {
1075 : NS_ReleaseOnMainThreadSystemGroup(
1076 0 : "nsUrlClassifierLookupCallback::mCallback", mCallback.forget());
1077 : }
1078 0 : }
1079 :
1080 : NS_IMETHODIMP
1081 0 : nsUrlClassifierLookupCallback::LookupComplete(UniquePtr<LookupResultArray> results)
1082 : {
1083 0 : NS_ASSERTION(mResults == nullptr,
1084 : "Should only get one set of results per nsUrlClassifierLookupCallback!");
1085 :
1086 0 : if (!results) {
1087 0 : HandleResults();
1088 0 : return NS_OK;
1089 : }
1090 :
1091 0 : mResults = std::move(results);
1092 :
1093 : // Check the results entries that need to be completed.
1094 0 : for (const auto& result : *mResults) {
1095 : // We will complete partial matches and matches that are stale.
1096 0 : if (!result->Confirmed()) {
1097 0 : nsCOMPtr<nsIUrlClassifierHashCompleter> completer;
1098 0 : nsCString gethashUrl;
1099 : nsresult rv;
1100 0 : nsCOMPtr<nsIUrlListManager> listManager = do_GetService(
1101 0 : "@mozilla.org/url-classifier/listmanager;1", &rv);
1102 0 : NS_ENSURE_SUCCESS(rv, rv);
1103 0 : rv = listManager->GetGethashUrl(result->mTableName, gethashUrl);
1104 0 : NS_ENSURE_SUCCESS(rv, rv);
1105 0 : LOG(("The match from %s needs to be completed at %s",
1106 : result->mTableName.get(), gethashUrl.get()));
1107 : // gethashUrls may be empty in 2 cases: test tables, and on startup where
1108 : // we may have found a prefix in an existing table before the listmanager
1109 : // has registered the table. In the second case we should not call
1110 : // complete.
1111 0 : if ((!gethashUrl.IsEmpty() ||
1112 0 : StringBeginsWith(result->mTableName, NS_LITERAL_CSTRING("test"))) &&
1113 0 : mDBService->GetCompleter(result->mTableName,
1114 0 : getter_AddRefs(completer))) {
1115 :
1116 : // Bug 1323953 - Send the first 4 bytes for completion no matter how
1117 : // long we matched the prefix.
1118 0 : nsresult rv = completer->Complete(result->PartialHash(),
1119 : gethashUrl,
1120 0 : result->mTableName,
1121 0 : this);
1122 0 : if (NS_SUCCEEDED(rv)) {
1123 0 : mPendingCompletions++;
1124 : }
1125 : } else {
1126 : // For tables with no hash completer, a complete hash match is
1127 : // good enough, we'll consider it is valid.
1128 0 : if (result->Complete()) {
1129 0 : result->mConfirmed = true;
1130 0 : LOG(("Skipping completion in a table without a valid completer (%s).",
1131 : result->mTableName.get()));
1132 : } else {
1133 0 : NS_WARNING("Partial match in a table without a valid completer, ignoring partial match.");
1134 : }
1135 : }
1136 : }
1137 : }
1138 :
1139 0 : LOG(("nsUrlClassifierLookupCallback::LookupComplete [%p] "
1140 : "%u pending completions", this, mPendingCompletions));
1141 0 : if (mPendingCompletions == 0) {
1142 : // All results were complete, we're ready!
1143 0 : HandleResults();
1144 : }
1145 :
1146 : return NS_OK;
1147 : }
1148 :
1149 : NS_IMETHODIMP
1150 0 : nsUrlClassifierLookupCallback::CompletionFinished(nsresult status)
1151 : {
1152 0 : if (LOG_ENABLED()) {
1153 0 : nsAutoCString errorName;
1154 0 : mozilla::GetErrorName(status, errorName);
1155 0 : LOG(("nsUrlClassifierLookupCallback::CompletionFinished [%p, %s]",
1156 : this, errorName.get()));
1157 : }
1158 :
1159 0 : mPendingCompletions--;
1160 0 : if (mPendingCompletions == 0) {
1161 0 : HandleResults();
1162 : }
1163 :
1164 0 : return NS_OK;
1165 : }
1166 :
1167 : NS_IMETHODIMP
1168 0 : nsUrlClassifierLookupCallback::CompletionV2(const nsACString& aCompleteHash,
1169 : const nsACString& aTableName,
1170 : uint32_t aChunkId)
1171 : {
1172 0 : LOG(("nsUrlClassifierLookupCallback::Completion [%p, %s, %d]",
1173 : this, PromiseFlatCString(aTableName).get(), aChunkId));
1174 :
1175 0 : MOZ_ASSERT(!StringEndsWith(aTableName, NS_LITERAL_CSTRING("-proto")));
1176 :
1177 0 : RefPtr<CacheResultV2> result = new CacheResultV2();
1178 :
1179 0 : result->table = aTableName;
1180 0 : result->prefix.Assign(aCompleteHash);
1181 0 : result->completion.Assign(aCompleteHash);
1182 0 : result->addChunk = aChunkId;
1183 :
1184 0 : return ProcessComplete(result);
1185 : }
1186 :
1187 : NS_IMETHODIMP
1188 0 : nsUrlClassifierLookupCallback::CompletionV4(const nsACString& aPartialHash,
1189 : const nsACString& aTableName,
1190 : uint32_t aNegativeCacheDuration,
1191 : nsIArray* aFullHashes)
1192 : {
1193 0 : LOG(("nsUrlClassifierLookupCallback::CompletionV4 [%p, %s, %d]",
1194 : this, PromiseFlatCString(aTableName).get(), aNegativeCacheDuration));
1195 :
1196 0 : MOZ_ASSERT(StringEndsWith(aTableName, NS_LITERAL_CSTRING("-proto")));
1197 :
1198 0 : if(!aFullHashes) {
1199 : return NS_ERROR_INVALID_ARG;
1200 : }
1201 :
1202 0 : if (aNegativeCacheDuration > MAXIMUM_NEGATIVE_CACHE_DURATION_SEC) {
1203 0 : LOG(("Negative cache duration too large, clamping it down to"
1204 : "a reasonable value."));
1205 : aNegativeCacheDuration = MAXIMUM_NEGATIVE_CACHE_DURATION_SEC;
1206 : }
1207 :
1208 0 : RefPtr<CacheResultV4> result = new CacheResultV4();
1209 :
1210 0 : int64_t nowSec = PR_Now() / PR_USEC_PER_SEC;
1211 :
1212 0 : result->table = aTableName;
1213 0 : result->prefix.Assign(aPartialHash);
1214 0 : result->response.negativeCacheExpirySec = nowSec + aNegativeCacheDuration;
1215 :
1216 : // Fill in positive cache entries.
1217 0 : uint32_t fullHashCount = 0;
1218 0 : nsresult rv = aFullHashes->GetLength(&fullHashCount);
1219 0 : if (NS_FAILED(rv)) {
1220 : return rv;
1221 : }
1222 :
1223 0 : for (uint32_t i = 0; i < fullHashCount; i++) {
1224 0 : nsCOMPtr<nsIFullHashMatch> match = do_QueryElementAt(aFullHashes, i);
1225 :
1226 0 : nsCString fullHash;
1227 0 : match->GetFullHash(fullHash);
1228 :
1229 : uint32_t duration;
1230 0 : match->GetCacheDuration(&duration);
1231 :
1232 0 : result->response.fullHashes.Put(fullHash, nowSec + duration);
1233 : }
1234 :
1235 0 : return ProcessComplete(result);
1236 : }
1237 :
1238 : nsresult
1239 0 : nsUrlClassifierLookupCallback::ProcessComplete(RefPtr<CacheResult> aCacheResult)
1240 : {
1241 0 : NS_ENSURE_ARG_POINTER(mResults);
1242 :
1243 : // OK if this fails, we just won't cache the item.
1244 0 : mCacheResults.AppendElement(aCacheResult, fallible);
1245 :
1246 : // Check if this matched any of our results.
1247 0 : for (const auto& result : *mResults) {
1248 : // Now, see if it verifies a lookup
1249 0 : if (!result->mNoise
1250 0 : && result->mTableName.Equals(aCacheResult->table)
1251 0 : && aCacheResult->findCompletion(result->CompleteHash())) {
1252 0 : result->mProtocolConfirmed = true;
1253 : }
1254 : }
1255 :
1256 0 : return NS_OK;
1257 : }
1258 :
1259 : nsresult
1260 0 : nsUrlClassifierLookupCallback::HandleResults()
1261 : {
1262 0 : if (!mResults) {
1263 : // No results, this URI is clean.
1264 0 : LOG(("nsUrlClassifierLookupCallback::HandleResults [%p, no results]", this));
1265 0 : return mCallback->HandleEvent(NS_LITERAL_CSTRING(""));
1266 : }
1267 0 : MOZ_ASSERT(mPendingCompletions == 0, "HandleResults() should never be "
1268 : "called while there are pending completions");
1269 :
1270 0 : LOG(("nsUrlClassifierLookupCallback::HandleResults [%p, %zu results]",
1271 : this, mResults->Length()));
1272 :
1273 : nsCOMPtr<nsIUrlClassifierClassifyCallback> classifyCallback =
1274 0 : do_QueryInterface(mCallback);
1275 :
1276 0 : nsTArray<nsCString> tables;
1277 : // Build a stringified list of result tables.
1278 0 : for (const auto& result : *mResults) {
1279 : // Leave out results that weren't confirmed, as their existence on
1280 : // the list can't be verified. Also leave out randomly-generated
1281 : // noise.
1282 0 : if (result->mNoise) {
1283 0 : LOG(("Skipping result %s from table %s (noise)",
1284 : result->PartialHashHex().get(), result->mTableName.get()));
1285 : continue;
1286 : }
1287 :
1288 0 : if (!result->Confirmed()) {
1289 0 : LOG(("Skipping result %s from table %s (not confirmed)",
1290 : result->PartialHashHex().get(), result->mTableName.get()));
1291 : continue;
1292 : }
1293 :
1294 0 : LOG(("Confirmed result %s from table %s",
1295 : result->PartialHashHex().get(), result->mTableName.get()));
1296 :
1297 0 : if (tables.IndexOf(result->mTableName) == nsTArray<nsCString>::NoIndex) {
1298 0 : tables.AppendElement(result->mTableName);
1299 : }
1300 :
1301 0 : if (classifyCallback) {
1302 0 : nsCString fullHashString;
1303 0 : result->hash.complete.ToString(fullHashString);
1304 0 : classifyCallback->HandleResult(result->mTableName, fullHashString);
1305 : }
1306 : }
1307 :
1308 : // Some parts of this gethash request generated no hits at all.
1309 : // Save the prefixes we checked to prevent repeated requests.
1310 0 : CacheMisses();
1311 :
1312 : // This hands ownership of the cache results array back to the worker
1313 : // thread.
1314 0 : mDBService->CacheCompletions(mCacheResults);
1315 0 : mCacheResults.Clear();
1316 :
1317 0 : nsAutoCString tableStr;
1318 0 : for (uint32_t i = 0; i < tables.Length(); i++) {
1319 0 : if (i != 0)
1320 0 : tableStr.Append(',');
1321 0 : tableStr.Append(tables[i]);
1322 : }
1323 :
1324 0 : return mCallback->HandleEvent(tableStr);
1325 : }
1326 :
1327 : nsresult
1328 0 : nsUrlClassifierLookupCallback::CacheMisses()
1329 : {
1330 0 : MOZ_ASSERT(mResults);
1331 :
1332 0 : for (const RefPtr<const LookupResult> result : *mResults) {
1333 : // Skip V4 because cache information is already included in the
1334 : // fullhash response so we don't need to manually add it here.
1335 0 : if (!result->mProtocolV2 || result->Confirmed() || result->mNoise) {
1336 0 : continue;
1337 : }
1338 :
1339 0 : RefPtr<CacheResultV2> cacheResult = new CacheResultV2();
1340 :
1341 0 : cacheResult->table = result->mTableName;
1342 0 : cacheResult->prefix = result->hash.fixedLengthPrefix;
1343 0 : cacheResult->miss = true;
1344 0 : if (!mCacheResults.AppendElement(cacheResult, fallible)) {
1345 0 : return NS_ERROR_OUT_OF_MEMORY;
1346 : }
1347 : }
1348 0 : return NS_OK;
1349 : }
1350 :
1351 0 : struct Provider {
1352 : nsCString name;
1353 : uint8_t priority;
1354 : };
1355 :
1356 : // Order matters
1357 : // Provider which is not included in this table has the lowest priority 0
1358 1 : static const Provider kBuiltInProviders[] = {
1359 0 : { NS_LITERAL_CSTRING("mozilla"), 1 },
1360 0 : { NS_LITERAL_CSTRING("google4"), 2 },
1361 0 : { NS_LITERAL_CSTRING("google"), 3 },
1362 1 : };
1363 :
1364 : // -------------------------------------------------------------------------
1365 : // Helper class for nsIURIClassifier implementation, handle classify result and
1366 : // send back to nsIURIClassifier
1367 :
1368 : class nsUrlClassifierClassifyCallback final : public nsIUrlClassifierCallback,
1369 : public nsIUrlClassifierClassifyCallback
1370 : {
1371 : public:
1372 : NS_DECL_THREADSAFE_ISUPPORTS
1373 : NS_DECL_NSIURLCLASSIFIERCALLBACK
1374 : NS_DECL_NSIURLCLASSIFIERCLASSIFYCALLBACK
1375 :
1376 0 : explicit nsUrlClassifierClassifyCallback(nsIURIClassifierCallback *c)
1377 0 : : mCallback(c)
1378 0 : {}
1379 :
1380 : private:
1381 :
1382 0 : struct ClassifyMatchedInfo {
1383 : nsCString table;
1384 : nsCString fullhash;
1385 : Provider provider;
1386 : nsresult errorCode;
1387 : };
1388 :
1389 0 : ~nsUrlClassifierClassifyCallback() {};
1390 :
1391 : nsCOMPtr<nsIURIClassifierCallback> mCallback;
1392 : nsTArray<ClassifyMatchedInfo> mMatchedArray;
1393 : };
1394 :
1395 0 : NS_IMPL_ISUPPORTS(nsUrlClassifierClassifyCallback,
1396 : nsIUrlClassifierCallback,
1397 : nsIUrlClassifierClassifyCallback)
1398 :
1399 : NS_IMETHODIMP
1400 0 : nsUrlClassifierClassifyCallback::HandleEvent(const nsACString& tables)
1401 : {
1402 0 : nsresult response = TablesToResponse(tables);
1403 0 : ClassifyMatchedInfo* matchedInfo = nullptr;
1404 :
1405 0 : if (NS_FAILED(response)) {
1406 : // Filter all matched info which has correct response
1407 : // In the case multiple tables found, use the higher priority provider
1408 0 : nsTArray<ClassifyMatchedInfo> matches;
1409 0 : for (uint32_t i = 0; i < mMatchedArray.Length(); i++) {
1410 0 : if (mMatchedArray[i].errorCode == response &&
1411 0 : (!matchedInfo ||
1412 0 : matchedInfo->provider.priority < mMatchedArray[i].provider.priority)) {
1413 0 : matchedInfo = &mMatchedArray[i];
1414 : }
1415 : }
1416 : }
1417 :
1418 0 : nsCString provider = matchedInfo ? matchedInfo->provider.name : EmptyCString();
1419 0 : nsCString fullhash = matchedInfo ? matchedInfo->fullhash : EmptyCString();
1420 0 : nsCString table = matchedInfo ? matchedInfo->table : EmptyCString();
1421 :
1422 0 : mCallback->OnClassifyComplete(response, table, provider, fullhash);
1423 0 : return NS_OK;
1424 : }
1425 :
1426 : NS_IMETHODIMP
1427 0 : nsUrlClassifierClassifyCallback::HandleResult(const nsACString& aTable,
1428 : const nsACString& aFullHash)
1429 : {
1430 0 : LOG(("nsUrlClassifierClassifyCallback::HandleResult [%p, table %s full hash %s]",
1431 : this, PromiseFlatCString(aTable).get(), PromiseFlatCString(aFullHash).get()));
1432 :
1433 0 : if (NS_WARN_IF(aTable.IsEmpty()) || NS_WARN_IF(aFullHash.IsEmpty())) {
1434 : return NS_ERROR_INVALID_ARG;
1435 : }
1436 :
1437 0 : ClassifyMatchedInfo* matchedInfo = mMatchedArray.AppendElement();
1438 0 : matchedInfo->table = aTable;
1439 0 : matchedInfo->fullhash = aFullHash;
1440 :
1441 : nsCOMPtr<nsIUrlClassifierUtils> urlUtil =
1442 0 : do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
1443 :
1444 0 : nsCString provider;
1445 0 : nsresult rv = urlUtil->GetProvider(aTable, provider);
1446 :
1447 0 : matchedInfo->provider.name = NS_SUCCEEDED(rv) ? provider : EmptyCString();
1448 0 : matchedInfo->provider.priority = 0;
1449 0 : for (uint8_t i = 0; i < ArrayLength(kBuiltInProviders); i++) {
1450 0 : if (kBuiltInProviders[i].name.Equals(matchedInfo->provider.name)) {
1451 0 : matchedInfo->provider.priority = kBuiltInProviders[i].priority;
1452 : }
1453 : }
1454 0 : matchedInfo->errorCode = TablesToResponse(aTable);
1455 :
1456 0 : return NS_OK;
1457 : }
1458 :
1459 : // -------------------------------------------------------------------------
1460 : // Proxy class implementation
1461 :
1462 106 : NS_IMPL_ADDREF(nsUrlClassifierDBService)
1463 54 : NS_IMPL_RELEASE(nsUrlClassifierDBService)
1464 0 : NS_INTERFACE_MAP_BEGIN(nsUrlClassifierDBService)
1465 : // Only nsIURIClassifier is supported in the content process!
1466 0 : NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIUrlClassifierDBService, XRE_IsParentProcess())
1467 26 : NS_INTERFACE_MAP_ENTRY(nsIURIClassifier)
1468 22 : NS_INTERFACE_MAP_ENTRY(nsIUrlClassifierInfo)
1469 0 : NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIObserver, XRE_IsParentProcess())
1470 11 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIClassifier)
1471 0 : NS_INTERFACE_MAP_END
1472 :
1473 : /* static */ nsUrlClassifierDBService*
1474 0 : nsUrlClassifierDBService::GetInstance(nsresult *result)
1475 : {
1476 0 : *result = NS_OK;
1477 0 : if (!sUrlClassifierDBService) {
1478 0 : sUrlClassifierDBService = new (fallible) nsUrlClassifierDBService();
1479 1 : if (!sUrlClassifierDBService) {
1480 0 : *result = NS_ERROR_OUT_OF_MEMORY;
1481 0 : return nullptr;
1482 : }
1483 :
1484 1 : NS_ADDREF(sUrlClassifierDBService); // addref the global
1485 :
1486 1 : *result = sUrlClassifierDBService->Init();
1487 1 : if (NS_FAILED(*result)) {
1488 0 : NS_RELEASE(sUrlClassifierDBService);
1489 0 : return nullptr;
1490 : }
1491 : } else {
1492 : // Already exists, just add a ref
1493 0 : NS_ADDREF(sUrlClassifierDBService); // addref the return result
1494 : }
1495 0 : return sUrlClassifierDBService;
1496 : }
1497 :
1498 :
1499 1 : nsUrlClassifierDBService::nsUrlClassifierDBService()
1500 : : mCheckMalware(CHECK_MALWARE_DEFAULT)
1501 : , mCheckPhishing(CHECK_PHISHING_DEFAULT)
1502 : , mCheckBlockedURIs(CHECK_BLOCKED_DEFAULT)
1503 0 : , mInUpdate(false)
1504 : {
1505 0 : }
1506 :
1507 0 : nsUrlClassifierDBService::~nsUrlClassifierDBService()
1508 : {
1509 0 : sUrlClassifierDBService = nullptr;
1510 0 : }
1511 :
1512 : void
1513 0 : AppendTables(const nsCString& aTables, nsCString &outTables)
1514 : {
1515 0 : if (!aTables.IsEmpty()) {
1516 0 : if (!outTables.IsEmpty()) {
1517 8 : outTables.Append(',');
1518 : }
1519 9 : outTables.Append(aTables);
1520 : }
1521 9 : }
1522 :
1523 : nsresult
1524 1 : nsUrlClassifierDBService::ReadTablesFromPrefs()
1525 : {
1526 0 : mCheckMalware = Preferences::GetBool(CHECK_MALWARE_PREF,
1527 : CHECK_MALWARE_DEFAULT);
1528 1 : mCheckPhishing = Preferences::GetBool(CHECK_PHISHING_PREF,
1529 : CHECK_PHISHING_DEFAULT);
1530 0 : mCheckBlockedURIs = Preferences::GetBool(CHECK_BLOCKED_PREF,
1531 : CHECK_BLOCKED_DEFAULT);
1532 :
1533 2 : nsAutoCString allTables;
1534 0 : nsAutoCString tables;
1535 :
1536 0 : mBaseTables.Truncate();
1537 0 : mTrackingProtectionTables.Truncate();
1538 :
1539 1 : Preferences::GetCString(PHISH_TABLE_PREF, allTables);
1540 0 : if (mCheckPhishing) {
1541 0 : AppendTables(allTables, mBaseTables);
1542 : }
1543 :
1544 0 : Preferences::GetCString(MALWARE_TABLE_PREF, tables);
1545 1 : AppendTables(tables, allTables);
1546 0 : if (mCheckMalware) {
1547 0 : AppendTables(tables, mBaseTables);
1548 : }
1549 :
1550 1 : Preferences::GetCString(BLOCKED_TABLE_PREF, tables);
1551 0 : AppendTables(tables, allTables);
1552 1 : if (mCheckBlockedURIs) {
1553 0 : AppendTables(tables, mBaseTables);
1554 : }
1555 :
1556 1 : Preferences::GetCString(DOWNLOAD_BLOCK_TABLE_PREF, tables);
1557 0 : AppendTables(tables, allTables);
1558 :
1559 1 : Preferences::GetCString(DOWNLOAD_ALLOW_TABLE_PREF, tables);
1560 0 : AppendTables(tables, allTables);
1561 :
1562 1 : Preferences::GetCString(PASSWORD_ALLOW_TABLE_PREF, tables);
1563 0 : AppendTables(tables, allTables);
1564 :
1565 1 : Preferences::GetCString(TRACKING_TABLE_PREF, tables);
1566 0 : AppendTables(tables, allTables);
1567 0 : AppendTables(tables, mTrackingProtectionTables);
1568 :
1569 1 : Preferences::GetCString(TRACKING_WHITELIST_TABLE_PREF, tables);
1570 1 : AppendTables(tables, allTables);
1571 0 : AppendTables(tables, mTrackingProtectionTables);
1572 :
1573 0 : Classifier::SplitTables(allTables, mGethashTables);
1574 :
1575 1 : Preferences::GetCString(DISALLOW_COMPLETION_TABLE_PREF, tables);
1576 1 : Classifier::SplitTables(tables, mDisallowCompletionsTables);
1577 :
1578 0 : return NS_OK;
1579 : }
1580 :
1581 : nsresult
1582 1 : nsUrlClassifierDBService::Init()
1583 : {
1584 0 : MOZ_ASSERT(NS_IsMainThread(), "Must initialize DB service on main thread");
1585 2 : nsCOMPtr<nsIXULRuntime> appInfo = do_GetService("@mozilla.org/xre/app-info;1");
1586 0 : if (appInfo) {
1587 0 : bool inSafeMode = false;
1588 1 : appInfo->GetInSafeMode(&inSafeMode);
1589 0 : if (inSafeMode) {
1590 0 : return NS_ERROR_NOT_AVAILABLE;
1591 : }
1592 : }
1593 :
1594 0 : switch (XRE_GetProcessType()) {
1595 : case GeckoProcessType_Default:
1596 : // The parent process is supported.
1597 : break;
1598 : case GeckoProcessType_Content:
1599 : // In a content process, we simply forward all requests to the parent process,
1600 : // so we can skip the initialization steps here.
1601 : // Note that since we never register an observer, Shutdown() will also never
1602 : // be called in the content process.
1603 : return NS_OK;
1604 : default:
1605 : // No other process type is supported!
1606 0 : return NS_ERROR_NOT_AVAILABLE;
1607 : }
1608 :
1609 0 : sGethashNoise = Preferences::GetUint(GETHASH_NOISE_PREF,
1610 : GETHASH_NOISE_DEFAULT);
1611 0 : ReadTablesFromPrefs();
1612 : nsresult rv;
1613 :
1614 : {
1615 : // Force nsIUrlClassifierUtils loading on main thread.
1616 : nsCOMPtr<nsIUrlClassifierUtils> dummy =
1617 0 : do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID, &rv);
1618 1 : NS_ENSURE_SUCCESS(rv, rv);
1619 : }
1620 :
1621 : // Directory providers must also be accessed on the main thread.
1622 2 : nsCOMPtr<nsIFile> cacheDir;
1623 1 : rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
1624 2 : getter_AddRefs(cacheDir));
1625 1 : if (NS_FAILED(rv)) {
1626 0 : rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1627 0 : getter_AddRefs(cacheDir));
1628 0 : if (NS_FAILED(rv)) {
1629 : return rv;
1630 : }
1631 : }
1632 :
1633 : // Start the background thread.
1634 1 : rv = NS_NewNamedThread("URL Classifier", &gDbBackgroundThread);
1635 1 : if (NS_FAILED(rv))
1636 : return rv;
1637 :
1638 0 : mWorker = new (fallible) nsUrlClassifierDBServiceWorker();
1639 2 : if (!mWorker) {
1640 : return NS_ERROR_OUT_OF_MEMORY;
1641 : }
1642 :
1643 1 : rv = mWorker->Init(sGethashNoise, cacheDir, this);
1644 0 : if (NS_FAILED(rv)) {
1645 0 : mWorker = nullptr;
1646 0 : return rv;
1647 : }
1648 :
1649 : // Proxy for calling the worker on the background thread
1650 0 : mWorkerProxy = new UrlClassifierDBServiceWorkerProxy(mWorker);
1651 0 : rv = mWorkerProxy->OpenDb();
1652 0 : if (NS_FAILED(rv)) {
1653 : return rv;
1654 : }
1655 :
1656 : // Add an observer for shutdown
1657 : nsCOMPtr<nsIObserverService> observerService =
1658 2 : mozilla::services::GetObserverService();
1659 1 : if (!observerService)
1660 : return NS_ERROR_FAILURE;
1661 :
1662 : // The application is about to quit
1663 1 : observerService->AddObserver(this, "quit-application", false);
1664 1 : observerService->AddObserver(this, "profile-before-change", false);
1665 :
1666 : // XXX: Do we *really* need to be able to change all of these at runtime?
1667 : // Note: These observers should only be added when everything else above has
1668 : // succeeded. Failing to do so can cause long shutdown times in certain
1669 : // situations. See Bug 1247798 and Bug 1244803.
1670 : Preferences::AddUintVarCache(&sGethashNoise, GETHASH_NOISE_PREF,
1671 0 : GETHASH_NOISE_DEFAULT);
1672 :
1673 1 : for (uint8_t i = 0; i < kObservedPrefs.Length(); i++) {
1674 22 : Preferences::AddStrongObserver(this, kObservedPrefs[i].get());
1675 : }
1676 :
1677 : return NS_OK;
1678 : }
1679 :
1680 : // nsChannelClassifier is the only consumer of this interface.
1681 : NS_IMETHODIMP
1682 2 : nsUrlClassifierDBService::Classify(nsIPrincipal* aPrincipal,
1683 : nsIEventTarget* aEventTarget,
1684 : bool aTrackingProtectionEnabled,
1685 : nsIURIClassifierCallback* c,
1686 : bool* result)
1687 : {
1688 2 : NS_ENSURE_ARG(aPrincipal);
1689 :
1690 0 : if (XRE_IsContentProcess()) {
1691 : using namespace mozilla::dom;
1692 :
1693 0 : ContentChild* content = ContentChild::GetSingleton();
1694 0 : MOZ_ASSERT(content);
1695 :
1696 : auto actor = static_cast<URLClassifierChild*>
1697 0 : (content->AllocPURLClassifierChild(IPC::Principal(aPrincipal),
1698 : aTrackingProtectionEnabled,
1699 0 : result));
1700 0 : MOZ_ASSERT(actor);
1701 :
1702 0 : if (aEventTarget) {
1703 0 : content->SetEventTargetForActor(actor, aEventTarget);
1704 : } else {
1705 : // In the case null event target we should use systemgroup event target
1706 0 : NS_WARNING(("Null event target, we should use SystemGroup to do labelling"));
1707 : nsCOMPtr<nsIEventTarget> systemGroupEventTarget
1708 0 : = mozilla::SystemGroup::EventTargetFor(mozilla::TaskCategory::Other);
1709 0 : content->SetEventTargetForActor(actor, systemGroupEventTarget);
1710 : }
1711 0 : if (!content->SendPURLClassifierConstructor(actor, IPC::Principal(aPrincipal),
1712 : aTrackingProtectionEnabled,
1713 : result)) {
1714 0 : *result = false;
1715 0 : return NS_ERROR_FAILURE;
1716 : }
1717 :
1718 0 : actor->SetCallback(c);
1719 0 : return NS_OK;
1720 : }
1721 :
1722 2 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
1723 :
1724 0 : if (!(mCheckMalware || mCheckPhishing || aTrackingProtectionEnabled ||
1725 2 : mCheckBlockedURIs)) {
1726 0 : *result = false;
1727 0 : return NS_OK;
1728 : }
1729 :
1730 : RefPtr<nsUrlClassifierClassifyCallback> callback =
1731 0 : new (fallible) nsUrlClassifierClassifyCallback(c);
1732 :
1733 0 : if (!callback) return NS_ERROR_OUT_OF_MEMORY;
1734 :
1735 0 : nsCString tables = mBaseTables;
1736 0 : if (aTrackingProtectionEnabled) {
1737 0 : AppendTables(mTrackingProtectionTables, tables);
1738 : }
1739 :
1740 0 : nsresult rv = LookupURI(aPrincipal, tables, callback, false, result);
1741 0 : if (rv == NS_ERROR_MALFORMED_URI) {
1742 0 : *result = false;
1743 : // The URI had no hostname, don't try to classify it.
1744 0 : return NS_OK;
1745 : }
1746 0 : NS_ENSURE_SUCCESS(rv, rv);
1747 :
1748 : return NS_OK;
1749 : }
1750 :
1751 : NS_IMETHODIMP
1752 0 : nsUrlClassifierDBService::ClassifyLocal(nsIURI *aURI,
1753 : const nsACString& aTables,
1754 : nsACString& aTableResults)
1755 : {
1756 0 : nsTArray<nsCString> results;
1757 0 : ClassifyLocalWithTables(aURI, aTables, results);
1758 :
1759 : // Convert the result array to a comma separated string
1760 0 : aTableResults.AssignLiteral("");
1761 0 : bool first = true;
1762 0 : for (nsCString& result : results) {
1763 0 : if (first) {
1764 : first = false;
1765 : } else {
1766 0 : aTableResults.AppendLiteral(",");
1767 : }
1768 0 : aTableResults.Append(result);
1769 : }
1770 0 : return NS_OK;
1771 : }
1772 :
1773 : NS_IMETHODIMP
1774 0 : nsUrlClassifierDBService::AsyncClassifyLocalWithTables(nsIURI *aURI,
1775 : const nsACString& aTables,
1776 : nsIURIClassifierCallback* aCallback)
1777 : {
1778 0 : MOZ_ASSERT(NS_IsMainThread(), "AsyncClassifyLocalWithTables must be called "
1779 : "on main thread");
1780 :
1781 : // We do this check no matter what process we are in to return
1782 : // error as early as possible.
1783 0 : nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI);
1784 0 : NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
1785 :
1786 0 : nsAutoCString key;
1787 : // Canonicalize the url
1788 : nsCOMPtr<nsIUrlClassifierUtils> utilsService =
1789 0 : do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
1790 0 : nsresult rv = utilsService->GetKeyForURI(uri, key);
1791 0 : NS_ENSURE_SUCCESS(rv, rv);
1792 :
1793 0 : if (XRE_IsContentProcess()) {
1794 : using namespace mozilla::dom;
1795 : using namespace mozilla::ipc;
1796 :
1797 0 : ContentChild* content = ContentChild::GetSingleton();
1798 0 : if (NS_WARN_IF(!content || content->IsShuttingDown())) {
1799 : return NS_ERROR_FAILURE;
1800 : }
1801 :
1802 0 : auto actor = new URLClassifierLocalChild();
1803 :
1804 : // TODO: Bug 1353701 - Supports custom event target for labelling.
1805 : nsCOMPtr<nsIEventTarget> systemGroupEventTarget
1806 0 : = mozilla::SystemGroup::EventTargetFor(mozilla::TaskCategory::Other);
1807 0 : content->SetEventTargetForActor(actor, systemGroupEventTarget);
1808 :
1809 0 : URIParams uri;
1810 0 : SerializeURI(aURI, uri);
1811 0 : nsAutoCString tables(aTables);
1812 0 : if (!content->SendPURLClassifierLocalConstructor(actor, uri, tables)) {
1813 : return NS_ERROR_FAILURE;
1814 : }
1815 :
1816 0 : actor->SetCallback(aCallback);
1817 0 : return NS_OK;
1818 : }
1819 :
1820 0 : if (gShuttingDownThread) {
1821 : return NS_ERROR_ABORT;
1822 : }
1823 :
1824 : using namespace mozilla::Telemetry;
1825 0 : auto startTime = TimeStamp::Now(); // For telemetry.
1826 :
1827 0 : auto worker = mWorker;
1828 0 : nsCString tables(aTables);
1829 :
1830 : // Since aCallback will be passed around threads...
1831 : nsMainThreadPtrHandle<nsIURIClassifierCallback> callback(
1832 : new nsMainThreadPtrHolder<nsIURIClassifierCallback>(
1833 0 : "nsIURIClassifierCallback", aCallback));
1834 :
1835 0 : nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
1836 : "nsUrlClassifierDBService::AsyncClassifyLocalWithTables",
1837 0 : [worker, key, tables, callback, startTime]() -> void {
1838 :
1839 0 : nsCString matchedLists;
1840 0 : LookupResultArray results;
1841 0 : nsresult rv = worker->DoLocalLookup(key, tables, results);
1842 0 : if (NS_SUCCEEDED(rv)) {
1843 0 : for (uint32_t i = 0; i < results.Length(); i++) {
1844 0 : if (i > 0) {
1845 0 : matchedLists.AppendLiteral(",");
1846 : }
1847 0 : matchedLists.Append(results[i]->mTableName);
1848 : }
1849 : }
1850 :
1851 0 : nsCOMPtr<nsIRunnable> cbRunnable = NS_NewRunnableFunction(
1852 : "nsUrlClassifierDBService::AsyncClassifyLocalWithTables",
1853 0 : [callback, matchedLists, startTime]() -> void {
1854 : // Measure the time diff between calling and callback.
1855 0 : AccumulateTimeDelta(Telemetry::URLCLASSIFIER_ASYNC_CLASSIFYLOCAL_TIME,
1856 0 : startTime);
1857 :
1858 : // |callback| is captured as const value so ...
1859 0 : auto cb = const_cast<nsIURIClassifierCallback*>(callback.get());
1860 0 : cb->OnClassifyComplete(NS_OK, // Not used.
1861 : matchedLists,
1862 0 : EmptyCString(), // provider. (Not used)
1863 0 : EmptyCString()); // prefix. (Not used)
1864 0 : });
1865 :
1866 0 : NS_DispatchToMainThread(cbRunnable);
1867 0 : });
1868 :
1869 0 : return gDbBackgroundThread->Dispatch(r, NS_DISPATCH_NORMAL);
1870 : }
1871 :
1872 : NS_IMETHODIMP
1873 0 : nsUrlClassifierDBService::ClassifyLocalWithTables(nsIURI *aURI,
1874 : const nsACString& aTables,
1875 : nsTArray<nsCString>& aTableResults)
1876 : {
1877 0 : MOZ_ASSERT(NS_IsMainThread(), "ClassifyLocalWithTables must be on main thread");
1878 0 : if (gShuttingDownThread) {
1879 : return NS_ERROR_ABORT;
1880 : }
1881 :
1882 : nsresult rv;
1883 0 : if (XRE_IsContentProcess()) {
1884 : using namespace mozilla::dom;
1885 : using namespace mozilla::ipc;
1886 0 : URIParams uri;
1887 0 : SerializeURI(aURI, uri);
1888 0 : nsAutoCString tables(aTables);
1889 0 : bool result = ContentChild::GetSingleton()->SendClassifyLocal(uri, tables,
1890 : &rv,
1891 0 : &aTableResults);
1892 0 : if (result) {
1893 0 : return rv;
1894 : }
1895 : return NS_ERROR_FAILURE;
1896 : }
1897 :
1898 0 : AUTO_PROFILER_LABEL("nsUrlClassifierDBService::ClassifyLocalWithTables",
1899 : OTHER);
1900 0 : Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_CLASSIFYLOCAL_TIME> timer;
1901 :
1902 0 : nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI);
1903 0 : NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
1904 :
1905 0 : nsAutoCString key;
1906 : // Canonicalize the url
1907 : nsCOMPtr<nsIUrlClassifierUtils> utilsService =
1908 0 : do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
1909 0 : rv = utilsService->GetKeyForURI(uri, key);
1910 0 : NS_ENSURE_SUCCESS(rv, rv);
1911 :
1912 0 : LookupResultArray results;
1913 :
1914 : // In unittests, we may not have been initalized, so don't crash.
1915 0 : rv = mWorkerProxy->DoLocalLookup(key, aTables, results);
1916 0 : if (NS_SUCCEEDED(rv)) {
1917 0 : rv = ProcessLookupResults(results, aTableResults);
1918 0 : NS_ENSURE_SUCCESS(rv, rv);
1919 : }
1920 : return NS_OK;
1921 : }
1922 :
1923 : class ThreatHitReportListener final
1924 : : public nsIStreamListener
1925 : {
1926 : public:
1927 : NS_DECL_ISUPPORTS
1928 : NS_DECL_NSIREQUESTOBSERVER
1929 : NS_DECL_NSISTREAMLISTENER
1930 :
1931 0 : ThreatHitReportListener() = default;
1932 :
1933 : private:
1934 : ~ThreatHitReportListener() = default;
1935 : };
1936 :
1937 0 : NS_IMPL_ISUPPORTS(ThreatHitReportListener, nsIStreamListener, nsIRequestObserver)
1938 :
1939 : NS_IMETHODIMP
1940 0 : ThreatHitReportListener::OnStartRequest(nsIRequest* aRequest,
1941 : nsISupports* aContext)
1942 : {
1943 0 : if (!LOG_ENABLED()) {
1944 : return NS_OK; // Nothing to do!
1945 : }
1946 :
1947 0 : nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
1948 0 : NS_ENSURE_TRUE(httpChannel, NS_OK);
1949 :
1950 : nsresult rv, status;
1951 0 : rv = httpChannel->GetStatus(&status);
1952 0 : NS_ENSURE_SUCCESS(rv, NS_OK);
1953 0 : nsAutoCString errorName;
1954 0 : mozilla::GetErrorName(status, errorName);
1955 :
1956 : uint32_t requestStatus;
1957 0 : rv = httpChannel->GetResponseStatus(&requestStatus);
1958 0 : NS_ENSURE_SUCCESS(rv, NS_OK);
1959 :
1960 0 : nsAutoCString spec;
1961 0 : nsCOMPtr<nsIURI> uri;
1962 0 : rv = httpChannel->GetURI(getter_AddRefs(uri));
1963 0 : if (NS_SUCCEEDED(rv) && uri) {
1964 0 : uri->GetAsciiSpec(spec);
1965 : }
1966 : nsCOMPtr<nsIURLFormatter> urlFormatter =
1967 0 : do_GetService("@mozilla.org/toolkit/URLFormatterService;1");
1968 0 : nsAutoString trimmed;
1969 0 : rv = urlFormatter->TrimSensitiveURLs(NS_ConvertUTF8toUTF16(spec), trimmed);
1970 0 : NS_ENSURE_SUCCESS(rv, NS_OK);
1971 :
1972 0 : LOG(("ThreatHitReportListener::OnStartRequest "
1973 : "(status=%s, code=%d, uri=%s, this=%p)", errorName.get(),
1974 : requestStatus, NS_ConvertUTF16toUTF8(trimmed).get(), this));
1975 :
1976 : return NS_OK;
1977 : }
1978 :
1979 : NS_IMETHODIMP
1980 0 : ThreatHitReportListener::OnDataAvailable(nsIRequest* aRequest,
1981 : nsISupports* aContext,
1982 : nsIInputStream* aInputStream,
1983 : uint64_t aOffset,
1984 : uint32_t aCount)
1985 : {
1986 0 : return NS_OK;
1987 : }
1988 :
1989 : NS_IMETHODIMP
1990 0 : ThreatHitReportListener::OnStopRequest(nsIRequest* aRequest,
1991 : nsISupports* aContext,
1992 : nsresult aStatus)
1993 : {
1994 0 : nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
1995 0 : NS_ENSURE_TRUE(httpChannel, aStatus);
1996 :
1997 0 : uint8_t netErrCode = NS_FAILED(aStatus) ?
1998 0 : mozilla::safebrowsing::NetworkErrorToBucket(aStatus) : 0;
1999 0 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::URLCLASSIFIER_THREATHIT_NETWORK_ERROR, netErrCode);
2000 :
2001 : uint32_t requestStatus;
2002 0 : nsresult rv = httpChannel->GetResponseStatus(&requestStatus);
2003 0 : NS_ENSURE_SUCCESS(rv, aStatus);
2004 0 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::URLCLASSIFIER_THREATHIT_REMOTE_STATUS,
2005 0 : mozilla::safebrowsing::HTTPStatusToBucket(requestStatus));
2006 :
2007 0 : if (LOG_ENABLED()) {
2008 0 : nsAutoCString errorName;
2009 0 : mozilla::GetErrorName(aStatus, errorName);
2010 :
2011 0 : nsAutoCString spec;
2012 0 : nsCOMPtr<nsIURI> uri;
2013 0 : rv = httpChannel->GetURI(getter_AddRefs(uri));
2014 0 : if (NS_SUCCEEDED(rv) && uri) {
2015 0 : uri->GetAsciiSpec(spec);
2016 : }
2017 : nsCOMPtr<nsIURLFormatter> urlFormatter =
2018 0 : do_GetService("@mozilla.org/toolkit/URLFormatterService;1");
2019 0 : nsString trimmed;
2020 0 : rv = urlFormatter->TrimSensitiveURLs(NS_ConvertUTF8toUTF16(spec), trimmed);
2021 0 : NS_ENSURE_SUCCESS(rv, aStatus);
2022 :
2023 0 : LOG(("ThreatHitReportListener::OnStopRequest "
2024 : "(status=%s, code=%d, uri=%s, this=%p)", errorName.get(),
2025 : requestStatus, NS_ConvertUTF16toUTF8(trimmed).get(), this));
2026 : }
2027 :
2028 : return aStatus;
2029 : }
2030 :
2031 : NS_IMETHODIMP
2032 0 : nsUrlClassifierDBService::SendThreatHitReport(nsIChannel *aChannel,
2033 : const nsACString& aProvider,
2034 : const nsACString& aList,
2035 : const nsACString& aFullHash)
2036 : {
2037 0 : NS_ENSURE_ARG_POINTER(aChannel);
2038 :
2039 0 : if (aProvider.IsEmpty()) {
2040 0 : LOG(("nsUrlClassifierDBService::SendThreatHitReport missing provider"));
2041 : return NS_ERROR_FAILURE;
2042 : }
2043 0 : if (aList.IsEmpty()) {
2044 0 : LOG(("nsUrlClassifierDBService::SendThreatHitReport missing list"));
2045 : return NS_ERROR_FAILURE;
2046 : }
2047 0 : if (aFullHash.IsEmpty()) {
2048 0 : LOG(("nsUrlClassifierDBService::SendThreatHitReport missing fullhash"));
2049 : return NS_ERROR_FAILURE;
2050 : }
2051 :
2052 : nsPrintfCString reportUrlPref("browser.safebrowsing.provider.%s.dataSharingURL",
2053 0 : PromiseFlatCString(aProvider).get());
2054 :
2055 : nsCOMPtr<nsIURLFormatter> formatter(
2056 0 : do_GetService("@mozilla.org/toolkit/URLFormatterService;1"));
2057 0 : if (!formatter) {
2058 : return NS_ERROR_UNEXPECTED;
2059 : }
2060 :
2061 0 : nsString urlStr;
2062 0 : nsresult rv = formatter->FormatURLPref(NS_ConvertUTF8toUTF16(reportUrlPref), urlStr);
2063 0 : NS_ENSURE_SUCCESS(rv, rv);
2064 :
2065 0 : if (urlStr.IsEmpty() || NS_LITERAL_STRING("about:blank").Equals(urlStr)) {
2066 0 : LOG(("%s is missing a ThreatHit data reporting URL.", PromiseFlatCString(aProvider).get()));
2067 : return NS_OK;
2068 : }
2069 :
2070 : nsCOMPtr<nsIUrlClassifierUtils> utilsService =
2071 0 : do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
2072 0 : if (!utilsService) {
2073 : return NS_ERROR_FAILURE;
2074 : }
2075 :
2076 0 : nsAutoCString reportBody;
2077 0 : rv = utilsService->MakeThreatHitReport(aChannel, aList, aFullHash, reportBody);
2078 0 : NS_ENSURE_SUCCESS(rv, rv);
2079 0 : nsCOMPtr<nsIStringInputStream> sis(do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID));
2080 0 : rv = sis->SetData(reportBody.get(), reportBody.Length());
2081 0 : NS_ENSURE_SUCCESS(rv, rv);
2082 :
2083 0 : LOG(("Sending the following ThreatHit report to %s about %s: %s",
2084 : PromiseFlatCString(aProvider).get(), PromiseFlatCString(aList).get(),
2085 : reportBody.get()));
2086 :
2087 0 : nsCOMPtr<nsIURI> reportURI;
2088 0 : rv = NS_NewURI(getter_AddRefs(reportURI), urlStr);
2089 0 : NS_ENSURE_SUCCESS(rv, rv);
2090 :
2091 : uint32_t loadFlags = nsIRequest::LOAD_ANONYMOUS | // no cookies
2092 : nsIChannel::INHIBIT_CACHING |
2093 0 : nsIChannel::LOAD_BYPASS_CACHE;
2094 :
2095 0 : nsCOMPtr<nsIChannel> reportChannel;
2096 0 : rv = NS_NewChannel(getter_AddRefs(reportChannel),
2097 : reportURI,
2098 : nsContentUtils::GetSystemPrincipal(),
2099 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
2100 : nsIContentPolicy::TYPE_OTHER,
2101 : nullptr, // aPerformanceStorage
2102 : nullptr, // aLoadGroup
2103 : nullptr,
2104 0 : loadFlags);
2105 0 : NS_ENSURE_SUCCESS(rv, rv);
2106 :
2107 0 : nsCOMPtr<nsILoadInfo> loadInfo = reportChannel->GetLoadInfo();
2108 0 : mozilla::OriginAttributes attrs;
2109 0 : attrs.mFirstPartyDomain.AssignLiteral(NECKO_SAFEBROWSING_FIRST_PARTY_DOMAIN);
2110 0 : if (loadInfo) {
2111 0 : loadInfo->SetOriginAttributes(attrs);
2112 : }
2113 :
2114 0 : nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(reportChannel));
2115 0 : NS_ENSURE_TRUE(uploadChannel, NS_ERROR_FAILURE);
2116 0 : rv = uploadChannel->SetUploadStream(sis, NS_LITERAL_CSTRING("application/x-protobuf"), -1);
2117 0 : NS_ENSURE_SUCCESS(rv, rv);
2118 :
2119 0 : nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(reportChannel));
2120 0 : NS_ENSURE_TRUE(httpChannel, NS_ERROR_FAILURE);
2121 0 : rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
2122 0 : NS_ENSURE_SUCCESS(rv, rv);
2123 : // Disable keepalive.
2124 0 : rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Connection"), NS_LITERAL_CSTRING("close"), false);
2125 0 : NS_ENSURE_SUCCESS(rv, rv);
2126 :
2127 0 : RefPtr<ThreatHitReportListener> listener = new ThreatHitReportListener();
2128 0 : rv = reportChannel->AsyncOpen2(listener);
2129 0 : if (NS_FAILED(rv)) {
2130 0 : LOG(("Failure to send Safe Browsing ThreatHit report"));
2131 : return rv;
2132 : }
2133 :
2134 : return NS_OK;
2135 : }
2136 :
2137 :
2138 : NS_IMETHODIMP
2139 0 : nsUrlClassifierDBService::Lookup(nsIPrincipal* aPrincipal,
2140 : const nsACString& tables,
2141 : nsIUrlClassifierCallback* c)
2142 : {
2143 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2144 :
2145 : bool dummy;
2146 0 : return LookupURI(aPrincipal, tables, c, true, &dummy);
2147 : }
2148 :
2149 : nsresult
2150 0 : nsUrlClassifierDBService::LookupURI(nsIPrincipal* aPrincipal,
2151 : const nsACString& tables,
2152 : nsIUrlClassifierCallback* c,
2153 : bool forceLookup,
2154 : bool *didLookup)
2155 : {
2156 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2157 0 : NS_ENSURE_ARG(aPrincipal);
2158 :
2159 0 : if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
2160 0 : *didLookup = false;
2161 0 : return NS_OK;
2162 : }
2163 :
2164 0 : nsCOMPtr<nsIURI> uri;
2165 0 : nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
2166 0 : NS_ENSURE_SUCCESS(rv, rv);
2167 0 : NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
2168 :
2169 0 : uri = NS_GetInnermostURI(uri);
2170 0 : NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
2171 :
2172 0 : nsAutoCString key;
2173 : // Canonicalize the url
2174 : nsCOMPtr<nsIUrlClassifierUtils> utilsService =
2175 0 : do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
2176 0 : rv = utilsService->GetKeyForURI(uri, key);
2177 0 : if (NS_FAILED(rv))
2178 : return rv;
2179 :
2180 0 : if (forceLookup) {
2181 0 : *didLookup = true;
2182 : } else {
2183 0 : bool clean = false;
2184 :
2185 : if (!clean) {
2186 : nsCOMPtr<nsIPermissionManager> permissionManager =
2187 0 : services::GetPermissionManager();
2188 :
2189 0 : if (permissionManager) {
2190 : uint32_t perm;
2191 0 : rv = permissionManager->TestPermissionFromPrincipal(aPrincipal,
2192 0 : "safe-browsing", &perm);
2193 0 : NS_ENSURE_SUCCESS(rv, rv);
2194 :
2195 0 : clean |= (perm == nsIPermissionManager::ALLOW_ACTION);
2196 : }
2197 : }
2198 :
2199 0 : *didLookup = !clean;
2200 0 : if (clean) {
2201 : return NS_OK;
2202 : }
2203 : }
2204 :
2205 : // Create an nsUrlClassifierLookupCallback object. This object will
2206 : // take care of confirming partial hash matches if necessary before
2207 : // calling the client's callback.
2208 : nsCOMPtr<nsIUrlClassifierLookupCallback> callback =
2209 0 : new (fallible) nsUrlClassifierLookupCallback(this, c);
2210 0 : if (!callback) {
2211 : return NS_ERROR_OUT_OF_MEMORY;
2212 : }
2213 :
2214 : nsCOMPtr<nsIUrlClassifierLookupCallback> proxyCallback =
2215 0 : new UrlClassifierLookupCallbackProxy(callback);
2216 :
2217 : // Queue this lookup and call the lookup function to flush the queue if
2218 : // necessary.
2219 0 : rv = mWorker->QueueLookup(key, tables, proxyCallback);
2220 0 : NS_ENSURE_SUCCESS(rv, rv);
2221 :
2222 : // This seems to just call HandlePendingLookups.
2223 0 : nsAutoCString dummy;
2224 0 : return mWorkerProxy->Lookup(nullptr, dummy, nullptr);
2225 : }
2226 :
2227 : NS_IMETHODIMP
2228 0 : nsUrlClassifierDBService::GetTables(nsIUrlClassifierCallback* c)
2229 : {
2230 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2231 :
2232 : // The proxy callback uses the current thread.
2233 : nsCOMPtr<nsIUrlClassifierCallback> proxyCallback =
2234 0 : new UrlClassifierCallbackProxy(c);
2235 :
2236 0 : return mWorkerProxy->GetTables(proxyCallback);
2237 : }
2238 :
2239 : NS_IMETHODIMP
2240 0 : nsUrlClassifierDBService::SetHashCompleter(const nsACString &tableName,
2241 : nsIUrlClassifierHashCompleter *completer)
2242 : {
2243 0 : if (completer) {
2244 0 : mCompleters.Put(tableName, completer);
2245 : } else {
2246 0 : mCompleters.Remove(tableName);
2247 : }
2248 0 : ClearLastResults();
2249 0 : return NS_OK;
2250 : }
2251 :
2252 : NS_IMETHODIMP
2253 0 : nsUrlClassifierDBService::ClearLastResults()
2254 : {
2255 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2256 :
2257 0 : return mWorkerProxy->ClearLastResults();
2258 : }
2259 :
2260 : NS_IMETHODIMP
2261 0 : nsUrlClassifierDBService::BeginUpdate(nsIUrlClassifierUpdateObserver *observer,
2262 : const nsACString &updateTables)
2263 : {
2264 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2265 :
2266 0 : if (mInUpdate) {
2267 0 : LOG(("Already updating, not available"));
2268 : return NS_ERROR_NOT_AVAILABLE;
2269 : }
2270 0 : if (mWorker->IsBusyUpdating()) {
2271 : // |mInUpdate| used to work well because "notifying update observer"
2272 : // is synchronously done in Worker::FinishUpdate(). Even if the
2273 : // update observer hasn't been notified at this point, we can still
2274 : // dispatch BeginUpdate() since it will NOT be run until the
2275 : // previous Worker::FinishUpdate() returns.
2276 : //
2277 : // However, some tasks in Worker::FinishUpdate() have been moved to
2278 : // another thread. The update observer will NOT be notified when
2279 : // Worker::FinishUpdate() returns. If we only check |mInUpdate|,
2280 : // the following sequence might happen on worker thread:
2281 : //
2282 : // Worker::FinishUpdate() // for update 1
2283 : // Worker::BeginUpdate() // for update 2
2284 : // Worker::NotifyUpdateObserver() // for update 1
2285 : //
2286 : // So, we have to find out a way to reject BeginUpdate() right here
2287 : // if the previous update observer hasn't been notified.
2288 : //
2289 : // Directly probing the worker's state is the most lightweight solution.
2290 : // No lock is required since Worker::BeginUpdate() and
2291 : // Worker::NotifyUpdateObserver() are by nature mutual exclusive.
2292 : // (both run on worker thread.)
2293 0 : LOG(("The previous update observer hasn't been notified."));
2294 : return NS_ERROR_NOT_AVAILABLE;
2295 : }
2296 :
2297 0 : mInUpdate = true;
2298 :
2299 : // The proxy observer uses the current thread
2300 : nsCOMPtr<nsIUrlClassifierUpdateObserver> proxyObserver =
2301 0 : new UrlClassifierUpdateObserverProxy(observer);
2302 :
2303 0 : return mWorkerProxy->BeginUpdate(proxyObserver, updateTables);
2304 : }
2305 :
2306 : NS_IMETHODIMP
2307 0 : nsUrlClassifierDBService::BeginStream(const nsACString &table)
2308 : {
2309 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2310 :
2311 0 : return mWorkerProxy->BeginStream(table);
2312 : }
2313 :
2314 : NS_IMETHODIMP
2315 0 : nsUrlClassifierDBService::UpdateStream(const nsACString& aUpdateChunk)
2316 : {
2317 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2318 :
2319 0 : return mWorkerProxy->UpdateStream(aUpdateChunk);
2320 : }
2321 :
2322 : NS_IMETHODIMP
2323 0 : nsUrlClassifierDBService::FinishStream()
2324 : {
2325 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2326 :
2327 0 : return mWorkerProxy->FinishStream();
2328 : }
2329 :
2330 : NS_IMETHODIMP
2331 0 : nsUrlClassifierDBService::FinishUpdate()
2332 : {
2333 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2334 :
2335 0 : mInUpdate = false;
2336 :
2337 0 : return mWorkerProxy->FinishUpdate();
2338 : }
2339 :
2340 :
2341 : NS_IMETHODIMP
2342 0 : nsUrlClassifierDBService::CancelUpdate()
2343 : {
2344 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2345 :
2346 0 : mInUpdate = false;
2347 :
2348 0 : return mWorkerProxy->CancelUpdate();
2349 : }
2350 :
2351 : NS_IMETHODIMP
2352 0 : nsUrlClassifierDBService::ResetDatabase()
2353 : {
2354 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2355 :
2356 0 : if (mWorker->IsBusyUpdating()) {
2357 0 : LOG(("Failed to ResetDatabase because of the unfinished update."));
2358 : return NS_ERROR_FAILURE;
2359 : }
2360 :
2361 0 : return mWorkerProxy->ResetDatabase();
2362 : }
2363 :
2364 : NS_IMETHODIMP
2365 0 : nsUrlClassifierDBService::ReloadDatabase()
2366 : {
2367 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2368 :
2369 0 : if (mWorker->IsBusyUpdating()) {
2370 0 : LOG(("Failed to ReloadDatabase because of the unfinished update."));
2371 : return NS_ERROR_FAILURE;
2372 : }
2373 :
2374 0 : return mWorkerProxy->ReloadDatabase();
2375 : }
2376 :
2377 : NS_IMETHODIMP
2378 0 : nsUrlClassifierDBService::ClearCache()
2379 : {
2380 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2381 :
2382 0 : return mWorkerProxy->ClearCache();
2383 : }
2384 :
2385 :
2386 : NS_IMETHODIMP
2387 0 : nsUrlClassifierDBService::GetCacheInfo(const nsACString& aTable,
2388 : nsIUrlClassifierGetCacheCallback* aCallback)
2389 : {
2390 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2391 :
2392 0 : return mWorkerProxy->GetCacheInfo(aTable, aCallback);
2393 : return NS_OK;
2394 : }
2395 :
2396 : nsresult
2397 0 : nsUrlClassifierDBService::CacheCompletions(const ConstCacheResultArray& results)
2398 : {
2399 0 : NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
2400 :
2401 0 : return mWorkerProxy->CacheCompletions(results);
2402 : }
2403 :
2404 : bool
2405 0 : nsUrlClassifierDBService::CanComplete(const nsACString &aTableName)
2406 : {
2407 0 : return mGethashTables.Contains(aTableName) &&
2408 0 : !mDisallowCompletionsTables.Contains(aTableName);
2409 : }
2410 :
2411 : bool
2412 0 : nsUrlClassifierDBService::GetCompleter(const nsACString &tableName,
2413 : nsIUrlClassifierHashCompleter **completer)
2414 : {
2415 : // If we have specified a completer, go ahead and query it. This is only
2416 : // used by tests.
2417 0 : if (mCompleters.Get(tableName, completer)) {
2418 : return true;
2419 : }
2420 :
2421 0 : if (!CanComplete(tableName)) {
2422 : return false;
2423 : }
2424 :
2425 : // Otherwise, call gethash to find the hash completions.
2426 0 : return NS_SUCCEEDED(CallGetService(NS_URLCLASSIFIERHASHCOMPLETER_CONTRACTID,
2427 : completer));
2428 : }
2429 :
2430 : NS_IMETHODIMP
2431 0 : nsUrlClassifierDBService::Observe(nsISupports *aSubject, const char *aTopic,
2432 : const char16_t *aData)
2433 : {
2434 0 : if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
2435 : nsresult rv;
2436 0 : nsCOMPtr<nsIPrefBranch> prefs(do_QueryInterface(aSubject, &rv));
2437 0 : NS_ENSURE_SUCCESS(rv, rv);
2438 : Unused << prefs;
2439 :
2440 0 : if (kObservedPrefs.Contains(NS_ConvertUTF16toUTF8(aData))) {
2441 0 : ReadTablesFromPrefs();
2442 : }
2443 0 : } else if (!strcmp(aTopic, "quit-application")) {
2444 : // Tell the update thread to finish as soon as possible.
2445 0 : gShuttingDownThread = true;
2446 :
2447 : // The code in ::Shutdown() is run on a 'profile-before-change' event and
2448 : // ensures that objects are freed by blocking on this freeing.
2449 : // We can however speed up the shutdown time by using the worker thread to
2450 : // release, in an earlier event, any objects that cannot affect an ongoing
2451 : // update on the update thread.
2452 0 : PreShutdown();
2453 0 : } else if (!strcmp(aTopic, "profile-before-change")) {
2454 0 : gShuttingDownThread = true;
2455 0 : Shutdown();
2456 : } else {
2457 : return NS_ERROR_UNEXPECTED;
2458 : }
2459 :
2460 : return NS_OK;
2461 : }
2462 :
2463 : // Post a PreShutdown task to worker thread to release objects without blocking
2464 : // main-thread. Notice that shutdown process may still be blocked by PreShutdown task
2465 : // when ::Shutdown() is executed and synchronously waits for worker thread to finish
2466 : // PreShutdown event.
2467 : nsresult
2468 0 : nsUrlClassifierDBService::PreShutdown()
2469 : {
2470 0 : MOZ_ASSERT(XRE_IsParentProcess());
2471 :
2472 0 : if (mWorkerProxy) {
2473 0 : mWorkerProxy->PreShutdown();
2474 : }
2475 :
2476 0 : return NS_OK;
2477 : }
2478 :
2479 : // Join the background thread if it exists.
2480 : nsresult
2481 0 : nsUrlClassifierDBService::Shutdown()
2482 : {
2483 0 : LOG(("shutting down db service\n"));
2484 0 : MOZ_ASSERT(XRE_IsParentProcess());
2485 :
2486 0 : if (!gDbBackgroundThread) {
2487 : return NS_OK;
2488 : }
2489 :
2490 0 : Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_SHUTDOWN_TIME> timer;
2491 :
2492 0 : mCompleters.Clear();
2493 :
2494 0 : nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
2495 0 : if (prefs) {
2496 0 : for (uint8_t i = 0; i < kObservedPrefs.Length(); i++) {
2497 0 : prefs->RemoveObserver(kObservedPrefs[i].get(), this);
2498 : }
2499 : }
2500 :
2501 : // 1. Synchronize with worker thread and update thread by
2502 : // *synchronously* dispatching an event to worker thread
2503 : // for shutting down the update thread. The reason not
2504 : // shutting down update thread directly from main thread
2505 : // is to avoid racing for Classifier::mUpdateThread
2506 : // between main thread and the worker thread. (Both threads
2507 : // would access Classifier::mUpdateThread.)
2508 0 : if (mWorker->IsDBOpened()) {
2509 : using Worker = nsUrlClassifierDBServiceWorker;
2510 0 : RefPtr<nsIRunnable> r = NewRunnableMethod(
2511 : "nsUrlClassifierDBServiceWorker::FlushAndDisableAsyncUpdate",
2512 : mWorker,
2513 0 : &Worker::FlushAndDisableAsyncUpdate);
2514 0 : SyncRunnable::DispatchToThread(gDbBackgroundThread, r);
2515 : }
2516 : // At this point the update thread has been shut down and
2517 : // the worker thread should only have at most one event,
2518 : // which is the callback event.
2519 :
2520 : // 2. Send CancelUpdate() event to notify the dangling update.
2521 : // (i.e. BeginUpdate is called but FinishUpdate is not.)
2522 : // and CloseDb() to clear mClassifier. They will be the last two
2523 : // events on the worker thread in the shutdown process.
2524 0 : DebugOnly<nsresult> rv;
2525 0 : rv = mWorkerProxy->CancelUpdate();
2526 0 : MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to post 'cancel update' event");
2527 0 : rv = mWorkerProxy->CloseDb();
2528 0 : MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to post 'close db' event");
2529 0 : mWorkerProxy = nullptr;
2530 :
2531 : // 3. Invalidate XPCOM APIs by nulling out gDbBackgroundThread
2532 : // since every API checks gDbBackgroundThread first. This has
2533 : // to be done before calling nsIThread.shutdown because it
2534 : // will cause the pending events on the joining thread to
2535 : // be processed.
2536 0 : nsIThread *backgroundThread = nullptr;
2537 0 : Swap(backgroundThread, gDbBackgroundThread);
2538 :
2539 : // 4. Wait until the worker thread is down.
2540 0 : if (backgroundThread) {
2541 0 : backgroundThread->Shutdown();
2542 0 : NS_RELEASE(backgroundThread);
2543 : }
2544 :
2545 0 : mWorker = nullptr;
2546 0 : return NS_OK;
2547 : }
2548 :
2549 : nsIThread*
2550 0 : nsUrlClassifierDBService::BackgroundThread()
2551 : {
2552 1 : return gDbBackgroundThread;
2553 : }
2554 :
2555 : // static
2556 : bool
2557 0 : nsUrlClassifierDBService::ShutdownHasStarted()
2558 : {
2559 0 : return gShuttingDownThread;
2560 : }
|