michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: Cu.import('resource://gre/modules/NetUtil.jsm'); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: const gAppRep = Cc["@mozilla.org/downloads/application-reputation-service;1"]. michael@0: getService(Ci.nsIApplicationReputationService); michael@0: let gHttpServ = null; michael@0: let gTables = {}; michael@0: michael@0: let ALLOW_LIST = 0; michael@0: let BLOCK_LIST = 1; michael@0: let NO_LIST = 2; michael@0: michael@0: function readFileToString(aFilename) { michael@0: let f = do_get_file(aFilename); michael@0: let stream = Cc["@mozilla.org/network/file-input-stream;1"] michael@0: .createInstance(Ci.nsIFileInputStream); michael@0: stream.init(f, -1, 0, 0); michael@0: let buf = NetUtil.readInputStreamToString(stream, stream.available()); michael@0: return buf; michael@0: } michael@0: michael@0: // Registers a table for which to serve update chunks. Returns a promise that michael@0: // resolves when that chunk has been downloaded. michael@0: function registerTableUpdate(aTable, aFilename) { michael@0: // If we haven't been given an update for this table yet, add it to the map michael@0: if (!(aTable in gTables)) { michael@0: gTables[aTable] = []; michael@0: } michael@0: michael@0: // The number of chunks associated with this table. michael@0: let numChunks = gTables[aTable].length + 1; michael@0: let redirectPath = "/" + aTable + "-" + numChunks; michael@0: let redirectUrl = "localhost:4444" + redirectPath; michael@0: michael@0: // Store redirect url for that table so we can return it later when we michael@0: // process an update request. michael@0: gTables[aTable].push(redirectUrl); michael@0: michael@0: gHttpServ.registerPathHandler(redirectPath, function(request, response) { michael@0: do_print("Mock safebrowsing server handling request for " + redirectPath); michael@0: let contents = readFileToString(aFilename); michael@0: do_print("Length of " + aFilename + ": " + contents.length); michael@0: response.setHeader("Content-Type", michael@0: "application/vnd.google.safebrowsing-update", false); michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(contents, contents.length); michael@0: }); michael@0: } michael@0: michael@0: function run_test() { michael@0: // Set up a local HTTP server to return bad verdicts. michael@0: Services.prefs.setCharPref("browser.safebrowsing.appRepURL", michael@0: "http://localhost:4444/download"); michael@0: // Ensure safebrowsing is enabled for this test, even if the app michael@0: // doesn't have it enabled. michael@0: Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", true); michael@0: do_register_cleanup(function() { michael@0: Services.prefs.clearUserPref("browser.safebrowsing.malware.enabled"); michael@0: }); michael@0: michael@0: // Set download_block_table explicitly. michael@0: Services.prefs.setCharPref("urlclassifier.downloadBlockTable", michael@0: "goog-badbinurl-shavar"); michael@0: do_register_cleanup(function() { michael@0: Services.prefs.clearUserPref("urlclassifier.downloadBlockTable"); michael@0: }); michael@0: michael@0: gHttpServ = new HttpServer(); michael@0: gHttpServ.registerDirectory("/", do_get_cwd()); michael@0: gHttpServ.registerPathHandler("/download", function(request, response) { michael@0: do_throw("This test should never make a remote lookup"); michael@0: }); michael@0: gHttpServ.start(4444); michael@0: michael@0: run_next_test(); michael@0: } michael@0: michael@0: function check_telemetry(aCount, michael@0: aShouldBlockCount, michael@0: aListCounts) { michael@0: let count = Cc["@mozilla.org/base/telemetry;1"] michael@0: .getService(Ci.nsITelemetry) michael@0: .getHistogramById("APPLICATION_REPUTATION_COUNT") michael@0: .snapshot(); michael@0: do_check_eq(count.counts[1], aCount); michael@0: let local = Cc["@mozilla.org/base/telemetry;1"] michael@0: .getService(Ci.nsITelemetry) michael@0: .getHistogramById("APPLICATION_REPUTATION_LOCAL") michael@0: .snapshot(); michael@0: do_check_eq(local.counts[ALLOW_LIST], aListCounts[ALLOW_LIST]); michael@0: do_check_eq(local.counts[BLOCK_LIST], aListCounts[BLOCK_LIST]); michael@0: do_check_eq(local.counts[NO_LIST], aListCounts[NO_LIST]); michael@0: let shouldBlock = Cc["@mozilla.org/base/telemetry;1"] michael@0: .getService(Ci.nsITelemetry) michael@0: .getHistogramById("APPLICATION_REPUTATION_SHOULD_BLOCK") michael@0: .snapshot(); michael@0: // SHOULD_BLOCK = true michael@0: do_check_eq(shouldBlock.counts[1], aShouldBlockCount); michael@0: // Sanity check that SHOULD_BLOCK total adds up to the COUNT. michael@0: do_check_eq(shouldBlock.counts[0] + shouldBlock.counts[1], aCount); michael@0: } michael@0: michael@0: function get_telemetry_counts() { michael@0: let count = Cc["@mozilla.org/base/telemetry;1"] michael@0: .getService(Ci.nsITelemetry) michael@0: .getHistogramById("APPLICATION_REPUTATION_COUNT") michael@0: .snapshot(); michael@0: let local = Cc["@mozilla.org/base/telemetry;1"] michael@0: .getService(Ci.nsITelemetry) michael@0: .getHistogramById("APPLICATION_REPUTATION_LOCAL") michael@0: .snapshot(); michael@0: let shouldBlock = Cc["@mozilla.org/base/telemetry;1"] michael@0: .getService(Ci.nsITelemetry) michael@0: .getHistogramById("APPLICATION_REPUTATION_SHOULD_BLOCK") michael@0: .snapshot(); michael@0: return { total: count.counts[1], michael@0: shouldBlock: shouldBlock.counts[1], michael@0: listCounts: local.counts }; michael@0: } michael@0: michael@0: add_test(function test_nullSourceURI() { michael@0: let counts = get_telemetry_counts(); michael@0: gAppRep.queryReputation({ michael@0: // No source URI michael@0: fileSize: 12, michael@0: }, function onComplete(aShouldBlock, aStatus) { michael@0: do_check_eq(Cr.NS_ERROR_UNEXPECTED, aStatus); michael@0: do_check_false(aShouldBlock); michael@0: check_telemetry(counts.total + 1, counts.shouldBlock, counts.listCounts); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_nullCallback() { michael@0: let counts = get_telemetry_counts(); michael@0: try { michael@0: gAppRep.queryReputation({ michael@0: sourceURI: createURI("http://example.com"), michael@0: fileSize: 12, michael@0: }, null); michael@0: do_throw("Callback cannot be null"); michael@0: } catch (ex if ex.result == Cr.NS_ERROR_INVALID_POINTER) { michael@0: // We don't even increment the count here, because there's no callback. michael@0: check_telemetry(counts.total, counts.shouldBlock, counts.listCounts); michael@0: run_next_test(); michael@0: } michael@0: }); michael@0: michael@0: add_test(function test_disabled() { michael@0: let counts = get_telemetry_counts(); michael@0: Services.prefs.setCharPref("browser.safebrowsing.appRepURL", ""); michael@0: gAppRep.queryReputation({ michael@0: sourceURI: createURI("http://example.com"), michael@0: fileSize: 12, michael@0: }, function onComplete(aShouldBlock, aStatus) { michael@0: // We should be getting NS_ERROR_NOT_AVAILABLE if the service is disabled michael@0: do_check_eq(Cr.NS_ERROR_NOT_AVAILABLE, aStatus); michael@0: do_check_false(aShouldBlock); michael@0: check_telemetry(counts.total + 1, counts.shouldBlock, counts.listCounts); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: // Set up the local whitelist. michael@0: add_test(function test_local_list() { michael@0: // Construct a response with redirect urls. michael@0: function processUpdateRequest() { michael@0: let response = "n:1000\n"; michael@0: for (let table in gTables) { michael@0: response += "i:" + table + "\n"; michael@0: for (let i = 0; i < gTables[table].length; ++i) { michael@0: response += "u:" + gTables[table][i] + "\n"; michael@0: } michael@0: } michael@0: do_print("Returning update response: " + response); michael@0: return response; michael@0: } michael@0: gHttpServ.registerPathHandler("/downloads", function(request, response) { michael@0: let buf = NetUtil.readInputStreamToString(request.bodyInputStream, michael@0: request.bodyInputStream.available()); michael@0: let blob = processUpdateRequest(); michael@0: response.setHeader("Content-Type", michael@0: "application/vnd.google.safebrowsing-update", false); michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(blob, blob.length); michael@0: }); michael@0: michael@0: let streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"] michael@0: .getService(Ci.nsIUrlClassifierStreamUpdater); michael@0: streamUpdater.updateUrl = "http://localhost:4444/downloads"; michael@0: michael@0: // Load up some update chunks for the safebrowsing server to serve. michael@0: // This chunk contains the hash of whitelisted.com/. michael@0: registerTableUpdate("goog-badbinurl-shavar", "data/block_digest.chunk"); michael@0: // This chunk contains the hash of blocklisted.com/. michael@0: registerTableUpdate("goog-downloadwhite-digest256", "data/digest.chunk"); michael@0: michael@0: // Download some updates, and don't continue until the downloads are done. michael@0: function updateSuccess(aEvent) { michael@0: // Timeout of n:1000 is constructed in processUpdateRequest above and michael@0: // passed back in the callback in nsIUrlClassifierStreamUpdater on success. michael@0: do_check_eq("1000", aEvent); michael@0: do_print("All data processed"); michael@0: run_next_test(); michael@0: } michael@0: // Just throw if we ever get an update or download error. michael@0: function handleError(aEvent) { michael@0: do_throw("We didn't download or update correctly: " + aEvent); michael@0: } michael@0: streamUpdater.downloadUpdates( michael@0: "goog-downloadwhite-digest256,goog-badbinurl-shavar", michael@0: "goog-downloadwhite-digest256,goog-badbinurl-shavar;\n", michael@0: updateSuccess, handleError, handleError); michael@0: }); michael@0: michael@0: add_test(function test_unlisted() { michael@0: Services.prefs.setCharPref("browser.safebrowsing.appRepURL", michael@0: "http://localhost:4444/download"); michael@0: let counts = get_telemetry_counts(); michael@0: let listCounts = counts.listCounts; michael@0: listCounts[NO_LIST]++; michael@0: gAppRep.queryReputation({ michael@0: sourceURI: createURI("http://example.com"), michael@0: fileSize: 12, michael@0: }, function onComplete(aShouldBlock, aStatus) { michael@0: do_check_eq(Cr.NS_OK, aStatus); michael@0: do_check_false(aShouldBlock); michael@0: check_telemetry(counts.total + 1, counts.shouldBlock, listCounts); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_local_blacklist() { michael@0: Services.prefs.setCharPref("browser.safebrowsing.appRepURL", michael@0: "http://localhost:4444/download"); michael@0: let counts = get_telemetry_counts(); michael@0: let listCounts = counts.listCounts; michael@0: listCounts[BLOCK_LIST]++; michael@0: gAppRep.queryReputation({ michael@0: sourceURI: createURI("http://blocklisted.com"), michael@0: fileSize: 12, michael@0: }, function onComplete(aShouldBlock, aStatus) { michael@0: do_check_eq(Cr.NS_OK, aStatus); michael@0: do_check_true(aShouldBlock); michael@0: check_telemetry(counts.total + 1, counts.shouldBlock + 1, listCounts); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_referer_blacklist() { michael@0: Services.prefs.setCharPref("browser.safebrowsing.appRepURL", michael@0: "http://localhost:4444/download"); michael@0: let counts = get_telemetry_counts(); michael@0: let listCounts = counts.listCounts; michael@0: listCounts[BLOCK_LIST]++; michael@0: gAppRep.queryReputation({ michael@0: sourceURI: createURI("http://example.com"), michael@0: referrerURI: createURI("http://blocklisted.com"), michael@0: fileSize: 12, michael@0: }, function onComplete(aShouldBlock, aStatus) { michael@0: do_check_eq(Cr.NS_OK, aStatus); michael@0: do_check_true(aShouldBlock); michael@0: check_telemetry(counts.total + 1, counts.shouldBlock + 1, listCounts); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_blocklist_trumps_allowlist() { michael@0: Services.prefs.setCharPref("browser.safebrowsing.appRepURL", michael@0: "http://localhost:4444/download"); michael@0: let counts = get_telemetry_counts(); michael@0: let listCounts = counts.listCounts; michael@0: listCounts[BLOCK_LIST]++; michael@0: gAppRep.queryReputation({ michael@0: sourceURI: createURI("http://whitelisted.com"), michael@0: referrerURI: createURI("http://blocklisted.com"), michael@0: fileSize: 12, michael@0: }, function onComplete(aShouldBlock, aStatus) { michael@0: do_check_eq(Cr.NS_OK, aStatus); michael@0: do_check_true(aShouldBlock); michael@0: check_telemetry(counts.total + 1, counts.shouldBlock + 1, listCounts); michael@0: run_next_test(); michael@0: }); michael@0: });