From 34bcc1a5c2750b6f6fa9d9b971ac8aff796ddd1c Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Wed, 6 Apr 2022 17:35:05 +1000 Subject: linting - eslint - also adding eslintrc --- .eslintrc.js | 17 ++ bg/ExternalLicenses.js | 29 ++-- bg/ListManager.js | 20 +-- bg/ResponseMetaData.js | 18 +- bg/ResponseProcessor.js | 32 ++-- common/Storage.js | 24 +-- common/Test.js | 6 +- main_background.js | 427 +++++++++++++++++++++-------------------------- test/spec/LibreJSSpec.js | 140 ++++++++-------- 9 files changed, 344 insertions(+), 369 deletions(-) create mode 100644 .eslintrc.js 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 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: [""], types }, - ["blocking", "responseHeaders"] + { 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 . */ -"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 . */ -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,52 +411,34 @@ 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 */ @@ -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 = `\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 ${before}`); - let addToBody = (html, fragment) => html.replace("", `${fragment}`); + let addToBody = (html, fragment) => html.replace('', `${fragment}`); let jsUrl = js => `javascript:${encodeURIComponent(js)}`; - function extractScripts(html, def = "") { + function extractScripts(html, def = '') { let matches = html && html.match(/