!import
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1 //@line 37 "/home/visbrero/mnt/roisin/rev_control/hg/mozilla/toolkit/components/url-classifier/src/nsUrlClassifierListManager.js"
2
3 const Cc = Components.classes;
4 const Ci = Components.interfaces;
5
6 //@line 37 "/home/visbrero/mnt/roisin/rev_control/hg/mozilla/toolkit/components/url-classifier/content/listmanager.js"
7
8
9 // A class that manages lists, namely white and black lists for
10 // phishing or malware protection. The ListManager knows how to fetch,
11 // update, and store lists.
12 //
13 // There is a single listmanager for the whole application.
14 //
15 // TODO more comprehensive update tests, for example add unittest check
16 // that the listmanagers tables are properly written on updates
17
18 // How frequently we check for updates (30 minutes)
19 const kUpdateInterval = 30 * 60 * 1000;
20
QueryAdapter
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
21 function QueryAdapter(callback) {
22 this.callback_ = callback;
23 };
24
handleResponse
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
25 QueryAdapter.prototype.handleResponse = function(value) {
26 this.callback_.handleEvent(value);
27 }
28
29 /**
30 * A ListManager keeps track of black and white lists and knows
31 * how to update them.
32 *
33 * @constructor
34 */
PROT_ListManager
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
35 function PROT_ListManager() {
36 this.debugZone = "listmanager";
37 G_debugService.enableZone(this.debugZone);
38
39 this.currentUpdateChecker_ = null; // set when we toggle updates
40 this.prefs_ = new G_Preferences();
41
42 this.updateserverURL_ = null;
43 this.gethashURL_ = null;
44
45 this.isTesting_ = false;
46
47 this.tablesData = {};
48
49 this.observerServiceObserver_ = new G_ObserverServiceObserver(
50 'xpcom-shutdown',
51 BindToObject(this.shutdown_, this),
52 true /*only once*/);
53
54 // Lazily create the key manager (to avoid fetching keys when they
55 // aren't needed).
56 this.keyManager_ = null;
57
58 this.rekeyObserver_ = new G_ObserverServiceObserver(
59 'url-classifier-rekey-requested',
60 BindToObject(this.rekey_, this),
61 false);
62 this.updateWaitingForKey_ = false;
63
64 this.cookieObserver_ = new G_ObserverServiceObserver(
65 'cookie-changed',
66 BindToObject(this.cookieChanged_, this),
67 false);
68
69 /* Backoff interval should be between 30 and 60 minutes. */
70 var backoffInterval = 30 * 60 * 1000;
71 backoffInterval += Math.floor(Math.random() * (30 * 60 * 1000));
72
73 this.requestBackoff_ = new RequestBackoff(2 /* max errors */,
74 60*1000 /* retry interval, 1 min */,
75 4 /* num requests */,
76 60*60*1000 /* request time, 60 min */,
77 backoffInterval /* backoff interval, 60 min */,
78 8*60*60*1000 /* max backoff, 8hr */);
79
80 this.dbService_ = Cc["@mozilla.org/url-classifier/dbservice;1"]
81 .getService(Ci.nsIUrlClassifierDBService);
82
83 this.hashCompleter_ = Cc["@mozilla.org/url-classifier/hashcompleter;1"]
84 .getService(Ci.nsIUrlClassifierHashCompleter);
85 }
86
87 /**
88 * xpcom-shutdown callback
89 * Delete all of our data tables which seem to leak otherwise.
90 */
shutdown_
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
91 PROT_ListManager.prototype.shutdown_ = function() {
92 for (var name in this.tablesData) {
93 delete this.tablesData[name];
94 }
95 }
96
97 /**
98 * Set the url we check for updates. If the new url is valid and different,
99 * update our table list.
100 *
101 * After setting the update url, the caller is responsible for registering
102 * tables and then toggling update checking. All the code for this logic is
103 * currently in browser/components/safebrowsing. Maybe it should be part of
104 * the listmanger?
105 */
setUpdateUrl
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
106 PROT_ListManager.prototype.setUpdateUrl = function(url) {
107 G_Debug(this, "Set update url: " + url);
108 if (url != this.updateserverURL_) {
109 this.updateserverURL_ = url;
110 this.requestBackoff_.reset();
111
112 // Remove old tables which probably aren't valid for the new provider.
113 for (var name in this.tablesData) {
114 delete this.tablesData[name];
115 }
116 }
117 }
118
119 /**
120 * Set the gethash url.
121 */
setGethashUrl
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
122 PROT_ListManager.prototype.setGethashUrl = function(url) {
123 G_Debug(this, "Set gethash url: " + url);
124 if (url != this.gethashURL_) {
125 this.gethashURL_ = url;
126 this.hashCompleter_.gethashUrl = url;
127 }
128 }
129
130 /**
131 * Set the crypto key url.
132 * @param url String
133 */
setKeyUrl
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
134 PROT_ListManager.prototype.setKeyUrl = function(url) {
135 G_Debug(this, "Set key url: " + url);
136 if (!this.keyManager_) {
137 this.keyManager_ = new PROT_UrlCryptoKeyManager();
138 this.keyManager_.onNewKey(BindToObject(this.newKey_, this));
139
140 this.hashCompleter_.setKeys(this.keyManager_.getClientKey(),
141 this.keyManager_.getWrappedKey());
142 }
143
144 this.keyManager_.setKeyUrl(url);
145 }
146
147 /**
148 * Register a new table table
149 * @param tableName - the name of the table
150 * @param opt_requireMac true if a mac is required on update, false otherwise
151 * @returns true if the table could be created; false otherwise
152 */
registerTable
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
153 PROT_ListManager.prototype.registerTable = function(tableName,
154 opt_requireMac) {
155 this.tablesData[tableName] = {};
156 this.tablesData[tableName].needsUpdate = false;
157
158 return true;
159 }
160
161 /**
162 * Enable updates for some tables
163 * @param tables - an array of table names that need updating
164 */
enableUpdate
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
165 PROT_ListManager.prototype.enableUpdate = function(tableName) {
166 var changed = false;
167 var table = this.tablesData[tableName];
168 if (table) {
169 G_Debug(this, "Enabling table updates for " + tableName);
170 table.needsUpdate = true;
171 changed = true;
172 }
173
174 if (changed === true)
175 this.maybeToggleUpdateChecking();
176 }
177
178 /**
179 * Disables updates for some tables
180 * @param tables - an array of table names that no longer need updating
181 */
disableUpdate
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
182 PROT_ListManager.prototype.disableUpdate = function(tableName) {
183 var changed = false;
184 var table = this.tablesData[tableName];
185 if (table) {
186 G_Debug(this, "Disabling table updates for " + tableName);
187 table.needsUpdate = false;
188 changed = true;
189 }
190
191 if (changed === true)
192 this.maybeToggleUpdateChecking();
193 }
194
195 /**
196 * Determine if we have some tables that need updating.
197 */
requireTableUpdates
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
198 PROT_ListManager.prototype.requireTableUpdates = function() {
199 for (var type in this.tablesData) {
200 // Tables that need updating even if other tables dont require it
201 if (this.tablesData[type].needsUpdate)
202 return true;
203 }
204
205 return false;
206 }
207
208 /**
209 * Start managing the lists we know about. We don't do this automatically
210 * when the listmanager is instantiated because their profile directory
211 * (where we store the lists) might not be available.
212 */
maybeStartManagingUpdates
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
213 PROT_ListManager.prototype.maybeStartManagingUpdates = function() {
214 if (this.isTesting_)
215 return;
216
217 // We might have been told about tables already, so see if we should be
218 // actually updating.
219 this.maybeToggleUpdateChecking();
220 }
221
kickoffUpdate_
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
222 PROT_ListManager.prototype.kickoffUpdate_ = function (tableData)
223 {
224 this.startingUpdate_ = false;
225 // If the user has never downloaded tables, do the check now.
226 // If the user has tables, add a fuzz of a few minutes.
227 var initialUpdateDelay = 3000;
228 if (tableData != "") {
229 // Add a fuzz of 0-5 minutes.
230 initialUpdateDelay += Math.floor(Math.random() * (5 * 60 * 1000));
231 }
232
233 this.currentUpdateChecker_ =
234 new G_Alarm(BindToObject(this.checkForUpdates, this),
235 initialUpdateDelay);
236 }
237
238 /**
239 * Determine if we have any tables that require updating. Different
240 * Wardens may call us with new tables that need to be updated.
241 */
maybeToggleUpdateChecking
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
242 PROT_ListManager.prototype.maybeToggleUpdateChecking = function() {
243 // If we are testing or dont have an application directory yet, we should
244 // not start reading tables from disk or schedule remote updates
245 if (this.isTesting_)
246 return;
247
248 // We update tables if we have some tables that want updates. If there
249 // are no tables that want to be updated - we dont need to check anything.
250 if (this.requireTableUpdates() === true) {
251 G_Debug(this, "Starting managing lists");
252 this.startUpdateChecker();
253
254 // Multiple warden can ask us to reenable updates at the same time, but we
255 // really just need to schedule a single update.
256 if (!this.currentUpdateChecker && !this.startingUpdate_) {
257 this.startingUpdate_ = true;
258 // check the current state of tables in the database
259 this.dbService_.getTables(BindToObject(this.kickoffUpdate_, this));
260 }
261 } else {
262 G_Debug(this, "Stopping managing lists (if currently active)");
263 this.stopUpdateChecker(); // Cancel pending updates
264 }
265 }
266
267 /**
268 * Start periodic checks for updates. Idempotent.
269 * We want to distribute update checks evenly across the update period (an
270 * hour). To do this, we pick a random number of time between 0 and 30
271 * minutes. The client first checks at 15 + rand, then every 30 minutes after
272 * that.
273 */
startUpdateChecker
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
274 PROT_ListManager.prototype.startUpdateChecker = function() {
275 this.stopUpdateChecker();
276
277 // Schedule the first check for between 15 and 45 minutes.
278 var repeatingUpdateDelay = kUpdateInterval / 2;
279 repeatingUpdateDelay += Math.floor(Math.random() * kUpdateInterval);
280 this.updateChecker_ = new G_Alarm(BindToObject(this.initialUpdateCheck_,
281 this),
282 repeatingUpdateDelay);
283 }
284
285 /**
286 * Callback for the first update check.
287 * We go ahead and check for table updates, then start a regular timer (once
288 * every 30 minutes).
289 */
initialUpdateCheck_
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
290 PROT_ListManager.prototype.initialUpdateCheck_ = function() {
291 this.checkForUpdates();
292 this.updateChecker_ = new G_Alarm(BindToObject(this.checkForUpdates, this),
293 kUpdateInterval, true /* repeat */);
294 }
295
296 /**
297 * Stop checking for updates. Idempotent.
298 */
stopUpdateChecker
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
299 PROT_ListManager.prototype.stopUpdateChecker = function() {
300 if (this.updateChecker_) {
301 this.updateChecker_.cancel();
302 this.updateChecker_ = null;
303 }
304 // Cancel the oneoff check from maybeToggleUpdateChecking.
305 if (this.currentUpdateChecker_) {
306 this.currentUpdateChecker_.cancel();
307 this.currentUpdateChecker_ = null;
308 }
309 }
310
311 /**
312 * Provides an exception free way to look up the data in a table. We
313 * use this because at certain points our tables might not be loaded,
314 * and querying them could throw.
315 *
316 * @param table String Name of the table that we want to consult
317 * @param key String Key for table lookup
318 * @param callback nsIUrlListManagerCallback (ie., Function) given false or the
319 * value in the table corresponding to key. If the table name does not
320 * exist, we return false, too.
321 */
safeLookup
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
322 PROT_ListManager.prototype.safeLookup = function(key, callback) {
323 try {
324 G_Debug(this, "safeLookup: " + key);
325 var cb = new QueryAdapter(callback);
326 this.dbService_.lookup(key,
327 BindToObject(cb.handleResponse, cb),
328 true);
329 } catch(e) {
330 G_Debug(this, "safeLookup masked failure for key " + key + ": " + e);
331 callback.handleEvent("");
332 }
333 }
334
335 /**
336 * Updates our internal tables from the update server
337 *
338 * @returns true when a new request was scheduled, false if an old request
339 * was still pending.
340 */
checkForUpdates
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
341 PROT_ListManager.prototype.checkForUpdates = function() {
342 // Allow new updates to be scheduled from maybeToggleUpdateChecking()
343 this.currentUpdateChecker_ = null;
344
345 if (!this.updateserverURL_) {
346 G_Debug(this, 'checkForUpdates: no update server url');
347 return false;
348 }
349
350 // See if we've triggered the request backoff logic.
351 if (!this.requestBackoff_.canMakeRequest())
352 return false;
353
354 // Grab the current state of the tables from the database
355 this.dbService_.getTables(BindToObject(this.makeUpdateRequest_, this));
356 return true;
357 }
358
359 /**
360 * Method that fires the actual HTTP update request.
361 * First we reset any tables that have disappeared.
362 * @param tableData List of table data already in the database, in the form
363 * tablename;<chunk ranges>\n
364 */
makeUpdateRequest_
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
365 PROT_ListManager.prototype.makeUpdateRequest_ = function(tableData) {
366 if (!this.keyManager_)
367 return;
368
369 if (!this.keyManager_.hasKey()) {
370 // We don't have a client key yet. Schedule a rekey, and rerequest
371 // when we have one.
372
373 // If there's already an update waiting for a new key, don't bother.
374 if (this.updateWaitingForKey_)
375 return;
376
377 // If maybeReKey() returns false we have asked for too many keys,
378 // and won't be getting a new one. Since we don't want to do
379 // updates without a client key, we'll skip this update if maybeReKey()
380 // fails.
381 if (this.keyManager_.maybeReKey())
382 this.updateWaitingForKey_ = true;
383
384 return;
385 }
386
387 var tableList;
388 var tableNames = {};
389 for (var tableName in this.tablesData) {
390 if (this.tablesData[tableName].needsUpdate)
391 tableNames[tableName] = true;
392 if (!tableList) {
393 tableList = tableName;
394 } else {
395 tableList += "," + tableName;
396 }
397 }
398
399 var request = "";
400
401 // For each table already in the database, include the chunk data from
402 // the database
403 var lines = tableData.split("\n");
404 for (var i = 0; i < lines.length; i++) {
405 var fields = lines[i].split(";");
406 if (tableNames[fields[0]]) {
407 request += lines[i] + ":mac\n";
408 delete tableNames[fields[0]];
409 }
410 }
411
412 // For each requested table that didn't have chunk data in the database,
413 // request it fresh
414 for (var tableName in tableNames) {
415 request += tableName + ";:mac\n";
416 }
417
418 G_Debug(this, 'checkForUpdates: scheduling request..');
419 var streamer = Cc["@mozilla.org/url-classifier/streamupdater;1"]
420 .getService(Ci.nsIUrlClassifierStreamUpdater);
421 try {
422 streamer.updateUrl = this.updateserverURL_ +
423 "&wrkey=" + this.keyManager_.getWrappedKey();
424 } catch (e) {
425 G_Debug(this, 'invalid url');
426 return;
427 }
428
429 this.requestBackoff_.noteRequest();
430
431 if (!streamer.downloadUpdates(tableList,
432 request,
433 this.keyManager_.getClientKey(),
434 BindToObject(this.updateSuccess_, this),
435 BindToObject(this.updateError_, this),
436 BindToObject(this.downloadError_, this))) {
437 G_Debug(this, "pending update, wait until later");
438 }
439 }
440
441 /**
442 * Callback function if the update request succeeded.
443 * @param waitForUpdate String The number of seconds that the client should
444 * wait before requesting again.
445 */
updateSuccess_
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
446 PROT_ListManager.prototype.updateSuccess_ = function(waitForUpdate) {
447 G_Debug(this, "update success: " + waitForUpdate);
448 if (waitForUpdate) {
449 var delay = parseInt(waitForUpdate, 10);
450 // As long as the delay is something sane (5 minutes or more), update
451 // our delay time for requesting updates
452 if (delay >= (5 * 60) && this.updateChecker_)
453 this.updateChecker_.setDelay(delay * 1000);
454 }
455
456 // Let the backoff object know that we completed successfully.
457 this.requestBackoff_.noteServerResponse(200);
458 }
459
460 /**
461 * Callback function if the update request succeeded.
462 * @param result String The error code of the failure
463 */
updateError_
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
464 PROT_ListManager.prototype.updateError_ = function(result) {
465 G_Debug(this, "update error: " + result);
466 // XXX: there was some trouble applying the updates.
467 }
468
469 /**
470 * Callback function when the download failed
471 * @param status String http status or an empty string if connection refused.
472 */
downloadError_
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
473 PROT_ListManager.prototype.downloadError_ = function(status) {
474 G_Debug(this, "download error: " + status);
475 // If status is empty, then we assume that we got an NS_CONNECTION_REFUSED
476 // error. In this case, we treat this is a http 500 error.
477 if (!status) {
478 status = 500;
479 }
480 status = parseInt(status, 10);
481 this.requestBackoff_.noteServerResponse(status);
482
483 if (this.requestBackoff_.isErrorStatus(status)) {
484 // Schedule an update for when our backoff is complete
485 this.currentUpdateChecker_ =
486 new G_Alarm(BindToObject(this.checkForUpdates, this),
487 this.requestBackoff_.nextRequestDelay());
488 }
489 }
490
491 /**
492 * Called when either the update process or a gethash request signals
493 * that the server requested a rekey.
494 */
rekey_
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
495 PROT_ListManager.prototype.rekey_ = function() {
496 G_Debug(this, "rekey requested");
497
498 // The current key is no good anymore.
499 this.keyManager_.dropKey();
500 this.keyManager_.maybeReKey();
501 }
502
503 /**
504 * Called when cookies are cleared - clears the current MAC keys.
505 */
cookieChanged_
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
506 PROT_ListManager.prototype.cookieChanged_ = function(subject, topic, data) {
507 if (data != "cleared")
508 return;
509
510 G_Debug(this, "cookies cleared");
511 this.keyManager_.dropKey();
512 }
513
514 /**
515 * Called when we've received a new key from the server.
516 */
newKey_
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
517 PROT_ListManager.prototype.newKey_ = function() {
518 G_Debug(this, "got a new MAC key");
519
520 this.hashCompleter_.setKeys(this.keyManager_.getClientKey(),
521 this.keyManager_.getWrappedKey());
522
523 if (this.keyManager_.hasKey()) {
524 if (this.updateWaitingForKey_) {
525 this.updateWaitingForKey_ = false;
526 this.checkForUpdates();
527 }
528 }
529 }
530
QueryInterface
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
531 PROT_ListManager.prototype.QueryInterface = function(iid) {
532 if (iid.equals(Ci.nsISupports) ||
533 iid.equals(Ci.nsIUrlListManager) ||
534 iid.equals(Ci.nsITimerCallback))
535 return this;
536
537 Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
538 return null;
539 }
540 //@line 42 "/home/visbrero/mnt/roisin/rev_control/hg/mozilla/toolkit/components/url-classifier/src/nsUrlClassifierListManager.js"
541
542 var modScope = this;
Init
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
543 function Init() {
544 // Pull the library in.
545 var jslib = Cc["@mozilla.org/url-classifier/jslib;1"]
546 .getService().wrappedJSObject;
547 Function.prototype.inherits = jslib.Function.prototype.inherits;
548 modScope.G_Preferences = jslib.G_Preferences;
549 modScope.G_PreferenceObserver = jslib.G_PreferenceObserver;
550 modScope.G_ObserverServiceObserver = jslib.G_ObserverServiceObserver;
551 modScope.G_Debug = jslib.G_Debug;
552 modScope.G_Assert = jslib.G_Assert;
553 modScope.G_debugService = jslib.G_debugService;
554 modScope.G_Alarm = jslib.G_Alarm;
555 modScope.BindToObject = jslib.BindToObject;
556 modScope.PROT_XMLFetcher = jslib.PROT_XMLFetcher;
557 modScope.PROT_UrlCryptoKeyManager = jslib.PROT_UrlCryptoKeyManager;
558 modScope.RequestBackoff = jslib.RequestBackoff;
559
560 // We only need to call Init once.
anon:561:18
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
561 modScope.Init = function() {};
562 }
563
564 // Module object
UrlClassifierListManagerMod
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
565 function UrlClassifierListManagerMod() {
566 this.firstTime = true;
567 this.cid = Components.ID("{ca168834-cc00-48f9-b83c-fd018e58cae3}");
568 this.progid = "@mozilla.org/url-classifier/listmanager;1";
569 }
570
registerSelf
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
571 UrlClassifierListManagerMod.prototype.registerSelf = function(compMgr, fileSpec, loc, type) {
572 if (this.firstTime) {
573 this.firstTime = false;
574 throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
575 }
576 compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
577 compMgr.registerFactoryLocation(this.cid,
578 "UrlClassifier List Manager Module",
579 this.progid,
580 fileSpec,
581 loc,
582 type);
583 };
584
getClassObject
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
585 UrlClassifierListManagerMod.prototype.getClassObject = function(compMgr, cid, iid) {
586 if (!cid.equals(this.cid))
587 throw Components.results.NS_ERROR_NO_INTERFACE;
588 if (!iid.equals(Ci.nsIFactory))
589 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
590
591 return this.factory;
592 }
593
canUnload
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
594 UrlClassifierListManagerMod.prototype.canUnload = function(compMgr) {
595 return true;
596 }
597
598 UrlClassifierListManagerMod.prototype.factory = {
createInstance
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
599 createInstance: function(outer, iid) {
600 if (outer != null)
601 throw Components.results.NS_ERROR_NO_AGGREGATION;
602 Init();
603 return (new PROT_ListManager()).QueryInterface(iid);
604 }
605 };
606
607 var ListManagerModInst = new UrlClassifierListManagerMod();
608
NSGetModule
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
609 function NSGetModule(compMgr, fileSpec) {
610 return ListManagerModInst;
611 }