/* 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/. */
const UPDATE_BEGIN =
"safebrowsing-update-begin";
const UPDATE_FINISH =
"safebrowsing-update-finished";
const JSLOG_PREF =
"browser.safebrowsing.debug";
window.onunload =
function () {
Search.uninit();
Provider.uninit();
Cache.uninit();
Debug.uninit();
};
window.onload =
function () {
Search.init();
Provider.init();
Cache.init();
Debug.init();
};
/*
* Search
*/
var Search = {
init() {
let classifier = Cc[
"@mozilla.org/url-classifier/dbservice;1"].getService(
Ci.nsIURIClassifier
);
let featureNames = classifier.getFeatureNames();
let fragment = document.createDocumentFragment();
featureNames.forEach(featureName => {
let container = document.createElement(
"label");
container.className =
"toggle-container-with-text";
fragment.appendChild(container);
let checkbox = document.createElement(
"input");
checkbox.id =
"feature_" + featureName;
checkbox.type =
"checkbox";
checkbox.checked =
true;
container.appendChild(checkbox);
let span = document.createElement(
"span");
container.appendChild(span);
let text = document.createTextNode(featureName);
span.appendChild(text);
});
let list = document.getElementById(
"search-features");
list.appendChild(fragment);
let btn = document.getElementById(
"search-button");
btn.addEventListener(
"click",
this.search);
this.hideError();
this.hideResults();
},
uninit() {
let list = document.getElementById(
"search-features");
while (list.firstChild) {
list.firstChild.remove();
}
let btn = document.getElementById(
"search-button");
btn.removeEventListener(
"click",
this.search);
},
search() {
Search.hideError();
Search.hideResults();
let input = document.getElementById(
"search-input").value;
let uri;
try {
uri = Services.io.newURI(input);
if (!uri) {
Search.reportError(
"url-classifier-search-error-invalid-url");
return;
}
}
catch (ex) {
Search.reportError(
"url-classifier-search-error-invalid-url");
return;
}
let classifier = Cc[
"@mozilla.org/url-classifier/dbservice;1"].getService(
Ci.nsIURIClassifier
);
let featureNames = classifier.getFeatureNames();
let features = [];
featureNames.forEach(featureName => {
if (document.getElementById(
"feature_" + featureName).checked) {
let feature = classifier.getFeatureByName(featureName);
if (feature) {
features.push(feature);
}
}
});
if (!features.length) {
Search.reportError(
"url-classifier-search-error-no-features");
return;
}
let listType =
document.getElementById(
"search-listtype").value == 0
? Ci.nsIUrlClassifierFeature.blocklist
: Ci.nsIUrlClassifierFeature.entitylist;
classifier.asyncClassifyLocalWithFeatures(uri, features, listType, list =>
Search.showResults(list)
);
Search.hideError();
},
hideError() {
let errorMessage = document.getElementById(
"search-error-message");
errorMessage.style.display =
"none";
},
reportError(msg) {
let errorMessage = document.getElementById(
"search-error-message");
document.l10n.setAttributes(errorMessage, msg);
errorMessage.style.display =
"";
},
hideResults() {
let resultTitle = document.getElementById(
"result-title");
resultTitle.style.display =
"none";
let resultTable = document.getElementById(
"result-table");
resultTable.style.display =
"none";
},
showResults(results) {
let fragment = document.createDocumentFragment();
results.forEach(result => {
let tr = document.createElement(
"tr");
fragment.appendChild(tr);
let th = document.createElement(
"th");
tr.appendChild(th);
th.appendChild(document.createTextNode(result.feature.name));
let td = document.createElement(
"td");
tr.appendChild(td);
let featureName = document.createElement(
"div");
document.l10n.setAttributes(
featureName,
"url-classifier-search-result-uri",
{ uri: result.uri.spec }
);
td.appendChild(featureName);
let list = document.createElement(
"div");
document.l10n.setAttributes(list,
"url-classifier-search-result-list", {
list: result.list,
});
td.appendChild(list);
});
let resultTable = document.getElementById(
"result-table");
while (resultTable.firstChild) {
resultTable.firstChild.remove();
}
resultTable.appendChild(fragment);
resultTable.style.display =
"";
let resultTitle = document.getElementById(
"result-title");
resultTitle.style.display =
"";
},
};
/*
* Provider
*/
var Provider = {
providers:
null,
updatingProvider:
"",
init() {
this.providers =
new Set();
let branch = Services.prefs.getBranch(
"browser.safebrowsing.provider.");
let children = branch.getChildList(
"");
for (let child of children) {
let provider = child.split(
".")[0];
if (
this.isActiveProvider(provider)) {
this.providers.add(provider);
}
}
this.register();
this.render();
this.refresh();
},
uninit() {
Services.obs.removeObserver(
this.onBeginUpdate, UPDATE_BEGIN);
Services.obs.removeObserver(
this.onFinishUpdate, UPDATE_FINISH);
},
onBeginUpdate(aSubject, aTopic, aData) {
this.updatingProvider = aData;
let p =
this.updatingProvider;
// Disable update button for the provider while we are doing update.
document.getElementById(
"update-" + p).disabled =
true;
let elem = document.getElementById(p +
"-col-lastupdateresult");
document.l10n.setAttributes(elem,
"url-classifier-updating");
},
onFinishUpdate(aSubject, aTopic, aData) {
let p =
this.updatingProvider;
this.updatingProvider =
"";
// It is possible that we get update-finished event only because
// about::url-classifier is opened after update-begin event is fired.
if (p ===
"") {
this.refresh();
return;
}
this.refresh([p]);
document.getElementById(
"update-" + p).disabled =
false;
let elem = document.getElementById(p +
"-col-lastupdateresult");
if (aData.startsWith(
"success")) {
document.l10n.setAttributes(elem,
"url-classifier-success");
}
else if (aData.startsWith(
"update error")) {
document.l10n.setAttributes(elem,
"url-classifier-update-error", {
error: aData.split(
": ")[1],
});
}
else if (aData.startsWith(
"download error")) {
document.l10n.setAttributes(elem,
"url-classifier-download-error", {
error: aData.split(
": ")[1],
});
}
else {
elem.childNodes[0].nodeValue = aData;
}
},
register() {
// Handle begin update
this.onBeginUpdate =
this.onBeginUpdate.bind(
this);
Services.obs.addObserver(
this.onBeginUpdate, UPDATE_BEGIN);
// Handle finish update
this.onFinishUpdate =
this.onFinishUpdate.bind(
this);
Services.obs.addObserver(
this.onFinishUpdate, UPDATE_FINISH);
},
// This should only be called once because we assume number of providers
// won't change.
render() {
let tbody = document.getElementById(
"provider-table-body");
for (let provider of
this.providers) {
let tr = document.createElement(
"tr");
let cols = document.getElementById(
"provider-head-row").childNodes;
for (let column of cols) {
if (!column.id) {
continue;
}
let td = document.createElement(
"td");
td.id = provider +
"-" + column.id;
if (column.id ===
"col-update") {
let btn = document.createElement(
"button");
btn.id =
"update-" + provider;
btn.addEventListener(
"click", () => {
this.update(provider);
});
document.l10n.setAttributes(btn,
"url-classifier-trigger-update");
td.appendChild(btn);
}
else if (column.id ===
"col-lastupdateresult") {
document.l10n.setAttributes(td,
"url-classifier-not-available");
}
else {
td.appendChild(document.createTextNode(
""));
}
tr.appendChild(td);
}
tbody.appendChild(tr);
}
},
refresh(listProviders =
this.providers) {
for (let provider of listProviders) {
let values = {};
values[
"col-provider"] = provider;
let pref =
"browser.safebrowsing.provider." + provider +
".lastupdatetime";
let lut = Services.prefs.getCharPref(pref,
"");
values[
"col-lastupdatetime"] = lut ?
new Date(lut * 1) :
null;
pref =
"browser.safebrowsing.provider." + provider +
".nextupdatetime";
let nut = Services.prefs.getCharPref(pref,
"");
values[
"col-nextupdatetime"] = nut ?
new Date(nut * 1) :
null;
let listmanager = Cc[
"@mozilla.org/url-classifier/listmanager;1"
].getService(Ci.nsIUrlListManager);
let bot = listmanager.getBackOffTime(provider);
values[
"col-backofftime"] = bot ?
new Date(bot * 1) :
null;
for (let key of Object.keys(values)) {
let elem = document.getElementById(provider +
"-" + key);
if (values[key]) {
elem.removeAttribute(
"data-l10n-id");
elem.childNodes[0].nodeValue = values[key];
}
else {
document.l10n.setAttributes(elem,
"url-classifier-not-available");
}
}
}
},
// Call update for the provider.
update(provider) {
let listmanager = Cc[
"@mozilla.org/url-classifier/listmanager;1"
].getService(Ci.nsIUrlListManager);
let pref =
"browser.safebrowsing.provider." + provider +
".lists";
let tables = Services.prefs.getCharPref(pref,
"");
if (!listmanager.forceUpdates(tables)) {
// This may because of back-off algorithm.
let elem = document.getElementById(provider +
"-col-lastupdateresult");
document.l10n.setAttributes(elem,
"url-classifier-cannot-update");
}
},
// if we can find any table registered an updateURL in the listmanager,
// the provider is active. This is used to filter out google v2 provider
// without changing the preference.
isActiveProvider(provider) {
let listmanager = Cc[
"@mozilla.org/url-classifier/listmanager;1"
].getService(Ci.nsIUrlListManager);
let pref =
"browser.safebrowsing.provider." + provider +
".lists";
let tables = Services.prefs.getCharPref(pref,
"").split(
",");
for (let i = 0; i < tables.length; i++) {
let updateUrl = listmanager.getUpdateUrl(tables[i]);
if (updateUrl) {
return true;
}
}
return false;
},
};
/*
* Cache
*/
var Cache = {
// Tables that show cahe entries.
showCacheEnties:
null,
init() {
this.showCacheEnties =
new Set();
this.register();
this.render();
},
uninit() {
Services.obs.removeObserver(
this.refresh, UPDATE_FINISH);
},
register() {
this.refresh =
this.refresh.bind(
this);
Services.obs.addObserver(
this.refresh, UPDATE_FINISH);
},
render() {
this.createCacheEntries();
let refreshBtn = document.getElementById(
"refresh-cache-btn");
refreshBtn.addEventListener(
"click", () => {
this.refresh();
});
let clearBtn = document.getElementById(
"clear-cache-btn");
clearBtn.addEventListener(
"click", () => {
let dbservice = Cc[
"@mozilla.org/url-classifier/dbservice;1"].getService(
Ci.nsIUrlClassifierDBService
);
dbservice.clearCache();
// Since clearCache is async call, we just simply assume it will be
// updated in 100 milli-seconds.
setTimeout(() => {
this.refresh();
}, 100);
});
},
refresh() {
this.clearCacheEntries();
this.createCacheEntries();
},
clearCacheEntries() {
let ctbody = document.getElementById(
"cache-table-body");
while (ctbody.firstChild) {
ctbody.firstChild.remove();
}
let cetbody = document.getElementById(
"cache-entries-table-body");
while (cetbody.firstChild) {
cetbody.firstChild.remove();
}
},
createCacheEntries() {
function createRow(tds, body, cols) {
let tr = document.createElement(
"tr");
tds.forEach(
function (v, i) {
let td = document.createElement(
"td");
if (i == 0 && tds.length != cols) {
td.setAttribute(
"colspan", cols - tds.length + 1);
}
if (
typeof v ===
"object") {
if (v.l10n) {
document.l10n.setAttributes(td, v.l10n);
}
else {
td.removeAttribute(
"data-l10n-id");
td.appendChild(v);
}
}
else {
td.removeAttribute(
"data-l10n-id");
td.textContent = v;
}
tr.appendChild(td);
});
body.appendChild(tr);
}
let dbservice = Cc[
"@mozilla.org/url-classifier/dbservice;1"].getService(
Ci.nsIUrlClassifierInfo
);
for (let provider of Provider.providers) {
let pref =
"browser.safebrowsing.provider." + provider +
".lists";
let tables = Services.prefs.getCharPref(pref,
"").split(
",");
for (let table of tables) {
dbservice.getCacheInfo(table, {
onGetCacheComplete: aCache => {
let entries = aCache.entries;
if (entries.length === 0) {
this.showCacheEnties.
delete(table);
return;
}
let positiveCacheCount = 0;
for (let i = 0; i < entries.length; i++) {
let entry = entries.queryElementAt(
i,
Ci.nsIUrlClassifierCacheEntry
);
let matches = entry.matches;
positiveCacheCount += matches.length;
// If we don't have to show cache entries for this table then just
// skip the following code.
if (!
this.showCacheEnties.has(table)) {
continue;
}
let tds = [
table,
entry.prefix,
new Date(entry.expiry * 1000).toString(),
];
let j = 0;
do {
if (matches.length >= 1) {
let match = matches.queryElementAt(
j,
Ci.nsIUrlClassifierPositiveCacheEntry
);
let list = [
match.fullhash,
new Date(match.expiry * 1000).toString(),
];
tds = tds.concat(list);
}
else {
tds = tds.concat([
{ l10n:
"url-classifier-not-available" },
{ l10n:
"url-classifier-not-available" },
]);
}
createRow(
tds,
document.getElementById(
"cache-entries-table-body"),
5
);
j++;
tds = [
""];
}
while (j < matches.length);
}
// Create cache information entries.
let chk = document.createElement(
"input");
chk.type =
"checkbox";
chk.checked =
this.showCacheEnties.has(table);
chk.addEventListener(
"click", () => {
if (chk.checked) {
this.showCacheEnties.add(table);
}
else {
this.showCacheEnties.
delete(table);
}
this.refresh();
});
let tds = [table, entries.length, positiveCacheCount, chk];
createRow(
tds,
document.getElementById(
"cache-table-body"),
tds.length
);
},
});
}
}
let entries_div = document.getElementById(
"cache-entries");
entries_div.style.display =
this.showCacheEnties.size == 0 ?
"none" :
"block";
},
};
/*
* Debug
*/
var Debug = {
// url-classifier NSPR Log modules.
modules: [
"UrlClassifierDbService",
"nsChannelClassifier",
"UrlClassifier",
"UrlClassifierProtocolParser",
"UrlClassifierStreamUpdater",
"UrlClassifierPrefixSet",
"ApplicationReputation",
],
init() {
this.register();
this.render();
this.refresh();
},
uninit() {
Services.prefs.removeObserver(JSLOG_PREF,
this.refreshJSDebug);
},
register() {
this.refreshJSDebug =
this.refreshJSDebug.bind(
this);
Services.prefs.addObserver(JSLOG_PREF,
this.refreshJSDebug);
},
render() {
// This function update the log module text field if we click
// safebrowsing log module check box.
function logModuleUpdate(module) {
let txt = document.getElementById(
"log-modules");
let chk = document.getElementById(
"chk-" + module);
let dst = chk.checked ?
"," + module +
":5" :
"";
let re =
new RegExp(
",?" + module +
":[0-9]");
let str = txt.value.replace(re, dst);
if (chk.checked) {
str = txt.value === str ? str + dst : str;
}
txt.value = str.replace(/^,/,
"");
}
let setLog = document.getElementById(
"set-log-modules");
setLog.addEventListener(
"click",
this.nsprlog);
let setLogFile = document.getElementById(
"set-log-file");
setLogFile.addEventListener(
"click",
this.logfile);
let setJSLog = document.getElementById(
"js-log");
setJSLog.addEventListener(
"click",
this.jslog);
let modules = document.getElementById(
"log-modules");
let sbModules = document.getElementById(
"sb-log-modules");
for (let module of
this.modules) {
let container = document.createElement(
"label");
container.className =
"toggle-container-with-text";
sbModules.appendChild(container);
let chk = document.createElement(
"input");
chk.id =
"chk-" + module;
chk.type =
"checkbox";
chk.checked =
true;
chk.addEventListener(
"click", () => {
logModuleUpdate(module);
});
container.appendChild(chk, modules);
let span = document.createElement(
"span");
span.appendChild(document.createTextNode(module));
container.appendChild(span, modules);
}
this.modules.map(logModuleUpdate);
let file = Services.dirsvc.get(
"TmpD", Ci.nsIFile);
file.append(
"safebrowsing.log");
let logFile = document.getElementById(
"log-file");
logFile.value = file.path;
let curLog = document.getElementById(
"cur-log-modules");
curLog.childNodes[0].nodeValue =
"";
let curLogFile = document.getElementById(
"cur-log-file");
curLogFile.childNodes[0].nodeValue =
"";
},
refresh() {
this.refreshJSDebug();
// Disable configure log modules if log modules are already set
// by environment variable.
let logModules =
Services.env.get(
"MOZ_LOG") ||
Services.env.get(
"MOZ_LOG_MODULES") ||
Services.env.get(
"NSPR_LOG_MODULES");
if (logModules.length) {
document.getElementById(
"set-log-modules").disabled =
true;
for (let module of
this.modules) {
document.getElementById(
"chk-" + module).disabled =
true;
}
let curLogModules = document.getElementById(
"cur-log-modules");
curLogModules.childNodes[0].nodeValue = logModules;
}
// Disable set log file if log file is already set
// by environment variable.
let logFile =
Services.env.get(
"MOZ_LOG_FILE") || Services.env.get(
"NSPR_LOG_FILE");
if (logFile.length) {
document.getElementById(
"set-log-file").disabled =
true;
document.getElementById(
"log-file").value = logFile;
}
},
refreshJSDebug() {
let enabled = Services.prefs.getBoolPref(JSLOG_PREF,
false);
let jsChk = document.getElementById(
"js-log");
jsChk.checked = enabled;
let curJSLog = document.getElementById(
"cur-js-log");
if (enabled) {
document.l10n.setAttributes(curJSLog,
"url-classifier-enabled");
}
else {
document.l10n.setAttributes(curJSLog,
"url-classifier-disabled");
}
},
jslog() {
let enabled = Services.prefs.getBoolPref(JSLOG_PREF,
false);
Services.prefs.setBoolPref(JSLOG_PREF, !enabled);
},
nsprlog() {
// Turn off debugging for all the modules.
let children = Services.prefs.getBranch(
"logging.").getChildList(
"");
for (let pref of children) {
if (!pref.startsWith(
"config.")) {
Services.prefs.clearUserPref(`logging.${pref}`);
}
}
let value = document.getElementById(
"log-modules").value;
let logModules = value.split(
",");
for (let module of logModules) {
let [key, value] = module.split(
":");
Services.prefs.setIntPref(`logging.${key}`, parseInt(value, 10));
}
let curLogModules = document.getElementById(
"cur-log-modules");
curLogModules.childNodes[0].nodeValue = value;
},
logfile() {
let logFile = document.getElementById(
"log-file").value.trim();
Services.prefs.setCharPref(
"logging.config.LOG_FILE", logFile);
let curLogFile = document.getElementById(
"cur-log-file");
curLogFile.childNodes[0].nodeValue = logFile;
},
};