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