From 9884643742b54b88f94111d62f5e89a12a60e604 Mon Sep 17 00:00:00 2001 From: hackademix Date: Tue, 26 Mar 2019 22:45:37 +0100 Subject: Subdomain wildcard support. --- bg/ListManager.js | 35 ++++++++++++++++++++++----- html/display_panel/content/main_panel.js | 6 ++++- html/preferences_panel/pref.js | 3 +++ html/preferences_panel/preferences_panel.html | 4 +-- main_background.js | 16 ++++++------ 5 files changed, 47 insertions(+), 17 deletions(-) diff --git a/bg/ListManager.js b/bg/ListManager.js index 2f5f74e..745e599 100644 --- a/bg/ListManager.js +++ b/bg/ListManager.js @@ -36,13 +36,13 @@ class ListManager { } async whitelist(...keys) { - ListManager.move(this.lists.blacklist, this.lists.whitelist, ...keys); + await ListManager.move(this.lists.blacklist, this.lists.whitelist, ...keys); } async blacklist(...keys) { - ListManager.move(this.lists.whitelist, this.lists.blacklist, ...keys); + await ListManager.move(this.lists.whitelist, this.lists.blacklist, ...keys); } async forget(...keys) { - await Promise.all(Object.values(this.lists).map(l => l.remove(...keys))); + await Promise.all(Object.values(this.lists).map(async l => await l.remove(...keys))); } /* key is a string representing either a URL or an optional path with a trailing (hash). @@ -62,10 +62,11 @@ class ListManager { if (!match) { let url = ListStore.urlItem(key); let site = ListStore.siteItem(key); - return (blacklist.contains(url) || blacklist.contains(site)) + return (blacklist.contains(url) || ListManager.siteMatch(site, blacklist) ? "blacklisted" - : whitelist.contains(url) || whitelist.contains(site) - ? "whitelisted" : defValue; + : whitelist.contains(url) || ListManager.siteMatch(site, whitelist) + ? "whitelisted" : defValue + ); } let [hashItem, srcHash] = match; // (hash), hash @@ -74,6 +75,28 @@ class ListManager { ? "whitelisted" : defValue; } + + /* + Matches by whole site ("http://some.domain.com/*") supporting also + wildcarded subdomains ("https://*.domain.com/*"). + */ + static siteMatch(url, list) { + let site = ListStore.siteItem(url); + if (list.contains(site)) { + return site; + } + site = site.replace(/^([\w-]+:\/\/)?(\w)/, "$1*.$2"); + for (;;) { + if (list.contains(site)) { + return site; + } + let oldKey = site; + site = site.replace(/(?:\*\.)*\w+(?=\.)/, "*"); + if (site === oldKey) { + return null; + } + } + } } module.exports = { ListManager }; diff --git a/html/display_panel/content/main_panel.js b/html/display_panel/content/main_panel.js index 83c3a6b..9172265 100644 --- a/html/display_panel/content/main_panel.js +++ b/html/display_panel/content/main_panel.js @@ -59,6 +59,7 @@ document.querySelector("#info").addEventListener("click", e => { setTimeout(close, 100); return; } + if (!button.tagName === "BUTTON") button = button.closest("button"); if (button.matches(".toggle-source")) { let parent = button.parentNode; if (!parent.querySelector(".source").textContent) { @@ -69,10 +70,13 @@ document.querySelector("#info").addEventListener("click", e => { return; } if (!button.matches(".buttons > button")) return; + let domain = button.querySelector(".domain"); + let li = button.closest("li"); let entry = li && li._scriptEntry || [currentReport.url, "Page's site"]; let action = button.className; - let site = button.name === "*"; + let site = domain ? domain.textContent : button.name === "*" ? currentReport.site : ""; + if (site) { ([action] = action.split("-")); } diff --git a/html/preferences_panel/pref.js b/html/preferences_panel/pref.js index 9cecbb6..debb468 100644 --- a/html/preferences_panel/pref.js +++ b/html/preferences_panel/pref.js @@ -40,6 +40,9 @@ error = "Only one single trailing path wildcard (/*) allowed"; } } catch (e) { + if (/^https?:\/\/\*\./.test(url)) { + return this.malformedUrl(url.replace("*.", "")); + } error = "Invalid URL"; if (url && !url.includes("://")) error += ": missing protocol, either http:// or https://"; else if (url.endsWith("://")) error += ": missing domain name"; diff --git a/html/preferences_panel/preferences_panel.html b/html/preferences_panel/preferences_panel.html index 3a0ad7a..081ae07 100644 --- a/html/preferences_panel/preferences_panel.html +++ b/html/preferences_panel/preferences_panel.html @@ -45,10 +45,10 @@

Settings

-
Allow or block scripts matching the following URLs ("*" matches any path) +
Allow or block scripts matching the following URLs ("*."" matches any subdomain, "/*" matches any path)
- +
diff --git a/main_background.js b/main_background.js index 8060ebd..f1c7f09 100644 --- a/main_background.js +++ b/main_background.js @@ -326,7 +326,7 @@ async function connected(p) { if (m[action]) { let [key] = m[action]; if (m.site) { - key = ListStore.siteItem(key); + key = ListStore.siteItem(m.site); } else { key = ListStore.inlineItem(key) || key; } @@ -745,12 +745,12 @@ async function get_script(response, url, tabId = -1, whitelisted = false, index let scriptName = url.split("/").pop(); if (whitelisted) { if (tabId !== -1) { - let site = ListStore.siteItem(url); + let site = ListManager.siteMatch(url, whitelist); // Accept without reading script, it was explicitly whitelisted - let reason = whitelist.contains(site) + let reason = site ? `All ${site} whitelisted by user` : "Address whitelisted by user"; - addReportEntry(tabId, url, {"whitelisted": [url, reason], url}); + addReportEntry(tabId, url, {"whitelisted": [site, reason], url}); } if (response.startsWith("javascript:")) return result(response); @@ -855,7 +855,7 @@ var ResponseHandler = { url = ListStore.urlItem(url); let site = ListStore.siteItem(url); - let blacklistedSite = blacklist.contains(site); + let blacklistedSite = ListManager.siteMatch(site, blacklist); let blacklisted = blacklistedSite || blacklist.contains(url); let topUrl = request.frameAncestors && request.frameAncestors.pop() || documentUrl; @@ -863,7 +863,7 @@ var ResponseHandler = { if (type === "script") { // abort the request before the response gets fetched addReportEntry(tabId, url, {url: topUrl, - "blacklisted": [url, blacklistedSite ? `User blacklisted ${site}` : "Blacklisted by user"]}); + "blacklisted": [url, blacklistedSite ? `User blacklisted ${blacklistedSite}` : "Blacklisted by user"]}); return ResponseProcessor.REJECT; } // use CSP to restrict JavaScript execution in the page @@ -872,13 +872,13 @@ var ResponseHandler = { value: `script-src '${blacklistedSite ? 'self' : 'none'}';` }); } else { - let whitelistedSite = whitelist.contains(site); + let whitelistedSite = ListManager.siteMatch(site, whitelist); let whitelisted = response.whitelisted = whitelistedSite || whitelist.contains(url); if (type === "script") { if (whitelisted) { // accept the script and stop processing addReportEntry(tabId, url, {url: topUrl, - "whitelisted": [url, whitelistedSite ? `User whitelisted ${site}` : "Whitelisted by user"]}); + "whitelisted": [url, whitelistedSite ? `User whitelisted ${whitelistedSite}` : "Whitelisted by user"]}); return ResponseProcessor.ACCEPT; } else { let scriptInfo = await ExternalLicenses.check({url: fullUrl, tabId, frameId, documentUrl}); -- cgit v1.2.3 From a99f7d91acf0fe82a8c82440c28ffc5fe379924a Mon Sep 17 00:00:00 2001 From: hackademix Date: Tue, 26 Mar 2019 22:47:00 +0100 Subject: Automated regression tests for whitelist and blacklist management, including wildcards. --- main_background.js | 1 + test/spec/LibreJSSpec.js | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/main_background.js b/main_background.js index f1c7f09..5943fb8 100644 --- a/main_background.js +++ b/main_background.js @@ -1208,6 +1208,7 @@ async function init_addon() { editHtml, handle_script, ExternalLicenses, + ListManager, ListStore, Storage, }; // create or focus the autotest tab if it's a debugging session if ((await browser.management.getSelf()).installType === "development") { diff --git a/test/spec/LibreJSSpec.js b/test/spec/LibreJSSpec.js index 57c7f65..43f37a7 100644 --- a/test/spec/LibreJSSpec.js +++ b/test/spec/LibreJSSpec.js @@ -38,15 +38,53 @@ describe("LibreJS' components", () => { let licensed = `// @license ${license.magnet} ${license.id}\n${nontrivial}\n// @license-end`; let unknownLicensed = `// @license ${unknownLicense.magnet} ${unknownLicense.id}\n${nontrivial}\n// @license-end`; let malformedLicensed = `// @license\n${nontrivial}`; - let tab, documentUrl; beforeAll(async () => { let url = browser.extension.getURL("/test/resources/index.html"); tab = (await browser.tabs.query({url}))[0] || (await browser.tabs.create({url})); documentUrl = url; + }); + describe("The whitelist/blacklist manager", () => { + let {ListManager, ListStore, Storage} = LibreJS; + let lm = new ListManager(new ListStore("_test.whitelist", Storage.CSV), new ListStore("_test.blacklist", Storage.CSV), new Set()); + let forgot = ["http://formerly.whitelist.ed/", "http://formerly.blacklist.ed/"]; + + beforeAll(async () => { + await lm.whitelist("https://fsf.org/*", "https://*.gnu.org/*", forgot[0]); + await lm.blacklist("https://*.evil.gnu.org/*", "https://verybad.com/*", forgot[1]); + }); + + it("Should handle basic CRUD operations", async () => { + expect(lm.getStatus(forgot[0])).toBe("whitelisted"); + expect(lm.getStatus(forgot[1])).toBe("blacklisted"); + + await lm.forget(...forgot); + + for (let url of forgot) { + expect(lm.getStatus(url)).toBe("unknown"); + } + }); + + it("Should support full path wildcards", () => { + expect(lm.getStatus("https://unknown.org")).toBe("unknown"); + expect(lm.getStatus("https://fsf.org/some/path")).toBe("whitelisted"); + expect(lm.getStatus("https://fsf.org/")).toBe("whitelisted"); + expect(lm.getStatus("https://fsf.org")).toBe("whitelisted"); + expect(lm.getStatus("https://subdomain.fsf.org")).toBe("unknown"); + expect(lm.getStatus("https://verybad.com/some/other/path?with=querystring")).toBe("blacklisted"); + }); + it("Should support subdomain wildcards", () => { + expect(lm.getStatus("https://gnu.org")).toBe("whitelisted"); + expect(lm.getStatus("https://www.gnu.org")).toBe("whitelisted"); + expect(lm.getStatus("https://evil.gnu.org")).toBe("blacklisted"); + expect(lm.getStatus("https://more.evil.gnu.org")).toBe("blacklisted"); + expect(lm.getStatus("https://more.evil.gnu.org/some/evil/path?too")).toBe("blacklisted"); + }); + }) + describe("The external script source processor", () => { let url = "https://www.gnu.org/mock-script.js"; -- cgit v1.2.3 From f128d868d319c45222e451af75e3aa397b7f8144 Mon Sep 17 00:00:00 2001 From: hackademix Date: Mon, 8 Apr 2019 18:37:54 +0200 Subject: Add permanent reload button in the popup UI (in the "whole site" information line). --- html/display_panel/content/display-panel.html | 3 ++- html/display_panel/content/main_panel.js | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/html/display_panel/content/display-panel.html b/html/display_panel/content/display-panel.html index 5b9f99f..3221ded 100644 --- a/html/display_panel/content/display-panel.html +++ b/html/display_panel/content/display-panel.html @@ -64,12 +64,13 @@ +

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