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