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