diff options
Diffstat (limited to 'main_background.js')
-rw-r--r-- | main_background.js | 427 |
1 files changed, 192 insertions, 235 deletions
diff --git a/main_background.js b/main_background.js index 0bfb33c..d3d7bb4 100644 --- a/main_background.js +++ b/main_background.js @@ -20,15 +20,15 @@ * along with GNU LibreJS. If not, see <http://www.gnu.org/licenses/>. */ -var acorn = require('acorn'); -var acornLoose = require('acorn-loose'); -var legacy_license_lib = require("./legacy_license_check.js"); -var { ResponseProcessor } = require("./bg/ResponseProcessor"); -var { Storage, ListStore, hash } = require("./common/Storage"); -var { ListManager } = require("./bg/ListManager"); -var { ExternalLicenses } = require("./bg/ExternalLicenses"); - -console.log("main_background.js"); +const acorn = require('acorn'); +const legacy_license_lib = require('./legacy_license_check.js'); +const { ResponseProcessor } = require('./bg/ResponseProcessor'); +const { Storage, ListStore, hash } = require('./common/Storage'); +const { ListManager } = require('./bg/ListManager'); +const { ExternalLicenses } = require('./bg/ExternalLicenses'); +const { licenses } = require('./license_definitions'); + +console.log('main_background.js'); /** * If this is true, it evaluates entire scripts instead of returning as soon as it encounters a violation. * @@ -41,7 +41,7 @@ var time = Date.now(); function dbg_print(a, b) { if (PRINT_DEBUG == true) { - console.log("Time spent so far: " + (Date.now() - time) / 1000 + " seconds"); + console.log('Time spent so far: ' + (Date.now() - time) / 1000 + ' seconds'); if (b === undefined) { console.log(a); } else { @@ -64,24 +64,18 @@ function dbg_print(a, b) { - "// @license [magnet link] [identifier]" then "// @license-end" (may also use /* comments) - Automatic whitelist: (http://bzr.savannah.gnu.org/lh/librejs/dev/annotate/head:/data/script_libraries/script-libraries.json_ */ -const { licenses } = require("./license_definitions"); // These are objects that it will search for in an initial regex pass over non-free scripts. var reserved_objects = [ //"document", //"window", - "fetch", - "XMLHttpRequest", - "chrome", // only on chrome - "browser", // only on firefox - "eval" + 'fetch', + 'XMLHttpRequest', + 'chrome', // only on chrome + 'browser', // only on firefox + 'eval' ]; -// Generates JSON key for local storage -function get_storage_key(script_name, src_hash) { - return script_name; -} - /* * * Called when something changes the persistent data of the add-on. @@ -95,21 +89,13 @@ function get_storage_key(script_name, src_hash) { * */ function options_listener(changes, area) { - // The cache must be flushed when settings are changed - // TODO: See if this can be minimized - function flushed() { - dbg_print("cache flushed"); - } - //var flushingCache = browser.webRequest.handlerBehaviorChanged(flushed); - - - dbg_print("Items updated in area" + area + ": "); + dbg_print('Items updated in area' + area + ': '); var changedItems = Object.keys(changes); - var changed_items = ""; + var changed_items = ''; for (var i = 0; i < changedItems.length; i++) { var item = changedItems[i]; - changed_items += item + ","; + changed_items += item + ','; } dbg_print(changed_items); @@ -120,21 +106,21 @@ var activeMessagePorts = {}; var activityReports = {}; async function createReport(initializer) { if (!(initializer && (initializer.url || initializer.tabId))) { - throw new Error("createReport() needs an URL or a tabId at least"); + throw new Error('createReport() needs an URL or a tabId at least'); } let template = { - "accepted": [], - "blocked": [], - "blacklisted": [], - "whitelisted": [], - "unknown": [], + 'accepted': [], + 'blocked': [], + 'blacklisted': [], + 'whitelisted': [], + 'unknown': [], }; template = Object.assign(template, initializer); - let [url] = (template.url || (await browser.tabs.get(initializer.tabId)).url).split("#"); + let [url] = (template.url || (await browser.tabs.get(initializer.tabId)).url).split('#'); template.url = url; template.site = ListStore.siteItem(url); template.siteStatus = listManager.getStatus(template.site); - let list = { "whitelisted": whitelist, "blacklisted": blacklist }[template.siteStatus]; + let list = { 'whitelisted': whitelist, 'blacklisted': blacklist }[template.siteStatus]; if (list) { template.listedSite = ListManager.siteMatch(template.site, list); } @@ -159,7 +145,7 @@ async function openReportInTab(data) { */ function debug_delete_local() { browser.storage.local.clear(); - dbg_print("Local storage cleared"); + dbg_print('Local storage cleared'); } /** @@ -169,12 +155,12 @@ function debug_delete_local() { */ function debug_print_local() { function storage_got(items) { - console.log("%c Local storage: ", 'color: red;'); + console.log('%c Local storage: ', 'color: red;'); for (var i in items) { - console.log("%c " + i + " = " + items[i], 'color: blue;'); + console.log('%c ' + i + ' = ' + items[i], 'color: blue;'); } } - console.log("%c Variable 'activityReports': ", 'color: red;'); + console.log('%c Variable \'activityReports\': ', 'color: red;'); console.log(activityReports); browser.storage.local.get(storage_got); } @@ -200,7 +186,7 @@ async function updateReport(tabId, oldReport, updateUI = false) { for (let property of Object.keys(oldReport)) { let entries = oldReport[property]; if (!Array.isArray(entries)) continue; - let defValue = property === "accepted" || property === "blocked" ? property : "unknown"; + let defValue = property === 'accepted' || property === 'blocked' ? property : 'unknown'; for (let script of entries) { let status = listManager.getStatus(script[0], defValue); if (Array.isArray(newReport[status])) newReport[status].push(script); @@ -235,20 +221,20 @@ async function updateReport(tabId, oldReport, updateUI = false) { * Make sure it will use the right URL when refering to a certain script. * */ -async function addReportEntry(tabId, scriptHashOrUrl, action) { +async function addReportEntry(tabId, action) { let report = activityReports[tabId]; if (!report) report = activityReports[tabId] = await createReport({ tabId }); let type, actionValue; - for (type of ["accepted", "blocked", "whitelisted", "blacklisted"]) { + for (type of ['accepted', 'blocked', 'whitelisted', 'blacklisted']) { if (type in action) { actionValue = action[type]; break; } } if (!actionValue) { - console.debug("Something wrong with action", action); - return ""; + console.debug('Something wrong with action', action); + return ''; } // Search unused data for the given entry @@ -271,15 +257,12 @@ async function addReportEntry(tabId, scriptHashOrUrl, action) { entries.push(actionValue); } } catch (e) { - console.error("action %o, type %s, entryType %s", action, type, entryType, e); - entryType = "unknown"; + console.error('action %o, type %s, entryType %s', action, type, entryType, e); + entryType = 'unknown'; } if (activeMessagePorts[tabId]) { - try { - activeMessagePorts[tabId].postMessage({ show_info: report }); - } catch (e) { - } + activeMessagePorts[tabId].postMessage({ show_info: report }); } if (browser.sessions) browser.sessions.setTabValue(tabId, report.url, report); @@ -290,14 +273,14 @@ async function addReportEntry(tabId, scriptHashOrUrl, action) { function get_domain(url) { var domain = url.replace('http://', '').replace('https://', '').split(/[/?#]/)[0]; - if (url.indexOf("http://") == 0) { - domain = "http://" + domain; + if (url.indexOf('http://') == 0) { + domain = 'http://' + domain; } - else if (url.indexOf("https://") == 0) { - domain = "https://" + domain; + else if (url.indexOf('https://') == 0) { + domain = 'https://' + domain; } - domain = domain + "/"; - domain = domain.replace(/ /g, ""); + domain = domain + '/'; + domain = domain.replace(/ /g, ''); return domain; } @@ -306,26 +289,25 @@ function get_domain(url) { * This is the callback where the content scripts of the browser action will contact the background script. * */ -var portFromCS; async function connected(p) { - if (p.name === "contact_finder") { + if (p.name === 'contact_finder') { // style the contact finder panel await browser.tabs.insertCSS(p.sender.tab.id, { - file: "/content/dialog.css", - cssOrigin: "user", + file: '/content/dialog.css', + cssOrigin: 'user', matchAboutBlank: true, allFrames: true }); // Send a message back with the relevant settings - p.postMessage(await browser.storage.local.get(["prefs_subject", "prefs_body"])); + p.postMessage(await browser.storage.local.get(['prefs_subject', 'prefs_body'])); return; } p.onMessage.addListener(async function(m) { var update = false; var contact_finder = false; - for (let action of ["whitelist", "blacklist", "forget"]) { + for (let action of ['whitelist', 'blacklist', 'forget']) { if (m[action]) { let [key] = m[action]; if (m.site) { @@ -342,18 +324,18 @@ async function connected(p) { openReportInTab(m.report_tab); } // a debug feature - if (m["printlocalstorage"] !== undefined) { - console.log("Print local storage"); + if (m['printlocalstorage'] !== undefined) { + console.log('Print local storage'); debug_print_local(); } // invoke_contact_finder - if (m["invoke_contact_finder"] !== undefined) { + if (m['invoke_contact_finder'] !== undefined) { contact_finder = true; await injectContactFinder(); } // a debug feature (maybe give the user an option to do this?) - if (m["deletelocalstorage"] !== undefined) { - console.log("Delete local storage"); + if (m['deletelocalstorage'] !== undefined) { + console.log('Delete local storage'); debug_delete_local(); } @@ -365,8 +347,8 @@ async function connected(p) { //inject_contact_finder(tabs[0]["id"]); } if (update || m.update && activityReports[m.tabId]) { - let tabId = "tabId" in m ? m.tabId : tabs.pop().id; - dbg_print(`%c updating tab ${tabId}`, "color: red;"); + let tabId = 'tabId' in m ? m.tabId : tabs.pop().id; + dbg_print(`%c updating tab ${tabId}`, 'color: red;'); activeMessagePorts[tabId] = p; await updateReport(tabId, activityReports[tabId], true); } else { @@ -374,10 +356,10 @@ async function connected(p) { if (activityReports[tab.id]) { // If we have some data stored here for this tabID, send it dbg_print(`[TABID: ${tab.id}] Sending stored data associated with browser action'`); - p.postMessage({ "show_info": activityReports[tab.id] }); + p.postMessage({ 'show_info': activityReports[tab.id] }); } else { // create a new entry - let report = activityReports[tab.id] = await createReport({ "url": tab.url, tabId: tab.id }); + let report = activityReports[tab.id] = await createReport({ 'url': tab.url, tabId: tab.id }); p.postMessage({ show_info: report }); dbg_print(`[TABID: ${tab.id}] No data found, creating a new entry for this window.`); } @@ -392,8 +374,8 @@ async function connected(p) { * Delete the info we are storing about this tab if there is any. * */ -function delete_removed_tab_info(tab_id, remove_info) { - dbg_print("[TABID:" + tab_id + "]" + "Deleting stored info about closed tab"); +function delete_removed_tab_info(tab_id, _) { + dbg_print('[TABID:' + tab_id + ']' + 'Deleting stored info about closed tab'); if (activityReports[tab_id] !== undefined) { delete activityReports[tab_id]; } @@ -411,8 +393,8 @@ function delete_removed_tab_info(tab_id, remove_info) { * */ -async function onTabUpdated(tabId, changedInfo, tab) { - let [url] = tab.url.split("#"); +async function onTabUpdated(tabId, _, tab) { + let [url] = tab.url.split('#'); let report = activityReports[tabId]; if (!(report && report.url === url)) { let cache = browser.sessions && @@ -429,53 +411,35 @@ async function onTabActivated({ tabId }) { /* *********************************************************************************************** */ -var fname_data = require("./fname_data.json").fname_data; +var fname_data = require('./fname_data.json').fname_data; //************************this part can be tested in the HTML file index.html's script test.js**************************** function full_evaluate(script) { - var res = true; - if (script === undefined || script == "") { - return [true, "Harmless null script"]; + if (script === undefined || script == '') { + return [true, 'Harmless null script']; } - var ast = acornLoose.parse(script).body[0]; - - var flag = false; var amtloops = 0; - var loopkeys = { "for": true, "if": true, "while": true, "switch": true }; - var operators = { "||": true, "&&": true, "=": true, "==": true, "++": true, "--": true, "+=": true, "-=": true, "*": true }; + var loopkeys = { 'for': true, 'if': true, 'while': true, 'switch': true }; + var operators = { '||': true, '&&': true, '=': true, '==': true, '++': true, '--': true, '+=': true, '-=': true, '*': true }; try { var tokens = acorn.tokenizer(script); } catch (e) { - console.warn("Tokenizer could not be initiated (probably invalid code)"); - return [false, "Tokenizer could not be initiated (probably invalid code)"]; + console.warn('Tokenizer could not be initiated (probably invalid code)'); + return [false, 'Tokenizer could not be initiated (probably invalid code)']; } try { var toke = tokens.getToken(); } catch (e) { console.log(script); console.log(e); - console.warn("couldn't get first token (probably invalid code)"); - console.warn("Continuing evaluation"); + console.warn('couldn\'t get first token (probably invalid code)'); + console.warn('Continuing evaluation'); } /** - * Given the end of an identifer token, it tests for bracket suffix notation - */ - function being_called(end) { - var i = 0; - while (script.charAt(end + i).match(/\s/g) !== null) { - i++; - if (i >= script.length - 1) { - return false; - } - } - - return script.charAt(end + i) == "("; - } - /** * Given the end of an identifer token, it tests for parentheses */ function is_bsn(end) { @@ -486,9 +450,8 @@ function full_evaluate(script) { return false; } } - return script.charAt(end + i) == "["; + return script.charAt(end + i) == '['; } - var error_count = 0; var defines_functions = false; while (toke !== undefined && toke.type != acorn.tokTypes.eof) { if (toke.type.keyword !== undefined) { @@ -497,17 +460,17 @@ function full_evaluate(script) { // This type of loop detection ignores functional loop alternatives and ternary operators - if (toke.type.keyword == "function") { - dbg_print("%c NOTICE: Function declaration.", "color:green"); + if (toke.type.keyword == 'function') { + dbg_print('%c NOTICE: Function declaration.', 'color:green'); defines_functions = true; } if (loopkeys[toke.type.keyword] !== undefined) { amtloops++; if (amtloops > 3) { - dbg_print("%c NONTRIVIAL: Too many loops/conditionals.", "color:red"); + dbg_print('%c NONTRIVIAL: Too many loops/conditionals.', 'color:red'); if (DEBUG == false) { - return [false, "NONTRIVIAL: Too many loops/conditionals."]; + return [false, 'NONTRIVIAL: Too many loops/conditionals.']; } } } @@ -517,44 +480,44 @@ function full_evaluate(script) { } else if (toke.value !== undefined) { var status = fname_data[toke.value]; if (status === true) { // is the identifier banned? - dbg_print("%c NONTRIVIAL: nontrivial token: '" + toke.value + "'", "color:red"); + dbg_print('%c NONTRIVIAL: nontrivial token: \'' + toke.value + '\'', 'color:red'); if (DEBUG == false) { - return [false, "NONTRIVIAL: nontrivial token: '" + toke.value + "'"]; + return [false, 'NONTRIVIAL: nontrivial token: \'' + toke.value + '\'']; } } else if (status === false) {// is the identifier not banned? // Is there bracket suffix notation? if (is_bsn(toke.end)) { - dbg_print("%c NONTRIVIAL: Bracket suffix notation on variable '" + toke.value + "'", "color:red"); + dbg_print('%c NONTRIVIAL: Bracket suffix notation on variable \'' + toke.value + '\'', 'color:red'); if (DEBUG == false) { - return [false, "%c NONTRIVIAL: Bracket suffix notation on variable '" + toke.value + "'"]; + return [false, '%c NONTRIVIAL: Bracket suffix notation on variable \'' + toke.value + '\'']; } } } else if (status === undefined) {// is the identifier user defined? // Is there bracket suffix notation? if (is_bsn(toke.end)) { - dbg_print("%c NONTRIVIAL: Bracket suffix notation on variable '" + toke.value + "'", "color:red"); + dbg_print('%c NONTRIVIAL: Bracket suffix notation on variable \'' + toke.value + '\'', 'color:red'); if (DEBUG == false) { - return [false, "NONTRIVIAL: Bracket suffix notation on variable '" + toke.value + "'"]; + return [false, 'NONTRIVIAL: Bracket suffix notation on variable \'' + toke.value + '\'']; } } } else { - dbg_print("trivial token:" + toke.value); + dbg_print('trivial token:' + toke.value); } } // If not a keyword or an identifier it's some kind of operator, field parenthesis, brackets try { toke = tokens.getToken(); } catch (e) { - dbg_print("Denied script because it cannot be parsed."); - return [false, "NONTRIVIAL: Cannot be parsed. This could mean it is a 404 error."]; + dbg_print('Denied script because it cannot be parsed.'); + return [false, 'NONTRIVIAL: Cannot be parsed. This could mean it is a 404 error.']; } } - dbg_print("%cAppears to be trivial.", "color:green;"); + dbg_print('%cAppears to be trivial.', 'color:green;'); if (defines_functions === true) - return [true, "Script appears to be trivial but defines functions."]; + return [true, 'Script appears to be trivial but defines functions.']; else - return [true, "Script appears to be trivial."]; + return [true, 'Script appears to be trivial.']; } @@ -573,35 +536,31 @@ function full_evaluate(script) { */ function evaluate(script, name) { function reserved_object_regex(object) { - var arith_operators = "\\+\\-\\*\\/\\%\\="; - var scope_chars = "\{\}\]\[\(\)\,"; - var trailing_chars = "\s*" + "\(\.\["; - return new RegExp("(?:[^\\w\\d]|^|(?:" + arith_operators + "))" + object + '(?:\\s*?(?:[\\;\\,\\.\\(\\[])\\s*?)', "g"); + var arith_operators = '\\+\\-\\*\\/\\%\\='; + return new RegExp('(?:[^\\w\\d]|^|(?:' + arith_operators + '))' + object + '(?:\\s*?(?:[\\;\\,\\.\\(\\[])\\s*?)', 'g'); } - reserved_object_regex("window"); - var all_strings = new RegExp('".*?"' + "|'.*?'", "gm"); + reserved_object_regex('window'); var ml_comment = /\/\*([\s\S]+?)\*\//g; var il_comment = /\/\/.+/gm; - var bracket_pairs = /\[.+?\]/g; - var temp = script.replace(/'.+?'+/gm, "'string'"); + var temp = script.replace(/'.+?'+/gm, '\'string\''); temp = temp.replace(/".+?"+/gm, '"string"'); - temp = temp.replace(ml_comment, ""); - temp = temp.replace(il_comment, ""); - dbg_print("%c ------evaluation results for " + name + "------", "color:white"); - dbg_print("Script accesses reserved objects?"); + temp = temp.replace(ml_comment, ''); + temp = temp.replace(il_comment, ''); + dbg_print('%c ------evaluation results for ' + name + '------', 'color:white'); + dbg_print('Script accesses reserved objects?'); var flag = true; - var reason = "" + var reason = '' // This is where individual "passes" are made over the code for (var i = 0; i < reserved_objects.length; i++) { var res = reserved_object_regex(reserved_objects[i]).exec(temp); if (res != null) { - dbg_print("%c fail", "color:red;"); + dbg_print('%c fail', 'color:red;'); flag = false; - reason = "Script uses a reserved object (" + reserved_objects[i] + ")"; + reason = 'Script uses a reserved object (' + reserved_objects[i] + ')'; } } if (flag) { - dbg_print("%c pass", "color:green;"); + dbg_print('%c pass', 'color:green;'); } else { return [flag, reason]; } @@ -612,15 +571,15 @@ function evaluate(script, name) { function validateLicense(matches) { if (!(Array.isArray(matches) && matches.length >= 4)) { - return [false, "Malformed or unrecognized license tag."]; + return [false, 'Malformed or unrecognized license tag.']; } - const [all, first] = [matches[0], matches[2].replace("&", "&")]; + const [all, first] = [matches[0], matches[2].replace('&', '&')]; for (const key in licenses) { // Match by link on first parameter (legacy) for (const url of licenses[key].canonicalUrl) { - if (first === url || first === url.replace("^http://", "https://")) { + if (first === url || first === url.replace('^http://', 'https://')) { return [true, `Recognized license: "${licenses[key].licenseName}".`]; } } @@ -647,12 +606,12 @@ function license_read(scriptSrc, name, external = false) { return [true, scriptSrc, `Licensed under: ${license}`]; } if (listManager.builtInHashes.has(hash(scriptSrc))) { - return [true, scriptSrc, "Common script known to be free software."]; + return [true, scriptSrc, 'Common script known to be free software.']; } - let editedSrc = ""; + let editedSrc = ''; let uneditedSrc = scriptSrc.trim(); - let reason = uneditedSrc ? "" : "Empty source."; + let reason = uneditedSrc ? '' : 'Empty source.'; let partsDenied = false; let partsAccepted = false; @@ -661,14 +620,14 @@ function license_read(scriptSrc, name, external = false) { return true; // empty, ignore it } const [trivial, message] = external ? - [false, "External script with no known license"] + [false, 'External script with no known license'] : evaluate(s, name); if (trivial) { partsAccepted = true; editedSrc += s; } else { partsDenied = true; - if (s.startsWith("javascript:")) + if (s.startsWith('javascript:')) editedSrc += `# LIBREJS BLOCKED: ${message}`; else editedSrc += `/*\nLIBREJS BLOCKED: ${message}\n*/`; @@ -678,7 +637,7 @@ function license_read(scriptSrc, name, external = false) { } while (uneditedSrc) { - const openingMatch = /\/[\/\*]\s*?(@license)\s+(\S+)\s+(\S+).*$/mi.exec(uneditedSrc); + const openingMatch = /\/[/*]\s*?(@license)\s+(\S+)\s+(\S+).*$/mi.exec(uneditedSrc); if (!openingMatch) { // no license found, check for triviality checkTriviality(uneditedSrc); break; @@ -694,12 +653,12 @@ function license_read(scriptSrc, name, external = false) { const closureMatch = /\/([*/])\s*@license-end\b[^*/\n]*/i.exec(uneditedSrc); if (!closureMatch) { - const msg = "ERROR: @license with no @license-end"; + const msg = 'ERROR: @license with no @license-end'; return [false, `\n/*\n ${msg} \n*/\n`, msg]; } - const closureEndIndex = closureMatch.index + closureMatch[0].length; - const commentEndOffset = uneditedSrc.substring(closureEndIndex).indexOf(closureMatch[1] === "*" ? "*/" : "\n"); + let closureEndIndex = closureMatch.index + closureMatch[0].length; + const commentEndOffset = uneditedSrc.substring(closureEndIndex).indexOf(closureMatch[1] === '*' ? '*/' : '\n'); if (commentEndOffset !== -1) { closureEndIndex += commentEndOffset; } @@ -741,17 +700,17 @@ async function get_script(response, url, tabId = -1, whitelisted = false, index } - let scriptName = url.split("/").pop(); + let scriptName = url.split('/').pop(); if (whitelisted) { if (tabId !== -1) { let site = ListManager.siteMatch(url, whitelist); // Accept without reading script, it was explicitly whitelisted let reason = site ? `All ${site} whitelisted by user` - : "Address whitelisted by user"; - addReportEntry(tabId, url, { "whitelisted": [site || url, reason], url }); + : 'Address whitelisted by user'; + addReportEntry(tabId, { 'whitelisted': [site || url, reason], url }); } - if (response.startsWith("javascript:")) + if (response.startsWith('javascript:')) return result(response); else return result(`/* LibreJS: script whitelisted by user preference. */\n${response}`); @@ -763,25 +722,27 @@ async function get_script(response, url, tabId = -1, whitelisted = false, index return result(verdict ? response : editedSource); } - let sourceHash = hash(response); let domain = get_domain(url); let report = activityReports[tabId] || (activityReports[tabId] = await createReport({ tabId })); updateBadge(tabId, report, !verdict); - let category = await addReportEntry(tabId, sourceHash, { "url": domain, [verdict ? "accepted" : "blocked"]: [url, reason] }); + let category = await addReportEntry(tabId, { 'url': domain, [verdict ? 'accepted' : 'blocked']: [url, reason] }); switch (category) { - case "blacklisted": + case 'blacklisted': { editedSource = `/* LibreJS: script ${category} by user. */`; - return result(response.startsWith("javascript:") + return result(response.startsWith('javascript:') ? `javascript:void(${encodeURIComponent(editedSource)})` : editedSource); - case "whitelisted": - return result(response.startsWith("javascript:") + } + case 'whitelisted': { + return result(response.startsWith('javascript:') ? response : `/* LibreJS: script ${category} by user. */\n${response}`); - default: + } + default: { let scriptSource = verdict ? response : editedSource; - return result(response.startsWith("javascript:") + return result(response.startsWith('javascript:') ? (verdict ? scriptSource : `javascript:void(/* ${scriptSource} */)`) : `/* LibreJS: script ${category}. */\n${scriptSource}` ); + } } } @@ -789,9 +750,9 @@ async function get_script(response, url, tabId = -1, whitelisted = false, index function updateBadge(tabId, report = null, forceRed = false) { let blockedCount = report ? report.blocked.length + report.blacklisted.length : 0; let [text, color] = blockedCount > 0 || forceRed - ? [blockedCount && blockedCount.toString() || "!", "red"] : ["✓", "green"] + ? [blockedCount && blockedCount.toString() || '!', 'red'] : ['✓', 'green'] let { browserAction } = browser; - if ("setBadgeText" in browserAction) { + if ('setBadgeText' in browserAction) { browserAction.setBadgeText({ text, tabId }); browserAction.setBadgeBackgroundColor({ color, tabId }); } else { @@ -815,11 +776,11 @@ async function blockBlacklistedScripts(request) { let { url, tabId, documentUrl } = request; url = ListStore.urlItem(url); let status = listManager.getStatus(url); - if (status !== "blacklisted") return {}; + if (status !== 'blacklisted') return {}; let blacklistedSite = ListManager.siteMatch(url, blacklist); - await addReportEntry(tabId, url, { + await addReportEntry(tabId, { url: documentUrl, - "blacklisted": [url, /\*/.test(blacklistedSite) ? `User blacklisted ${blacklistedSite}` : "Blacklisted by user"] + 'blacklisted': [url, /\*/.test(blacklistedSite) ? `User blacklisted ${blacklistedSite}` : 'Blacklisted by user'] }); return { cancel: true }; } @@ -844,36 +805,36 @@ var ResponseHandler = { let blacklistedSite = ListManager.siteMatch(site, blacklist); let blacklisted = blacklistedSite || blacklist.contains(url); - let topUrl = type === "sub_frame" && request.frameAncestors && request.frameAncestors.pop() || documentUrl; + let topUrl = type === 'sub_frame' && request.frameAncestors && request.frameAncestors.pop() || documentUrl; if (blacklisted) { - if (type === "script") { + if (type === 'script') { // this shouldn't happen, because we intercept earlier in blockBlacklistedScripts() return ResponseProcessor.REJECT; } - if (type === "main_frame") { // we handle the page change here too, since we won't call edit_html() + if (type === 'main_frame') { // we handle the page change here too, since we won't call edit_html() activityReports[tabId] = await createReport({ url: fullUrl, tabId }); // Go on without parsing the page: it was explicitly blacklisted let reason = blacklistedSite ? `All ${blacklistedSite} blacklisted by user` - : "Address blacklisted by user"; - await addReportEntry(tabId, url, { "blacklisted": [blacklistedSite || url, reason], url: fullUrl }); + : 'Address blacklisted by user'; + await addReportEntry(tabId, { 'blacklisted': [blacklistedSite || url, reason], url: fullUrl }); } // use CSP to restrict JavaScript execution in the page request.responseHeaders.unshift({ - name: `Content-security-policy`, - value: `script-src 'none';` + name: 'Content-security-policy', + value: 'script-src \'none\';' }); return { responseHeaders: request.responseHeaders }; // let's skip the inline script parsing, since we block by CSP } else { let whitelistedSite = ListManager.siteMatch(site, whitelist); let whitelisted = response.whitelisted = whitelistedSite || whitelist.contains(url); - if (type === "script") { + if (type === 'script') { if (whitelisted) { // accept the script and stop processing - addReportEntry(tabId, url, { + addReportEntry(tabId, { url: topUrl, - "whitelisted": [url, whitelistedSite ? `User whitelisted ${whitelistedSite}` : "Whitelisted by user"] + 'whitelisted': [url, whitelistedSite ? `User whitelisted ${whitelistedSite}` : 'Whitelisted by user'] }); return ResponseProcessor.ACCEPT; } else { @@ -882,13 +843,13 @@ var ResponseHandler = { let verdict, ret; let msg = scriptInfo.toString(); if (scriptInfo.free) { - verdict = "accepted"; + verdict = 'accepted'; ret = ResponseProcessor.ACCEPT; } else { - verdict = "blocked"; + verdict = 'blocked'; ret = ResponseProcessor.REJECT; } - addReportEntry(tabId, url, { url, [verdict]: [url, msg] }); + addReportEntry(tabId, { url, [verdict]: [url, msg] }); return ret; } } @@ -904,7 +865,7 @@ var ResponseHandler = { */ async post(response) { let { type } = response.request; - let handle_it = type === "script" ? handle_script : handle_html; + let handle_it = type === 'script' ? handle_script : handle_html; return await handle_it(response, response.whitelisted); } } @@ -914,7 +875,7 @@ var ResponseHandler = { */ async function handle_script(response, whitelisted) { let { text, request } = response; - let { url, tabId, frameId } = request; + let { url, tabId } = request; url = ListStore.urlItem(url); let edited = await get_script(text, url, tabId, whitelisted, -2); return Array.isArray(edited) ? edited[0] : edited; @@ -928,7 +889,7 @@ function doc2HTML(doc) { let s = doc.documentElement.outerHTML; if (doc.doctype) { let dt = doc.doctype; - let sDoctype = `<!DOCTYPE ${dt.name || "html"}`; + let sDoctype = `<!DOCTYPE ${dt.name || 'html'}`; if (dt.publicId) sDoctype += ` PUBLIC "${dt.publicId}"`; if (dt.systemId) sDoctype += ` "${dt.systemId}"`; s = `${sDoctype}>\n${s}`; @@ -940,7 +901,7 @@ function doc2HTML(doc) { * Shortcut to create a correctly namespaced DOM HTML elements */ function createHTMLElement(doc, name) { - return doc.createElementNS("http://www.w3.org/1999/xhtml", name); + return doc.createElementNS('http://www.w3.org/1999/xhtml', name); } /** @@ -948,7 +909,7 @@ function createHTMLElement(doc, name) { * NOSCRIPT elements to visible the same way as NoScript and uBlock do) */ function forceElement(doc, element) { - let replacement = createHTMLElement(doc, "span"); + let replacement = createHTMLElement(doc, 'span'); replacement.innerHTML = element.innerHTML; element.replaceWith(replacement); return replacement; @@ -962,12 +923,11 @@ function forceElement(doc, element) { function forceNoscriptElements(doc) { let shown = 0; // inspired by NoScript's onScriptDisabled.js - for (let noscript of doc.querySelectorAll("noscript:not([data-librejs-nodisplay])")) { + for (let noscript of doc.querySelectorAll('noscript:not([data-librejs-nodisplay])')) { let replacement = forceElement(doc, noscript); // emulate meta-refresh let meta = replacement.querySelector('meta[http-equiv="refresh"]'); if (meta) { - refresh = true; doc.head.appendChild(meta); } shown++; @@ -981,7 +941,7 @@ function forceNoscriptElements(doc) { */ function showConditionalElements(doc) { let shown = 0; - for (let element of document.querySelectorAll("[data-librejs-display]")) { + for (let element of document.querySelectorAll('[data-librejs-display]')) { forceElement(doc, element); shown++; } @@ -998,27 +958,27 @@ function read_metadata(meta_element) { return; } - console.log("metadata found"); + console.log('metadata found'); var metadata = {}; try { metadata = JSON.parse(meta_element.innerHTML); } catch (error) { - console.log("Could not parse metadata on page.") + console.log('Could not parse metadata on page.') return false; } - var license_str = metadata["intrinsic-events"]; + var license_str = metadata['intrinsic-events']; if (license_str === undefined) { - console.log("No intrinsic events license"); + console.log('No intrinsic events license'); return false; } console.log(license_str); - var parts = license_str.split(" "); + var parts = license_str.split(' '); if (parts.length != 2) { - console.log("invalid (>2 tokens)"); + console.log('invalid (>2 tokens)'); return false; } @@ -1026,15 +986,15 @@ function read_metadata(meta_element) { parts[0] = parts[0].replace(/&/g, '&'); try { - for (const url of licenses[part[1]].canonicalUrl) { - if (url.startsWith("magnet:") && url == parts[0]) { + for (const url of licenses[parts[1]].canonicalUrl) { + if (url.startsWith('magnet:') && url == parts[0]) { return true; } } - console.log("invalid (doesn't match licenses)"); + console.log('invalid (doesn\'t match licenses)'); return false; } catch (error) { - console.log("invalid (threw error, key didn't exist)"); + console.log('invalid (threw error, key didn\'t exist)'); return false; } } @@ -1045,7 +1005,7 @@ function read_metadata(meta_element) { async function editHtml(html, documentUrl, tabId, frameId, whitelisted) { var parser = new DOMParser(); - var html_doc = parser.parseFromString(html, "text/html"); + var html_doc = parser.parseFromString(html, 'text/html'); // moves external licenses reference, if any, before any <SCRIPT> element ExternalLicenses.optimizeDocument(html_doc, { tabId, frameId, documentUrl }); @@ -1059,8 +1019,8 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted) { var scripts = html_doc.scripts; - var meta_element = html_doc.getElementById("LibreJS-info"); - var first_script_src = ""; + var meta_element = html_doc.getElementById('LibreJS-info'); + var first_script_src = ''; // get the potential inline source that can contain a license for (let script of scripts) { @@ -1072,38 +1032,36 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted) { } let license = false; - if (first_script_src != "") { + if (first_script_src != '') { license = legacy_license_lib.check(first_script_src); } let findLine = finder => finder.test(html) && html.substring(0, finder.lastIndex).split(/\n/).length || 0; if (read_metadata(meta_element) || license) { - console.log("Valid license for intrinsic events found"); + console.log('Valid license for intrinsic events found'); let line, extras; if (meta_element) { line = findLine(/id\s*=\s*['"]?LibreJS-info\b/gi); - extras = "(0)"; + extras = '(0)'; } else if (license) { line = html.substring(0, html.indexOf(first_script_src)).split(/\n/).length; - extras = "\n" + first_script_src; + extras = '\n' + first_script_src; } - let viewUrl = line ? `view-source:${documentUrl}#line${line}(<${meta_element ? meta_element.tagName : "SCRIPT"}>)${extras}` : url; - addReportEntry(tabId, url, { url, "accepted": [viewUrl, `Global license for the page: ${license}`] }); + let viewUrl = line ? `view-source:${documentUrl}#line${line}(<${meta_element ? meta_element.tagName : 'SCRIPT'}>)${extras}` : url; + addReportEntry(tabId, { url, 'accepted': [viewUrl, `Global license for the page: ${license}`] }); // Do not process inline scripts scripts = []; } else { let dejaVu = new Map(); // deduplication map & edited script cache let modified = false; // Deal with intrinsic events - let intrinsecindex = 0; let intrinsicFinder = /<[a-z][^>]*\b(on\w+|href\s*=\s*['"]?javascript:)/gi; - for (let element of html_doc.all) { + for (let element of html_doc.querySelectorAll('*')) { let line = -1; for (let attr of element.attributes) { let { name, value } = attr; value = value.trim(); - if (name.startsWith("on") || (name === "href" && value.toLowerCase().startsWith("javascript:"))) { - intrinsecindex++; + if (name.startsWith('on') || (name === 'href' && value.toLowerCase().startsWith('javascript:'))) { if (line === -1) { line = findLine(intrinsicFinder); } @@ -1114,7 +1072,7 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted) { edited = dejaVu.get(key); } else { let url = `view-source:${documentUrl}#line${line}(<${element.tagName} ${name}>)\n${value.trim()}`; - if (name === "href") value = decodeURIComponent(value); + if (name === 'href') value = decodeURIComponent(value); edited = await get_script(value, url, tabId, whitelist.contains(url)); dejaVu.set(key, edited); } if (edited && edited !== value) { @@ -1133,7 +1091,7 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted) { for (let i = 0, len = scripts.length; i < len; i++) { let script = scripts[i]; let line = findLine(scriptFinder); - if (!script.src && !(script.type && script.type !== "text/javascript")) { + if (!script.src && !(script.type && script.type !== 'text/javascript')) { let source = script.textContent.trim(); let editedSource; if (dejaVu.has(source)) { @@ -1170,18 +1128,18 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted) { async function handle_html(response, whitelisted) { let { text, request } = response; let { url, tabId, frameId, type } = request; - if (type === "main_frame") { + if (type === 'main_frame') { activityReports[tabId] = await createReport({ url, tabId }); updateBadge(tabId); } return await editHtml(text, url, tabId, frameId, whitelisted); } -var whitelist = new ListStore("pref_whitelist", Storage.CSV); -var blacklist = new ListStore("pref_blacklist", Storage.CSV); +var whitelist = new ListStore('pref_whitelist', Storage.CSV); +var blacklist = new ListStore('pref_blacklist', Storage.CSV); var listManager = new ListManager(whitelist, blacklist, // built-in whitelist of script hashes, e.g. jQuery - Object.values(require("./hash_script/whitelist").whitelist) + Object.values(require('./hash_script/whitelist').whitelist) .reduce((a, b) => a.concat(b)) // as a flat array .map(script => script.hash) ); @@ -1189,7 +1147,7 @@ var listManager = new ListManager(whitelist, blacklist, async function initDefaults() { let defaults = { - pref_subject: "Issues with Javascript on your website", + pref_subject: 'Issues with Javascript on your website', pref_body: `Please consider using a free license for the Javascript on your website. [Message generated by LibreJS. See https://www.gnu.org/software/librejs/ for more information] @@ -1223,18 +1181,18 @@ async function init_addon() { browser.tabs.onActivated.addListener(onTabActivated); // Prevents Google Analytics from being loaded from Google servers let all_types = [ - "beacon", "csp_report", "font", "image", "imageset", "main_frame", "media", - "object", "object_subrequest", "ping", "script", "stylesheet", "sub_frame", - "web_manifest", "websocket", "xbl", "xml_dtd", "xmlhttprequest", "xslt", - "other" + 'beacon', 'csp_report', 'font', 'image', 'imageset', 'main_frame', 'media', + 'object', 'object_subrequest', 'ping', 'script', 'stylesheet', 'sub_frame', + 'web_manifest', 'websocket', 'xbl', 'xml_dtd', 'xmlhttprequest', 'xslt', + 'other' ]; browser.webRequest.onBeforeRequest.addListener(blockGoogleAnalytics, - { urls: ["<all_urls>"], types: all_types }, - ["blocking"] + { urls: ['<all_urls>'], types: all_types }, + ['blocking'] ); browser.webRequest.onBeforeRequest.addListener(blockBlacklistedScripts, - { urls: ["<all_urls>"], types: ["script"] }, - ["blocking"] + { urls: ['<all_urls>'], types: ['script'] }, + ['blocking'] ); browser.webRequest.onResponseStarted.addListener(request => { let { tabId } = request; @@ -1242,15 +1200,14 @@ async function init_addon() { if (report) { updateBadge(tabId, activityReports[tabId]); } - }, { urls: ["<all_urls>"], types: ["main_frame"] }); + }, { urls: ['<all_urls>'], types: ['main_frame'] }); // Analyzes all the html documents and external scripts as they're loaded ResponseProcessor.install(ResponseHandler); legacy_license_lib.init(); - - let Test = require("./common/Test"); + const Test = require('./common/Test'); if (Test.getURL()) { // export testable functions to the global scope this.LibreJS = { @@ -1260,7 +1217,7 @@ async function init_addon() { ListManager, ListStore, Storage, }; // create or focus the autotest tab if it's a debugging session - if ((await browser.management.getSelf()).installType === "development") { + if ((await browser.management.getSelf()).installType === 'development') { Test.getTab(true); } } @@ -1272,8 +1229,8 @@ async function init_addon() { */ async function injectContactFinder(tabId) { await Promise.all([ - browser.tabs.insertCSS(tabId, { file: "/content/overlay.css", cssOrigin: "user" }), - browser.tabs.executeScript(tabId, { file: "/content/contactFinder.js" }), + browser.tabs.insertCSS(tabId, { file: '/content/overlay.css', cssOrigin: 'user' }), + browser.tabs.executeScript(tabId, { file: '/content/contactFinder.js' }), ]); } |