diff options
Diffstat (limited to 'toolkit/content/aboutUrlClassifier.js')
-rw-r--r-- | toolkit/content/aboutUrlClassifier.js | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/toolkit/content/aboutUrlClassifier.js b/toolkit/content/aboutUrlClassifier.js new file mode 100644 index 0000000000..28a2381593 --- /dev/null +++ b/toolkit/content/aboutUrlClassifier.js @@ -0,0 +1,719 @@ +/* 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, a) { + 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; + }, +}; |