diff options
Diffstat (limited to 'background/ResponseHandler.mjs')
-rw-r--r-- | background/ResponseHandler.mjs | 257 |
1 files changed, 0 insertions, 257 deletions
diff --git a/background/ResponseHandler.mjs b/background/ResponseHandler.mjs deleted file mode 100644 index 6b979e6..0000000 --- a/background/ResponseHandler.mjs +++ /dev/null @@ -1,257 +0,0 @@ -/** -* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. -* * -* Copyright (C) 2017, 2018 Nathan Nichols -* Copyright (C) 2018 Ruben Rodriguez <ruben@gnu.org> -* -* 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 <http://www.gnu.org/licenses/>. -*/ - -/** -* 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 = `<!DOCTYPE ${dt.name || "html"}`; - if (dt.publicId) sDoctype += ` PUBLIC "${dt.publicId}"`; - if (dt.systemId) sDoctype += ` "${dt.systemId}"`; - s = `${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 -* <noscript> elements on pages where LibreJS disabled inline scripts (unless -* they have the "data-librejs-nodisplay" attribute). -*/ -function forceNoscriptElements(doc) { - let shown = 0; - // inspired by NoScript's onScriptDisabled.js - for (let noscript of doc.querySelectorAll("noscript:not([data-librejs-nodisplay])")) { - let replacement = forceElement(doc, noscript); - // emulate meta-refresh - let meta = replacement.querySelector('meta[http-equiv="refresh"]'); - if (meta) { - refresh = true; - doc.head.appendChild(meta); - } - shown++; - } - return shown; -} - -/** -* Forces displaying any element having the "data-librejs-display" attribute and -* <noscript> elements on pages where LibreJS disabled inline scripts (unless -* they have the "data-librejs-nodisplay" attribute). -*/ -function showConditionalElements(doc) { - let shown = 0; - for (let element of document.querySelectorAll("[data-librejs-display]")) { - forceElement(doc, element); - shown++; - } - return shown; -} - -/** - -* Reads/changes the HTML of a page and the scripts within it. -*/ -async function editHtml(html, documentUrl, tabId, frameId, whitelisted){ - - var parser = new DOMParser(); - var html_doc = parser.parseFromString(html, "text/html"); - - if (whitelisted) { // don't bother rewriting - return null; - } - - var scripts = html_doc.scripts; - - let findLine = finder => finder.test(html) && html.substring(0, finder.lastIndex).split(/\n/).length || 0; - - let modified = false; - // Deal with intrinsic events - let intrinsicFinder = /<[a-z][^>]*\b(on\w+|href\s*=\s*['"]?javascript:)/gi; - for (let element of html_doc.all) { - let line = -1; - for (let attr of element.attributes) { - let {name, value} = attr; - value = value.trim(); - if (name.startsWith("on")) { - attr.value = "console.log(\"event script blocked by myext\")"; - } else if (name === "href" && value.toLowerCase().startsWith("javascript:")){ - if (line === -1) { - line = findLine(intrinsicFinder); - } - try { - attr.value = `view-source:${documentUrl}#line${line}`; - } catch (e) { - console.error(e); - } - } - } - } - - let modifiedInline = false; - let scriptFinder = /<script\b/ig; - for(let i = 0, len = scripts.length; i < len; i++) { - let script = scripts[i]; - let line = findLine(scriptFinder); - if (!script.src) { - script.textContent = `//script blocked, you can examine it at view-source:${documentUrl}#line${line}`; - } else { - let src = script.src; - script.removeAttribute("src"); - script.setAttribute("blocked-src", src); - script.textContent = "//script blocked"; - } - } - - showConditionalElements(html_doc); - forceNoscriptElements(html_doc); - await inject_scripts(documentUrl, html_doc); - return doc2HTML(html_doc); -} - -/** -* Here we handle html document responses -*/ -async function handle_html(response, whitelisted) { - let {text, request} = response; - let {url, tabId, frameId, type} = request; - if (type === "main_frame") { - //activityReports[tabId] = await createReport({url, tabId}); - //updateBadge(tabId); - } - return await editHtml(text, url, tabId, frameId, whitelisted); -} - -export default ResponseHandler; |