From 9b1a1af8234ef5dbf628c141e3b89b1061e49757 Mon Sep 17 00:00:00 2001 From: hackademix Date: Jul 29 2018 15:29:46 +0000 Subject: [PATCH 1/18] White/Black lists back-end refactoring, fixing entry truncation on removal and possible duplications on addition. --- diff --git a/bg/Storage.js b/bg/Storage.js new file mode 100644 index 0000000..1386538 --- /dev/null +++ b/bg/Storage.js @@ -0,0 +1,79 @@ +/** +* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. +* +* Copyright (C) 2018 Giorgio Maone +* +* This file is part of GNU LibreJS. +* +* GNU LibreJS is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* GNU LibreJS is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with GNU LibreJS. If not, see . +*/ + +/** + A tiny wrapper around extensions storage API, supporting CSV serialization for + retro-compatibility +*/ + +var Storage = { + ARRAY: { + async load(key) { + let array = (await browser.storage.local.get(key))[key]; + return array ? new Set(array) : new Set(); + }, + async save(key, list) { + return await browser.storage.local.set({[key]: [...list]}); + }, + }, + + CSV: { + async load(key) { + let csv = (await browser.storage.local.get(key))[key]; + return csv ? new Set(csv.split(/\s*,\s*/)) : new Set(); + }, + + async save(key, list) { + return await browser.storage.local.set({[key]: [...list].join(",")}); + } + } +}; + +/** + A class to hold and persist blacklists and whitelists +*/ + +class ListStore { + constructor(key, storage = Storage.ARRAY) { + this.key = key; + this.storage = storage; + this.items = new Set(); + } + + async save() { + return await this.storage.save(this.key, this.items); + } + + async load() { + return await this.storage.load(this.key); + } + + async store(item) { + let size = this.items.size; + return (size !== this.items.add(item).size) && await this.save(); + } + + async remove(item) { + return this.items.delete(item) && await this.save(); + } +} + +module.exports = { ListStore, Storage }; diff --git a/main_background.js b/main_background.js index ff68479..95ae39d 100644 --- a/main_background.js +++ b/main_background.js @@ -26,6 +26,7 @@ var jssha = require('jssha'); var walk = require("acorn/dist/walk"); var legacy_license_lib = require("./legacy_license_check.js"); var {ResponseProcessor} = require("./bg/ResponseProcessor"); +var {Storage, ListStore} = require("./bg/Storage"); console.log("main_background.js"); /** @@ -502,6 +503,7 @@ function connected(p) { return; } p.onMessage.addListener(function(m) { + console.debug("LibreJS BG: received message", m); /** * Updates the entry of the current URL in storage */ @@ -1271,12 +1273,14 @@ async function handle_html(response, whitelisted) { return await edit_html(text, url, tabId, false); } +var pageWhitelist = new ListStore("pref_whitelist", Storage.CSV); + /** * Initializes various add-on functions * only meant to be called once when the script starts */ -function init_addon(){ - +async function init_addon(){ + await pageWhitelist.load(); set_webex(); webex.runtime.onConnect.addListener(connected); webex.storage.onChanged.addListener(options_listener); @@ -1352,45 +1356,15 @@ function inject_contact_finder(tab_id){ /** * Adds given domain to the whitelist in options */ -function add_csv_whitelist(domain){ - function storage_got(items){ - if(items["pref_whitelist"] == ""){ - items["pref_whitelist"] = domain + "*"; - } else if(items["pref_whitelist"] == "undefined"){ - items["pref_whitelist"] = domain + "*"; - } else{ - items["pref_whitelist"] += "," + domain + "*"; - } - dbg_print("New CSV whitelist:"); - dbg_print(items["pref_whitelist"]); - webex.storage.local.set({"pref_whitelist":items["pref_whitelist"]}); - } - webex.storage.local.get(storage_got); +async function add_csv_whitelist(domain){ + await pageWhitelist.store(`${domain}*`); } /** * removes given domain from the whitelist in options */ -function remove_csv_whitelist(domain){ - function storage_got(items){ - if(items["pref_whitelist"] != ""){ - domain = domain + "\\*"; - domain.replace(/\./g,"\."); - // remove domain - dbg_print(new RegExp(domain,"g")); - items["pref_whitelist"] = items["pref_whitelist"].replace(new RegExp(domain,"g"),"") - // if an entry was deleted, it will leave an extra comma - items["pref_whitelist"] = items["pref_whitelist"].replace(/,+/g,","); - // remove trailing comma if the last one was deleted - if(items["pref_whitelist"].charAt(items["pref_whitelist"].length-1) == ","){ - items["pref_whitelist"] = items["pref_whitelist"].substr(0,items["pref_whitelist"].length-2); - } - } - dbg_print("New CSV whitelist:"); - dbg_print(items["pref_whitelist"]); - webex.storage.local.set({"pref_whitelist":items["pref_whitelist"]}); - } - webex.storage.local.get(storage_got); +async function remove_csv_whitelist(domain) { + return pageWhitelist.remove(`${domain}*`); } init_addon(); From 0f938f6dd25dbad9f7a25255c87ae95d3258d43a Mon Sep 17 00:00:00 2001 From: hackademix Date: Jul 29 2018 15:32:38 +0000 Subject: [PATCH 2/18] Bumped version to 7.15 --- diff --git a/manifest.json b/manifest.json index 92bb739..e77f69b 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "GNU LibreJS [webExtensions]", "short_name": "LibreJS [experimental]", - "version": "7.14.2", + "version": "7.15", "author": "various", "description": "Only allows free and/or trivial Javascript to run.", "applications": { From 3dec5974112550595e8451666cb94b0690096542 Mon Sep 17 00:00:00 2001 From: hackademix Date: Jul 29 2018 15:35:50 +0000 Subject: [PATCH 3/18] Display actual extension version number in UI --- diff --git a/html/display_panel/content/display-panel.html b/html/display_panel/content/display-panel.html index 4f2da4f..2879826 100644 --- a/html/display_panel/content/display-panel.html +++ b/html/display_panel/content/display-panel.html @@ -11,6 +11,7 @@ * Copyright (C) 2011, 2012, 2014 Loic J. Duros * Copyright (C) 2017, 2018 NateN1222 * Copyright (C) 2018 Ruben Rodriguez + * Copyright (C) 2018 Giorgio Maone * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,7 +31,6 @@ - + diff --git a/html/display_panel/content/main_panel.js b/html/display_panel/content/main_panel.js index 3086859..edbd909 100644 --- a/html/display_panel/content/main_panel.js +++ b/html/display_panel/content/main_panel.js @@ -58,7 +58,8 @@ var myPort = webex.runtime.connect({name:"port-from-cs"}); var current_blocked_data; - +// Display the actual extension version Number +document.querySelector("#version").textContent = browser.runtime.getManifest().version; /* * Makes a button appear that calls a function when you press it. From 096cd90010b093e7c211f3318cbe41711ecfdef9 Mon Sep 17 00:00:00 2001 From: hackademix Date: Jul 29 2018 16:04:42 +0000 Subject: [PATCH 4/18] Fixed typo causing pages containing no script elements not to be rendered at all --- diff --git a/main_background.js b/main_background.js index 95ae39d..093aa86 100644 --- a/main_background.js +++ b/main_background.js @@ -1175,7 +1175,7 @@ function edit_html(html,url,tabid,wl){ var scripts = html_doc.scripts; var meta_element = html_doc.getElementById("LibreJS-info"); - var first_scipt_src = ""; + var first_script_src = ""; // get the potential inline source that can contain a license for(var i = 0; i < scripts.length; i++){ From 5e0ab1515ab1c399a6405f579c3b931563e4020d Mon Sep 17 00:00:00 2001 From: hackademix Date: Jul 30 2018 14:18:17 +0000 Subject: [PATCH 5/18] Implement early whitelisting / blacklisting logic. --- diff --git a/bg/ResponseProcessor.js b/bg/ResponseProcessor.js index 3f3151b..400cb42 100644 --- a/bg/ResponseProcessor.js +++ b/bg/ResponseProcessor.js @@ -52,6 +52,13 @@ class ResponseProcessor { } } +Object.assign(ResponseProcessor, { + // control flow values to be returned by handler.pre() callbacks + ACCEPT: {}, + REJECT: {cancel: true}, + CONTINUE: null +}); + class ResponseTextFilter { constructor(request) { this.request = request; @@ -64,9 +71,16 @@ class ResponseTextFilter { } process(handler) { - if (!this.canProcess) return {}; - let metaData = this.metaData; - let {requestId, responseHeaders} = this.request; + if (!this.canProcess) return ResponseProcessor.ACCEPT; + let {metaData, request} = this; + if (typeof handler.pre === "function") { + let res = handler.pre({request, metaData}); + if (res) return res; + if (handler.post) handler = handler.post; + if (typeof handler !== "function") ResponseProcessor.ACCEPT; + } + + let {requestId, responseHeaders} = request; let filter = browser.webRequest.filterResponseData(requestId); let buffer = []; @@ -83,7 +97,7 @@ class ResponseTextFilter { let editedText = null; try { let response = { - request: this.request, + request, metaData, text, }; @@ -103,7 +117,7 @@ class ResponseTextFilter { filter.disconnect(); } - return metaData.forceUTF8() ? {responseHeaders} : {}; + return metaData.forceUTF8() ? {responseHeaders} : ResponseProcessor.ACCEPT;; } } diff --git a/bg/Storage.js b/bg/Storage.js index 1386538..ecdc9e4 100644 --- a/bg/Storage.js +++ b/bg/Storage.js @@ -58,14 +58,35 @@ class ListStore { this.items = new Set(); } + static hashItem(hash) { + return hash.startsWith("(") ? hash : `(${hash})`; + } + static urlItem(url) { + let queryPos = url.indexOf("?"); + return queryPos === -1 ? url : url.substring(0, queryPos); + } + static siteItem(url) { + if (url.endsWith("/*")) return url; + try { + return `${new URL(url).origin}/*`; + } catch (e) { + return `${url}/*`; + } + } + async save() { return await this.storage.save(this.key, this.items); } async load() { - return await this.storage.load(this.key); + try { + this.items = await this.storage.load(this.key); + } catch (e) { + console.error(e); + } + return this.items; } - + async store(item) { let size = this.items.size; return (size !== this.items.add(item).size) && await this.save(); @@ -74,6 +95,10 @@ class ListStore { async remove(item) { return this.items.delete(item) && await this.save(); } + + contains(item) { + return this.items.has(item); + } } module.exports = { ListStore, Storage }; diff --git a/main_background.js b/main_background.js index 093aa86..c20a257 100644 --- a/main_background.js +++ b/main_background.js @@ -177,19 +177,31 @@ function options_listener(changes, area){ } + +var active_connections = {}; +var unused_data = {}; +function createReport(initializer = null) { + let template = { + "accepted": [], + "blocked": [], + "blacklisted": [], + "whitelisted": [], + url: "", + }; + return initializer ? Object.assign(template, initializer) : template; +} + /** * Executes the "Display this report in new tab" function * by opening a new tab with whatever HTML is in the popup * at the moment. */ -var active_connections = {}; -var unused_data = {}; function open_popup_tab(data){ dbg_print(data); function gotPopup(popupURL){ var creating = webex.tabs.create({"url":popupURL},function(a){ dbg_print("[TABID:"+a["id"]+"] creating unused data entry from parent window's content"); - unused_data[a["id"]] = data; + unused_data[a["id"]] = createReport(data); }); } @@ -288,13 +300,7 @@ function update_popup(tab_id,blocked_info,update=false){ } else return false; } - new_blocked_data = { - "accepted":[], - "blocked":[], - "blacklisted":[], - "whitelisted":[], - "url": url - }; + new_blocked_data = createReport({url}); for(var type in blocked_info){ for(var script_arr in blocked_info[type]){ if(is_bl(blocked_info[type][script_arr][0])){ @@ -368,19 +374,9 @@ function add_popup_entry(tab_id,src_hash,blocked_info,update=false){ } if(unused_data[tab_id] === undefined){ - unused_data[tab_id] = { - "accepted":[], - "blocked":[], - "blacklisted":[], - "whitelisted":[], - "url": url - }; + unused_data[tab_id] = createReport({url}); } - if(unused_data[tab_id]["accepted"] === undefined){unused_data[tab_id]["accepted"] = [];} - if(unused_data[tab_id]["blocked"] === undefined){unused_data[tab_id]["blocked"] = [];} - if(unused_data[tab_id]["blacklisted"] === undefined){unused_data[tab_id]["blacklisted"] = [];} - if(unused_data[tab_id]["whitelisted"] === undefined){unused_data[tab_id]["whitelisted"] = [];} - + var type = ""; if(blocked_info["accepted"] !== undefined){ @@ -443,29 +439,33 @@ function add_popup_entry(tab_id,src_hash,blocked_info,update=false){ } var type_key = ""; var res = ""; - if(is_bl(blocked_info[type][0])){ - type_key = "blacklisted"; - res = "bl"; - //console.log("Script " + blocked_info[type][0] + " is blacklisted"); - } - else if(is_wl(blocked_info[type][0])){ - type_key = "whitelisted"; - res = "wl"; - //console.log("Script " + blocked_info[type][0] + " is whitelisted"); - } else{ - type_key = type; - res = "none"; - //console.log("Script " + blocked_info[type][0] + " isn't whitelisted or blacklisted"); - } - if(not_duplicate(type_key,blocked_info[type])){ - dbg_print(unused_data); - dbg_print(unused_data[tab_id]); - dbg_print(type_key); - unused_data[tab_id][type_key].push(blocked_info[type]); - resolve(res); - } else{ - resolve(res); + try { + if(is_bl(blocked_info[type][0])){ + type_key = "blacklisted"; + res = "bl"; + //console.log("Script " + blocked_info[type][0] + " is blacklisted"); + } + else if(is_wl(blocked_info[type][0])){ + type_key = "whitelisted"; + res = "wl"; + //console.log("Script " + blocked_info[type][0] + " is whitelisted"); + } else{ + type_key = type; + res = "none"; + //console.log("Script " + blocked_info[type][0] + " isn't whitelisted or blacklisted"); + } + + if(not_duplicate(type_key,blocked_info[type])){ + dbg_print(unused_data); + dbg_print(unused_data[tab_id]); + dbg_print(type_key); + unused_data[tab_id][type_key].push(blocked_info[type]); + resolve(res); + } + } catch (e) { + console.error(e, "blocked_info %o, type %s, type_key %s", blocked_info, type, type_key); } + resolve(res); } webex.storage.local.get(get_sto); @@ -592,7 +592,7 @@ function connected(p) { p.postMessage({"show_info":unused_data[tab_id]}); } else{ // create a new entry - unused_data[tab_id] = {"url":tab["url"],"blocked":"","accepted":""}; + unused_data[tab_id] = createReport({"url": tab.url}); p.postMessage({"show_info":unused_data[tab_id]}); dbg_print("[TABID:"+tab_id+"]"+"No data found, creating a new entry for this window."); } @@ -623,23 +623,10 @@ function delete_removed_tab_info(tab_id, remove_info){ * Check whitelisted by hash * */ -function blocked_status(hash){ - return new Promise((resolve, reject) => { - function cb(items){ - var wl = items["pref_whitelist"]; - for(var i in items){ - var res = i.match(/\(.*?\)/g); - if(res != null){ - var test_hash = res[res.length-1].substr(1,res[0].length-2); - if(test_hash == hash){ - resolve(items[i]); - } - } - } - resolve("none"); - } - webex.storage.local.get(cb); - }); +function blocked_status(hash) { + let hashItem = ListStore.hashItem(hash); + return whitelist.contains(hashItem) ? + "whitelisted" : blacklist.contains(hashItem) ? "blacklisted" : "none"; } /* *********************************************************************************************** */ @@ -947,92 +934,44 @@ function license_read(script_src, name, external = false){ // TODO: Test if this script is being loaded from another domain compared to unused_data[tabid]["url"] /** -* -* Returns a promise that resolves with the final edited script as a string. +* Asynchronous function, returns the final edited script as a string, +* or an array containing it and the index, if the latter !== -1 */ -function get_script(response,url,tabid,wl,index=-1){ - return new Promise((resolve, reject) => { - if(unused_data[tabid] === undefined){ - unused_data[tabid] = {"url":url,"accepted":[],"blocked":[]}; - } - var edited; - var tok_index = url.split("/").length; - var scriptname = url.split("/")[tok_index-1]; - if(wl == true){ - // Accept without reading script, it was explicitly whitelisted - if(typeof(unused_data[tabid]["accepted"].push) != "function"){ - unused_data[tabid]["accepted"] = [[url,"Page is whitelisted in preferences"]]; - } else{ - unused_data[tabid]["accepted"].push([url,"Page is whitelisted in preferences"]); - } - resolve("\n/*\n LibreJS: Script whitelisted by user (From a URL found in comma seperated whitelist)\n*/\n"+response); - if(index != -1){ - resolve(["\n/*\n LibreJS: Script whitelisted by user (From a URL found in comma seperated whitelist)\n*/\n"+response,index]); - } else{ - resolve("\n/*\n LibreJS: Script whitelisted by user (From a URL found in comma seperated whitelist)\n*/\n"+response); - } - edited = [true,response,"Page is whitelisted in preferences"]; - }else{ - edited = license_read(response,scriptname,index == -2); - } - var src_hash = hash(response); - var verdict = edited[0]; - var popup_res; - var domain = get_domain(url); - - var badge_str = 0; - - if(unused_data[tabid]["blocked"] !== undefined){ - badge_str += unused_data[tabid]["blocked"].length; - } - - if(unused_data[tabid]["blacklisted"] !== undefined){ - badge_str += unused_data[tabid]["blacklisted"].length; - } - dbg_print("amt. blocked on page:"+badge_str); - if(badge_str > 0 || verdict == false){ - webex.browserAction.setBadgeText({ - text: "!", - tabId: tabid - }); - webex.browserAction.setBadgeBackgroundColor({ - color: "red", - tabId: tabid - }); - } - - if(verdict == true){ - popup_res = add_popup_entry(tabid,src_hash,{"url":domain,"accepted":[url,edited[2]]}); - } else{ - popup_res = add_popup_entry(tabid,src_hash,{"url":domain,"blocked":[url,edited[2]]}); - } +async function get_script(response, url, tabId, whitelisted = false, index = -1) { + function result(scriptSource) { + return index === -1 ? scriptSource : [scriptSource, index]; + } + let report = unused_data[tabId] || (unused_data[tabId] = createReport({url})); - popup_res.then(function(list_verdict){ - var blob; - if(list_verdict == "wl"){ - // redirect to the unedited version - if(index != -1){ - resolve(["/* LibreJS: Script whitelisted by user */\n"+response,index]); - } else{ - resolve("/* LibreJS: Script whitelisted by user */\n"+response); - } - }else if(list_verdict == "bl"){ - // Blank the entire script - if(index != -1){ - resolve(["/* LibreJS: Script blacklisted by user */\n",index]); - } else{ - resolve("/* LibreJS: Script blacklisted by user */\n"); - } - } else{ - // Return the edited (normal) version - if(index != -1){ - resolve(["/* LibreJS: Script acknowledged */\n"+edited[1],index]); - } else{ - resolve("/* LibreJS: Script acknowledged */\n"+edited[1]); - } - } + let scriptName = url.split("/").pop(); + if (whitelisted) { + // Accept without reading script, it was explicitly whitelisted + report.accepted.push([url, "Page is whitelisted in preferences"]); + return result(`/* LibreJS: script whitelisted by user preference. */\n${response}`); + } + let [verdict, editedSource, reason] = license_read(response, scriptName, index === -2); + let sourceHash = hash(response); + let domain = get_domain(url); + let blockedCount = report.blocked.length + report.blacklisted.length; + dbg_print(`amt. blocked on page: ${blockedCount}`); + if (blockedCount > 0 || !verdict) { + webex.browserAction.setBadgeText({ + text: "!", + tabId }); - }); + webex.browserAction.setBadgeBackgroundColor({ + color: "red", + tabId + }); + } + let listVerdict = await add_popup_entry(tabId, sourceHash, {"url":domain, [verdict ? "accepted" : "blocked"]: [url, reason]}); + switch(listVerdict) { + case "wl": case "bl": + let verdictText = listVerdict === "wl" ? "whitelisted" : "blacklisted"; + return result(`/* LibreJS: script ${verdictText} by user. */\n${response}`); + default: + return result(`/* LibreJS: script aknowledged. */\n${editedSource}`); + } } /** @@ -1073,15 +1012,53 @@ function block_ga(a){ /** * This listener gets called as soon as we've got all the HTTP headers, can guess -* content type and encoding, and therefore correctly parse HTML documents and -* and external script inclusion in search of non-free JavaScript +* content type and encoding, and therefore correctly parse HTML documents +* and external script inclusions in search of non-free JavaScript */ -async function responseHandler(response) { - let {url, type} = response.request; - let whitelisted = await test_url_whitelisted(url); - let handle_it = type === "script" ? handle_script : handle_html; - return await handle_it(response, whitelisted); +var ResponseHandler = { + /** + * Enforce white/black lists for url/site early (hashes will be handled later) + */ + pre(response) { + let {request} = response; + let {url, documentUrl, type, tabId} = request; + + let site = ListStore.siteItem(url); + + let blacklistedSite = blacklist.contains(site); + let blacklisted = blacklistedSite || blacklist.contains(url); + if (blacklisted) { + if (type === "script") { + // abort the request before the response gets fetched + add_popup_entry(tabId, url, {url, "blocked": [url, "Blacklisted by user"]}); + return ResponseProcessor.REJECT; + } + // use CSP to restrict JavaScript execution in the page + request.responseHeaders.unshift({ + name: `Content-security-policy`, + value: `script-src '${blacklistedSite ? 'self' : 'none'}';` + }); + } else if ( + response.whitelisted = (whitelist.contains(site) || whitelist.contains(url)) && + type === "script") { + // accept the script and stop processing + add_popup_entry(tabId, url, {url, "accepted": [url, "Whitelisted by user"]}); + return ResponseProcessor.ACCEPT; + } + // it's a page (it's too early to report) or an unknown script: + // let's keep processing + return ResponseProcessor.CONTINUE; + }, + + /** + * Here we do the heavylifting, analyzing unknown scripts + */ + async post(response) { + let {url, type} = response.request; + let handle_it = type === "script" ? handle_script : handle_html; + return await handle_it(response, response.whitelisted); + } } /** @@ -1273,14 +1250,15 @@ async function handle_html(response, whitelisted) { return await edit_html(text, url, tabId, false); } -var pageWhitelist = new ListStore("pref_whitelist", Storage.CSV); +var whitelist = new ListStore("pref_whitelist", Storage.CSV); +var blacklist = new ListStore("pref_blacklist", Storage.CSV); /** * Initializes various add-on functions * only meant to be called once when the script starts */ async function init_addon(){ - await pageWhitelist.load(); + await whitelist.load(); set_webex(); webex.runtime.onConnect.addListener(connected); webex.storage.onChanged.addListener(options_listener); @@ -1300,48 +1278,11 @@ async function init_addon(){ ); // Analyzes all the html documents and external scripts as they're loaded - ResponseProcessor.install(responseHandler); + ResponseProcessor.install(ResponseHandler); legacy_license_lib.init(); } -/** -* Test if a page is whitelisted/blacklisted. -* -* The input here is tested against the comma seperated string found in the options. -* -* It does NOT test against the individual entries created by hitting the "whitelist" -* button for a script in the browser action. -*/ -function test_url_whitelisted(url){ - return new Promise((resolve, reject) => { - function cb(items){ - var wl = items["pref_whitelist"]; - if(wl !== undefined && wl !== ""){ - wl = wl.split(","); - } else{ - resolve(false); - return; - } - var regex; - for(var i in wl){ - var s = wl[i].replace(/\*/g,"\\S*"); - s = s.replace(/\./g,"\\."); - regex = new RegExp(s, "g"); - if(url.match(regex)){ - //console.log("%c" + wl[i] + " matched " + url,"color: purple;"); - resolve(true); - return; - } else{ - //console.log("%c" + wl[i] + " didn't match " + url,"color: #dd0000;"); - } - } - resolve(false); - return; - } - webex.storage.local.get(cb); - }); -} /** * Loads the contact finder on the given tab ID. @@ -1357,14 +1298,14 @@ function inject_contact_finder(tab_id){ * Adds given domain to the whitelist in options */ async function add_csv_whitelist(domain){ - await pageWhitelist.store(`${domain}*`); + return await whitelist.store(`${domain}*`); } /** * removes given domain from the whitelist in options */ async function remove_csv_whitelist(domain) { - return pageWhitelist.remove(`${domain}*`); + return whitelist.remove(`${domain}*`); } init_addon(); From 4197a81f1e81ceb57e6cbab566e82727c7554ab8 Mon Sep 17 00:00:00 2001 From: hackademix Date: Jul 30 2018 22:49:30 +0000 Subject: [PATCH 6/18] Stateful response processing support. --- diff --git a/bg/ResponseProcessor.js b/bg/ResponseProcessor.js index 400cb42..0d6e36f 100644 --- a/bg/ResponseProcessor.js +++ b/bg/ResponseProcessor.js @@ -73,8 +73,9 @@ class ResponseTextFilter { 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") { - let res = handler.pre({request, metaData}); + let res = handler.pre(response); if (res) return res; if (handler.post) handler = handler.post; if (typeof handler !== "function") ResponseProcessor.ACCEPT; @@ -91,22 +92,17 @@ class ResponseTextFilter { filter.onstop = async event => { let decoder = metaData.createDecoder(); let params = {stream: true}; - let text = this.text = buffer.map( + response.text = buffer.map( chunk => decoder.decode(chunk, params)) .join(''); let editedText = null; try { - let response = { - request, - metaData, - text, - }; editedText = await handler(response); } catch(e) { console.error(e); } if (metaData.forcedUTF8 || - editedText !== null && text !== editedText) { + editedText !== null && response.text !== editedText) { // if we changed the charset, the text or both, let's re-encode filter.write(new TextEncoder().encode(editedText)); } else { From 0039b016a8e9d13de5b70e09833aa0cc768350c5 Mon Sep 17 00:00:00 2001 From: hackademix Date: Jul 30 2018 22:53:37 +0000 Subject: [PATCH 7/18] Whitelisted/blackilisted statuses reporting and modification support. --- diff --git a/main_background.js b/main_background.js index c20a257..108da28 100644 --- a/main_background.js +++ b/main_background.js @@ -350,127 +350,72 @@ function update_popup(tab_id,blocked_info,update=false){ * * Sends a message to the content script that adds a popup entry for a tab. * -* var example_blocked_info = { -* "accepted"or "blocked": ["name","reason"], -* "url": "example.com" +* The action argument is an object with two properties: one named either +* "accepted","blocked", "whitelisted" or "blacklisted", whose value is the array +* [scriptName, reason], and another named "url". Example: +* action = { +* "accepted": ["jquery.js (someHash)","Whitelisted by user"], +* "url": "https://example.com/js/jquery.js" * } * -* Returns true/false based on if script should be accepted/denied respectively +* Returns either "wl" (whitelisted), "bl" (blacklisted) or "none". * * NOTE: This WILL break if you provide inconsistent URLs to it. * Make sure it will use the right URL when refering to a certain script. * */ -function add_popup_entry(tab_id,src_hash,blocked_info,update=false){ - return new Promise((resolve, reject) => { - var new_blocked_data; - - // Make sure the entry in unused_data exists - - var url = blocked_info["url"]; - if(url === undefined){ - console.error("No url passed to update_popup"); - return 1; - } - - if(unused_data[tab_id] === undefined){ - unused_data[tab_id] = createReport({url}); - } - - var type = ""; - - if(blocked_info["accepted"] !== undefined){ - type = "accepted"; - } - if(blocked_info["blocked"] !== undefined){ - type = "blocked"; +async function addReportEntry(tabId, scriptHashOrUrl, action, update = false) { + if(!unused_data[tabId]) { + unused_data[tabId] = createReport({url: (await browser.tabs.get(tabId)).url}); + } + let type, actionValue; + 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 ""; + } - function get_sto(items){ - function get_status(script_name,src_hash){ - var temp = script_name.match(/\(.*?\)/g); - if(temp == null){ - return "none" - } - var src_hash = temp[temp.length-1].substr(1,temp[0].length-2); - - for(var i in items){ - var res = i.match(/\(.*?\)/g); - if(res != null){ - var test_hash = res[res.length-1].substr(1,res[0].length-2); - if(test_hash == src_hash){ - return items[i]; - } - } - } - - if(default_whitelist[src_hash] !== undefined){ - //console.log("Found script in default whitelist: "+default_whitelist[src_hash]); - return "whitelist"; - } else{ - //console.log("script " + script_name + " not in default whitelist."); - } - return "none"; - } - function is_bl(script_name){ - if(get_status(script_name) == "blacklist"){ - return true; - } - return false; - } - function is_wl(script_name){ - if(get_status(script_name) == "whitelist"){ - return true; - } - return false; - } - + function getStatus(scriptName) { + let match = scriptName.match(/\(([^)]+)\)(?=[^()]*$)/); + if (!match) return type; + + let [hashItem, srcHash] = match; // (hash), hash - // Search unused data for the given entry - function not_duplicate(entry,key){ - var flag = true; - for(var i = 0; i < unused_data[tab_id][entry].length; i++){ - if(unused_data[tab_id][entry][i][0] == key[0]){ - flag = false; - } - } - return flag; - } - var type_key = ""; - var res = ""; - try { - if(is_bl(blocked_info[type][0])){ - type_key = "blacklisted"; - res = "bl"; - //console.log("Script " + blocked_info[type][0] + " is blacklisted"); - } - else if(is_wl(blocked_info[type][0])){ - type_key = "whitelisted"; - res = "wl"; - //console.log("Script " + blocked_info[type][0] + " is whitelisted"); - } else{ - type_key = type; - res = "none"; - //console.log("Script " + blocked_info[type][0] + " isn't whitelisted or blacklisted"); - } - - if(not_duplicate(type_key,blocked_info[type])){ - dbg_print(unused_data); - dbg_print(unused_data[tab_id]); - dbg_print(type_key); - unused_data[tab_id][type_key].push(blocked_info[type]); - resolve(res); - } - } catch (e) { - console.error(e, "blocked_info %o, type %s, type_key %s", blocked_info, type, type_key); - } - resolve(res); + return (blacklist.contains(hashItem)) ? "blacklisted" + : (default_whitelist[srcHash] || whitelist.contains(hashItem)) ? "whitelisted" + : type; + } + // Search unused data for the given entry + function isNew(entries, item) { + for (let e of entries) { + if (e[0] === item) return false; } - webex.storage.local.get(get_sto); + return true; + } - return 0; - }); + let entryType, res; + let scriptName = actionValue[0]; + try { + entryType = getStatus(scriptName); + res = entryType.substring(0, 2); + let entries = unused_data[tabId][entryType]; + if(isNew(entries, scriptName)){ + dbg_print(unused_data); + dbg_print(unused_data[tabId]); + dbg_print(entryType); + entries.push(actionValue); + } + } catch (e) { + console.error(e, "action %o, type %s, entryType %s", action, type, entryType); + res = "none"; + } + return res; } @@ -517,15 +462,19 @@ function connected(p) { current_url = tabs[0]["url"]; var domain = get_domain(current_url); var scriptkey = m[val][0]; - if(val == "forget"){ - console.log("KEY:"); - console.log(scriptkey); - // TODO: This should produce a "Refresh the page for this change to take effect" message - var prom = webex.storage.local.remove(scriptkey); - } else{ - var newitem = {}; - newitem[scriptkey] = val; - webex.storage.local.set(newitem); + switch(val) { + case "forget": + whitelist.remove(scriptkey); + blacklist.remove(scriptkey); + break; + case "whitelist": + blacklist.remove(scriptkey); + whitelist.store(scriptkey); + break; + case "blacklist": + whitelist.remove(scriptkey); + blacklist.store(scriptkey); + break; } } var querying = webex.tabs.query({active: true,currentWindow: true},geturl); @@ -946,7 +895,10 @@ async function get_script(response, url, tabId, whitelisted = false, index = -1) let scriptName = url.split("/").pop(); if (whitelisted) { // Accept without reading script, it was explicitly whitelisted - report.accepted.push([url, "Page is whitelisted in preferences"]); + let reason = response.whitelistedSite + ? "Site ${response.whitelistedSite} whitelisted by user" + : "Page whitelisted by user"; + addReportEntry(tabId, url, {"whitelisted": [url, reason], url}); return result(`/* LibreJS: script whitelisted by user preference. */\n${response}`); } let [verdict, editedSource, reason] = license_read(response, scriptName, index === -2); @@ -964,7 +916,7 @@ async function get_script(response, url, tabId, whitelisted = false, index = -1) tabId }); } - let listVerdict = await add_popup_entry(tabId, sourceHash, {"url":domain, [verdict ? "accepted" : "blocked"]: [url, reason]}); + let listVerdict = await addReportEntry(tabId, sourceHash, {"url": domain, [verdict ? "accepted" : "blocked"]: [url, reason]}); switch(listVerdict) { case "wl": case "bl": let verdictText = listVerdict === "wl" ? "whitelisted" : "blacklisted"; @@ -1022,16 +974,18 @@ var ResponseHandler = { */ pre(response) { let {request} = response; - let {url, documentUrl, type, tabId} = request; + let {url, type, tabId} = request; let site = ListStore.siteItem(url); let blacklistedSite = blacklist.contains(site); let blacklisted = blacklistedSite || blacklist.contains(url); + let topUrl = request.frameAncestors && request.frameAncestors.pop() || request.documentUrl; if (blacklisted) { if (type === "script") { // abort the request before the response gets fetched - add_popup_entry(tabId, url, {url, "blocked": [url, "Blacklisted by user"]}); + addReportEntry(tabId, url, {url: topUrl, + "blacklisted": [url, blacklistedSite ? `User blacklisted ${site}` : "Blacklisted by user"]}); return ResponseProcessor.REJECT; } // use CSP to restrict JavaScript execution in the page @@ -1039,12 +993,16 @@ var ResponseHandler = { name: `Content-security-policy`, value: `script-src '${blacklistedSite ? 'self' : 'none'}';` }); - } else if ( - response.whitelisted = (whitelist.contains(site) || whitelist.contains(url)) && - type === "script") { - // accept the script and stop processing - add_popup_entry(tabId, url, {url, "accepted": [url, "Whitelisted by user"]}); - return ResponseProcessor.ACCEPT; + } else { + let whitelistedSite = whitelist.contains(site); + if (whitelistedSite) response.whitelistedSite = site; + if ((response.whitelisted = (whitelistedSite || whitelist.contains(url))) + && type === "script") { + // accept the script and stop processing + addReportEntry(tabId, url, {url: topUrl, + "whitelisted": [url, whitelistedSite ? `User whitelisted ${site}` : "Whitelisted by user"]}); + return ResponseProcessor.ACCEPT; + } } // it's a page (it's too early to report) or an unknown script: // let's keep processing @@ -1168,7 +1126,7 @@ function edit_html(html,url,tabid,wl){ license = legacy_license_lib.check(first_script_src); if(read_metadata(meta_element) || license != false ){ console.log("Valid license for intrinsic events found"); - add_popup_entry(tabid,url,{"url":url,"accepted":[url,"Global license for the page: "+license]}); + addReportEntry(tabid, url, {url, "accepted":[url, `Global license for the page: ${license}`]}); // Do not process inline scripts scripts=""; }else{ @@ -1237,8 +1195,8 @@ function edit_html(html,url,tabid,wl){ */ async function handle_html(response, whitelisted) { let {text, request} = response; - let {url, tabId} = request; - delete unused_data[tabId]; + let {url, tabId, type} = request; + if (type === "main_frame") delete unused_data[tabId]; browser.browserAction.setBadgeText({ text: "✓", tabId @@ -1247,7 +1205,7 @@ async function handle_html(response, whitelisted) { color: "green", tabId }); - return await edit_html(text, url, tabId, false); + return await edit_html(text, url, tabId, whitelisted); } var whitelist = new ListStore("pref_whitelist", Storage.CSV); From 5975e67b1de999c168209f8a5b7652f3d4551412 Mon Sep 17 00:00:00 2001 From: hackademix Date: Jul 31 2018 16:10:42 +0000 Subject: [PATCH 8/18] Refactoring out list management in its own class / bug fixing and simplifying UI synchronization. --- diff --git a/bg/ListManager.js b/bg/ListManager.js new file mode 100644 index 0000000..34d9531 --- /dev/null +++ b/bg/ListManager.js @@ -0,0 +1,71 @@ +/** +* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. +* +* Copyright (C) 2018 Giorgio Maone +* +* This file is part of GNU LibreJS. +* +* GNU LibreJS is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* GNU LibreJS is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with GNU LibreJS. If not, see . +*/ + +/* + A class to manage whitelist/blacklist operations +*/ + +let {ListStore} = require("./Storage"); + +class ListManager { + constructor(whitelist, blacklist, builtInHashes) { + this.lists = {whitelist, blacklist}; + this.builtInHashes = new Set(builtInHashes); + } + async whitelist(key) { + await this.lists.blacklist.remove(key); + await this.lists.whitelist.store(key); + } + async blacklist(key) { + await this.lists.whitelist.remove(key); + await this.lists.blacklist.store(key); + } + async forget(key) { + for (let list of Object.values(this.lists)) { + await list.remove(key); + } + } + /* key is a string representing either a URL or an optional path + with a trailing (hash). + Returns "blacklisted", "whitelisted" or defValue + */ + getStatus(key, defValue = "unknown") { + let {blacklist, whitelist} = this.lists; + let match = key.match(/\(([^)]+)\)(?=[^()]*$)/); + if (!match) { + let url = ListStore.urlItem(key); + let site = ListStore.siteItem(key); + return (blacklist.contains(url) || blacklist.contains(site)) + ? "blacklisted" + : whitelist.contains(url) || whitelist.contains(site) + ? "whitelisted" : defValue; + } + + let [hashItem, srcHash] = match; // (hash), hash + + return blacklist.contains(hashItem) ? "blacklisted" + : this.builtInHashes.has(srcHash) || whitelist.contains(hashItem) + ? "whitelisted" + : defValue; + } +} + +module.exports = { ListManager }; diff --git a/main_background.js b/main_background.js index 108da28..2b2a975 100644 --- a/main_background.js +++ b/main_background.js @@ -27,6 +27,7 @@ var walk = require("acorn/dist/walk"); var legacy_license_lib = require("./legacy_license_check.js"); var {ResponseProcessor} = require("./bg/ResponseProcessor"); var {Storage, ListStore} = require("./bg/Storage"); +var {ListManager} = require("./bg/ListManager"); console.log("main_background.js"); /** @@ -110,15 +111,6 @@ var reserved_objects = [ "eval" ]; - -// Default whitelist, comes from the script in hash_script -var wl_data = require("./hash_script/whitelist").whitelist.jquery; -var default_whitelist = {}; -for(var i = 0; i < wl_data.length; i++){ - default_whitelist[wl_data[i].hash] = true; -} - - /** * * Sets global variable "webex" to either "chrome" or "browser" for @@ -186,6 +178,7 @@ function createReport(initializer = null) { "blocked": [], "blacklisted": [], "whitelisted": [], + "unknown": [], url: "", }; return initializer ? Object.assign(template, initializer) : template; @@ -251,97 +244,24 @@ function debug_print_local(){ * Make sure it will use the right URL when refering to a certain script. * */ -function update_popup(tab_id,blocked_info,update=false){ - var new_blocked_data; - function get_sto(items){ - //************************************************************************// - // Move scripts that are accepted/blocked but whitelisted to "whitelisted" category - // (Ideally, they just would not be tested in the first place because that would be faster) - var url = blocked_info["url"]; - if(url === undefined){ - console.error("No url passed to update_popup"); - return 1; - } - - function get_status(script_name){ - var temp = script_name.match(/\(.*?\)/g); - if(temp == null){ - return "none" - } - var src_hash = temp[temp.length-1].substr(1,temp[0].length-2); - - for(var i in items){ - var res = i.match(/\(.*?\)/g); - if(res != null){ - var test_hash = res[res.length-1].substr(1,res[0].length-2); - if(test_hash == src_hash){ - return items[i]; - } - } - } - - if(default_whitelist[src_hash] !== undefined){ - //console.log("Found script in default whitelist: "+default_whitelist[src_hash]); - return "whitelist"; - } else{ - //console.log("script " + script_name + " not in default whitelist."); - return "none"; - } - } - function is_bl(script_name){ - if(get_status(script_name) == "blacklist"){ - return true; - } - else return false; - } - function is_wl(script_name){ - if(get_status(script_name) == "whitelist"){ - return true; - } - else return false; - } - new_blocked_data = createReport({url}); - for(var type in blocked_info){ - for(var script_arr in blocked_info[type]){ - if(is_bl(blocked_info[type][script_arr][0])){ - new_blocked_data["blacklisted"].push(blocked_info[type][script_arr]); - //console.log("Script " + blocked_info[type][script_arr][0] + " is blacklisted"); - continue; - } - if(is_wl(blocked_info[type][script_arr][0])){ - new_blocked_data["whitelisted"].push(blocked_info[type][script_arr]); - //console.log("Script " + blocked_info[type][script_arr][0] + " is whitelisted"); - continue; - } - if(type == "url"){ - continue; - } - // either "blocked" or "accepted" - new_blocked_data[type].push(blocked_info[type][script_arr]); - //console.log("Script " + blocked_info[type][script_arr][0] + " isn't whitelisted or blacklisted"); - } - } - dbg_print(new_blocked_data); - //***********************************************************************************************// - // store the blocked info until it is opened and needed - if(update == false && active_connections[tab_id] === undefined){ - dbg_print("[TABID:"+tab_id+"]"+"Storing blocked_info for when the browser action is opened or asks for it."); - if(tab_id == undefined){ - dbg_print("UNDEFINED TAB_ID"); - } - unused_data[tab_id] = new_blocked_data; - } else{ - if(tab_id == undefined){ - dbg_print("UNDEFINED TAB_ID"); - } - unused_data[tab_id] = new_blocked_data; - dbg_print("[TABID:"+tab_id+"]"+"Sending blocked_info directly to browser action"); - active_connections[tab_id].postMessage({"show_info":new_blocked_data}); - delete active_connections[tab_id]; +function updateReport(tabId, oldReport, updateUI = false){ + let {url} = oldReport; + let newReport = createReport({url}); + for (let property of Object.keys(oldReport)) { + if (property === "url") continue; + let entries = oldReport[property]; + 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); } - return 0; } - webex.storage.local.get(get_sto); + unused_data[tabId] = newReport; + dbg_print(newReport); + if (updateUI && active_connections[tabId]) { + dbg_print(`[TABID: ${tabId}] Sending script blocking report directly to browser action.`); + active_connections[tabId].postMessage({show_info: newReport}); + } } /** @@ -351,14 +271,14 @@ function update_popup(tab_id,blocked_info,update=false){ * Sends a message to the content script that adds a popup entry for a tab. * * The action argument is an object with two properties: one named either -* "accepted","blocked", "whitelisted" or "blacklisted", whose value is the array -* [scriptName, reason], and another named "url". Example: +* "accepted","blocked", "whitelisted", "blacklisted" or "unknown", whose value +* is the array [scriptName, reason], and another named "url". Example: * action = { * "accepted": ["jquery.js (someHash)","Whitelisted by user"], * "url": "https://example.com/js/jquery.js" * } * -* Returns either "wl" (whitelisted), "bl" (blacklisted) or "none". +* Returns either "wl" (whitelisted), "bl" (blacklisted) or "unknown". * * NOTE: This WILL break if you provide inconsistent URLs to it. * Make sure it will use the right URL when refering to a certain script. @@ -380,17 +300,6 @@ async function addReportEntry(tabId, scriptHashOrUrl, action, update = false) { return ""; } - - function getStatus(scriptName) { - let match = scriptName.match(/\(([^)]+)\)(?=[^()]*$)/); - if (!match) return type; - - let [hashItem, srcHash] = match; // (hash), hash - - return (blacklist.contains(hashItem)) ? "blacklisted" - : (default_whitelist[srcHash] || whitelist.contains(hashItem)) ? "whitelisted" - : type; - } // Search unused data for the given entry function isNew(entries, item) { for (let e of entries) { @@ -402,7 +311,7 @@ async function addReportEntry(tabId, scriptHashOrUrl, action, update = false) { let entryType, res; let scriptName = actionValue[0]; try { - entryType = getStatus(scriptName); + entryType = listManager.getStatus(scriptName, type); res = entryType.substring(0, 2); let entries = unused_data[tabId][entryType]; if(isNew(entries, scriptName)){ @@ -412,8 +321,8 @@ async function addReportEntry(tabId, scriptHashOrUrl, action, update = false) { entries.push(actionValue); } } catch (e) { - console.error(e, "action %o, type %s, entryType %s", action, type, entryType); - res = "none"; + console.error("action %o, type %s, entryType %s", action, type, entryType, e); + res = "unknown"; } return res; } @@ -447,52 +356,19 @@ function connected(p) { webex.storage.local.get(cb); return; } - p.onMessage.addListener(function(m) { + p.onMessage.addListener(async function(m) { console.debug("LibreJS BG: received message", m); - /** - * Updates the entry of the current URL in storage - */ - function set_script(script,val){ - if(val != "whitelist" && val != "forget" && val != "blacklist"){ - console.error("Key must be either 'whitelist', 'blacklist' or 'forget'"); - } - // (Remember that we do not trust the names of scripts.) - var current_url = ""; - function geturl(tabs) { - current_url = tabs[0]["url"]; - var domain = get_domain(current_url); - var scriptkey = m[val][0]; - switch(val) { - case "forget": - whitelist.remove(scriptkey); - blacklist.remove(scriptkey); - break; - case "whitelist": - blacklist.remove(scriptkey); - whitelist.store(scriptkey); - break; - case "blacklist": - whitelist.remove(scriptkey); - blacklist.store(scriptkey); - break; - } - } - var querying = webex.tabs.query({active: true,currentWindow: true},geturl); - } + var update = false; var contact_finder = false; - if(m["whitelist"] !== undefined){ - set_script(m["whitelist"][0],"whitelist"); - update = true; - } - if(m["blacklist"] !== undefined){ - set_script(m["blacklist"][0],"blacklist"); - update = true; - } - if(m["forget"] !== undefined){ - set_script(m["forget"][0],"forget"); - update = true; + + for (let action of ["whitelist", "blacklist", "forget"]) { + if (m[action]) { + await listManager[action](m[action][0]); + update = true; + } } + // if(m["open_popup_tab"] !== undefined){ open_popup_tab(m["open_popup_tab"]); @@ -513,42 +389,42 @@ function connected(p) { debug_delete_local(); } // Add this domain to the whitelist - if(m["allow_all"] !== undefined){ - var domain = get_domain(m["allow_all"]["url"]); - add_csv_whitelist(domain); + if(m.allow_all){ + await listManager.whitelist(ListStore.siteItem(m.allow_all.url)); + update = true; } - // Remote this domain from the whitelist - if(m["block_all"] !== undefined){ - var domain = get_domain(m["block_all"]["url"]); - remove_csv_whitelist(domain); + // Remove this domain from the whitelist + if(m.block_all){ + await listManager.forget(ListStore.siteItem(m.block_all.url)); + update = true; } - function logTabs(tabs) { - if(contact_finder){ - dbg_print("[TABID:"+tab_id+"] Injecting contact finder"); - //inject_contact_finder(tabs[0]["id"]); - } - if(update){ - dbg_print("%c updating tab "+tabs[0]["id"],"color: red;"); - update_popup(tabs[0]["id"],unused_data[tabs[0]["id"]],true); - active_connections[tabs[0]["id"]] = p; - } - for(var i = 0; i < tabs.length; i++) { - var tab = tabs[i]; - var tab_id = tab["id"]; - if(unused_data[tab_id] !== undefined){ + + let tabs = await browser.tabs.query({active: true, currentWindow: true}); + + if(contact_finder){ + let tab = tabs.pop(); + dbg_print(`[TABID:${tab.id}] Injecting contact finder`); + //inject_contact_finder(tabs[0]["id"]); + } + if(update){ + let tab = tabs.pop(); + dbg_print(`%c updating tab ${tab.id}`, "color: red;"); + active_connections[tab.id] = p; + await updateReport(tab.id, unused_data[tab.id], true); + } else { + for(let tab of tabs) { + if(unused_data[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":unused_data[tab_id]}); + dbg_print(`[TABID: ${tab.id}] Sending stored data associated with browser action'`); + p.postMessage({"show_info": unused_data[tab.id]}); } else{ // create a new entry - unused_data[tab_id] = createReport({"url": tab.url}); - p.postMessage({"show_info":unused_data[tab_id]}); - dbg_print("[TABID:"+tab_id+"]"+"No data found, creating a new entry for this window."); + let report = unused_data[tab.id] = createReport({"url": tab.url}); + p.postMessage({show_info: report}); + dbg_print(`[TABID: ${tab.id}] No data found, creating a new entry for this window.`); } } } - var querying = webex.tabs.query({active: true,currentWindow: true},logTabs); - }); } @@ -568,15 +444,6 @@ function delete_removed_tab_info(tab_id, remove_info){ } } -/** -* Check whitelisted by hash -* -*/ -function blocked_status(hash) { - let hashItem = ListStore.hashItem(hash); - return whitelist.contains(hashItem) ? - "whitelisted" : blacklist.contains(hashItem) ? "blacklisted" : "none"; -} /* *********************************************************************************************** */ var fname_data = require("./fname_data.json").fname_data; @@ -805,7 +672,7 @@ function license_read(script_src, name, external = false){ if(license != false){ return [true,script_src,"Licensed under: "+license]; } - if (default_whitelist[hash(script_src)]){ + if (listManager.builtInHashes.has(hash(script_src))){ return [true,script_src,"Common script known to be free software."]; } while(true){ // TODO: refactor me @@ -976,6 +843,7 @@ var ResponseHandler = { let {request} = response; let {url, type, tabId} = request; + url = ListStore.urlItem(url); let site = ListStore.siteItem(url); let blacklistedSite = blacklist.contains(site); @@ -1210,6 +1078,12 @@ async function handle_html(response, whitelisted) { 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) + .reduce((a, b) => a.concat(b)) // as a flat array + .map(script => script.hash) + ); /** * Initializes various add-on functions From f4f172340634a2e0892942336e7c986e9040cfc1 Mon Sep 17 00:00:00 2001 From: hackademix Date: Jul 31 2018 16:15:06 +0000 Subject: [PATCH 9/18] Temporarily display back hidden old UI elements to demonstrate whitelisting/backlisting bug fixes. --- diff --git a/html/display_panel/content/display-panel.html b/html/display_panel/content/display-panel.html index 2879826..0fc18a7 100644 --- a/html/display_panel/content/display-panel.html +++ b/html/display_panel/content/display-panel.html @@ -55,7 +55,7 @@
- - - -
- LibreJS -
- -
+ +
+ LibreJS +
+ +
-
-

-
    +
    +

    This whole site

    +
    + + + +
    +
    +
    +

    +

    + LibreJS will decide whether blocking these scripts next time this page is loaded. +

    +
      +
    • + : +

      +
      + + + + +
      +
    • +
    +
    +
    +

    +
      -
      -

      -
        +
        +

        +
          -
          -

          -
            +
            +

            +
              -
              -

              -
                +
                +

                +
                  diff --git a/html/display_panel/content/main_panel.js b/html/display_panel/content/main_panel.js index 8622493..a6e19dc 100644 --- a/html/display_panel/content/main_panel.js +++ b/html/display_panel/content/main_panel.js @@ -1,247 +1,172 @@ - /** - * GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. - * * - * Copyright (C) 2017, 2018 NateN1222 - * Copyright (C) 2018 Ruben Rodriguez - * Copyright (C) 2018 Giorgio Maone - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - /** -* -* Sets global variable "webex" to either "chrome" or "browser" for -* use on Chrome or a Firefox variant. +* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. +* * +* Copyright (C) 2017, 2018 NateN1222 +* Copyright (C) 2018 Ruben Rodriguez +* Copyright (C) 2018 Giorgio Maone +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. * -* Change this to support a new browser that isn't Chrome or Firefox, -* given that it supports webExtensions. +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . * -* (Use the variable "webex" for all API calls after calling this) */ -var PRINT_DEBUG = false; -function dbg_print(a,b){ - if(PRINT_DEBUG == true){ - if(b === undefined){ - console.log(a); - } else{ - console.log(a,b); - } - } -} - -var webex; -function set_webex(){ - if(typeof(browser) == "undefined"){ - webex = chrome; - } else{ - webex = browser; - } +var fromTab = window.location.hash.match(/^#fromTab=(\d+)/) && RegExp.$1; +if (fromTab) { + let browserStyle = document.createElement("link"); + browserStyle.rel = "stylesheet"; + browserStyle.href = "chrome://browser/content/extension.css"; + document.head.appendChild(browserStyle); + document.documentElement.classList.add("tab"); } -set_webex(); - -var myPort = webex.runtime.connect({name:"port-from-cs"}); -var current_blocked_data; +var myPort = browser.runtime.connect({name: "port-from-cs"}); +var currentReport; +// Sends a message that tells the background script the window is open +myPort.postMessage({"update": true, tabId: parseInt(currentReport && currentReport.tabId || fromTab) || ""}); // Display the actual extension version Number document.querySelector("#version").textContent = browser.runtime.getManifest().version; -/* -* Makes a button appear that calls a function when you press it. -* -* I copied and pasted this from something else I wrote. It's quite useful. -* -*/ -var button_i = 0; -function new_debug_button(name_text,callback){ - if(document.getElementById("abc123_main_div") === null){ - var to_insert = '
                  '; - document.body.insertAdjacentHTML('afterbegin', to_insert); - } - var button_html = '
                  '; - document.getElementById("abc123_main_div").insertAdjacentHTML('afterbegin', button_html); - document.getElementById("abc123_button_"+button_i).addEventListener("click",callback); - button_i = button_i + 1; -} - - +var liTemplate = document.querySelector("#li-template"); +liTemplate.remove(); + +document.querySelector("#info").addEventListener("click", e => { + let button = e.target; + if (!button.matches(".buttons > button")) return; + let li = button.closest("li"); + let entry = li && li._scriptEntry || [currentReport.url, "Page's site"]; + let action = button.className; + let site = button.name === "*"; + if (site) { + ([action] = action.split("-")); + } + myPort.postMessage({[action]: entry, site}); +}); +document.querySelector("#report-tab").onclick = e => { + myPort.postMessage({report_tab: currentReport}); + close(); +} +document.querySelector("#complain").onclick = e => { + myPort.postMessage({invoke_contact_finder: currentReport}); + close(); +} +document.querySelector("#reload").onclick = async e => { + let {tabId} = currentReport; + if (tabId) { + await browser.tabs.reload(tabId); + } +}; /* -* Takes in the script data and color of h2 element -* Writes to category specified by "name" as used in HTML -* (name will probably either be "blacklisted", "whitelisted", "accepted", or "blocked") +* Takes in the [[file_id, reason],...] array and the group name for one group +* of scripts found in this tab, rendering it as a list with management buttons. +* Groups are "unknown", "blacklisted", "whitelisted", "accepted", and "blocked". */ -function write_elements(data,name,color){ - var url = data["url"]; - var button_html = '
                  '; - var button_html_2 = '
                  '; - var button_html_3 = '
                  '; - var heading = document.getElementById(name).getElementsByTagName("h2")[0]; - var list = document.getElementById(name).getElementsByTagName("ul")[0]; - if(typeof(data[name]) == "undefined" || data[name].length == 0){ - // default message - list.innerHTML = "
                • No "+ name +" scripts on this page.
                • " - data[name] = []; - } else{ - heading.innerHTML = "

                  List of
                  " + name.toUpperCase() + "
                  javascript in " + data["url"]+":

                  "; - } - // Iterate over data[name] and generate list - for(var i = 0; i < data[name].length; i++){ - list.innerHTML += "
                • "+data[name][i][0]+ ":
                  " + data[name][i][1]+"
                  "+button_html+"\n"+button_html_2+"\n"+button_html_3+"
                • "; - document.getElementById("temp").id = name+"_"+i; - document.getElementById("temp2").id = name+"_2_"+i; - document.getElementById("temp3").id = name+"_3_"+i; - } - if(data[name].length != 0){ - // add click listeners to the buttons - for(var i = 0; i < data[name].length; i++){ - // Make sure this causes generate_html to get called again with updated data - document.getElementById(name+"_"+i).addEventListener("click",function(info){ - var temp = current_blocked_data[name][parseInt(info.target.id.match(/\d/g)[0])]; - console.log("Moving script " + temp[0] + " to blacklist"); - var script_name = this.parentElement.parentElement.parentElement.parentElement.id; - myPort.postMessage({"blacklist": temp}); - }); - document.getElementById(name+"_2_"+i).addEventListener("click",function(info){ - var temp = current_blocked_data[name][parseInt(info.target.id.match(/\d+/g)[1])]; - console.log("Moving script " + temp[0] + " to whitelist"); - var script_name = this.parentElement.parentElement.parentElement.parentElement.id; - myPort.postMessage({"whitelist": temp}); - }); - - document.getElementById(name+"_3_"+i).addEventListener("click",function(info){ - var temp = current_blocked_data[name][parseInt(info.target.id.match(/\d/g)[1])]; - console.log("Forget preferences for script " + temp[0]); - var script_name = this.parentElement.parentElement.parentElement.parentElement.id; - //this.parentElement.parentElement.getElementsByTagName("b")[0].insertAdjacentHTML("beforebegin","

                  Refresh the page to revaluate this script.

                  "); - myPort.postMessage({"forget": temp}); - }); - } - } +function createList(data, group){ + var {url} = data; + let entries = data[group]; + let container = document.getElementById(group); + let heading = container.querySelector("h2"); + var list = container.querySelector("ul"); + list.classList.toggle(group, true); + if (Array.isArray(entries) && entries.length) { + heading.innerHTML = `${group} scripts in ${url}:`; + container.classList.remove("empty"); + } else { + // default message + list.innerHTML = `
                • No ${group} scripts on this page.
                • ` + entries = data[group] = []; + container.classList.add("empty"); + } + // generate list + for (let entry of entries) { + let [scriptId, reason] = entry; + let li = liTemplate.cloneNode(true); + let a = li.querySelector("a"); + a.href = scriptId.split("(")[0]; + a.textContent = scriptId; + li.querySelector(".reason").textContent = reason; + let bySite = !!reason.match(/https?:\/\/[^/]+\/\*/); + li.classList.toggle("by-site", bySite); + if (bySite) { + let domain = li.querySelector(".forget .domain"); + if (domain) domain.textContent = RegExp.lastMatch; + } + li._scriptEntry = entry; + list.appendChild(li); + } } /** -* displays the button specified by HTML string "button" -*/ -var template = ''; -var lr_flag = true; -var button_num = 0; -function write_button(button,callback){ - if(document.getElementById("buttons_table").innerHTML.indexOf(button) != -1){ - return; - } - var id = "buttonno_"+button_num; - if(lr_flag){ - document.getElementById("buttons_table").insertAdjacentHTML("beforeend",template); - document.getElementById("c1").insertAdjacentHTML("beforeend","
                  " + button + "
                  "); - document.getElementById("c1").id = "cell_"+button_num; - }else{ - var temp = document.getElementById("c2"); - temp.id = "cell_"+button_num; - temp.insertAdjacentHTML("beforeend","
                  " + button + "
                  "); - } - - button_num = button_num+1; - lr_flag = !lr_flag; - - document.getElementById(id).addEventListener("click",callback); -} -/** -* update the HTML of the pop-up window. +* Updates scripts lists and buttons to act on them. * If return_HTML is true, it returns the HTML of the popup window without updating it. -* example input: -* -* var example_input = { +* example report argument: +* { * "accepted": [["FILENAME 1","REASON 1"],["FILENAME 2","REASON 2"]], * "blocked": [["FILENAME 1","REASON 1"],["FILENAME 2","REASON 2"]], * "whitelisted": [["FILENAME 1","REASON 1"],["FILENAME 2","REASON 2"]], * "blacklisted": [["FILENAME 1","REASON 1"],["FILENAME 2","REASON 2"]], +* "unknown": [["FILENAME 1","REASON 1"],["FILENAME 2","REASON 2"]], * "url":"example.com" * }; * */ -function generate_HTML(blocked_data){ - current_blocked_data = blocked_data;//unused? - - // This should send a message to invoke the content finder - var button_complain = 'Complain to site owner'; - // This should update the persistent options - var button_allow_all = ''+"Add page's domain to whitelist"+''; - // This will call "Forget preferences" on every script. - var button_block_nonfree = ''+"Remove page's domain from whitelist"+''; - // This should send a message that calls "open_popup_tab()" in the background script - var button_new_tab = 'Open this report in a new tab'; - - var to_clr = document.getElementsByClassName("blocked-js"); - - for(var i = 0; i < to_clr.length; i++){ - to_clr[i].innerHTML = ""; - } - dbg_print("REGEN HTML:"); - dbg_print(blocked_data); - write_elements(blocked_data,"accepted","green"); - write_elements(blocked_data,"whitelisted","green"); - write_elements(blocked_data,"blocked","red"); - write_elements(blocked_data,"blacklisted","red"); - - if( blocked_data["blacklisted"].length != 0 || blocked_data["blocked"].length != 0 || - blocked_data["whitelisted"].length != 0 || blocked_data["accepted"].length != 0){ - write_button(button_allow_all,function(){ - myPort.postMessage({"allow_all": blocked_data}); - }); - write_button(button_block_nonfree,function(){ - myPort.postMessage({"block_all": blocked_data}); - - }); - write_button(button_complain,function(){ - myPort.postMessage({"invoke_contact_finder": blocked_data}); - }); - write_button(button_new_tab,function(){ - myPort.postMessage({"open_popup_tab": blocked_data}); - }); - } else{ - write_button(button_new_tab,function(){ - myPort.postMessage({"open_popup_tab": blocked_data}); - }); - } +function refreshUI(report){ + console.debug("refreshUI", report); + currentReport = report; + + document.querySelector("#site").className = report.siteStatus || ""; + document.querySelector("#site h2").textContent = + `This site ${report.site}`; + + for (let toBeErased of document.querySelectorAll("#info h2:not(.site) > *, #info ul > *")) { + toBeErased.remove(); + } + + let scriptsCount = 0; + for (let group of ["unknown", "accepted", "whitelisted", "blocked", "blacklisted"]) { + if (group in report) createList(report, group); + scriptsCount += report[group].length; + } + + for (let b of document.querySelectorAll(`.forget, .whitelist, .blacklist`)) { + b.disabled = false; + } + for (let b of document.querySelectorAll( + `.unknown .forget, .accepted .forget, .blocked .forget, + .whitelisted .whitelist, .blacklisted .blacklist` + )) { + b.disabled = true; + } + + let noscript = scriptsCount === 0; + document.body.classList.toggle("empty", noscript); } -myPort.onMessage.addListener(function(m) { - if(m["show_info"] !== undefined){ - generate_HTML(m["show_info"]); - } +myPort.onMessage.addListener(m => { + if (m.show_info) { + refreshUI(m.show_info); + } }); -// Sends a message that tells the background script the window is open -function onGot(tabInfo) { - myPort.postMessage({"tab_info": tabInfo}); -} -var gettingCurrent = webex.tabs.getCurrent(onGot); - function print_local_storage(){ - myPort.postMessage({"printlocalstorage": true}); + myPort.postMessage({"printlocalstorage": true}); } function delete_local_storage(){ - myPort.postMessage({"deletelocalstorage":true}); + myPort.postMessage({"deletelocalstorage":true}); } - -//new_debug_button("Print local storage",print_local_storage); -//new_debug_button("Clear local storage",delete_local_storage); diff --git a/html/display_panel/content/panel-styles.css b/html/display_panel/content/panel-styles.css index c077a31..745c67f 100644 --- a/html/display_panel/content/panel-styles.css +++ b/html/display_panel/content/panel-styles.css @@ -68,17 +68,18 @@ ul { padding:0; list-style:none; } -ul.blocked-js li, ul.accepted-js li, ul.dryrun-js li { +#info li { padding:5px; border-bottom:2px solid #CCC; margin:0; + overflow: hidden; } -ul ul { +#info ul ul { margin:10px; list-style:disc; } -ul.blocked-js ul li, ul.accepted-js ul li, ul.dryrun-js ul li { +#info ul ul li { padding:5px; border-bottom:0; } @@ -86,63 +87,48 @@ ul.blocked-js ul li, ul.accepted-js ul li, ul.dryrun-js ul li { clear:both; } -/* - Pure JS button styles below taken from: - http://webdesignerwall.com/tutorials/css3-gradient-buttons - */ -.button { - display: inline-block; - outline: none; - cursor: pointer; - text-align: center; - text-decoration: none; - font-size: 1 em; - border-radius: .5em; - float:right; - padding:10px; -} -.small.button { - font-size:11px; - padding:.5em .5em; - margin-top:10px; -} -.button:hover { - text-decoration: none; -} -.button:active { - position: relative; - top: 1px; -} -.orange { - color: #fef4e9; - border: solid 1px #da7c0c; - background: #f78d1d; - background: -webkit-gradient(linear, left top, left bottom, from(#faa51a), to(#f47a20)); - background: -moz-linear-gradient(top, #faa51a, #f47a20); -} -.orange:hover { - background: #f47c20; - background: -webkit-gradient(linear, left top, left bottom, from(#f88e11), to(#f06015)); - background: -moz-linear-gradient(top, #f88e11, #f06015); -} -.orange:active { - color: #fcd3a5; - background: -webkit-gradient(linear, left top, left bottom, from(#f47a20), to(#faa51a)); - background: -moz-linear-gradient(top, #f47a20, #faa51a); -} -.white { - background: -moz-linear-gradient(center top , #FFFFFF, #EDEDED) repeat scroll 0 0 transparent; - border: 1px solid #B7B7B7; - /* color: #606060;*/ - color:#9d0d0d; -} -.white:hover { - background: -moz-linear-gradient(center top , #FFFFFF, #DCDCDC) repeat scroll 0 0 transparent; -} -.white:active { - background: -moz-linear-gradient(center top , #EDEDED, #FFFFFF) repeat scroll 0 0 transparent; -/*color: #999999;*/ - +#info .type-name { + text-transform: uppercase; + font-weight: bold; +} + +#info .accepted-js .type-name { + color: #080; +} + +#info .blocked-js .type-name { + color: #800; +} + +#info .unknown-js .type-name { + color: #008; +} + +#info .unknown-js .reason { + display: none; +} + +.by-site button.forget, button.forget[name="*"] { + display: none; +} + +.by-site button.forget[name="*"], #site .forget[name="*"] { + display: initial; +} + + +button.whitelist { + color: #080; +} +button.blacklist { + color: #800; +} +button.forget { + color: #008; +} + +button:disabled { + color: #888 !important; } span.accepted, span.blocked { @@ -151,9 +137,7 @@ span.accepted, span.blocked { font-variant:small-caps; font-weight:bold; } -ul.blocked-js li { - overflow:hidden; -} + span.blocked { color:#8e0000; } @@ -173,3 +157,17 @@ span.blocked { font-size: 14px; list-style-type: disc; } + + +.empty #site, .unknown-js.empty { + display: none; +} + +.tab #must-reload, .tab #buttons, .empty #buttons { + display: none; +} + +#buttons button { + width: 100%; + text-align: center; +} diff --git a/main_background.js b/main_background.js index b02c639..62ff18e 100644 --- a/main_background.js +++ b/main_background.js @@ -181,7 +181,12 @@ function createReport(initializer = null) { "unknown": [], url: "", }; - return initializer ? Object.assign(template, initializer) : template; + if (initializer) { + template = Object.assign(template, initializer); + } + template.site = ListStore.siteItem(template.url); + template.siteStatus = listManager.getStatus(template.site); + return template; } /** @@ -189,19 +194,12 @@ function createReport(initializer = null) { * by opening a new tab with whatever HTML is in the popup * at the moment. */ -function open_popup_tab(data){ - dbg_print(data); - function gotPopup(popupURL){ - var creating = webex.tabs.create({"url":popupURL},function(a){ - dbg_print("[TABID:"+a["id"]+"] creating unused data entry from parent window's content"); - unused_data[a["id"]] = createReport(data); - }); - } - - var gettingPopup = webex.browserAction.getPopup({},gotPopup); +async function openReportInTab(data) { + let popupURL = await browser.browserAction.getPopup({}); + let tab = await browser.tabs.create({url: `${popupURL}#fromTab=${data.tabId}`}); + unused_data[tab.id] = createReport(data); } - /** * * Clears local storage (the persistent data) @@ -246,10 +244,10 @@ function debug_print_local(){ */ function updateReport(tabId, oldReport, updateUI = false){ let {url} = oldReport; - let newReport = createReport({url}); + let newReport = createReport({url, tabId}); for (let property of Object.keys(oldReport)) { - if (property === "url") continue; let entries = oldReport[property]; + if (!Array.isArray(entries)) continue; let defValue = property === "accepted" || property === "blocked" ? property : "unknown"; for (let script of entries) { let status = listManager.getStatus(script[0], defValue); @@ -278,7 +276,7 @@ function updateReport(tabId, oldReport, updateUI = false){ * "url": "https://example.com/js/jquery.js" * } * -* Returns either "wl" (whitelisted), "bl" (blacklisted) or "unknown". +* Returns either "whitelisted, "blacklisted", "blocked", "accepted" or "unknown" * * NOTE: This WILL break if you provide inconsistent URLs to it. * Make sure it will use the right URL when refering to a certain script. @@ -308,11 +306,10 @@ async function addReportEntry(tabId, scriptHashOrUrl, action, update = false) { return true; } - let entryType, res; + let entryType; let scriptName = actionValue[0]; try { entryType = listManager.getStatus(scriptName, type); - res = entryType.substring(0, 2); let entries = unused_data[tabId][entryType]; if(isNew(entries, scriptName)){ dbg_print(unused_data); @@ -322,9 +319,18 @@ async function addReportEntry(tabId, scriptHashOrUrl, action, update = false) { } } catch (e) { console.error("action %o, type %s, entryType %s", action, type, entryType, e); - res = "unknown"; + entryType = "unknown"; + } + + if (active_connections[tabId]) { + try { + active_connections[tabId].postMessage({show_info: unused_data[tabId]}); + } catch(e) { + console.error(e); + } } - return res; + + return entryType; } @@ -364,14 +370,15 @@ function connected(p) { for (let action of ["whitelist", "blacklist", "forget"]) { if (m[action]) { - await listManager[action](m[action][0]); + let [key] = m[action]; + if (m.site) key = ListStore.siteItem(key); + await listManager[action](key); update = true; } } - // - if(m["open_popup_tab"] !== undefined){ - open_popup_tab(m["open_popup_tab"]); + if(m.report_tab){ + openReportInTab(m.report_tab); } // a debug feature if(m["printlocalstorage"] !== undefined){ @@ -388,17 +395,7 @@ function connected(p) { console.log("Delete local storage"); debug_delete_local(); } - // Add this domain to the whitelist - if(m.allow_all){ - await listManager.whitelist(ListStore.siteItem(m.allow_all.url)); - update = true; - } - // Remove this domain from the whitelist - if(m.block_all){ - await listManager.forget(ListStore.siteItem(m.block_all.url)); - update = true; - } - + let tabs = await browser.tabs.query({active: true, currentWindow: true}); if(contact_finder){ @@ -406,11 +403,11 @@ function connected(p) { dbg_print(`[TABID:${tab.id}] Injecting contact finder`); //inject_contact_finder(tabs[0]["id"]); } - if(update){ - let tab = tabs.pop(); - dbg_print(`%c updating tab ${tab.id}`, "color: red;"); - active_connections[tab.id] = p; - await updateReport(tab.id, unused_data[tab.id], true); + if (update || m.update && unused_data[m.tabId]) { + let tabId = "tabId" in m ? m.tabId : tabs.pop().id; + dbg_print(`%c updating tab ${tabId}`, "color: red;"); + active_connections[tabId] = p; + await updateReport(tabId, unused_data[tabId], true); } else { for(let tab of tabs) { if(unused_data[tab.id]){ @@ -419,7 +416,7 @@ function connected(p) { p.postMessage({"show_info": unused_data[tab.id]}); } else{ // create a new entry - let report = unused_data[tab.id] = createReport({"url": tab.url}); + let report = unused_data[tab.id] = 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.`); } @@ -757,14 +754,15 @@ async function get_script(response, url, tabId, whitelisted = false, index = -1) function result(scriptSource) { return index === -1 ? scriptSource : [scriptSource, index]; } - let report = unused_data[tabId] || (unused_data[tabId] = createReport({url})); + let report = unused_data[tabId] || (unused_data[tabId] = createReport({url, tabId})); let scriptName = url.split("/").pop(); if (whitelisted) { + let site = ListStore.siteItem(url); // Accept without reading script, it was explicitly whitelisted - let reason = response.whitelistedSite - ? "Site ${response.whitelistedSite} whitelisted by user" - : "Page whitelisted by user"; + let reason = whitelist.contains(site) + ? `All ${site} whitelisted by user` + : "Address whitelisted by user"; addReportEntry(tabId, url, {"whitelisted": [url, reason], url}); return result(`/* LibreJS: script whitelisted by user preference. */\n${response}`); } @@ -783,13 +781,14 @@ async function get_script(response, url, tabId, whitelisted = false, index = -1) tabId }); } - let listVerdict = await addReportEntry(tabId, sourceHash, {"url": domain, [verdict ? "accepted" : "blocked"]: [url, reason]}); - switch(listVerdict) { - case "wl": case "bl": - let verdictText = listVerdict === "wl" ? "whitelisted" : "blacklisted"; - return result(`/* LibreJS: script ${verdictText} by user. */\n${response}`); + let category = await addReportEntry(tabId, sourceHash, {"url": domain, [verdict ? "accepted" : "blocked"]: [url, reason]}); + let scriptSource = verdict ? response : editedSource; + switch(category) { + case "blacklisted": + case "whitelisted": + return result(`/* LibreJS: script ${category} by user. */\n${scriptSource}`); default: - return result(`/* LibreJS: script aknowledged. */\n${editedSource}`); + return result(`/* LibreJS: script ${category}. */\n${scriptSource}`); } } @@ -849,6 +848,7 @@ var ResponseHandler = { let blacklistedSite = blacklist.contains(site); let blacklisted = blacklistedSite || blacklist.contains(url); let topUrl = request.frameAncestors && request.frameAncestors.pop() || request.documentUrl; + if (blacklisted) { if (type === "script") { // abort the request before the response gets fetched @@ -863,7 +863,6 @@ var ResponseHandler = { }); } else { let whitelistedSite = whitelist.contains(site); - if (whitelistedSite) response.whitelistedSite = site; if ((response.whitelisted = (whitelistedSite || whitelist.contains(url))) && type === "script") { // accept the script and stop processing @@ -872,6 +871,7 @@ var ResponseHandler = { return ResponseProcessor.ACCEPT; } } + // it's a page (it's too early to report) or an unknown script: // let's keep processing return ResponseProcessor.CONTINUE; @@ -893,7 +893,8 @@ var ResponseHandler = { async function handle_script(response, whitelisted){ let {text, request} = response; let {url, tabId} = request; - return await get_script(text, url, tabId, whitelisted, -2); + let edited = await get_script(text, url, tabId, whitelisted, -2); + return Array.isArray(edited) ? edited[0] : edited; } /** @@ -1128,18 +1129,4 @@ function inject_contact_finder(tab_id){ var executing = webex.tabs.executeScript(tab_id, {file: "/contact_finder.js"}, executed); } -/** -* Adds given domain to the whitelist in options -*/ -async function add_csv_whitelist(domain){ - return await whitelist.store(`${domain}*`); -} - -/** -* removes given domain from the whitelist in options -*/ -async function remove_csv_whitelist(domain) { - return whitelist.remove(`${domain}*`); -} - init_addon(); From 2cb91e36eed08993c0a9ca693f910b153705da65 Mon Sep 17 00:00:00 2001 From: hackademix Date: Aug 06 2018 20:34:41 +0000 Subject: [PATCH 15/18] Fixed regression, commit #13ea9430ff74174b0e1043119e4d855259b62a30 breaking reporting when intrinsic scripts are detected. --- diff --git a/main_background.js b/main_background.js index 62ff18e..c409959 100644 --- a/main_background.js +++ b/main_background.js @@ -750,11 +750,11 @@ function license_read(script_src, name, external = false){ * Asynchronous function, returns the final edited script as a string, * or an array containing it and the index, if the latter !== -1 */ -async function get_script(response, url, tabId, whitelisted = false, index = -1) { +async function get_script(response, url, tabId = -1, whitelisted = false, index = -1) { function result(scriptSource) { return index === -1 ? scriptSource : [scriptSource, index]; } - let report = unused_data[tabId] || (unused_data[tabId] = createReport({url, tabId})); + let scriptName = url.split("/").pop(); if (whitelisted) { @@ -767,8 +767,14 @@ async function get_script(response, url, tabId, whitelisted = false, index = -1) return result(`/* LibreJS: script whitelisted by user preference. */\n${response}`); } let [verdict, editedSource, reason] = license_read(response, scriptName, index === -2); + + if (tabId < 0) { + return result(verdict ? response : editedSource); + } + let sourceHash = hash(response); let domain = get_domain(url); + let report = unused_data[tabId] || (unused_data[tabId] = createReport({url, tabId})); let blockedCount = report.blocked.length + report.blacklisted.length; dbg_print(`amt. blocked on page: ${blockedCount}`); if (blockedCount > 0 || !verdict) { @@ -1012,7 +1018,7 @@ function edit_html(html,url,tabid,wl){ // "i" is an index in html_doc.all // "j" is an index in intrinsic_events function edit_event(src,i,j,name){ - var edited = get_script(src, name, tabid); + var edited = get_script(src, name); edited.then(function(){ html_doc.all[i].attributes[intrinsic_events[j]].value = edited[0]; }); From 3f2aca651b85d73c8a217bda927c36bf7e6b43f8 Mon Sep 17 00:00:00 2001 From: hackademix Date: Aug 06 2018 20:57:13 +0000 Subject: [PATCH 16/18] Fixed report attempts when no tabId is available. --- diff --git a/main_background.js b/main_background.js index c409959..d401b96 100644 --- a/main_background.js +++ b/main_background.js @@ -758,14 +758,17 @@ async function get_script(response, url, tabId = -1, whitelisted = false, index let scriptName = url.split("/").pop(); if (whitelisted) { - let site = ListStore.siteItem(url); - // Accept without reading script, it was explicitly whitelisted - let reason = whitelist.contains(site) - ? `All ${site} whitelisted by user` - : "Address whitelisted by user"; + if (tabId !== -1) { + let site = ListStore.siteItem(url); + // Accept without reading script, it was explicitly whitelisted + let reason = whitelist.contains(site) + ? `All ${site} whitelisted by user` + : "Address whitelisted by user"; addReportEntry(tabId, url, {"whitelisted": [url, reason], url}); + } return result(`/* LibreJS: script whitelisted by user preference. */\n${response}`); } + let [verdict, editedSource, reason] = license_read(response, scriptName, index === -2); if (tabId < 0) { @@ -888,6 +891,7 @@ var ResponseHandler = { */ async post(response) { let {url, type} = response.request; + url = ListStore.urlItem(url); let handle_it = type === "script" ? handle_script : handle_html; return await handle_it(response, response.whitelisted); } From 540dc46044afc6909c9e453b983244468e0ac9b3 Mon Sep 17 00:00:00 2001 From: hackademix Date: Aug 06 2018 21:22:31 +0000 Subject: [PATCH 17/18] Fixed scripts whose URLs had a query string could not be whitelisted. --- diff --git a/main_background.js b/main_background.js index d401b96..e128f9b 100644 --- a/main_background.js +++ b/main_background.js @@ -326,7 +326,6 @@ async function addReportEntry(tabId, scriptHashOrUrl, action, update = false) { try { active_connections[tabId].postMessage({show_info: unused_data[tabId]}); } catch(e) { - console.error(e); } } @@ -890,8 +889,7 @@ var ResponseHandler = { * Here we do the heavylifting, analyzing unknown scripts */ async post(response) { - let {url, type} = response.request; - url = ListStore.urlItem(url); + let {type} = response.request; let handle_it = type === "script" ? handle_script : handle_html; return await handle_it(response, response.whitelisted); } @@ -903,6 +901,7 @@ var ResponseHandler = { async function handle_script(response, whitelisted){ let {text, request} = response; 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; } @@ -1075,6 +1074,7 @@ function edit_html(html,url,tabid,wl){ async function handle_html(response, whitelisted) { let {text, request} = response; let {url, tabId, type} = request; + url = ListStore.urlItem(url); if (type === "main_frame") { delete unused_data[tabId]; browser.browserAction.setBadgeText({ From 8ea490c2bc15c2ceb42f1a2b0f648d21703a851f Mon Sep 17 00:00:00 2001 From: hackademix Date: Aug 06 2018 22:04:52 +0000 Subject: [PATCH 18/18] Removed some debugging noise. --- diff --git a/html/display_panel/content/main_panel.js b/html/display_panel/content/main_panel.js index a6e19dc..958e2d2 100644 --- a/html/display_panel/content/main_panel.js +++ b/html/display_panel/content/main_panel.js @@ -126,8 +126,7 @@ function createList(data, group){ * }; * */ -function refreshUI(report){ - console.debug("refreshUI", report); +function refreshUI(report) { currentReport = report; document.querySelector("#site").className = report.siteStatus || ""; diff --git a/main_background.js b/main_background.js index e128f9b..15c8d34 100644 --- a/main_background.js +++ b/main_background.js @@ -362,8 +362,6 @@ function connected(p) { return; } p.onMessage.addListener(async function(m) { - console.debug("LibreJS BG: received message", m); - var update = false; var contact_finder = false;