/** * 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 *