From 01937dc9d5215ef96ce756e3ccda51bf29032f58 Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Mon, 10 May 2021 18:07:05 +0200 Subject: initial commit --- COPYING.txt | 4 + README.txt | 17 + TODOS.org | 51 +++ background/ResponseHandler.mjs | 257 ++++++++++++++ background/ResponseMetaData.mjs | 107 ++++++ background/ResponseProcessor.mjs | 145 ++++++++ background/background.html | 7 + background/main.mjs | 168 +++++++++ background/message_server.mjs | 31 ++ background/page_actions_server.mjs | 145 ++++++++ background/policy_smuggler.mjs | 60 ++++ background/reverse_use_info.mjs | 93 +++++ background/script_injector.mjs | 122 +++++++ background/sha256.mjs | 524 ++++++++++++++++++++++++++++ background/storage.mjs | 380 +++++++++++++++++++++ background/storage_server.mjs | 58 ++++ background/url_item.mjs | 18 + common/browser.mjs | 18 + common/connection_types.mjs | 21 ++ common/is_background.mjs | 17 + common/lock.mjs | 55 +++ common/once.mjs | 42 +++ common/storage_client.mjs | 186 ++++++++++ common/stored_types.mjs | 38 +++ content/main.js | 95 ++++++ content/page_actions.mjs | 58 ++++ html/display-panel.html | 11 + html/display-panel.mjs | 16 + html/options.html | 175 ++++++++++ html/options_main.mjs | 398 ++++++++++++++++++++++ icons/myext.png | Bin 0 -> 13130 bytes licenses/0bsd.txt | 12 + licenses/gpl-3.0.txt | 674 +++++++++++++++++++++++++++++++++++++ licenses/mit(expat).txt | 19 ++ manifest.json | 60 ++++ 35 files changed, 4082 insertions(+) create mode 100644 COPYING.txt create mode 100644 README.txt create mode 100644 TODOS.org create mode 100644 background/ResponseHandler.mjs create mode 100644 background/ResponseMetaData.mjs create mode 100644 background/ResponseProcessor.mjs create mode 100644 background/background.html create mode 100644 background/main.mjs create mode 100644 background/message_server.mjs create mode 100644 background/page_actions_server.mjs create mode 100644 background/policy_smuggler.mjs create mode 100644 background/reverse_use_info.mjs create mode 100644 background/script_injector.mjs create mode 100644 background/sha256.mjs create mode 100644 background/storage.mjs create mode 100644 background/storage_server.mjs create mode 100644 background/url_item.mjs create mode 100644 common/browser.mjs create mode 100644 common/connection_types.mjs create mode 100644 common/is_background.mjs create mode 100644 common/lock.mjs create mode 100644 common/once.mjs create mode 100644 common/storage_client.mjs create mode 100644 common/stored_types.mjs create mode 100644 content/main.js create mode 100644 content/page_actions.mjs create mode 100644 html/display-panel.html create mode 100644 html/display-panel.mjs create mode 100644 html/options.html create mode 100644 html/options_main.mjs create mode 100644 icons/myext.png create mode 100644 licenses/0bsd.txt create mode 100644 licenses/gpl-3.0.txt create mode 100644 licenses/mit(expat).txt create mode 100644 manifest.json diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 0000000..109c665 --- /dev/null +++ b/COPYING.txt @@ -0,0 +1,4 @@ +The extension can be redistributed under the terms of GPLv3+. +Certain parts are available under more permissive licenses. + +See licenses/ directry for legal legal texts. diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..571f0df --- /dev/null +++ b/README.txt @@ -0,0 +1,17 @@ +# My extension - Make The Web Great Again! # + +This extension's goal is to allow replacing javascript served by websites +with scripts specified by user. Something like NoScript and Greasemonkey +together. Such facility is necessary to enable browsing World Wide Web +without executing nonfree software. + +Currently, the target browsers for this extension are Ungoogled Chromium +and various forks of Firefox Quantum (right now, Ungoogled Chromium is +being used). + +This extension's development has just started. It doesn't do anything +useful yet. See TODOS.org + +## Installation ## +The extension can be loaded into Ungoogled Chromium as unpacked extension. +As of now, project's main directory is also the extension directory. diff --git a/TODOS.org b/TODOS.org new file mode 100644 index 0000000..2e3c210 --- /dev/null +++ b/TODOS.org @@ -0,0 +1,51 @@ +TODO: +- parallelize fetching of remote scripts +- make it possible to provide backup urls for remote scripts +- make it possible to cache remote scripts +- make it possible to use wildcards or something similar to be able to assign a script set to -- CRUCIAL + a set of domains or to a set of possible queries at a url +- make it possible to automatically download page's served scripts and save them (of course, this by itself -- CRUCIAL + would give little benefit, but it will make it easy to modify this set of scripts - useful, if some of + those scripts are already free, as is often the case) + - also, find some convenient way to automatically re-add "on" events ("onclick" & friends) +- add some good, sane error handling +- implement whitelisting (LibreJS had some code doing it, but we'll see if it's of any use for us) -- CRUCIAL +- make it possible to export page settings in some format -- CRUCIAL +- get rid of those warnings and exceptions in console (many are not even related to this extension; + who invented this thing?) (gecko-only) +- make page settings easily and conveniently editable in popup -- CRUCIAL + - in popup make it possible to edit both main frame page's + settings and settings for pages that currently happen to + live in iframes +- add some nice styling to settings page +- clean up the remnants of LibreJS +- stop using modules (not available on all browsers) -- CRUCIAL +- use non-predictable value in place of "myext-allow", utilizing hashes -- CRUCIAL +- rename the extension to something good +- port to gecko-based browsers -- CRUCIAL +- rename "bundles" to "bags" to avoid confusion with Web Bundles +- make it possible to modify CSP to suit our custom scripts' needs + - find a way to additionally block all other scripts using CSP + as an additional safety measure +- make blocking more torough -- CRUCIAL + - also block intrinsics -- CRUCIAL + - mind the data: urls -- CRUCIAL +- find out how and make it possible to whitelist non-https urls +- create a repository to host scripts + - enable the extension to automatically fetch script substitutes from the repo +- make it possible to inject scripts to arbitrary places in DOM + - make script blocking code omit those scripts +- facilitate waiting for script injection until DOM has loaded +- check if prerendering has to be blocked -- CRUCIAL +- block prefetch +- rearrange files in extension, add some mechanism to build the extension + +DONE: +- find way to also block scripts in non-http pages (e.g. file://) -- DONE 2021-05-07 (via content scripts, may not be perfect) + (NoScript seems to be doing this through CSP) +- make page settings easily and conveniently editable in a separate window/tab -- DONE 2021-05-05 +- replace comparisons with stricter ones (e.g. do `if(foo === undefined)` instead of `if(!foo)`) -- DONE +- make local storage safe (serialize storage accesses in background script) -- DONE +- split main.js into multiple files -- DONE 2021-01-05 +- make it possible to store entire script files in storage (not just links) -- DONE 2021-01-05 + - make it possible to re-use the same script or set of scripts multiple times -- DONE 2021-01-05 diff --git a/background/ResponseHandler.mjs b/background/ResponseHandler.mjs new file mode 100644 index 0000000..6b979e6 --- /dev/null +++ b/background/ResponseHandler.mjs @@ -0,0 +1,257 @@ +/** +* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. +* * +* Copyright (C) 2017, 2018 Nathan Nichols +* Copyright (C) 2018 Ruben Rodriguez +* +* 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 . +*/ + +/** +* 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 external script inclusions in search of crappy JavaScript +*/ + +import inject_scripts from './script_injector.mjs'; +import {ResponseProcessor} from './ResponseProcessor.mjs'; + +"use strict"; + +var ResponseHandler = { + /** + * Enforce white/black lists for url/site early (hashes will be handled later) + */ + async pre(response) { + // TODO: reimplement blacklisting/whitelisting later + if (true) return ResponseProcessor.CONTINUE; + + let {request} = response; + let {url, type, tabId, frameId, documentUrl} = request; + + let fullUrl = url; + url = ListStore.urlItem(url); + let site = ListStore.siteItem(url); + + let blacklistedSite = ListManager.siteMatch(site, blacklist); + let blacklisted = blacklistedSite || blacklist.contains(url); + let topUrl = type === "sub_frame" && request.frameAncestors && request.frameAncestors.pop() || documentUrl; + + if (blacklisted) { + if (type === "script") { + // this shouldn't happen, because we intercept earlier in blockBlacklistedScripts() + return ResponseProcessor.REJECT; + } + if (type === "main_frame") { // we handle the page change here too, since we won't call edit_html() + activityReports[tabId] = await createReport({url: fullUrl, tabId}); + // Go on without parsing the page: it was explicitly blacklisted + let reason = blacklistedSite + ? `All ${blacklistedSite} blacklisted by user` + : "Address blacklisted by user"; + await addReportEntry(tabId, url, {"blacklisted": [blacklistedSite || url, reason], url: fullUrl}); + } + // use CSP to restrict JavaScript execution in the page + request.responseHeaders.unshift({ + name: `Content-security-policy`, + value: `script-src 'none';` + }); + return {responseHeaders: request.responseHeaders}; // let's skip the inline script parsing, since we block by CSP + } else { + + let whitelistedSite = ListManager.siteMatch(site, whitelist); + let whitelisted = response.whitelisted = whitelistedSite || whitelist.contains(url); + if (type === "script") { + if (whitelisted) { + // accept the script and stop processing + addReportEntry(tabId, url, {url: topUrl, + "whitelisted": [url, whitelistedSite ? `User whitelisted ${whitelistedSite}` : "Whitelisted by user"]}); + return ResponseProcessor.ACCEPT; + } else { + let scriptInfo = await ExternalLicenses.check({url: fullUrl, tabId, frameId, documentUrl}); + if (scriptInfo) { + let verdict, ret; + let msg = scriptInfo.toString(); + if (scriptInfo.free) { + verdict = "accepted"; + ret = ResponseProcessor.ACCEPT; + } else { + verdict = "blocked"; + ret = ResponseProcessor.REJECT; + } + addReportEntry(tabId, url, {url, [verdict]: [url, msg]}); + return ret; + } + } + } + } + // 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 {type} = response.request; + return await handle_html(response, response.whitelisted); + } +} + +/** +* Serializes HTMLDocument objects including the root element and +* the DOCTYPE declaration +*/ +function doc2HTML(doc) { + let s = doc.documentElement.outerHTML; + if (doc.doctype) { + let dt = doc.doctype; + let sDoctype = `\n${s}`; + } + return s; +} + +/** +* Shortcut to create a correctly namespaced DOM HTML elements +*/ +function createHTMLElement(doc, name) { + return doc.createElementNS("http://www.w3.org/1999/xhtml", name); +} + +/** +* Replace any element with a span having the same content (useful to force +* NOSCRIPT elements to visible the same way as NoScript and uBlock do) +*/ +function forceElement(doc, element) { + let replacement = createHTMLElement(doc, "span"); + replacement.innerHTML = element.innerHTML; + element.replaceWith(replacement); + return replacement; +} + +/** +* Forces displaying any element having the "data-librejs-display" attribute and +*