From 01937dc9d5215ef96ce756e3ccda51bf29032f58 Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Mon, 10 May 2021 18:07:05 +0200 Subject: initial commit --- background/ResponseHandler.mjs | 257 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 background/ResponseHandler.mjs (limited to 'background/ResponseHandler.mjs') 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 +*