Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/* Unit tests for the nsIUrlClassifierRemoteSettingsService implementation. */
const { RemoteSettings } = ChromeUtils.importESModule(
);
const { SBRS_UPDATE_MINIMUM_DELAY } = ChromeUtils.importESModule(
"resource://gre/modules/UrlClassifierRemoteSettingsService.sys.mjs"
);
const COLLECTION_NAME = "tracking-protection-lists";
const REMOTE_SETTINGS_DATA = [
{
Name: "content-fingerprinting-track-digest256",
attachment: {
hash: "96a4a850a1a475001148fa8a3a5efea58951f7176d3624ad7614fbf32732ee48",
size: 948,
filename: "content-fingerprinting-track-digest256",
location:
"main-workspace/tracking-protection-lists/content-fingerprinting-track-digest256",
mimetype: "text/plain",
},
id: "content-fingerprinting-track-digest256",
Version: 1597417364,
},
{
Name: "mozplugin-block-digest256",
attachment: {
hash: "dd2b800c7e4bad17e1c79f3e530c0b94e0a039adf4566f30bc3c285a547fa4fc",
size: 3029,
filename: "mozplugin-block-digest256",
location:
"main-workspace/tracking-protection-lists/mozplugin-block-digest256",
mimetype: "text/plain",
},
id: "mozplugin-block-digest256",
Version: 1575583456,
},
{
Name: "google-trackwhite-digest256",
attachment: {
hash: "1cd6d9353e97d66ac737a9716cd3a33416d6a4884dd12dcd1d65266e4c81dfad",
size: 1470328,
filename: "google-trackwhite-digest256",
location:
"main-workspace/tracking-protection-lists/google-trackwhite-digest256",
mimetype: "text/plain",
},
id: "google-trackwhite-digest256",
Version: 1575583456,
},
// Entry with non-exist attachment
{
Name: "social-track-digest256",
attachment: {
location: "main-workspace/tracking-protection-lists/not-exist",
},
id: "social-track-digest256",
Version: 1111111111,
},
// Entry with corrupted attachment
{
Name: "analytic-track-digest256",
attachment: {
hash: "644a0662bcf7313570ee68490e3805f5cc7a0503c097f040525c28dc5bfe4c97",
size: 58,
filename: "invalid.chunk",
location: "main-workspace/tracking-protection-lists/invalid.chunk",
mimetype: "text/plain",
},
id: "analytic-track-digest256",
Version: 1111111111,
},
];
let gListService = Cc["@mozilla.org/url-classifier/list-service;1"].getService(
Ci.nsIUrlClassifierRemoteSettingsService
);
let gDbService = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(
Ci.nsIUrlClassifierDBService
);
class UpdateEvent extends EventTarget {}
function waitForEvent(element, eventName) {
return new Promise(function (resolve) {
element.addEventListener(eventName, e => resolve(e.detail), { once: true });
});
}
function buildPayload(tables) {
let payload = ``;
for (let table of tables) {
payload += table[0];
if (table[1] != null) {
payload += `;a:${table[1]}`;
}
payload += `\n`;
}
return payload;
}
let server;
add_setup(async function init() {
Services.prefs.setCharPref(
"browser.safebrowsing.provider.mozilla.updateURL",
);
// Setup HTTP server for remote setting
server = new HttpServer();
server.start(-1);
registerCleanupFunction(() => server.stop(() => {}));
server.registerDirectory(
"/cdn/main-workspace/tracking-protection-lists/",
do_get_file("data")
);
server.registerPathHandler("/v1/", (request, response) => {
response.write(
JSON.stringify({
capabilities: {
attachments: {
base_url: `http://localhost:${server.identity.primaryPort}/cdn/`,
},
},
})
);
response.setHeader("Content-Type", "application/json; charset=UTF-8");
response.setStatusLine(null, 200, "OK");
});
Services.prefs.setCharPref(
"services.settings.server",
`http://localhost:${server.identity.primaryPort}/v1`
);
// Setup remote setting initial data
let db = await RemoteSettings(COLLECTION_NAME).db;
await db.importChanges({}, 42, REMOTE_SETTINGS_DATA);
registerCleanupFunction(() => {
Services.prefs.clearUserPref(
"browser.safebrowsing.provider.mozilla.updateURL"
);
Services.prefs.clearUserPref("services.settings.server");
});
});
// Test updates from RemoteSettings when there is no local data
add_task(async function test_empty_update() {
let updateEvent = new UpdateEvent();
let promise = waitForEvent(updateEvent, "update");
const TEST_TABLES = [
["mozplugin-block-digest256", null], // empty
["content-fingerprinting-track-digest256", null], // empty
];
gListService.fetchList(buildPayload(TEST_TABLES), {
// nsIStreamListener observer
onStartRequest() {},
onDataAvailable(aRequest, aStream, aOffset, aCount) {
let stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
Ci.nsIScriptableInputStream
);
stream.init(aStream);
let event = new CustomEvent("update", {
detail: stream.readBytes(aCount),
});
updateEvent.dispatchEvent(event);
},
onStopRequest() {},
});
let expected = "n:" + SBRS_UPDATE_MINIMUM_DELAY + "\n";
for (const table of TEST_TABLES) {
expected += `i:${table[0]}\n` + readFileToString(`data/${table[0]}`);
}
Assert.equal(
await promise,
expected,
"Receive expected data from onDataAvailable"
);
gListService.clear();
});
// Test updates from RemoteSettings when we have an empty table,
// a table with an older version, and a table which is up-to-date.
add_task(async function test_update() {
let updateEvent = new UpdateEvent();
let promise = waitForEvent(updateEvent, "update");
const TEST_TABLES = [
["mozplugin-block-digest256", 1575583456], // up-to-date
["content-fingerprinting-track-digest256", 1575583456 - 1], // older version
];
gListService.fetchList(buildPayload(TEST_TABLES), {
// observer
// nsIStreamListener observer
onStartRequest() {},
onDataAvailable(aRequest, aStream, aOffset, aCount) {
let stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
Ci.nsIScriptableInputStream
);
stream.init(aStream);
let event = new CustomEvent("update", {
detail: stream.readBytes(aCount),
});
updateEvent.dispatchEvent(event);
},
onStopRequest() {},
});
// Build request with no version
let expected = "n:" + SBRS_UPDATE_MINIMUM_DELAY + "\n";
for (const table of TEST_TABLES) {
if (["content-fingerprinting-track-digest256"].includes(table[0])) {
expected += `i:${table[0]}\n`;
expected += `ad:${1575583456 - 1}\n`;
expected += readFileToString(`data/${table[0]}`);
}
}
Assert.equal(
await promise,
expected,
"Receive expected data from onDataAvailable"
);
gListService.clear();
});
// Test updates from RemoteSettings service when all tables are up-to-date.
add_task(async function test_no_update() {
let updateEvent = new UpdateEvent();
let promise = waitForEvent(updateEvent, "update");
const TEST_TABLES = [
["mozplugin-block-digest256", 1575583456], // up-to-date
["content-fingerprinting-track-digest256", 1597417364], // up-to-date
];
gListService.fetchList(buildPayload(TEST_TABLES), {
// nsIStreamListener observer
onStartRequest() {},
onDataAvailable(aRequest, aStream, aOffset, aCount) {
let stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
Ci.nsIScriptableInputStream
);
stream.init(aStream);
let event = new CustomEvent("update", {
detail: stream.readBytes(aCount),
});
updateEvent.dispatchEvent(event);
},
onStopRequest() {},
});
// No data is expected
let expected = "n:" + SBRS_UPDATE_MINIMUM_DELAY + "\n";
Assert.equal(
await promise,
expected,
"Receive expected data from onDataAvailable"
);
gListService.clear();
});
add_test(function test_update() {
let streamUpdater = Cc[
"@mozilla.org/url-classifier/streamupdater;1"
].getService(Ci.nsIUrlClassifierStreamUpdater);
// Download some updates, and don't continue until the downloads are done.
function updateSuccess(aEvent) {
Assert.equal(SBRS_UPDATE_MINIMUM_DELAY, aEvent);
info("All data processed");
run_next_test();
}
// Just throw if we ever get an update or download error.
function handleError(aEvent) {
do_throw("We didn't download or update correctly: " + aEvent);
}
streamUpdater.downloadUpdates(
"content-fingerprinting-track-digest256",
"content-fingerprinting-track-digest256;\n",
true,
updateSuccess,
handleError,
handleError
);
});
add_test(function test_url_not_denylisted() {
let uri = Services.io.newURI("http://example.com");
let principal = Services.scriptSecurityManager.createContentPrincipal(
uri,
{}
);
gDbService.lookup(
principal,
"content-fingerprinting-track-digest256",
function handleEvent(aEvent) {
// This URI is not on any lists.
Assert.equal("", aEvent);
run_next_test();
}
);
});
add_test(function test_url_denylisted() {
let uri = Services.io.newURI("https://www.foresee.com");
let principal = Services.scriptSecurityManager.createContentPrincipal(
uri,
{}
);
gDbService.lookup(
principal,
"content-fingerprinting-track-digest256",
function handleEvent(aEvent) {
Assert.equal("content-fingerprinting-track-digest256", aEvent);
run_next_test();
}
);
});
add_test(function test_update_download_error() {
let streamUpdater = Cc[
"@mozilla.org/url-classifier/streamupdater;1"
].getService(Ci.nsIUrlClassifierStreamUpdater);
// Download some updates, and don't continue until the downloads are done.
function updateSuccessOrError() {
do_throw("Should be downbload error");
}
// Just throw if we ever get an update or download error.
function downloadError() {
run_next_test();
}
streamUpdater.downloadUpdates(
"social-track-digest256",
"social-track-digest256;\n",
true,
updateSuccessOrError,
updateSuccessOrError,
downloadError
);
});
add_test(function test_update_update_error() {
let streamUpdater = Cc[
"@mozilla.org/url-classifier/streamupdater;1"
].getService(Ci.nsIUrlClassifierStreamUpdater);
// Download some updates, and don't continue until the downloads are done.
function updateSuccessOrDownloadError() {
do_throw("Should be update error");
}
// Just throw if we ever get an update or download error.
function updateError() {
run_next_test();
}
streamUpdater.downloadUpdates(
"analytic-track-digest256",
"analytic-track-digest256;\n",
true,
updateSuccessOrDownloadError,
updateError,
updateSuccessOrDownloadError
);
});
add_task(async function test_update_large_file() {
let updateEvent = new UpdateEvent();
let promise = waitForEvent(updateEvent, "update");
const TEST_TABLES = [
["google-trackwhite-digest256", 1575583456 - 1], // up-to-date
];
gListService.fetchList(buildPayload(TEST_TABLES), {
// observer
// nsIStreamListener observer
onStartRequest() {},
onDataAvailable(aRequest, aStream, aOffset, aCount) {
let stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
Ci.nsIScriptableInputStream
);
stream.init(aStream);
let event = new CustomEvent("update", {
detail: stream.readBytes(aCount),
});
updateEvent.dispatchEvent(event);
},
onStopRequest() {},
});
// Build request with no version
let expected = "n:" + SBRS_UPDATE_MINIMUM_DELAY + "\n";
for (const table of TEST_TABLES) {
if (["google-trackwhite-digest256"].includes(table[0])) {
expected += `i:${table[0]}\n`;
expected += `ad:${1575583456 - 1}\n`;
expected += readFileToString(`data/${table[0]}`);
}
}
Assert.equal(
await promise,
expected,
"Receive expected data from onDataAvailable"
);
gListService.clear();
});