|
1 //* -*- Mode: Javascript; tab-width: 8; indent-tabs-mode: nil; js-indent-level: 2 -*- * |
|
2 function dumpn(s) { |
|
3 dump(s + "\n"); |
|
4 } |
|
5 |
|
6 const NS_APP_USER_PROFILE_50_DIR = "ProfD"; |
|
7 const NS_APP_USER_PROFILE_LOCAL_50_DIR = "ProfLD"; |
|
8 |
|
9 const Cc = Components.classes; |
|
10 const Ci = Components.interfaces; |
|
11 const Cu = Components.utils; |
|
12 const Cr = Components.results; |
|
13 |
|
14 Cu.import("resource://testing-common/httpd.js"); |
|
15 |
|
16 do_get_profile(); |
|
17 |
|
18 var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); |
|
19 |
|
20 var iosvc = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); |
|
21 |
|
22 var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"] |
|
23 .getService(Ci.nsIScriptSecurityManager); |
|
24 |
|
25 // Disable hashcompleter noise for tests |
|
26 var prefBranch = Cc["@mozilla.org/preferences-service;1"]. |
|
27 getService(Ci.nsIPrefBranch); |
|
28 prefBranch.setIntPref("urlclassifier.gethashnoise", 0); |
|
29 |
|
30 // Enable malware/phishing checking for tests |
|
31 prefBranch.setBoolPref("browser.safebrowsing.malware.enabled", true); |
|
32 prefBranch.setBoolPref("browser.safebrowsing.enabled", true); |
|
33 |
|
34 // Enable all completions for tests |
|
35 prefBranch.setCharPref("urlclassifier.disallow_completions", ""); |
|
36 |
|
37 function delFile(name) { |
|
38 try { |
|
39 // Delete a previously created sqlite file |
|
40 var file = dirSvc.get('ProfLD', Ci.nsIFile); |
|
41 file.append(name); |
|
42 if (file.exists()) |
|
43 file.remove(false); |
|
44 } catch(e) { |
|
45 } |
|
46 } |
|
47 |
|
48 function cleanUp() { |
|
49 delFile("urlclassifier3.sqlite"); |
|
50 delFile("safebrowsing/classifier.hashkey"); |
|
51 delFile("safebrowsing/test-phish-simple.sbstore"); |
|
52 delFile("safebrowsing/test-malware-simple.sbstore"); |
|
53 delFile("safebrowsing/test-phish-simple.cache"); |
|
54 delFile("safebrowsing/test-malware-simple.cache"); |
|
55 delFile("safebrowsing/test-phish-simple.pset"); |
|
56 delFile("safebrowsing/test-malware-simple.pset"); |
|
57 } |
|
58 |
|
59 var allTables = "test-phish-simple,test-malware-simple"; |
|
60 |
|
61 var dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(Ci.nsIUrlClassifierDBService); |
|
62 var streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"] |
|
63 .getService(Ci.nsIUrlClassifierStreamUpdater); |
|
64 |
|
65 |
|
66 /* |
|
67 * Builds an update from an object that looks like: |
|
68 *{ "test-phish-simple" : [{ |
|
69 * "chunkType" : "a", // 'a' is assumed if not specified |
|
70 * "chunkNum" : 1, // numerically-increasing chunk numbers are assumed |
|
71 * // if not specified |
|
72 * "urls" : [ "foo.com/a", "foo.com/b", "bar.com/" ] |
|
73 * } |
|
74 */ |
|
75 |
|
76 function buildUpdate(update, hashSize) { |
|
77 if (!hashSize) { |
|
78 hashSize = 32; |
|
79 } |
|
80 var updateStr = "n:1000\n"; |
|
81 |
|
82 for (var tableName in update) { |
|
83 if (tableName != "") |
|
84 updateStr += "i:" + tableName + "\n"; |
|
85 var chunks = update[tableName]; |
|
86 for (var j = 0; j < chunks.length; j++) { |
|
87 var chunk = chunks[j]; |
|
88 var chunkType = chunk.chunkType ? chunk.chunkType : 'a'; |
|
89 var chunkNum = chunk.chunkNum ? chunk.chunkNum : j; |
|
90 updateStr += chunkType + ':' + chunkNum + ':' + hashSize; |
|
91 |
|
92 if (chunk.urls) { |
|
93 var chunkData = chunk.urls.join("\n"); |
|
94 updateStr += ":" + chunkData.length + "\n" + chunkData; |
|
95 } |
|
96 |
|
97 updateStr += "\n"; |
|
98 } |
|
99 } |
|
100 |
|
101 return updateStr; |
|
102 } |
|
103 |
|
104 function buildPhishingUpdate(chunks, hashSize) { |
|
105 return buildUpdate({"test-phish-simple" : chunks}, hashSize); |
|
106 } |
|
107 |
|
108 function buildMalwareUpdate(chunks, hashSize) { |
|
109 return buildUpdate({"test-malware-simple" : chunks}, hashSize); |
|
110 } |
|
111 |
|
112 function buildBareUpdate(chunks, hashSize) { |
|
113 return buildUpdate({"" : chunks}, hashSize); |
|
114 } |
|
115 |
|
116 /** |
|
117 * Performs an update of the dbservice manually, bypassing the stream updater |
|
118 */ |
|
119 function doSimpleUpdate(updateText, success, failure) { |
|
120 var listener = { |
|
121 QueryInterface: function(iid) |
|
122 { |
|
123 if (iid.equals(Ci.nsISupports) || |
|
124 iid.equals(Ci.nsIUrlClassifierUpdateObserver)) |
|
125 return this; |
|
126 throw Cr.NS_ERROR_NO_INTERFACE; |
|
127 }, |
|
128 |
|
129 updateUrlRequested: function(url) { }, |
|
130 streamFinished: function(status) { }, |
|
131 updateError: function(errorCode) { failure(errorCode); }, |
|
132 updateSuccess: function(requestedTimeout) { success(requestedTimeout); } |
|
133 }; |
|
134 |
|
135 dbservice.beginUpdate(listener, |
|
136 "test-phish-simple,test-malware-simple"); |
|
137 dbservice.beginStream("", ""); |
|
138 dbservice.updateStream(updateText); |
|
139 dbservice.finishStream(); |
|
140 dbservice.finishUpdate(); |
|
141 } |
|
142 |
|
143 /** |
|
144 * Simulates a failed database update. |
|
145 */ |
|
146 function doErrorUpdate(tables, success, failure) { |
|
147 var listener = { |
|
148 QueryInterface: function(iid) |
|
149 { |
|
150 if (iid.equals(Ci.nsISupports) || |
|
151 iid.equals(Ci.nsIUrlClassifierUpdateObserver)) |
|
152 return this; |
|
153 throw Cr.NS_ERROR_NO_INTERFACE; |
|
154 }, |
|
155 |
|
156 updateUrlRequested: function(url) { }, |
|
157 streamFinished: function(status) { }, |
|
158 updateError: function(errorCode) { success(errorCode); }, |
|
159 updateSuccess: function(requestedTimeout) { failure(requestedTimeout); } |
|
160 }; |
|
161 |
|
162 dbservice.beginUpdate(listener, tables, null); |
|
163 dbservice.beginStream("", ""); |
|
164 dbservice.cancelUpdate(); |
|
165 } |
|
166 |
|
167 /** |
|
168 * Performs an update of the dbservice using the stream updater and a |
|
169 * data: uri |
|
170 */ |
|
171 function doStreamUpdate(updateText, success, failure, downloadFailure) { |
|
172 var dataUpdate = "data:," + encodeURIComponent(updateText); |
|
173 |
|
174 if (!downloadFailure) |
|
175 downloadFailure = failure; |
|
176 |
|
177 streamUpdater.updateUrl = dataUpdate; |
|
178 streamUpdater.downloadUpdates("test-phish-simple,test-malware-simple", "", |
|
179 success, failure, downloadFailure); |
|
180 } |
|
181 |
|
182 var gAssertions = { |
|
183 |
|
184 tableData : function(expectedTables, cb) |
|
185 { |
|
186 dbservice.getTables(function(tables) { |
|
187 // rebuild the tables in a predictable order. |
|
188 var parts = tables.split("\n"); |
|
189 while (parts[parts.length - 1] == '') { |
|
190 parts.pop(); |
|
191 } |
|
192 parts.sort(); |
|
193 tables = parts.join("\n"); |
|
194 |
|
195 do_check_eq(tables, expectedTables); |
|
196 cb(); |
|
197 }); |
|
198 }, |
|
199 |
|
200 checkUrls: function(urls, expected, cb) |
|
201 { |
|
202 // work with a copy of the list. |
|
203 urls = urls.slice(0); |
|
204 var doLookup = function() { |
|
205 if (urls.length > 0) { |
|
206 var fragment = urls.shift(); |
|
207 var principal = secMan.getNoAppCodebasePrincipal(iosvc.newURI("http://" + fragment, null, null)); |
|
208 dbservice.lookup(principal, allTables, |
|
209 function(arg) { |
|
210 do_check_eq(expected, arg); |
|
211 doLookup(); |
|
212 }, true); |
|
213 } else { |
|
214 cb(); |
|
215 } |
|
216 }; |
|
217 doLookup(); |
|
218 }, |
|
219 |
|
220 urlsDontExist: function(urls, cb) |
|
221 { |
|
222 this.checkUrls(urls, '', cb); |
|
223 }, |
|
224 |
|
225 urlsExist: function(urls, cb) |
|
226 { |
|
227 this.checkUrls(urls, 'test-phish-simple', cb); |
|
228 }, |
|
229 |
|
230 malwareUrlsExist: function(urls, cb) |
|
231 { |
|
232 this.checkUrls(urls, 'test-malware-simple', cb); |
|
233 }, |
|
234 |
|
235 subsDontExist: function(urls, cb) |
|
236 { |
|
237 // XXX: there's no interface for checking items in the subs table |
|
238 cb(); |
|
239 }, |
|
240 |
|
241 subsExist: function(urls, cb) |
|
242 { |
|
243 // XXX: there's no interface for checking items in the subs table |
|
244 cb(); |
|
245 } |
|
246 |
|
247 }; |
|
248 |
|
249 /** |
|
250 * Check a set of assertions against the gAssertions table. |
|
251 */ |
|
252 function checkAssertions(assertions, doneCallback) |
|
253 { |
|
254 var checkAssertion = function() { |
|
255 for (var i in assertions) { |
|
256 var data = assertions[i]; |
|
257 delete assertions[i]; |
|
258 gAssertions[i](data, checkAssertion); |
|
259 return; |
|
260 } |
|
261 |
|
262 doneCallback(); |
|
263 } |
|
264 |
|
265 checkAssertion(); |
|
266 } |
|
267 |
|
268 function updateError(arg) |
|
269 { |
|
270 do_throw(arg); |
|
271 } |
|
272 |
|
273 // Runs a set of updates, and then checks a set of assertions. |
|
274 function doUpdateTest(updates, assertions, successCallback, errorCallback) { |
|
275 var errorUpdate = function() { |
|
276 checkAssertions(assertions, errorCallback); |
|
277 } |
|
278 |
|
279 var runUpdate = function() { |
|
280 if (updates.length > 0) { |
|
281 var update = updates.shift(); |
|
282 doStreamUpdate(update, runUpdate, errorUpdate, null); |
|
283 } else { |
|
284 checkAssertions(assertions, successCallback); |
|
285 } |
|
286 } |
|
287 |
|
288 runUpdate(); |
|
289 } |
|
290 |
|
291 var gTests; |
|
292 var gNextTest = 0; |
|
293 |
|
294 function runNextTest() |
|
295 { |
|
296 if (gNextTest >= gTests.length) { |
|
297 do_test_finished(); |
|
298 return; |
|
299 } |
|
300 |
|
301 dbservice.resetDatabase(); |
|
302 dbservice.setHashCompleter('test-phish-simple', null); |
|
303 |
|
304 let test = gTests[gNextTest++]; |
|
305 dump("running " + test.name + "\n"); |
|
306 test(); |
|
307 } |
|
308 |
|
309 function runTests(tests) |
|
310 { |
|
311 gTests = tests; |
|
312 runNextTest(); |
|
313 } |
|
314 |
|
315 var timerArray = []; |
|
316 |
|
317 function Timer(delay, cb) { |
|
318 this.cb = cb; |
|
319 var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
|
320 timer.initWithCallback(this, delay, timer.TYPE_ONE_SHOT); |
|
321 timerArray.push(timer); |
|
322 } |
|
323 |
|
324 Timer.prototype = { |
|
325 QueryInterface: function(iid) { |
|
326 if (!iid.equals(Ci.nsISupports) && !iid.equals(Ci.nsITimerCallback)) { |
|
327 throw Cr.NS_ERROR_NO_INTERFACE; |
|
328 } |
|
329 return this; |
|
330 }, |
|
331 notify: function(timer) { |
|
332 this.cb(); |
|
333 } |
|
334 } |
|
335 |
|
336 // LFSRgenerator is a 32-bit linear feedback shift register random number |
|
337 // generator. It is highly predictable and is not intended to be used for |
|
338 // cryptography but rather to allow easier debugging than a test that uses |
|
339 // Math.random(). |
|
340 function LFSRgenerator(seed) { |
|
341 // Force |seed| to be a number. |
|
342 seed = +seed; |
|
343 // LFSR generators do not work with a value of 0. |
|
344 if (seed == 0) |
|
345 seed = 1; |
|
346 |
|
347 this._value = seed; |
|
348 } |
|
349 LFSRgenerator.prototype = { |
|
350 // nextNum returns a random unsigned integer of in the range [0,2^|bits|]. |
|
351 nextNum: function(bits) { |
|
352 if (!bits) |
|
353 bits = 32; |
|
354 |
|
355 let val = this._value; |
|
356 // Taps are 32, 22, 2 and 1. |
|
357 let bit = ((val >>> 0) ^ (val >>> 10) ^ (val >>> 30) ^ (val >>> 31)) & 1; |
|
358 val = (val >>> 1) | (bit << 31); |
|
359 this._value = val; |
|
360 |
|
361 return (val >>> (32 - bits)); |
|
362 }, |
|
363 }; |
|
364 |
|
365 cleanUp(); |