aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bg/ExternalLicenses.js96
-rw-r--r--content/externalLicenseChecker.js87
-rw-r--r--main_background.js27
-rw-r--r--manifest.json14
4 files changed, 218 insertions, 6 deletions
diff --git a/bg/ExternalLicenses.js b/bg/ExternalLicenses.js
new file mode 100644
index 0000000..74692d7
--- /dev/null
+++ b/bg/ExternalLicenses.js
@@ -0,0 +1,96 @@
+/**
+* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+*
+* Copyright (C) 2018 Giorgio Maone <giorgio@maone.net>
+*
+* 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/>.
+*/
+
+/**
+ Singleton to handle external licenses, e.g. WebLabels
+*/
+
+"use strict";
+
+let licensesByURL = new Map();
+{
+ let {licenses} = require("../license_definitions");
+ for (let l of Object.values(licenses).filter(l => l.canonicalUrl)) {
+ for (let url of l.canonicalUrl) {
+ licensesByURL.set(url, l);
+ }
+ }
+}
+
+var ExternalLicenses = {
+ async check(script) {
+ let {url, tabId, frameId} = script;
+ let scriptInfo = await browser.tabs.sendMessage(tabId, {
+ action: "checkLicensedScript",
+ url
+ }, {frameId});
+ if (!(scriptInfo && scriptInfo.licenseURLs.length)) {
+ return null;
+ }
+ scriptInfo.licenses = new Set();
+ scriptInfo.allFree = true;
+ scriptInfo.toString = function() {
+ let licenseIds = [...this.licenses].map(l => l.identifier).sort().join(", ");
+ return this.allFree ? `Free license${licenseIds.length > 1 ? "s" : ""} (${licenseIds})` : `Mixed free (${licenseIds}) and unknown licenses`;
+ }
+
+ for (let u of scriptInfo.licenseURLs) {
+ if (licensesByURL.has(u)) {
+ scriptInfo.licenses.add(licensesByURL.get(u));
+ } else {
+ scriptInfo.allFree = false;
+ break;
+ }
+ }
+ return scriptInfo;
+ },
+
+ /**
+ * moves / creates external license references before any script in the page
+ * if needed, to have them ready when the first script load is triggered
+ * Returns true if the document has been actually modified, false otherwise.
+ */
+ optimizeDocument(document) {
+ let link = document.querySelector(`link[rel="jslicense"], link[data-jslicense="1"], a[rel="jslicense"], a[data-jslicense="1"]`);
+ if (link) {
+ let move = () => !!document.head.insertBefore(link, document.head.firstChild);
+ if (link.parentNode === document.head) {
+ for (let node; node = link.previousElementSibling;) {
+ if (node.tagName.toUpperCase() === "SCRIPT") {
+ return move();
+ }
+ }
+ } else { // the reference is only in the body
+ if (link.tagName.toUpperCase() === "A") {
+ let newLink = document.createElement("link");
+ newLink.rel = "jslicense";
+ newLink.setAttribute("href", link.getAttribute("href"));
+ link = newLink;
+ }
+ return move();
+ }
+ }
+ return false;
+ }
+};
+
+
+module.exports = { ExternalLicenses };
diff --git a/content/externalLicenseChecker.js b/content/externalLicenseChecker.js
new file mode 100644
index 0000000..89f871f
--- /dev/null
+++ b/content/externalLicenseChecker.js
@@ -0,0 +1,87 @@
+/**
+* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+*
+* Copyright (C) 2018 Giorgio Maone <giorgio@maone.net>
+*
+* 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/>.
+*/
+"use strict";
+{
+ let licensedScripts = null;
+
+ let fetchWebLabels = async (map = new Map()) => {
+ // see https://www.gnu.org/software/librejs/free-your-javascript.html#step3
+
+ let link = document.querySelector(`link[rel="jslicense"], link[data-jslicense="1"], a[rel="jslicense"], a[data-jslicense="1"]`);
+ if (link) try {
+ let baseURL = link.href;
+ let response = await fetch(baseURL);
+ if (!response.ok) throw `${response.status} ${response.statusText}`;
+ let doc = new DOMParser().parseFromString(
+ await response.text(),
+ "text/html"
+ );
+ let base = doc.querySelector("base");
+ if (base) {
+ base.href = base.href;
+ } else {
+ doc.head.appendChild(doc.createElement("base")).href = baseURL;
+ }
+ let firstURL = parent => parent.querySelector("a").href;
+ let allURLs = parent => Array.map(parent.querySelectorAll("a"), a => a.href);
+ for (let row of doc.querySelectorAll("table#jslicense-labels1 tr")) {
+ let cols = row.querySelectorAll("td");
+ let scriptURL = firstURL(cols[0]);
+ let licenseURLs = allURLs(cols[1]);
+ let sourceURLs = cols[2] ? allURLs(cols[2]) : [];
+ map.set(scriptURL, {scriptURL, licenseURLs, sourceURLs});
+ }
+ } catch (e) {
+ console.error("Error fetching Web Labels at %o", link, e);
+ }
+ return map;
+ }
+
+ let fetchLicenseInfo = async () => {
+ let map = new Map();
+
+ // in the fetchXxx methods we add to a map whatever license(s)
+ // URLs and source code references we can find in various formats
+ // (WebLabels is currently the only implementation), keyed by script URLs.
+ await Promise.all([
+ fetchWebLabels(map),
+ // fetchXmlSpdx(),
+ // fetchTxtSpdx(),
+ // ...
+ ]);
+ return map;
+ }
+
+ let handlers = {
+ async checkLicensedScript(m) {
+ let {url} = m;
+ if (!licensedScripts) licensedScripts = await fetchLicenseInfo();
+ return licensedScripts.get(url);
+ }
+ }
+
+ browser.runtime.onMessage.addListener(async m => {
+ if (m.action in handlers) {
+ debug("Received message", m);
+ return await handlers[m.action](m);
+ }
+ });
+}
diff --git a/main_background.js b/main_background.js
index 799c69f..1235e4a 100644
--- a/main_background.js
+++ b/main_background.js
@@ -28,6 +28,7 @@ var legacy_license_lib = require("./legacy_license_check.js");
var {ResponseProcessor} = require("./bg/ResponseProcessor");
var {Storage, ListStore} = require("./bg/Storage");
var {ListManager} = require("./bg/ListManager");
+var {ExternalLicenses} = require("./bg/ExternalLicenses");
console.log("main_background.js");
/**
@@ -263,7 +264,7 @@ function updateReport(tabId, oldReport, updateUI = false){
* Make sure it will use the right URL when refering to a certain script.
*
*/
-async function addReportEntry(tabId, scriptHashOrUrl, action, update = false) {
+async function addReportEntry(tabId, scriptHashOrUrl, action) {
let report = activityReports[tabId];
if (!report) report = activityReports[tabId] =
createReport({url: (await browser.tabs.get(tabId)).url});
@@ -312,7 +313,7 @@ async function addReportEntry(tabId, scriptHashOrUrl, action, update = false) {
}
browser.sessions.setTabValue(tabId, report.url, report);
-
+ updateBadge(tabId, report);
return entryType;
}
@@ -903,8 +904,23 @@ var ResponseHandler = {
*/
async function handle_script(response, whitelisted){
let {text, request} = response;
- let {url, tabId} = request;
+ let {url, tabId, frameId} = request;
url = ListStore.urlItem(url);
+ if (!whitelisted) {
+ let scriptInfo = await ExternalLicenses.check({url, tabId, frameId});
+ if (scriptInfo) {
+ let verdict;
+ let msg = scriptInfo.toString();
+ if (scriptInfo.allFree) {
+ verdict = "accepted";
+ } else {
+ verdict = "blocked";
+ text = `/* ${msg} */`;
+ }
+ addReportEntry(tabId, url, {url, [verdict]: [url, msg]});
+ return text;
+ }
+ }
let edited = await get_script(text, url, tabId, whitelisted, -2);
return Array.isArray(edited) ? edited[0] : edited;
}
@@ -985,7 +1001,10 @@ function edit_html(html,url,tabid,wl){
var parser = new DOMParser();
var html_doc = parser.parseFromString(html, "text/html");
-
+
+ // moves external licenses reference, if any, before any <SCRIPT> element
+ ExternalLicenses.optimizeDocument(html_doc);
+
var amt_scripts = 0;
var total_scripts = 0;
var scripts = html_doc.scripts;
diff --git a/manifest.json b/manifest.json
index 17f8670..8356a9c 100644
--- a/manifest.json
+++ b/manifest.json
@@ -41,6 +41,16 @@
],
"background": {
"scripts": ["bundle.js"]
- }
-
+ },
+ "content_scripts": [
+ {
+ "run_at": "document_start",
+ "matches": ["<all_urls>"],
+ "match_about_blank": true,
+ "all_frames": true,
+ "js": [
+ "content/externalLicenseChecker.js"
+ ]
+ }
+ ]
}