diff options
-rw-r--r-- | .eslintrc.js | 17 | ||||
-rw-r--r-- | bg/ExternalLicenses.js | 29 | ||||
-rw-r--r-- | bg/ListManager.js | 20 | ||||
-rw-r--r-- | bg/ResponseMetaData.js | 18 | ||||
-rw-r--r-- | bg/ResponseProcessor.js | 32 | ||||
-rw-r--r-- | common/Storage.js | 24 | ||||
-rw-r--r-- | common/Test.js | 6 | ||||
-rw-r--r-- | main_background.js | 427 | ||||
-rw-r--r-- | test/spec/LibreJSSpec.js | 140 |
9 files changed, 344 insertions, 369 deletions
diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..d48f883 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,17 @@ +module.exports = { + "env": { + "browser": true, + "commonjs": true, + "es2021": true, + "node": true, + "webextensions": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": "latest" + }, + "rules": { + "quotes": ["error", "single"], + "no-console": ["off"] + } +} diff --git a/bg/ExternalLicenses.js b/bg/ExternalLicenses.js index 0e09b6d..1a8e58c 100644 --- a/bg/ExternalLicenses.js +++ b/bg/ExternalLicenses.js @@ -23,12 +23,12 @@ Singleton to handle external licenses, e.g. WebLabels */ -"use strict"; +'use strict'; let licensesByLabel = new Map(); let licensesByUrl = new Map(); { - let { licenses } = require("../license_definitions"); + let { licenses } = require('../license_definitions'); let mapByLabel = (label, license) => licensesByLabel.set(label.toUpperCase(), license); for (let [id, l] of Object.entries(licenses)) { let { identifier, canonicalUrl, licenseName } = l; @@ -64,7 +64,7 @@ var ExternalLicenses = { let frameCache = tabCache && tabCache.get(frameId); let cache = frameCache && frameCache.get(documentUrl); let scriptInfo = await browser.tabs.sendMessage(tabId, { - action: "checkLicensedScript", + action: 'checkLicensedScript', url, cache, }, { frameId }); @@ -74,10 +74,10 @@ var ExternalLicenses = { } scriptInfo.licenses = new Set(); scriptInfo.toString = function() { - let licenseIds = [...this.licenses].map(l => l.identifier).sort().join(", "); + let licenseIds = [...this.licenses].map(l => l.identifier).sort().join(', '); return licenseIds - ? `Free license${this.licenses.size > 1 ? "s" : ""} (${licenseIds})` - : "Unknown license(s)"; + ? `Free license${this.licenses.size > 1 ? 's' : ''} (${licenseIds})` + : 'Unknown license(s)'; } let match = (map, key) => { if (map.has(key)) { @@ -112,22 +112,23 @@ var ExternalLicenses = { } frameCache.set(frameId, new Map([[documentUrl, cache]])); - let link = doc.querySelector(`link[rel="jslicense"], link[data-jslicense="1"], a[rel="jslicense"], a[data-jslicense="1"]`); + let link = doc.querySelector('link[rel="jslicense"], link[data-jslicense="1"], a[rel="jslicense"], a[data-jslicense="1"]'); if (link) { - let href = link.getAttribute("href"); + let href = link.getAttribute('href'); cache.webLabels = { href }; let move = () => !!doc.head.insertBefore(link, doc.head.firstChild); if (link.parentNode === doc.head) { - for (let node = link; node = node.previousElementSibling;) { - if (node.tagName.toUpperCase() === "SCRIPT") { + let node = link.previousElementSibling; + for (; node; node = node.previousElementSibling) { + if (node.tagName.toUpperCase() === 'SCRIPT') { return move(); } } } else { // the reference is only in the body - if (link.tagName.toUpperCase() === "A") { - let newLink = doc.createElement("link"); - newLink.rel = "jslicense"; - newLink.setAttribute("href", href); + if (link.tagName.toUpperCase() === 'A') { + let newLink = doc.createElement('link'); + newLink.rel = 'jslicense'; + newLink.setAttribute('href', href); link = newLink; } return move(); diff --git a/bg/ListManager.js b/bg/ListManager.js index f712356..3020197 100644 --- a/bg/ListManager.js +++ b/bg/ListManager.js @@ -23,7 +23,7 @@ A class to manage whitelist/blacklist operations */ -let { ListStore } = require("../common/Storage"); +let { ListStore } = require('../common/Storage'); class ListManager { constructor(whitelist, blacklist, builtInHashes) { @@ -48,13 +48,13 @@ class ListManager { with a trailing (hash). Returns "blacklisted", "whitelisted" or defValue */ - getStatus(key, defValue = "unknown") { + getStatus(key, defValue = 'unknown') { let { blacklist, whitelist } = this.lists; let inline = ListStore.inlineItem(key); if (inline) { return blacklist.contains(inline) - ? "blacklisted" - : whitelist.contains(inline) ? "whitelisted" + ? 'blacklisted' + : whitelist.contains(inline) ? 'whitelisted' : defValue; } @@ -63,16 +63,16 @@ class ListManager { let url = ListStore.urlItem(key); let site = ListStore.siteItem(key); return (blacklist.contains(url) || ListManager.siteMatch(site, blacklist) - ? "blacklisted" + ? 'blacklisted' : whitelist.contains(url) || ListManager.siteMatch(site, whitelist) - ? "whitelisted" : defValue + ? 'whitelisted' : defValue ); } let [hashItem, srcHash] = match; // (hash), hash - return blacklist.contains(hashItem) ? "blacklisted" + return blacklist.contains(hashItem) ? 'blacklisted' : this.builtInHashes.has(srcHash) || whitelist.contains(hashItem) - ? "whitelisted" + ? 'whitelisted' : defValue; } @@ -85,13 +85,13 @@ class ListManager { if (list.contains(site)) { return site; } - site = site.replace(/^([\w-]+:\/\/)?(\w)/, "$1*.$2"); + site = site.replace(/^([\w-]+:\/\/)?(\w)/, '$1*.$2'); for (; ;) { if (list.contains(site)) { return site; } let oldKey = site; - site = site.replace(/(?:\*\.)*\w+(?=\.)/, "*"); + site = site.replace(/(?:\*\.)*\w+(?=\.)/, '*'); if (site === oldKey) { return null; } diff --git a/bg/ResponseMetaData.js b/bg/ResponseMetaData.js index 4570120..cb3fb2a 100644 --- a/bg/ResponseMetaData.js +++ b/bg/ResponseMetaData.js @@ -34,24 +34,24 @@ class ResponseMetaData { this.headers = {}; for (let h of responseHeaders) { if (/^\s*Content-(Type|Disposition)\s*$/i.test(h.name)) { - let propertyName = h.name.split("-")[1].trim(); + let propertyName = h.name.split('-')[1].trim(); propertyName = `content${propertyName.charAt(0).toUpperCase()}${propertyName.substring(1).toLowerCase()}`; this[propertyName] = h.value; this.headers[propertyName] = h; } } - this.computedCharset = ""; + this.computedCharset = ''; } get charset() { - let charset = ""; + let charset = ''; if (this.contentType) { let m = this.contentType.match(/;\s*charset\s*=\s*(\S+)/); if (m) { charset = m[1]; } } - Object.defineProperty(this, "charset", { value: charset, writable: false, configurable: true }); + Object.defineProperty(this, 'charset', { value: charset, writable: false, configurable: true }); return this.computedCharset = charset; } @@ -69,12 +69,12 @@ class ResponseMetaData { // let's try figuring out the charset from <meta> tags let parser = new DOMParser(); - let doc = parser.parseFromString(text, "text/html"); + let doc = parser.parseFromString(text, 'text/html'); let meta = doc.querySelectorAll('meta[charset], meta[http-equiv="content-type"], meta[content*="charset"]'); for (let m of meta) { - charset = m.getAttribute("charset"); + charset = m.getAttribute('charset'); if (!charset) { - let match = m.getAttribute("content").match(/;\s*charset\s*=\s*([\w-]+)/i) + let match = m.getAttribute('content').match(/;\s*charset\s*=\s*([\w-]+)/i) if (match) charset = match[1]; } if (charset) { @@ -89,7 +89,7 @@ class ResponseMetaData { return text; } - createDecoder(charset = this.charset, def = "latin1") { + createDecoder(charset = this.charset, def = 'latin1') { if (charset) { try { return new TextDecoder(charset); @@ -99,7 +99,7 @@ class ResponseMetaData { } return def ? new TextDecoder(def) : null; } -}; +} ResponseMetaData.UTF8BOM = new Uint8Array(BOM); module.exports = { ResponseMetaData }; diff --git a/bg/ResponseProcessor.js b/bg/ResponseProcessor.js index 078f38a..d964dab 100644 --- a/bg/ResponseProcessor.js +++ b/bg/ResponseProcessor.js @@ -24,22 +24,22 @@ only the "interesting" HTML and script requests and leaving the other alone */ -let { ResponseMetaData } = require("./ResponseMetaData"); +let { ResponseMetaData } = require('./ResponseMetaData'); let listeners = new WeakMap(); let webRequestEvent = browser.webRequest.onHeadersReceived; class ResponseProcessor { - static install(handler, types = ["main_frame", "sub_frame", "script"]) { + static install(handler, types = ['main_frame', 'sub_frame', 'script']) { if (listeners.has(handler)) return false; let listener = async request => await new ResponseTextFilter(request).process(handler); listeners.set(handler, listener); webRequestEvent.addListener( listener, - { urls: ["<all_urls>"], types }, - ["blocking", "responseHeaders"] + { urls: ['<all_urls>'], types }, + ['blocking', 'responseHeaders'] ); return true; } @@ -67,21 +67,21 @@ class ResponseTextFilter { this.canProcess = // we want to process html documents and scripts only (statusCode < 300 || statusCode >= 400) && // skip redirections !md.disposition && // skip forced downloads - (type === "script" || /\bhtml\b/i.test(md.contentType)); + (type === 'script' || /\bhtml\b/i.test(md.contentType)); } async process(handler) { if (!this.canProcess) return ResponseProcessor.ACCEPT; let { metaData, request } = this; let response = { request, metaData }; // we keep it around allowing callbacks to store state - if (typeof handler.pre === "function") { + if (typeof handler.pre === 'function') { let res = await handler.pre(response); if (res) return res; if (handler.post) handler = handler.post; - if (typeof handler !== "function") return ResponseProcessor.ACCEPT; + if (typeof handler !== 'function') return ResponseProcessor.ACCEPT; } - let { requestId, responseHeaders } = request; + let { requestId } = request; let filter = browser.webRequest.filterResponseData(requestId); let buffer = []; @@ -89,9 +89,9 @@ class ResponseTextFilter { buffer.push(event.data); }; - filter.onstop = async event => { + filter.onstop = async unused => { // concatenate chunks - let size = buffer.reduce((sum, chunk, n) => sum + chunk.byteLength, 0) + let size = buffer.reduce((sum, chunk) => sum + chunk.byteLength, 0) let allBytes = new Uint8Array(size); let pos = 0; for (let chunk of buffer) { @@ -100,13 +100,13 @@ class ResponseTextFilter { } buffer = null; // allow garbage collection if (allBytes.indexOf(0) !== -1) { - console.debug("Warning: zeroes in bytestream, probable cached encoding mismatch.", request); - if (request.type === "script") { - console.debug("It's a script, trying to refetch it."); - response.text = await (await fetch(request.url, { cache: "reload", credentials: "include" })).text(); + console.debug('Warning: zeroes in bytestream, probable cached encoding mismatch.', request); + if (request.type === 'script') { + console.debug('It\'s a script, trying to refetch it.'); + response.text = await (await fetch(request.url, { cache: 'reload', credentials: 'include' })).text(); } else { - console.debug("It's a %s, trying to decode it as UTF-16.", request.type); - response.text = new TextDecoder("utf-16be").decode(allBytes, { stream: true }); + console.debug('It\'s a %s, trying to decode it as UTF-16.', request.type); + response.text = new TextDecoder('utf-16be').decode(allBytes, { stream: true }); } } else { response.text = metaData.decode(allBytes); diff --git a/common/Storage.js b/common/Storage.js index 47261c5..6254d66 100644 --- a/common/Storage.js +++ b/common/Storage.js @@ -23,7 +23,7 @@ A tiny wrapper around extensions storage API, supporting CSV serialization for retro-compatibility */ -"use strict"; +'use strict'; var Storage = { ARRAY: { @@ -45,7 +45,7 @@ var Storage = { }, async save(key, list) { - return await browser.storage.local.set({ [key]: [...list].join(",") }); + return await browser.storage.local.set({ [key]: [...list].join(',') }); } } }; @@ -68,20 +68,20 @@ class ListStore { static inlineItem(url) { // here we simplify and hash inline script references - return url.startsWith("inline:") ? url - : url.startsWith("view-source:") - && url.replace(/^view-source:[\w-+]+:\/+([^/]+).*#line\d+/, "inline://$1#") - .replace(/\n[^]*/, s => s.replace(/\s+/g, ' ').substring(0, 16) + "…" + hash(s.trim())); + return url.startsWith('inline:') ? url + : url.startsWith('view-source:') + && url.replace(/^view-source:[\w-+]+:\/+([^/]+).*#line\d+/, 'inline://$1#') + .replace(/\n[^]*/, s => s.replace(/\s+/g, ' ').substring(0, 16) + '…' + hash(s.trim())); } static hashItem(hash) { - return hash.startsWith("(") ? hash : `(${hash})`; + return hash.startsWith('(') ? hash : `(${hash})`; } static urlItem(url) { - let queryPos = url.indexOf("?"); + let queryPos = url.indexOf('?'); return queryPos === -1 ? url : url.substring(0, queryPos); } static siteItem(url) { - if (url.endsWith("/*")) return url; + if (url.endsWith('/*')) return url; try { return `${new URL(url).origin}/*`; } catch (e) { @@ -134,12 +134,12 @@ class ListStore { } function hash(source) { - var shaObj = new jssha("SHA-256", "TEXT") + var shaObj = new jssha('SHA-256', 'TEXT') shaObj.update(source); - return shaObj.getHash("HEX"); + return shaObj.getHash('HEX'); } -if (typeof module === "object") { +if (typeof module === 'object') { module.exports = { ListStore, Storage, hash }; var jssha = require('jssha'); } diff --git a/common/Test.js b/common/Test.js index 7acbfa0..88baff2 100644 --- a/common/Test.js +++ b/common/Test.js @@ -19,9 +19,9 @@ * along with GNU LibreJS. If not, see <http://www.gnu.org/licenses/>. */ -"use strict"; +'use strict'; var Test = (() => { - const RUNNER_URL = browser.extension.getURL("/test/SpecRunner.html"); + const RUNNER_URL = browser.extension.getURL('/test/SpecRunner.html'); return { /* returns RUNNER_URL if it's a test-enabled build or an about:debugging @@ -50,6 +50,6 @@ var Test = (() => { } }; })(); -if (typeof module === "object") { +if (typeof module === 'object') { module.exports = Test; } 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' }), ]); } diff --git a/test/spec/LibreJSSpec.js b/test/spec/LibreJSSpec.js index 1320a2a..9e96af5 100644 --- a/test/spec/LibreJSSpec.js +++ b/test/spec/LibreJSSpec.js @@ -18,9 +18,9 @@ * You should have received a copy of the GNU General Public License * along with GNU LibreJS. If not, see <http://www.gnu.org/licenses/>. */ -"use strict"; +'use strict'; -describe("LibreJS' components", () => { +describe('LibreJS\' components', () => { let LibreJS = browser.extension.getBackgroundPage().LibreJS; let license = { id: 'GPL-3.0', @@ -33,60 +33,60 @@ describe("LibreJS' components", () => { magnet: 'magnet:?xt=urn:btih:2f739d935676111cfff4b4693e3816e664797050&dn=acme-1.5.txt' }; - let trivial = "1+1"; - let nontrivial = `function nt() { document.documentElement.innerHTML=""; nt(); }`; + let trivial = '1+1'; + let nontrivial = 'function nt() { document.documentElement.innerHTML=""; nt(); }'; let licensed = `// @license ${license.magnet} ${license.id}\n${nontrivial}\n// @license-end`; let unknownLicensed = `// @license ${unknownLicense.magnet} ${unknownLicense.id}\n${nontrivial}\n// @license-end`; let malformedLicensed = `// @license\n${nontrivial}`; let tab, documentUrl; beforeAll(async () => { - let url = browser.extension.getURL("/test/resources/index.html"); + let url = browser.extension.getURL('/test/resources/index.html'); tab = (await browser.tabs.query({ url }))[0] || (await browser.tabs.create({ url })); documentUrl = url; }); - describe("The whitelist/blacklist manager", () => { + describe('The whitelist/blacklist manager', () => { let { ListManager, ListStore, Storage } = LibreJS; - let lm = new ListManager(new ListStore("_test.whitelist", Storage.CSV), new ListStore("_test.blacklist", Storage.CSV), new Set()); - let forgot = ["http://formerly.whitelist.ed/", "http://formerly.blacklist.ed/"]; + let lm = new ListManager(new ListStore('_test.whitelist', Storage.CSV), new ListStore('_test.blacklist', Storage.CSV), new Set()); + let forgot = ['http://formerly.whitelist.ed/', 'http://formerly.blacklist.ed/']; beforeAll(async () => { - await lm.whitelist("https://fsf.org/*", "https://*.gnu.org/*", forgot[0]); - await lm.blacklist("https://*.evil.gnu.org/*", "https://verybad.com/*", forgot[1]); + await lm.whitelist('https://fsf.org/*', 'https://*.gnu.org/*', forgot[0]); + await lm.blacklist('https://*.evil.gnu.org/*', 'https://verybad.com/*', forgot[1]); }); - it("Should handle basic CRUD operations", async () => { - expect(lm.getStatus(forgot[0])).toBe("whitelisted"); - expect(lm.getStatus(forgot[1])).toBe("blacklisted"); + it('Should handle basic CRUD operations', async () => { + expect(lm.getStatus(forgot[0])).toBe('whitelisted'); + expect(lm.getStatus(forgot[1])).toBe('blacklisted'); await lm.forget(...forgot); for (let url of forgot) { - expect(lm.getStatus(url)).toBe("unknown"); + expect(lm.getStatus(url)).toBe('unknown'); } }); - it("Should support full path wildcards", () => { - expect(lm.getStatus("https://unknown.org")).toBe("unknown"); - expect(lm.getStatus("https://fsf.org/some/path")).toBe("whitelisted"); - expect(lm.getStatus("https://fsf.org/")).toBe("whitelisted"); - expect(lm.getStatus("https://fsf.org")).toBe("whitelisted"); - expect(lm.getStatus("https://subdomain.fsf.org")).toBe("unknown"); - expect(lm.getStatus("https://verybad.com/some/other/path?with=querystring")).toBe("blacklisted"); + it('Should support full path wildcards', () => { + expect(lm.getStatus('https://unknown.org')).toBe('unknown'); + expect(lm.getStatus('https://fsf.org/some/path')).toBe('whitelisted'); + expect(lm.getStatus('https://fsf.org/')).toBe('whitelisted'); + expect(lm.getStatus('https://fsf.org')).toBe('whitelisted'); + expect(lm.getStatus('https://subdomain.fsf.org')).toBe('unknown'); + expect(lm.getStatus('https://verybad.com/some/other/path?with=querystring')).toBe('blacklisted'); }); - it("Should support subdomain wildcards", () => { - expect(lm.getStatus("https://gnu.org")).toBe("whitelisted"); - expect(lm.getStatus("https://www.gnu.org")).toBe("whitelisted"); - expect(lm.getStatus("https://evil.gnu.org")).toBe("blacklisted"); - expect(lm.getStatus("https://more.evil.gnu.org")).toBe("blacklisted"); - expect(lm.getStatus("https://more.evil.gnu.org/some/evil/path?too")).toBe("blacklisted"); + it('Should support subdomain wildcards', () => { + expect(lm.getStatus('https://gnu.org')).toBe('whitelisted'); + expect(lm.getStatus('https://www.gnu.org')).toBe('whitelisted'); + expect(lm.getStatus('https://evil.gnu.org')).toBe('blacklisted'); + expect(lm.getStatus('https://more.evil.gnu.org')).toBe('blacklisted'); + expect(lm.getStatus('https://more.evil.gnu.org/some/evil/path?too')).toBe('blacklisted'); }); }) - describe("The external script source processor", () => { - let url = "https://www.gnu.org/mock-script.js"; + describe('The external script source processor', () => { + let url = 'https://www.gnu.org/mock-script.js'; let processScript = async (source, whitelisted = false) => await LibreJS.handle_script({ @@ -94,125 +94,125 @@ describe("LibreJS' components", () => { request: { url, tabId: tab.id, documentUrl, frameId: 0 }, }, whitelisted); - it("should accept whitelisted scripts", async () => { + it('should accept whitelisted scripts', async () => { expect(await processScript(nontrivial, true) || nontrivial).toContain(nontrivial); }); - it("should block trivial scripts too", async () => { + it('should block trivial scripts too', async () => { let processed = await processScript(trivial); expect(processed || trivial).not.toContain(trivial); }); - it("should block non-trivial scripts", async () => { + it('should block non-trivial scripts', async () => { let processed = await processScript(nontrivial); expect(processed || nontrivial).not.toContain(nontrivial); }); - it("should accept scripts with known free license tags", async () => { + it('should accept scripts with known free license tags', async () => { let processed = await processScript(licensed); expect(processed || licensed).toContain(nontrivial); }); - it("should block scripts with unknown license tags", async () => { + it('should block scripts with unknown license tags', async () => { let processed = await processScript(unknownLicensed); expect(processed).not.toContain(nontrivial); }); - it("should block scripts with malformed license tags", async () => { + it('should block scripts with malformed license tags', async () => { let processed = await processScript(malformedLicensed); expect(processed).not.toContain(nontrivial); }); }); - describe("The HTML processor", () => { + describe('The HTML processor', () => { let processHtml = async (html, whitelisted = false) => LibreJS.editHtml(html, tab.url, tab.id, 0, whitelisted); - let addScript = (html, script, before = "</head>") => + let addScript = (html, script, before = '</head>') => html.replace(before, `<script>${script}</script>${before}`); - let addToBody = (html, fragment) => html.replace("</body>", `${fragment}</body>`); + let addToBody = (html, fragment) => html.replace('</body>', `${fragment}</body>`); let jsUrl = js => `javascript:${encodeURIComponent(js)}`; - function extractScripts(html, def = "") { + function extractScripts(html, def = '') { let matches = html && html.match(/<script>[^]*?<\/script>/g); - return matches && matches.join("") || def; + return matches && matches.join('') || def; } let html, nontrivialInHtml; beforeAll(async () => { html = (await browser.tabs.executeScript(tab.id, { - runAt: "document_start", - code: "document.documentElement.outerHTML" + runAt: 'document_start', + code: 'document.documentElement.outerHTML' }))[0]; nontrivialInHtml = addScript(html, nontrivial); }); - it("should not modify scriptless documents", async () => { + it('should not modify scriptless documents', async () => { expect(await processHtml(html)).toBeNull(); }); - it("should not modify whitelisted documents", async () => { + it('should not modify whitelisted documents', async () => { expect(await processHtml(nontrivialInHtml, true)).toBeNull(); }); - it("should accept trivial scripts", async () => { + it('should accept trivial scripts', async () => { let trivialInHtml = addScript(html, trivial); let processed = await processHtml(trivialInHtml); expect(extractScripts(processed, trivial)).toContain(trivial); }); - it("should block non-trivial scripts", async () => { + it('should block non-trivial scripts', async () => { let processed = await processHtml(nontrivialInHtml); expect(extractScripts(processed, nontrivial)).not.toContain(nontrivial); }); - it("should accept scripts with known free license tags", async () => { + it('should accept scripts with known free license tags', async () => { let licensedInHtml = addScript(html, licensed); let processed = await processHtml(licensedInHtml); expect(extractScripts(processed, licensed)).toContain(nontrivial); }); - it("should block scripts with unknown license tags", async () => { + it('should block scripts with unknown license tags', async () => { let unknownInHtml = addScript(html, unknownLicensed); let processed = await processHtml(unknownInHtml); expect(extractScripts(processed, nontrivial)).not.toContain(nontrivial); }); - it("should block scripts with malformed license tags", async () => { + it('should block scripts with malformed license tags', async () => { let malformedInHtml = addScript(html, malformedLicensed); let processed = await processHtml(malformedInHtml); expect(extractScripts(processed, nontrivial)).not.toContain(nontrivial); }); - it("should accept scripts on globally licensed pages", async () => { + it('should accept scripts on globally licensed pages', async () => { let globalLicense = `/* @licstart The following is the entire license notice for the JavaScript code in this page. -- Some free license -- @licend The above is the entire license notice for the JavaScript code in this page. */`; - let licensed = addScript(nontrivialInHtml, globalLicense, "<script>"); + let licensed = addScript(nontrivialInHtml, globalLicense, '<script>'); let processed = await processHtml(html); expect(extractScripts(processed, licensed)).toContain(nontrivial); }); - it("should discriminate trivial, non-trivial and licensed mixed on the same page", async () => { + it('should discriminate trivial, non-trivial and licensed mixed on the same page', async () => { let mixedPage = addScript(addScript(nontrivialInHtml, trivial), licensed); let processed = await processHtml(mixedPage); expect(processed).not.toBeNull(); let scripts = extractScripts(processed, nontrivial); expect(scripts).toContain(trivial); expect(scripts).toContain(licensed); - expect(scripts.replace(licensed, "")).not.toContain(nontrivial); + expect(scripts.replace(licensed, '')).not.toContain(nontrivial); }); - it("should correctly process (de)duplicated inline scripts", async () => { + it('should correctly process (de)duplicated inline scripts', async () => { let trivialAsUrl = jsUrl(trivial); let nontrivialAsUrl = jsUrl(nontrivial); let a = (url, label) => `<a href="${url}">${label}</a>`; - let mixedPage = `<body></body>`; + let mixedPage = '<body></body>'; for (let dup = 0; dup < 3; dup++) { mixedPage = addToBody(mixedPage, a(trivialAsUrl, `Trivial #${dup}`)); mixedPage = addToBody(mixedPage, a(nontrivialAsUrl, `Nontrivial #${dup}`)); @@ -223,8 +223,8 @@ describe("LibreJS' components", () => { expect(processed).not.toContain(nontrivialAsUrl); }); - it("should force displaying NOSCRIPT elements (except those with @data-librejs-nodisplay) where scripts have been blocked", async () => { - let noscriptContent = "I'm NOSCRIPT content"; + it('should force displaying NOSCRIPT elements (except those with @data-librejs-nodisplay) where scripts have been blocked', async () => { + let noscriptContent = 'I\'m NOSCRIPT content'; let asNoscript = `<noscript>${noscriptContent}</noscript>`; let asNodisplay = `<noscript data-librejs-nodisplay>${noscriptContent}</noscript>`; let asSpan = `<span>${noscriptContent}</span>`; @@ -235,11 +235,11 @@ describe("LibreJS' components", () => { expect(processed).not.toContain(asNodisplay); }); - it("should always force displaying @data-librejs-display elements", async () => { - let content = "I'm FORCED content"; + it('should always force displaying @data-librejs-display elements', async () => { + let content = 'I\'m FORCED content'; let asDisplay = `<span data-librejs-display>${content}</span>`; let asSpan = `<span>${content}</span>`; - for (let page of [nontrivialInHtml, "<body></body>"]) { + for (let page of [nontrivialInHtml, '<body></body>']) { page = addToBody(page, asDisplay); let processed = await processHtml(page); expect(processed).not.toContain(asDisplay); @@ -248,7 +248,7 @@ describe("LibreJS' components", () => { }); }); - describe("The external (Web Labels) license checker", () => { + describe('The external (Web Labels) license checker', () => { let { ExternalLicenses } = LibreJS; let check; @@ -257,27 +257,27 @@ describe("LibreJS' components", () => { let resolve = url => new URL(url, documentUrl).href; check = async url => await ExternalLicenses.check(Object.assign({ url: resolve(url) }, args)); await browser.tabs.executeScript(tab.id, { - file: "/content/externalLicenseChecker.js" + file: '/content/externalLicenseChecker.js' }); }); - it("should recognize free licenses", async () => { - let scriptInfo = await check("jquery.js"); + it('should recognize free licenses', async () => { + let scriptInfo = await check('jquery.js'); console.debug(scriptInfo); expect(scriptInfo.free).toBeTruthy(); }); - it("should accept scripts if any of multiple licenses is free", async () => { - let scriptInfo = await check("app-trilicensed.js"); + it('should accept scripts if any of multiple licenses is free', async () => { + let scriptInfo = await check('app-trilicensed.js'); console.debug(scriptInfo); expect(scriptInfo.free).toBeTruthy(); }); - it("should block scripts declaring only proprietary license(s)", async () => { - let scriptInfo = await check("proprietary.js"); + it('should block scripts declaring only proprietary license(s)', async () => { + let scriptInfo = await check('proprietary.js'); console.debug(scriptInfo); expect(scriptInfo.free).toBeFalsy(); }); - it("should block scripts not declaring any license", async () => { - let scriptInfo = await check("tracker.js"); + it('should block scripts not declaring any license', async () => { + let scriptInfo = await check('tracker.js'); console.debug(scriptInfo); expect(scriptInfo).toBeNull(); }); |