summaryrefslogtreecommitdiffstats
path: root/toolkit/components/url-classifier/tests/unit/test_partial.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/url-classifier/tests/unit/test_partial.js')
-rw-r--r--toolkit/components/url-classifier/tests/unit/test_partial.js607
1 files changed, 607 insertions, 0 deletions
diff --git a/toolkit/components/url-classifier/tests/unit/test_partial.js b/toolkit/components/url-classifier/tests/unit/test_partial.js
new file mode 100644
index 0000000000..1220665063
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/unit/test_partial.js
@@ -0,0 +1,607 @@
+/**
+ * DummyCompleter() lets tests easily specify the results of a partial
+ * hash completion request.
+ */
+function DummyCompleter() {
+ this.fragments = {};
+ this.queries = [];
+ this.tableName = "test-phish-simple";
+}
+
+DummyCompleter.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIUrlClassifierHashCompleter"]),
+
+ complete(partialHash, gethashUrl, tableName, cb) {
+ this.queries.push(partialHash);
+ var fragments = this.fragments;
+ var self = this;
+ var doCallback = function () {
+ if (self.alwaysFail) {
+ cb.completionFinished(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+ if (fragments[partialHash]) {
+ for (var i = 0; i < fragments[partialHash].length; i++) {
+ var chunkId = fragments[partialHash][i][0];
+ var hash = fragments[partialHash][i][1];
+ cb.completionV2(hash, self.tableName, chunkId);
+ }
+ }
+ cb.completionFinished(0);
+ };
+ executeSoon(doCallback);
+ },
+
+ getHash(fragment) {
+ var data = new TextEncoder().encode(fragment);
+ var ch = Cc["@mozilla.org/security/hash;1"].createInstance(
+ Ci.nsICryptoHash
+ );
+ ch.init(ch.SHA256);
+ ch.update(data, data.length);
+ var hash = ch.finish(false);
+ return hash.slice(0, 32);
+ },
+
+ addFragment(chunkId, fragment) {
+ this.addHash(chunkId, this.getHash(fragment));
+ },
+
+ // This method allows the caller to generate complete hashes that match the
+ // prefix of a real fragment, but have different complete hashes.
+ addConflict(chunkId, fragment) {
+ var realHash = this.getHash(fragment);
+ var invalidHash = this.getHash("blah blah blah blah blah");
+ this.addHash(chunkId, realHash.slice(0, 4) + invalidHash.slice(4, 32));
+ },
+
+ addHash(chunkId, hash) {
+ var partial = hash.slice(0, 4);
+ if (this.fragments[partial]) {
+ this.fragments[partial].push([chunkId, hash]);
+ } else {
+ this.fragments[partial] = [[chunkId, hash]];
+ }
+ },
+
+ compareQueries(fragments) {
+ var expectedQueries = [];
+ for (let i = 0; i < fragments.length; i++) {
+ expectedQueries.push(this.getHash(fragments[i]).slice(0, 4));
+ }
+ Assert.equal(this.queries.length, expectedQueries.length);
+ expectedQueries.sort();
+ this.queries.sort();
+ for (let i = 0; i < this.queries.length; i++) {
+ Assert.equal(this.queries[i], expectedQueries[i]);
+ }
+ },
+};
+
+function setupCompleter(table, hits, conflicts) {
+ var completer = new DummyCompleter();
+ completer.tableName = table;
+ for (let i = 0; i < hits.length; i++) {
+ let chunkId = hits[i][0];
+ let fragments = hits[i][1];
+ for (let j = 0; j < fragments.length; j++) {
+ completer.addFragment(chunkId, fragments[j]);
+ }
+ }
+ for (let i = 0; i < conflicts.length; i++) {
+ let chunkId = conflicts[i][0];
+ let fragments = conflicts[i][1];
+ for (let j = 0; j < fragments.length; j++) {
+ completer.addConflict(chunkId, fragments[j]);
+ }
+ }
+
+ dbservice.setHashCompleter(table, completer);
+
+ return completer;
+}
+
+function installCompleter(table, fragments, conflictFragments) {
+ return setupCompleter(table, fragments, conflictFragments);
+}
+
+function installFailingCompleter(table) {
+ var completer = setupCompleter(table, [], []);
+ completer.alwaysFail = true;
+ return completer;
+}
+
+// Helper assertion for checking dummy completer queries
+gAssertions.completerQueried = function (data, cb) {
+ var completer = data[0];
+ completer.compareQueries(data[1]);
+ cb();
+};
+
+function doTest(updates, assertions) {
+ doUpdateTest(updates, assertions, runNextTest, updateError);
+}
+
+// Test an add of two partial urls to a fresh database
+function testPartialAdds() {
+ var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"];
+ var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4);
+
+ var completer = installCompleter("test-phish-simple", [[1, addUrls]], []);
+
+ var assertions = {
+ tableData: "test-phish-simple;a:1",
+ urlsExist: addUrls,
+ completerQueried: [completer, addUrls],
+ };
+
+ doTest([update], assertions);
+}
+
+function testPartialAddsWithConflicts() {
+ var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"];
+ var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4);
+
+ // Each result will have both a real match and a conflict
+ var completer = installCompleter(
+ "test-phish-simple",
+ [[1, addUrls]],
+ [[1, addUrls]]
+ );
+
+ var assertions = {
+ tableData: "test-phish-simple;a:1",
+ urlsExist: addUrls,
+ completerQueried: [completer, addUrls],
+ };
+
+ doTest([update], assertions);
+}
+
+// Test whether the fragmenting code does not cause duplicated completions
+function testFragments() {
+ var addUrls = ["foo.com/a/b/c", "foo.net/", "foo.com/c/"];
+ var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4);
+
+ var completer = installCompleter("test-phish-simple", [[1, addUrls]], []);
+
+ var assertions = {
+ tableData: "test-phish-simple;a:1",
+ urlsExist: addUrls,
+ completerQueried: [completer, addUrls],
+ };
+
+ doTest([update], assertions);
+}
+
+// Test http://code.google.com/p/google-safe-browsing/wiki/Protocolv2Spec
+// section 6.2 example 1
+function testSpecFragments() {
+ var probeUrls = ["a.b.c/1/2.html?param=1"];
+
+ var addUrls = [
+ "a.b.c/1/2.html",
+ "a.b.c/",
+ "a.b.c/1/",
+ "b.c/1/2.html?param=1",
+ "b.c/1/2.html",
+ "b.c/",
+ "b.c/1/",
+ "a.b.c/1/2.html?param=1",
+ ];
+
+ var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4);
+
+ var completer = installCompleter("test-phish-simple", [[1, addUrls]], []);
+
+ var assertions = {
+ tableData: "test-phish-simple;a:1",
+ urlsExist: probeUrls,
+ completerQueried: [completer, addUrls],
+ };
+
+ doTest([update], assertions);
+}
+
+// Test http://code.google.com/p/google-safe-browsing/wiki/Protocolv2Spec
+// section 6.2 example 2
+function testMoreSpecFragments() {
+ var probeUrls = ["a.b.c.d.e.f.g/1.html"];
+
+ var addUrls = [
+ "a.b.c.d.e.f.g/1.html",
+ "a.b.c.d.e.f.g/",
+ "c.d.e.f.g/1.html",
+ "c.d.e.f.g/",
+ "d.e.f.g/1.html",
+ "d.e.f.g/",
+ "e.f.g/1.html",
+ "e.f.g/",
+ "f.g/1.html",
+ "f.g/",
+ ];
+
+ var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4);
+
+ var completer = installCompleter("test-phish-simple", [[1, addUrls]], []);
+
+ var assertions = {
+ tableData: "test-phish-simple;a:1",
+ urlsExist: probeUrls,
+ completerQueried: [completer, addUrls],
+ };
+
+ doTest([update], assertions);
+}
+
+function testFalsePositives() {
+ var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"];
+ var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4);
+
+ // Each result will have no matching complete hashes and a non-matching
+ // conflict
+ var completer = installCompleter("test-phish-simple", [], [[1, addUrls]]);
+
+ var assertions = {
+ tableData: "test-phish-simple;a:1",
+ urlsDontExist: addUrls,
+ completerQueried: [completer, addUrls],
+ };
+
+ doTest([update], assertions);
+}
+
+function testEmptyCompleter() {
+ var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"];
+ var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4);
+
+ // Completer will never return full hashes
+ var completer = installCompleter("test-phish-simple", [], []);
+
+ var assertions = {
+ tableData: "test-phish-simple;a:1",
+ urlsDontExist: addUrls,
+ completerQueried: [completer, addUrls],
+ };
+
+ doTest([update], assertions);
+}
+
+function testCompleterFailure() {
+ var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"];
+ var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4);
+
+ // Completer will never return full hashes
+ var completer = installFailingCompleter("test-phish-simple");
+
+ var assertions = {
+ tableData: "test-phish-simple;a:1",
+ urlsDontExist: addUrls,
+ completerQueried: [completer, addUrls],
+ };
+
+ doTest([update], assertions);
+}
+
+function testMixedSizesSameDomain() {
+ var add1Urls = ["foo.com/a"];
+ var add2Urls = ["foo.com/b"];
+
+ var update1 = buildPhishingUpdate([{ chunkNum: 1, urls: add1Urls }], 4);
+ var update2 = buildPhishingUpdate([{ chunkNum: 2, urls: add2Urls }], 32);
+
+ // We should only need to complete the partial hashes
+ var completer = installCompleter("test-phish-simple", [[1, add1Urls]], []);
+
+ var assertions = {
+ tableData: "test-phish-simple;a:1-2",
+ // both urls should match...
+ urlsExist: add1Urls.concat(add2Urls),
+ // ... but the completer should only be queried for the partial entry
+ completerQueried: [completer, add1Urls],
+ };
+
+ doTest([update1, update2], assertions);
+}
+
+function testMixedSizesDifferentDomains() {
+ var add1Urls = ["foo.com/a"];
+ var add2Urls = ["bar.com/b"];
+
+ var update1 = buildPhishingUpdate([{ chunkNum: 1, urls: add1Urls }], 4);
+ var update2 = buildPhishingUpdate([{ chunkNum: 2, urls: add2Urls }], 32);
+
+ // We should only need to complete the partial hashes
+ var completer = installCompleter("test-phish-simple", [[1, add1Urls]], []);
+
+ var assertions = {
+ tableData: "test-phish-simple;a:1-2",
+ // both urls should match...
+ urlsExist: add1Urls.concat(add2Urls),
+ // ... but the completer should only be queried for the partial entry
+ completerQueried: [completer, add1Urls],
+ };
+
+ doTest([update1, update2], assertions);
+}
+
+function testInvalidHashSize() {
+ var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"];
+ var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 12); // only 4 and 32 are legal hash sizes
+
+ var addUrls2 = ["zaz.com/a", "xyz.com/b"];
+ var update2 = buildPhishingUpdate([{ chunkNum: 2, urls: addUrls2 }], 4);
+
+ installCompleter("test-phish-simple", [[1, addUrls]], []);
+
+ var assertions = {
+ tableData: "test-phish-simple;a:2",
+ urlsDontExist: addUrls,
+ };
+
+ // A successful update will trigger an error
+ doUpdateTest([update2, update], assertions, updateError, runNextTest);
+}
+
+function testWrongTable() {
+ var addUrls = ["foo.com/a"];
+ var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4);
+ var completer = installCompleter(
+ "test-malware-simple", // wrong table
+ [[1, addUrls]],
+ []
+ );
+
+ // The above installCompleter installs the completer for test-malware-simple,
+ // we want it to be used for test-phish-simple too.
+ dbservice.setHashCompleter("test-phish-simple", completer);
+
+ var assertions = {
+ tableData: "test-phish-simple;a:1",
+ // The urls were added as phishing urls, but the completer is claiming
+ // that they are malware urls, and we trust the completer in this case.
+ // The result will be discarded, so we can only check for non-existence.
+ urlsDontExist: addUrls,
+ // Make sure the completer was actually queried.
+ completerQueried: [completer, addUrls],
+ };
+
+ doUpdateTest(
+ [update],
+ assertions,
+ function () {
+ // Give the dbservice a chance to (not) cache the result.
+ do_timeout(3000, function () {
+ // The miss earlier will have caused a miss to be cached.
+ // Resetting the completer does not count as an update,
+ // so we will not be probed again.
+ var newCompleter = installCompleter(
+ "test-malware-simple",
+ [[1, addUrls]],
+ []
+ );
+ dbservice.setHashCompleter("test-phish-simple", newCompleter);
+
+ var assertions1 = {
+ urlsDontExist: addUrls,
+ };
+ checkAssertions(assertions1, runNextTest);
+ });
+ },
+ updateError
+ );
+}
+
+function setupCachedResults(addUrls, part2) {
+ var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4);
+
+ var completer = installCompleter("test-phish-simple", [[1, addUrls]], []);
+
+ var assertions = {
+ tableData: "test-phish-simple;a:1",
+ // Request the add url. This should cause the completion to be cached.
+ urlsExist: addUrls,
+ // Make sure the completer was actually queried.
+ completerQueried: [completer, addUrls],
+ };
+
+ doUpdateTest(
+ [update],
+ assertions,
+ function () {
+ // Give the dbservice a chance to cache the result.
+ do_timeout(3000, part2);
+ },
+ updateError
+ );
+}
+
+function testCachedResults() {
+ setupCachedResults(["foo.com/a"], function (add) {
+ // This is called after setupCachedResults(). Verify that
+ // checking the url again does not cause a completer request.
+
+ // install a new completer, this one should never be queried.
+ var newCompleter = installCompleter("test-phish-simple", [[1, []]], []);
+
+ var assertions = {
+ urlsExist: ["foo.com/a"],
+ completerQueried: [newCompleter, []],
+ };
+ checkAssertions(assertions, runNextTest);
+ });
+}
+
+function testCachedResultsWithSub() {
+ setupCachedResults(["foo.com/a"], function () {
+ // install a new completer, this one should never be queried.
+ var newCompleter = installCompleter("test-phish-simple", [[1, []]], []);
+
+ var removeUpdate = buildPhishingUpdate(
+ [{ chunkNum: 2, chunkType: "s", urls: ["1:foo.com/a"] }],
+ 4
+ );
+
+ var assertions = {
+ urlsDontExist: ["foo.com/a"],
+ completerQueried: [newCompleter, []],
+ };
+
+ doTest([removeUpdate], assertions);
+ });
+}
+
+function testCachedResultsWithExpire() {
+ setupCachedResults(["foo.com/a"], function () {
+ // install a new completer, this one should never be queried.
+ var newCompleter = installCompleter("test-phish-simple", [[1, []]], []);
+
+ var expireUpdate = "n:1000\ni:test-phish-simple\nad:1\n";
+
+ var assertions = {
+ urlsDontExist: ["foo.com/a"],
+ completerQueried: [newCompleter, []],
+ };
+ doTest([expireUpdate], assertions);
+ });
+}
+
+function testCachedResultsFailure() {
+ var existUrls = ["foo.com/a"];
+ setupCachedResults(existUrls, function () {
+ // This is called after setupCachedResults(). Verify that
+ // checking the url again does not cause a completer request.
+
+ // install a new completer, this one should never be queried.
+ var newCompleter = installCompleter("test-phish-simple", [[1, []]], []);
+
+ var assertions = {
+ urlsExist: existUrls,
+ completerQueried: [newCompleter, []],
+ };
+
+ checkAssertions(assertions, function () {
+ // Apply the update. The cached completes should be gone.
+ doErrorUpdate(
+ "test-phish-simple,test-malware-simple",
+ function () {
+ // Now the completer gets queried again.
+ var newCompleter2 = installCompleter(
+ "test-phish-simple",
+ [[1, existUrls]],
+ []
+ );
+ var assertions2 = {
+ tableData: "test-phish-simple;a:1",
+ urlsExist: existUrls,
+ completerQueried: [newCompleter2, existUrls],
+ };
+ checkAssertions(assertions2, runNextTest);
+ },
+ updateError
+ );
+ });
+ });
+}
+
+function testErrorList() {
+ var addUrls = ["foo.com/a", "foo.com/b", "bar.com/c"];
+ var update = buildPhishingUpdate([{ chunkNum: 1, urls: addUrls }], 4);
+ // The update failure should will kill the completes, so the above
+ // must be a prefix to get any hit at all past the update failure.
+
+ var completer = installCompleter("test-phish-simple", [[1, addUrls]], []);
+
+ var assertions = {
+ tableData: "test-phish-simple;a:1",
+ urlsExist: addUrls,
+ // These are complete urls, and will only be completed if the
+ // list is stale.
+ completerQueried: [completer, addUrls],
+ };
+
+ // Apply the update.
+ doStreamUpdate(
+ update,
+ function () {
+ // Now the test-phish-simple and test-malware-simple tables are marked
+ // as fresh. Fake an update failure to mark them stale.
+ doErrorUpdate(
+ "test-phish-simple,test-malware-simple",
+ function () {
+ // Now the lists should be marked stale. Check assertions.
+ checkAssertions(assertions, runNextTest);
+ },
+ updateError
+ );
+ },
+ updateError
+ );
+}
+
+// Verify that different lists (test-phish-simple,
+// test-malware-simple) maintain their freshness separately.
+function testErrorListIndependent() {
+ var phishUrls = ["phish.com/a"];
+ var malwareUrls = ["attack.com/a"];
+ var update = buildPhishingUpdate([{ chunkNum: 1, urls: phishUrls }], 4);
+ // These have to persist past the update failure, so they must be prefixes,
+ // not completes.
+
+ update += buildMalwareUpdate([{ chunkNum: 2, urls: malwareUrls }], 32);
+
+ var completer = installCompleter("test-phish-simple", [[1, phishUrls]], []);
+
+ var assertions = {
+ tableData: "test-malware-simple;a:2\ntest-phish-simple;a:1",
+ urlsExist: phishUrls,
+ malwareUrlsExist: malwareUrls,
+ // Only this phishing urls should be completed, because only the phishing
+ // urls will be stale.
+ completerQueried: [completer, phishUrls],
+ };
+
+ // Apply the update.
+ doStreamUpdate(
+ update,
+ function () {
+ // Now the test-phish-simple and test-malware-simple tables are
+ // marked as fresh. Fake an update failure to mark *just*
+ // phishing data as stale.
+ doErrorUpdate(
+ "test-phish-simple",
+ function () {
+ // Now the lists should be marked stale. Check assertions.
+ checkAssertions(assertions, runNextTest);
+ },
+ updateError
+ );
+ },
+ updateError
+ );
+}
+
+function run_test() {
+ runTests([
+ testPartialAdds,
+ testPartialAddsWithConflicts,
+ testFragments,
+ testSpecFragments,
+ testMoreSpecFragments,
+ testFalsePositives,
+ testEmptyCompleter,
+ testCompleterFailure,
+ testMixedSizesSameDomain,
+ testMixedSizesDifferentDomains,
+ testInvalidHashSize,
+ testWrongTable,
+ testCachedResults,
+ testCachedResultsWithSub,
+ testCachedResultsWithExpire,
+ testCachedResultsFailure,
+ testErrorList,
+ testErrorListIndependent,
+ ]);
+}
+
+do_test_pending();