/* 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/. */ var Ci = Components.interfaces; var Cc = Components.classes; var Cu = Components.utils; ChromeUtils.import("resource://gre/modules/Services.jsm"); const bundle = Services.strings.createBundle( "chrome://global/locale/aboutUrlClassifier.properties"); const UPDATE_BEGIN = "safebrowsing-update-begin"; const UPDATE_FINISH = "safebrowsing-update-finished"; const JSLOG_PREF = "browser.safebrowsing.debug"; const STR_NA = bundle.GetStringFromName("NotAvailable"); function unLoad() { window.removeEventListener("unload", unLoad); Provider.uninit(); Cache.uninit(); Debug.uninit(); } function onLoad() { window.removeEventListener("load", onLoad); window.addEventListener("unload", unLoad); Provider.init(); Cache.init(); Debug.init(); } /* * 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) { this.providers.add(child.split(".")[0]); } 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"); elem.childNodes[0].nodeValue = bundle.GetStringFromName("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")) { elem.childNodes[0].nodeValue = bundle.GetStringFromName("success"); } else if (aData.startsWith("update error")) { elem.childNodes[0].nodeValue = bundle.formatStringFromName("updateError", [aData.split(": ")[1]], 1); } else if (aData.startsWith("download error")) { elem.childNodes[0].nodeValue = bundle.formatStringFromName("downloadError", [aData.split(": ")[1]], 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); }); let str = bundle.GetStringFromName("TriggerUpdate"); btn.appendChild(document.createTextNode(str)); td.appendChild(btn); } else { let str = column.id === "col-lastupdateresult" ? STR_NA : ""; td.appendChild(document.createTextNode(str)); } 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) : STR_NA; pref = "browser.safebrowsing.provider." + provider + ".nextupdatetime"; let nut = Services.prefs.getCharPref(pref, ""); values["col-nextupdatetime"] = nut ? new Date(nut * 1) : STR_NA; 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) : STR_NA; for (let key of Object.keys(values)) { let elem = document.getElementById(provider + "-" + key); elem.childNodes[0].nodeValue = values[key]; } } }, // 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"); elem.childNodes[0].nodeValue = bundle.GetStringFromName("CannotUpdate"); } }, }; /* * 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, a) { let td = document.createElement("td"); if (i == 0 && tds.length != cols) { td.setAttribute("colspan", cols - tds.length + 1); } let elem = typeof v === "object" ? v : document.createTextNode(v); td.appendChild(elem); 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([STR_NA, STR_NA]); } 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", "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("div"); 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 label = document.createElement("label"); label.for = chk.id; label.appendChild(document.createTextNode(module)); container.appendChild(label, 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 env = Cc["@mozilla.org/process/environment;1"] .getService(Ci.nsIEnvironment); let logModules = env.get("MOZ_LOG") || env.get("MOZ_LOG_MODULES") || env.get("NSPR_LOG_MODULES"); if (logModules.length > 0) { 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 = env.get("MOZ_LOG_FILE") || env.get("NSPR_LOG_FILE"); if (logFile.length > 0) { 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"); curJSLog.childNodes[0].nodeValue = enabled ? bundle.GetStringFromName("Enabled") : bundle.GetStringFromName("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; } };