// These tables share the same updateURL. const TEST_TABLE_DATA_LIST = [ // 0: { tableName: "test-listmanager0-digest256", providerName: "google", updateUrl: "http://localhost:4444/safebrowsing/update", gethashUrl: "http://localhost:4444/safebrowsing/gethash0", }, // 1: { tableName: "test-listmanager1-digest256", providerName: "google", updateUrl: "http://localhost:4444/safebrowsing/update", gethashUrl: "http://localhost:4444/safebrowsing/gethash1", }, // 2. { tableName: "test-listmanager2-digest256", providerName: "google", updateUrl: "http://localhost:4444/safebrowsing/update", gethashUrl: "http://localhost:4444/safebrowsing/gethash2", }, ]; // These tables have a different update URL (for v4). const TEST_TABLE_DATA_V4 = { tableName: "test-phish-proto", providerName: "google4", updateUrl: "http://localhost:5555/safebrowsing/update?", gethashUrl: "http://localhost:5555/safebrowsing/gethash-v4", }; const TEST_TABLE_DATA_V4_DISABLED = { tableName: "test-unwanted-proto", providerName: "google4", updateUrl: "http://localhost:5555/safebrowsing/update?", gethashUrl: "http://localhost:5555/safebrowsing/gethash-v4", }; const PREF_NEXTUPDATETIME = "browser.safebrowsing.provider.google.nextupdatetime"; const PREF_NEXTUPDATETIME_V4 = "browser.safebrowsing.provider.google4.nextupdatetime"; let gListManager = Cc["@mozilla.org/url-classifier/listmanager;1"].getService( Ci.nsIUrlListManager ); let gUrlUtils = Cc["@mozilla.org/url-classifier/utils;1"].getService( Ci.nsIUrlClassifierUtils ); // Global test server for serving safebrowsing updates. let gHttpServ = null; let gUpdateResponse = ""; let gExpectedUpdateRequest = ""; let gExpectedQueryV4 = ""; // Handles request for TEST_TABLE_DATA_V4. let gHttpServV4 = null; // These two variables are used to synchronize the last two racing updates // (in terms of "update URL") in test_update_all_tables(). let gUpdatedCntForTableData = 0; // For TEST_TABLE_DATA_LIST. let gIsV4Updated = false; // For TEST_TABLE_DATA_V4. const NEW_CLIENT_STATE = "sta\0te"; const CHECKSUM = "\x30\x67\xc7\x2c\x5e\x50\x1c\x31\xe3\xfe\xca\x73\xf0\x47\xdc\x34\x1a\x95\x63\x99\xec\x70\x5e\x0a\xee\x9e\xfb\x17\xa1\x55\x35\x78"; Services.prefs.setBoolPref("browser.safebrowsing.debug", true); // The "\xFF\xFF" is to generate a base64 string with "/". Services.prefs.setCharPref("browser.safebrowsing.id", "Firefox\xFF\xFF"); // Register tables. TEST_TABLE_DATA_LIST.forEach(function (t) { gListManager.registerTable( t.tableName, t.providerName, t.updateUrl, t.gethashUrl ); }); gListManager.registerTable( TEST_TABLE_DATA_V4.tableName, TEST_TABLE_DATA_V4.providerName, TEST_TABLE_DATA_V4.updateUrl, TEST_TABLE_DATA_V4.gethashUrl ); // To test Bug 1302044. gListManager.registerTable( TEST_TABLE_DATA_V4_DISABLED.tableName, TEST_TABLE_DATA_V4_DISABLED.providerName, TEST_TABLE_DATA_V4_DISABLED.updateUrl, TEST_TABLE_DATA_V4_DISABLED.gethashUrl ); const SERVER_INVOLVED_TEST_CASE_LIST = [ // - Do table0 update. // - Server would respond "a:5:32:32\n[DATA]". function test_update_table0() { disableAllUpdates(); gListManager.enableUpdate(TEST_TABLE_DATA_LIST[0].tableName); gExpectedUpdateRequest = TEST_TABLE_DATA_LIST[0].tableName + ";\n"; gUpdateResponse = "n:1000\ni:" + TEST_TABLE_DATA_LIST[0].tableName + "\n"; gUpdateResponse += readFileToString("data/digest2.chunk"); forceTableUpdate(); }, // - Do table0 update again. Since chunk 5 was added to table0 in the last // update, the expected request contains "a:5". // - Server would respond "s;2-12\n[DATA]". function test_update_table0_with_existing_chunks() { disableAllUpdates(); gListManager.enableUpdate(TEST_TABLE_DATA_LIST[0].tableName); gExpectedUpdateRequest = TEST_TABLE_DATA_LIST[0].tableName + ";a:5\n"; gUpdateResponse = "n:1000\ni:" + TEST_TABLE_DATA_LIST[0].tableName + "\n"; gUpdateResponse += readFileToString("data/digest1.chunk"); forceTableUpdate(); }, // - Do all-table update. // - Server would respond no chunk control. // // Note that this test MUST be the last one in the array since we rely on // the number of sever-involved test case to synchronize the racing last // two udpates for different URL. function test_update_all_tables() { disableAllUpdates(); // Enable all tables including TEST_TABLE_DATA_V4! TEST_TABLE_DATA_LIST.forEach(function (t) { gListManager.enableUpdate(t.tableName); }); // We register two v4 tables but only enable one of them // to verify that the disabled tables are not updated. // See Bug 1302044. gListManager.enableUpdate(TEST_TABLE_DATA_V4.tableName); gListManager.disableUpdate(TEST_TABLE_DATA_V4_DISABLED.tableName); // Expected results for v2. gExpectedUpdateRequest = TEST_TABLE_DATA_LIST[0].tableName + ";a:5:s:2-12\n" + TEST_TABLE_DATA_LIST[1].tableName + ";\n" + TEST_TABLE_DATA_LIST[2].tableName + ";\n"; gUpdateResponse = "n:1000\n"; // We test the request against the query string since v4 request // would be appened to the query string. The request is generated // by protobuf API (binary) then encoded to base64 format. let requestV4 = gUrlUtils.makeUpdateRequestV4( [TEST_TABLE_DATA_V4.tableName], [""] ); gExpectedQueryV4 = "&$req=" + requestV4; forceTableUpdate(); }, ]; SERVER_INVOLVED_TEST_CASE_LIST.forEach(t => add_test(t)); add_test(function test_partialUpdateV4() { disableAllUpdates(); gListManager.enableUpdate(TEST_TABLE_DATA_V4.tableName); // Since the new client state has been responded and saved in // test_update_all_tables, this update request should send // a partial update to the server. let requestV4 = gUrlUtils.makeUpdateRequestV4( [TEST_TABLE_DATA_V4.tableName], [btoa(NEW_CLIENT_STATE)] ); gExpectedQueryV4 = "&$req=" + requestV4; forceTableUpdate(); }); // Tests nsIUrlListManager.getGethashUrl. add_test(function test_getGethashUrl() { TEST_TABLE_DATA_LIST.forEach(function (t) { equal(gListManager.getGethashUrl(t.tableName), t.gethashUrl); }); equal( gListManager.getGethashUrl(TEST_TABLE_DATA_V4.tableName), TEST_TABLE_DATA_V4.gethashUrl ); run_next_test(); }); function run_test() { // Setup primary testing server. gHttpServ = new HttpServer(); gHttpServ.registerDirectory("/", do_get_cwd()); gHttpServ.registerPathHandler( "/safebrowsing/update", function (request, response) { let body = NetUtil.readInputStreamToString( request.bodyInputStream, request.bodyInputStream.available() ); // Verify if the request is as expected. equal(body, gExpectedUpdateRequest); // Respond the update which is controlled by the test case. response.setHeader( "Content-Type", "application/vnd.google.safebrowsing-update", false ); response.setStatusLine(request.httpVersion, 200, "OK"); response.bodyOutputStream.write(gUpdateResponse, gUpdateResponse.length); gUpdatedCntForTableData++; if (gUpdatedCntForTableData !== SERVER_INVOLVED_TEST_CASE_LIST.length) { // This is not the last test case so run the next once upon the // the update success. waitForUpdateSuccess(run_next_test); return; } if (gIsV4Updated) { run_next_test(); // All tests are done. Just finish. return; } info("Waiting for TEST_TABLE_DATA_V4 to be tested ..."); } ); gHttpServ.start(4444); // Setup v4 testing server for the different update URL. gHttpServV4 = new HttpServer(); gHttpServV4.registerDirectory("/", do_get_cwd()); gHttpServV4.registerPathHandler( "/safebrowsing/update", function (request, response) { // V4 update request body should be empty. equal(request.bodyInputStream.available(), 0); // Not on the spec. Found in Chromium source code... equal(request.getHeader("X-HTTP-Method-Override"), "POST"); // V4 update request uses GET. equal(request.method, "GET"); // V4 append the base64 encoded request to the query string. equal(request.queryString, gExpectedQueryV4); equal(request.queryString.indexOf("+"), -1); equal(request.queryString.indexOf("/"), -1); // Respond a V2 compatible content for now. In the future we can // send a meaningful response to test Bug 1284178 to see if the // update is successfully stored to database. response.setHeader( "Content-Type", "application/vnd.google.safebrowsing-update", false ); response.setStatusLine(request.httpVersion, 200, "OK"); // The protobuf binary represention of response: // // [ // { // 'threat_type': 2, // SOCIAL_ENGINEERING_PUBLIC // 'response_type': 2, // FULL_UPDATE // 'new_client_state': 'sta\x00te', // NEW_CLIENT_STATE // 'checksum': { "sha256": CHECKSUM }, // CHECKSUM // 'additions': { 'compression_type': RAW, // 'prefix_size': 4, // 'raw_hashes': "00000001000000020000000300000004"} // } // ] // let content = "\x0A\x4A\x08\x02\x20\x02\x2A\x18\x08\x01\x12\x14\x08\x04\x12\x10\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x3A\x06\x73\x74\x61\x00\x74\x65\x42\x22\x0A\x20\x30\x67\xC7\x2C\x5E\x50\x1C\x31\xE3\xFE\xCA\x73\xF0\x47\xDC\x34\x1A\x95\x63\x99\xEC\x70\x5E\x0A\xEE\x9E\xFB\x17\xA1\x55\x35\x78\x12\x08\x08\x08\x10\x80\x94\xEB\xDC\x03"; response.bodyOutputStream.write(content, content.length); if (gIsV4Updated) { // This falls to the case where test_partialUpdateV4 is running. // We are supposed to have verified the update request contains // the state we set in the previous request. waitForUpdateSuccess(run_next_test); return; } waitUntilMetaDataSaved(NEW_CLIENT_STATE, CHECKSUM, () => { gIsV4Updated = true; if (gUpdatedCntForTableData === SERVER_INVOLVED_TEST_CASE_LIST.length) { // All tests are done! run_next_test(); return; } info("Wait for all sever-involved tests to be done ..."); }); } ); gHttpServV4.start(5555); registerCleanupFunction(function () { return (async function () { await Promise.all([gHttpServ.stop(), gHttpServV4.stop()]); })(); }); run_next_test(); } // A trick to force updating tables. However, before calling this, we have to // call disableAllUpdates() first to clean up the updateCheckers in listmanager. function forceTableUpdate() { throwOnUpdateErrors(); Services.prefs.setCharPref(PREF_NEXTUPDATETIME, "1"); Services.prefs.setCharPref(PREF_NEXTUPDATETIME_V4, "1"); gListManager.maybeToggleUpdateChecking(); } function disableAllUpdates() { stopThrowingOnUpdateErrors(); TEST_TABLE_DATA_LIST.forEach(t => gListManager.disableUpdate(t.tableName)); gListManager.disableUpdate(TEST_TABLE_DATA_V4.tableName); } function waitForUpdateSuccess(callback) { Services.obs.addObserver(function listener() { Services.obs.removeObserver(listener, "safebrowsing-update-finished"); callback(); }, "safebrowsing-update-finished"); }