Coverage Report

Created: 2018-09-25 14:53

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