From 7859a9131fcda359265dc16ef55933e5ed218119 Mon Sep 17 00:00:00 2001 From: Ruben Rodriguez Date: Fri, 10 May 2019 19:05:20 -0400 Subject: Updated extensions bundle --- .../jid1-KtlZuoiikVfFew@jetpack/bundle.js | 6830 +++----------------- 1 file changed, 730 insertions(+), 6100 deletions(-) (limited to 'data/extensions/jid1-KtlZuoiikVfFew@jetpack/bundle.js') diff --git a/data/extensions/jid1-KtlZuoiikVfFew@jetpack/bundle.js b/data/extensions/jid1-KtlZuoiikVfFew@jetpack/bundle.js index b35ef98..8527080 100644 --- a/data/extensions/jid1-KtlZuoiikVfFew@jetpack/bundle.js +++ b/data/extensions/jid1-KtlZuoiikVfFew@jetpack/bundle.js @@ -181,13 +181,13 @@ class ListManager { } async whitelist(...keys) { - ListManager.move(this.lists.blacklist, this.lists.whitelist, ...keys); + await ListManager.move(this.lists.blacklist, this.lists.whitelist, ...keys); } async blacklist(...keys) { - ListManager.move(this.lists.whitelist, this.lists.blacklist, ...keys); + await ListManager.move(this.lists.whitelist, this.lists.blacklist, ...keys); } async forget(...keys) { - await Promise.all(Object.values(this.lists).map(l => l.remove(...keys))); + await Promise.all(Object.values(this.lists).map(async l => await l.remove(...keys))); } /* key is a string representing either a URL or an optional path with a trailing (hash). @@ -195,23 +195,53 @@ class ListManager { */ getStatus(key, defValue = "unknown") { let {blacklist, whitelist} = this.lists; + let inline = ListStore.inlineItem(key); + if (inline) { + return blacklist.contains(inline) + ? "blacklisted" + : whitelist.contains(inline) ? "whitelisted" + : defValue; + } + let match = key.match(/\(([^)]+)\)(?=[^()]*$)/); if (!match) { let url = ListStore.urlItem(key); let site = ListStore.siteItem(key); - return (blacklist.contains(url) || blacklist.contains(site)) + return (blacklist.contains(url) || ListManager.siteMatch(site, blacklist) ? "blacklisted" - : whitelist.contains(url) || whitelist.contains(site) - ? "whitelisted" : defValue; + : whitelist.contains(url) || ListManager.siteMatch(site, whitelist) + ? "whitelisted" : defValue + ); } let [hashItem, srcHash] = match; // (hash), hash - return blacklist.contains(hashItem) ? "blacklisted" : this.builtInHashes.has(srcHash) || whitelist.contains(hashItem) ? "whitelisted" : defValue; } + + /* + Matches by whole site ("http://some.domain.com/*") supporting also + wildcarded subdomains ("https://*.domain.com/*"). + */ + static siteMatch(url, list) { + let site = ListStore.siteItem(url); + if (list.contains(site)) { + return site; + } + site = site.replace(/^([\w-]+:\/\/)?(\w)/, "$1*.$2"); + for (;;) { + if (list.contains(site)) { + return site; + } + let oldKey = site; + site = site.replace(/(?:\*\.)*\w+(?=\.)/, "*"); + if (site === oldKey) { + return null; + } + } + } } module.exports = { ListManager }; @@ -244,6 +274,9 @@ module.exports = { ListManager }; to parse textual data through a decoder. */ +const BOM = [0xEF, 0xBB, 0xBF]; +const DECODER_PARAMS = {stream: true}; + class ResponseMetaData { constructor(request) { let {responseHeaders} = request; @@ -256,7 +289,7 @@ class ResponseMetaData { this.headers[propertyName] = h; } } - this.forcedUTF8 = false; + this.computedCharset = ""; } get charset() { @@ -268,35 +301,55 @@ class ResponseMetaData { } } Object.defineProperty(this, "charset", { value: charset, writable: false, configurable: true }); - return charset; + return this.computedCharset = charset; } - get isUTF8() { - return /^utf-?8$/i.test(this.charset); - } + decode(data) { + let charset = this.charset; + let decoder = this.createDecoder(); + let text = decoder.decode(data, DECODER_PARAMS); + if (!charset && /html/i.test(this.contentType)) { + // missing HTTP charset, sniffing in content... + + if (data[0] === BOM[0] && data[1] === BOM[1] && data[2] === BOM[2]) { + // forced UTF-8, nothing to do + return text; + } - forceUTF8() { - if (!(this.forcedUTF8 || this.isUTF8)) { - let h = this.headers.contentType; - if (h) { - h.value = h.value.replace(/;\s*charset\s*=.*|$/, "; charset=utf8"); - this.forcedUTF8 = true; - } // if the header doesn't exist the browser should default to UTF-8 anyway + // let's try figuring out the charset from tags + let parser = new DOMParser(); + let doc = parser.parseFromString(text, "text/html"); + let meta = doc.querySelectorAll('meta[charset], meta[http-equiv="content-type"], meta[content*="charset"]'); + for (let m of meta) { + charset = m.getAttribute("charset"); + if (!charset) { + let match = m.getAttribute("content").match(/;\s*charset\s*=\s*([\w-]+)/i) + if (match) charset = match[1]; + } + if (charset) { + decoder = this.createDecoder(charset, null); + if (decoder) { + this.computedCharset = charset; + return decoder.decode(data, DECODER_PARAMS); + } + } + } } - return this.forcedUTF8; + return text; } - createDecoder() { - if (this.charset) { + createDecoder(charset = this.charset, def = "latin1") { + if (charset) { try { - return new TextDecoder(this.charset); + return new TextDecoder(charset); } catch (e) { console.error(e); } } - return new TextDecoder("utf-8"); + return def ? new TextDecoder(def) : null; } }; +ResponseMetaData.UTF8BOM = new Uint8Array(BOM); module.exports = { ResponseMetaData }; @@ -381,7 +434,7 @@ class ResponseTextFilter { let res = await handler.pre(response); if (res) return res; if (handler.post) handler = handler.post; - if (typeof handler !== "function") ResponseProcessor.ACCEPT; + if (typeof handler !== "function") return ResponseProcessor.ACCEPT; } let {requestId, responseHeaders} = request; @@ -393,8 +446,6 @@ class ResponseTextFilter { }; filter.onstop = async event => { - - let params = {stream: true}; // concatenate chunks let size = buffer.reduce((sum, chunk, n) => sum + chunk.byteLength, 0) let allBytes = new Uint8Array(size); @@ -411,10 +462,10 @@ class ResponseTextFilter { response.text = await (await fetch(request.url, {cache: "reload", credentials: "include"})).text(); } else { console.debug("It's a %s, trying to decode it as UTF-16.", request.type); - response.text = new TextDecoder("utf-16be").decode(allBytes); + response.text = new TextDecoder("utf-16be").decode(allBytes, {stream: true}); } } else { - response.text = metaData.createDecoder().decode(allBytes, {stream: true}); + response.text = metaData.decode(allBytes); } let editedText = null; try { @@ -422,19 +473,19 @@ class ResponseTextFilter { } catch(e) { console.error(e); } - if (editedText !== null && - (metaData.forcedUTF8 && request.type !== "script" || - response.text !== editedText)) { - // if we changed the charset, the text or both, let's re-encode - filter.write(new TextEncoder().encode(editedText)); - } else { - // ... otherwise pass all the raw bytes through - filter.write(allBytes); + if (editedText !== null) { + // we changed the content, let's re-encode + let encoded = new TextEncoder().encode(editedText); + // pre-pending the UTF-8 BOM will force the charset per HTML 5 specs + allBytes = new Uint8Array(encoded.byteLength + 3); + allBytes.set(ResponseMetaData.UTF8BOM, 0); // UTF-8 BOM + allBytes.set(encoded, 3); } + filter.write(allBytes); filter.close(); } - return metaData.forceUTF8() ? {responseHeaders} : ResponseProcessor.ACCEPT;; + return ResponseProcessor.ACCEPT; } } @@ -509,6 +560,13 @@ class ListStore { }); } + static inlineItem(url) { + // here we simplify and hash inline script references + return url.startsWith("inline:") ? url + : url.startsWith("view-source:") + && url.replace(/^view-source:[\w-+]+:\/+([^/]+).*#line\d+/,"inline://$1#") + .replace(/\n[^]*/, s => s.replace(/\s+/g, ' ').substring(0, 16) + "…" + hash(s.trim())); + } static hashItem(hash) { return hash.startsWith("(") ? hash : `(${hash})`; } @@ -568,11 +626,19 @@ class ListStore { return this.items.has(item); } } + +function hash(source){ + var shaObj = new jssha("SHA-256","TEXT") + shaObj.update(source); + return shaObj.getHash("HEX"); +} + if (typeof module === "object") { - module.exports = { ListStore, Storage }; + module.exports = { ListStore, Storage, hash }; + var jssha = require('jssha'); } -},{}],6:[function(require,module,exports){ +},{"jssha":16}],6:[function(require,module,exports){ /** * GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. * @@ -1899,91 +1965,217 @@ exports.licenses = { module.exports=module.exports = { licenses: { 'Apache-2.0':{ + 'Name': 'Apache 2.0', 'URL': 'http://www.apache.org/licenses/LICENSE-2.0', 'Magnet link': 'magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt' }, - // No identifier was present in documentation 'Artistic-2.0':{ + 'Name': 'Artistic 2.0', 'URL': 'http://www.perlfoundation.org/artistic_license_2_0', 'Magnet link': 'magnet:?xt=urn:btih:54fd2283f9dbdf29466d2df1a98bf8f65cafe314&dn=artistic-2.0.txt' }, - // No identifier was present in documentation 'Boost':{ + 'Name': 'Boost', 'URL': 'http://www.boost.org/LICENSE_1_0.txt', 'Magnet link': 'magnet:?xt=urn:btih:89a97c535628232f2f3888c2b7b8ffd4c078cec0&dn=Boost-1.0.txt' }, - // No identifier was present in documentation - 'BSD-3-Clause':{ - 'URL': 'http://opensource.org/licenses/BSD-3-Clause', - 'Magnet link': 'magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt', - }, 'CPAL-1.0':{ + 'Name': 'CPAL 1.0', 'URL': 'http://opensource.org/licenses/cpal_1.0', 'Magnet link': 'magnet:?xt=urn:btih:84143bc45939fc8fa42921d619a95462c2031c5c&dn=cpal-1.0.txt' }, 'CC0-1.0':{ + 'Name': 'CC0 1.0', 'URL': 'http://creativecommons.org/publicdomain/zero/1.0/legalcode', 'Magnet link': 'magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt' }, + 'CC-BY-SA-1.0':{ + 'Name': 'CC-BY-SA 1.0', + 'URL': 'https://creativecommons.org/licenses/by-sa/1.0/', + 'Magnet link': '' + }, + 'CC-BY-SA-2.0':{ + 'Name': 'CC-BY-SA 2.0', + 'URL': 'https://creativecommons.org/licenses/by-sa/2.0/', + 'Magnet link': '' + }, + 'CC-BY-SA-2.5':{ + 'Name': 'CC-BY-SA 2.5', + 'URL': 'https://creativecommons.org/licenses/by-sa/2.5/', + 'Magnet link': '' + }, + 'CC-BY-SA-3.0':{ + 'Name': 'CC-BY-SA 3.0', + 'URL': 'https://creativecommons.org/licenses/by-sa/3.0/', + 'Magnet link': '' + }, + 'CC-BY-SA-4.0':{ + 'Name': 'CC-BY-SA 4.0', + 'URL': 'https://creativecommons.org/licenses/by-sa/4.0/', + 'Magnet link': '' + }, + 'CC-BY-1.0':{ + 'Name': 'CC-BY 1.0', + 'URL': 'https://creativecommons.org/licenses/by/1.0/', + 'Magnet link': '' + }, + 'CC-BY-2.0':{ + 'Name': 'CC-BY 2.0', + 'URL': 'https://creativecommons.org/licenses/by/2.0/', + 'Magnet link': '' + }, + 'CC-BY-2.5':{ + 'Name': 'CC-BY 2.5', + 'URL': 'https://creativecommons.org/licenses/by/2.5/', + 'Magnet link': '' + }, + 'CC-BY-3.0':{ + 'Name': 'CC-BY 3.0', + 'URL': 'https://creativecommons.org/licenses/by/3.0/', + 'Magnet link': '' + }, + 'CC-BY-4.0':{ + 'Name': 'CC-BY 4.0', + 'URL': 'https://creativecommons.org/licenses/by/4.0/', + 'Magnet link': '' + }, 'EPL-1.0':{ + 'Name': 'EPL 1.0', 'URL': 'http://www.eclipse.org/legal/epl-v10.html', 'Magnet link': 'magnet:?xt=urn:btih:4c6a2ad0018cd461e9b0fc44e1b340d2c1828b22&dn=epl-1.0.txt' }, 'Expat':{ + 'Name': 'Expat', 'URL': 'http://www.jclark.com/xml/copying.txt', 'Magnet link': 'magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt' }, - 'FreeBSD':{ - 'URL': 'http://www.freebsd.org/copyright/freebsd-license.html', - 'Magnet link': 'magnet:?xt=urn:btih:87f119ba0b429ba17a44b4bffcab33165ebdacc0&dn=freebsd.txt' + 'MIT':{ + 'Name': 'Expat', + 'URL': 'http://www.jclark.com/xml/copying.txt', + 'Magnet link': 'magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt' + }, + 'X11':{ + 'Name': 'X11', + 'URL': 'http://www.xfree86.org/3.3.6/COPYRIGHT2.html#3', + 'Magnet link': 'magnet:?xt=urn:btih:5305d91886084f776adcf57509a648432709a7c7&dn=x11.txt' }, 'GPL-2.0':{ + 'Name': 'GPL 2.0', 'URL': 'http://www.gnu.org/licenses/gpl-2.0.html', 'Magnet link': 'magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt' }, 'GPL-3.0':{ + 'Name': 'GPL 3.0', 'URL': 'http://www.gnu.org/licenses/gpl-3.0.html', 'Magnet link': 'magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt' }, 'LGPL-2.1':{ + 'Name': 'LGPL 2.1', 'URL': 'http://www.gnu.org/licenses/lgpl-2.1.html', 'Magnet link': 'magnet:?xt=urn:btih:5de60da917303dbfad4f93fb1b985ced5a89eac2&dn=lgpl-2.1.txt' }, 'LGPL-3.0':{ + 'Name': 'LGPL 3.0', 'URL': 'http://www.gnu.org/licenses/lgpl-3.0.html', 'Magnet link': 'magnet:?xt=urn:btih:0ef1b8170b3b615170ff270def6427c317705f85&dn=lgpl-3.0.txt' }, 'AGPL-3.0':{ + 'Name': 'AGPL 3.0', + 'URL': 'http://www.gnu.org/licenses/agpl-3.0.html', + 'Magnet link': 'magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt' + }, + 'GPL-2.0-only':{ + 'Name': 'GPL 2.0', + 'URL': 'http://www.gnu.org/licenses/gpl-2.0.html', + 'Magnet link': '' + }, + 'GPL-3.0-only':{ + 'Name': 'GPL 3.0', + 'URL': 'http://www.gnu.org/licenses/gpl-3.0.html', + 'Magnet link': '' + }, + 'LGPL-2.1-only':{ + 'Name': 'LGPL 2.1', + 'URL': 'http://www.gnu.org/licenses/lgpl-2.1.html', + 'Magnet link': '' + }, + 'LGPL-3.0-only':{ + 'Name': 'LGPL 3.0', + 'URL': 'http://www.gnu.org/licenses/lgpl-3.0.html', + 'Magnet link': '' + }, + 'AGPL-3.0-only':{ + 'Name': 'AGPL 3.0', + 'URL': 'http://www.gnu.org/licenses/agpl-3.0.html', + 'Magnet link': '' + }, + 'GPL-2.0-or-later':{ + 'Name': 'GPL 2.0 or later', + 'URL': 'http://www.gnu.org/licenses/gpl-2.0.html', + 'Magnet link': 'magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt' + }, + 'GPL-3.0-or-later':{ + 'Name': 'GPL 3.0 or later', + 'URL': 'http://www.gnu.org/licenses/gpl-3.0.html', + 'Magnet link': 'magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt' + }, + 'LGPL-2.1-or-later':{ + 'Name': 'LGPL 2.1 or later', + 'URL': 'http://www.gnu.org/licenses/lgpl-2.1.html', + 'Magnet link': 'magnet:?xt=urn:btih:5de60da917303dbfad4f93fb1b985ced5a89eac2&dn=lgpl-2.1.txt' + }, + 'LGPL-3.0-or-later':{ + 'Name': 'LGPL 3.0 or later', + 'URL': 'http://www.gnu.org/licenses/lgpl-3.0.html', + 'Magnet link': 'magnet:?xt=urn:btih:0ef1b8170b3b615170ff270def6427c317705f85&dn=lgpl-3.0.txt' + }, + 'AGPL-3.0-or-later':{ + 'Name': 'AGPL 3.0 or later', 'URL': 'http://www.gnu.org/licenses/agpl-3.0.html', 'Magnet link': 'magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt' }, 'ISC':{ + 'Name': 'ISC', 'URL': 'https://www.isc.org/downloads/software-support-policy/isc-license/', 'Magnet link': 'magnet:?xt=urn:btih:b8999bbaf509c08d127678643c515b9ab0836bae&dn=ISC.txt' }, 'MPL-2.0':{ + 'Name': 'MPL 2.0', 'URL': 'http://www.mozilla.org/MPL/2.0', 'Magnet link': 'magnet:?xt=urn:btih:3877d6d54b3accd4bc32f8a48bf32ebc0901502a&dn=mpl-2.0.txt' }, 'UPL-1.0': { + 'Name': 'UPL 1.0', 'URL': 'https://oss.oracle.com/licenses/upl/', 'Magnet link': 'magnet:?xt=urn:btih:478974f4d41c3fa84c4befba25f283527fad107d&dn=upl-1.0.txt' }, 'WTFPL': { + 'Name': 'WTFPL', 'URL': 'http://www.wtfpl.net/txt/copying/', 'Magnet link': 'magnet:?xt=urn:btih:723febf9f6185544f57f0660a41489c7d6b4931b&dn=wtfpl.txt' }, 'Unlicense':{ + 'Name': 'Unlicense', 'URL': 'http://unlicense.org/UNLICENSE', 'Magnet link': 'magnet:?xt=urn:btih:5ac446d35272cc2e4e85e4325b146d0b7ca8f50c&dn=unlicense.txt' }, - // No identifier was present in documentation - 'X11':{ - 'URL': 'http://www.xfree86.org/3.3.6/COPYRIGHT2.html#3', - 'Magnet link': 'magnet:?xt=urn:btih:5305d91886084f776adcf57509a648432709a7c7&dn=x11.txt' + 'FreeBSD':{ + 'Name': 'FreeBSD', + 'URL': 'http://www.freebsd.org/copyright/freebsd-license.html', + 'Magnet link': 'magnet:?xt=urn:btih:87f119ba0b429ba17a44b4bffcab33165ebdacc0&dn=freebsd.txt' + }, + 'BSD-2-Clause':{ + 'Name': 'FreeBSD (BSD-2-Clause)', + 'URL': 'http://www.freebsd.org/copyright/freebsd-license.html', + 'Magnet link': '' + }, + 'BSD-3-Clause':{ + 'Name': 'Modified BSD (BSD-3-Clause)', + 'URL': 'http://opensource.org/licenses/BSD-3-Clause', + 'Magnet link': 'magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt' }, - // Picked one of the two links that were there - 'Modified-BSD':{ + 'XFree86-1.1':{ + 'Name': 'XFree86 1.1', 'URL': 'http://www.xfree86.org/current/LICENSE4.html', 'Magnet link': 'magnet:?xt=urn:btih:12f2ec9e8de2a3b0002a33d518d6010cc8ab2ae9&dn=xfree86.txt' } @@ -2015,10 +2207,9 @@ module.exports=module.exports = { var acorn = require('acorn'); var acornLoose = require('acorn-loose'); -var jssha = require('jssha'); var legacy_license_lib = require("./legacy_license_check.js"); var {ResponseProcessor} = require("./bg/ResponseProcessor"); -var {Storage, ListStore} = require("./common/Storage"); +var {Storage, ListStore, hash} = require("./common/Storage"); var {ListManager} = require("./bg/ListManager"); var {ExternalLicenses} = require("./bg/ExternalLicenses"); @@ -2044,16 +2235,6 @@ function dbg_print(a,b){ } } -/** -* Wrapper around crypto lib -* -*/ -function hash(source){ - var shaObj = new jssha("SHA-256","TEXT") - shaObj.update(source); - return shaObj.getHash("HEX"); -} - /* NONTRIVIAL THINGS: - Fetch @@ -2138,6 +2319,10 @@ async function createReport(initializer) { template.url = url; template.site = ListStore.siteItem(url); template.siteStatus = listManager.getStatus(template.site); + let list = {"whitelisted": whitelist, "blacklisted": blacklist}[template.siteStatus]; + if (list) { + template.listedSite = ListManager.siteMatch(template.site, list); + } return template; } @@ -2207,7 +2392,7 @@ async function updateReport(tabId, oldReport, updateUI = false){ } } activityReports[tabId] = newReport; - browser.sessions.setTabValue(tabId, url, newReport); + if (browser.sessions) browser.sessions.setTabValue(tabId, url, newReport); dbg_print(newReport); if (updateUI && activeMessagePorts[tabId]) { dbg_print(`[TABID: ${tabId}] Sending script blocking report directly to browser action.`); @@ -2239,7 +2424,6 @@ async function addReportEntry(tabId, scriptHashOrUrl, action) { let report = activityReports[tabId]; if (!report) report = activityReports[tabId] = await createReport({tabId}); - let type, actionValue; for (type of ["accepted", "blocked", "whitelisted", "blacklisted"]) { if (type in action) { @@ -2283,7 +2467,7 @@ async function addReportEntry(tabId, scriptHashOrUrl, action) { } } - browser.sessions.setTabValue(tabId, report.url, report); + if (browser.sessions) browser.sessions.setTabValue(tabId, report.url, report); updateBadge(tabId, report); return entryType; } @@ -2329,7 +2513,11 @@ async function connected(p) { for (let action of ["whitelist", "blacklist", "forget"]) { if (m[action]) { let [key] = m[action]; - if (m.site) key = ListStore.siteItem(key); + if (m.site) { + key = ListStore.siteItem(m.site); + } else { + key = ListStore.inlineItem(key) || key; + } await listManager[action](key); update = true; } @@ -2412,7 +2600,8 @@ async function onTabUpdated(tabId, changedInfo, tab) { let [url] = tab.url.split("#"); let report = activityReports[tabId]; if (!(report && report.url === url)) { - let cache = await browser.sessions.getTabValue(tabId, url); + let cache = browser.sessions && + await browser.sessions.getTabValue(tabId, url) || null; // on session restore tabIds may change if (cache && cache.tabId !== tabId) cache.tabId = tabId; updateBadge(tabId, activityReports[tabId] = cache); @@ -2606,29 +2795,29 @@ function evaluate(script,name){ } - function validateLicense(matches) { if (!(Array.isArray(matches) && matches.length >= 4)){ return [false, "Malformed or unrecognized license tag."]; } - let [all, tag, link, id] = matches; - let license = null; - if (licenses[id]) - license = licenses[id]; + + let [all, tag, first, second] = matches; + for (let key in licenses){ - if (licenses[key]["Magnet link"] === link) - license = licenses[key]; - if (licenses[key]["URL"] === link) - license = licenses[key]; - } - if(!license){ - return [false, `Unrecognized license "${id}"`]; - } - if (!(license["Magnet link"] === link || license["URL"] === link)){ - return [false, `License magnet link does not match for "${id}".`]; + // Match by id on first or second parameter, ignoring case + if (key.toLowerCase() === first.toLowerCase() || + key.toLowerCase() === second.toLowerCase()) { + return [true, `Recognized license: "${licenses[key]['Name']}" `]; + } + // Match by link on first parameter (legacy) + if (licenses[key]["Magnet link"] === first.replace("&","&") || + licenses[key]["URL"] === first.replace("&","&")) { + return [true, `Recognized license: "${licenses[key]['Name']}".`]; + } } - return [true, `Recognized license: "${id}".`]; + return [false, `Unrecognized license tag: "${all}"`]; } + + /** * * Evaluates the content of a script (license, if it is non-trivial) @@ -2744,12 +2933,12 @@ async function get_script(response, url, tabId = -1, whitelisted = false, index let scriptName = url.split("/").pop(); if (whitelisted) { if (tabId !== -1) { - let site = ListStore.siteItem(url); + let site = ListManager.siteMatch(url, whitelist); // Accept without reading script, it was explicitly whitelisted - let reason = whitelist.contains(site) + let reason = site ? `All ${site} whitelisted by user` : "Address whitelisted by user"; - addReportEntry(tabId, url, {"whitelisted": [url, reason], url}); + addReportEntry(tabId, url, {"whitelisted": [site || url, reason], url}); } if (response.startsWith("javascript:")) return result(response); @@ -2768,23 +2957,20 @@ async function get_script(response, url, tabId = -1, whitelisted = false, index let report = activityReports[tabId] || (activityReports[tabId] = await createReport({tabId})); updateBadge(tabId, report, !verdict); let category = await addReportEntry(tabId, sourceHash, {"url": domain, [verdict ? "accepted" : "blocked"]: [url, reason]}); - let scriptSource = verdict ? response : editedSource; switch(category) { case "blacklisted": - if (response.startsWith("javascript:")) - return result(`# LibreJS: script ${category} by user.`); - else - return result(`/* LibreJS: script ${category} by user. */`); + editedSource = `/* LibreJS: script ${category} by user. */`; + return result(response.startsWith("javascript:") + ? `javascript:void(${encodeURIComponent(editedSource)})` : editedSource); case "whitelisted": - if (response.startsWith("javascript:")) - return result(scriptSource); - else - return result(`/* LibreJS: script ${category} by user. */\n${scriptSource}`); + return result(response.startsWith("javascript:") + ? response : `/* LibreJS: script ${category} by user. */\n${response}`); default: - if (response.startsWith("javascript:")) - return result(scriptSource); - else - return result(`/* LibreJS: script ${category}. */\n${scriptSource}`); + let scriptSource = verdict ? response : editedSource; + return result(response.startsWith("javascript:") + ? (verdict ? scriptSource : `javascript:void(/* ${scriptSource} */)`) + : `/* LibreJS: script ${category}. */\n${scriptSource}` + ); } } @@ -2793,45 +2979,37 @@ function updateBadge(tabId, report = null, forceRed = false) { let blockedCount = report ? report.blocked.length + report.blacklisted.length : 0; let [text, color] = blockedCount > 0 || forceRed ? [blockedCount && blockedCount.toString() || "!" , "red"] : ["✓", "green"] - browser.browserAction.setBadgeText({text, tabId}); - browser.browserAction.setBadgeBackgroundColor({color, tabId}); -} - -/** -* Tests if a request is google analytics or not -*/ -function test_GA(a){ // TODO: DRY me - // This is just an HTML page - if(a.url == 'https://www.google.com/analytics/#?modal_active=none'){ - return false; - } - else if(a.url.match(/https:\/\/www\.google\.com\/analytics\//g)){ - dbg_print("%c Google analytics (1)","color:red"); - return {cancel: true}; - } - else if(a.url == 'https://www.google-analytics.com/analytics.js'){ - dbg_print("%c Google analytics (2)","color:red"); - return {cancel: true}; - } - else if(a.url == 'https://www.google.com/analytics/js/analytics.min.js'){ - dbg_print("%c Google analytics (3)","color:red"); - return {cancel: true}; + let {browserAction} = browser; + if ("setBadgeText" in browserAction) { + browserAction.setBadgeText({text, tabId}); + browserAction.setBadgeBackgroundColor({color, tabId}); + } else { + // Mobile + browserAction.setTitle({title: `LibreJS (${text})`, tabId}); } - else return false; } -/** -* A callback that every type of request invokes. -*/ -function block_ga(a){ - var GA = test_GA(a); - if(GA != false){ - return GA; +function blockGoogleAnalytics(request) { + let {url} = request; + let res = {}; + if (url === 'https://www.google-analytics.com/analytics.js' || + /^https:\/\/www\.google\.com\/analytics\/[^#]/.test(url) + ) { + res.cancel = true; } - else return {}; + return res; } - +async function blockBlacklistedScripts(request) { + let {url, tabId, documentUrl} = request; + url = ListStore.urlItem(url); + let status = listManager.getStatus(url); + if (status !== "blacklisted") return {}; + let blacklistedSite = ListManager.siteMatch(url, blacklist); + await addReportEntry(tabId, url, {url: documentUrl, + "blacklisted": [url, /\*/.test(blacklistedSite) ? `User blacklisted ${blacklistedSite}` : "Blacklisted by user"]}); + return {cancel: true}; +} /** * This listener gets called as soon as we've got all the HTTP headers, can guess @@ -2851,30 +3029,37 @@ var ResponseHandler = { url = ListStore.urlItem(url); let site = ListStore.siteItem(url); - let blacklistedSite = blacklist.contains(site); + let blacklistedSite = ListManager.siteMatch(site, blacklist); let blacklisted = blacklistedSite || blacklist.contains(url); - let topUrl = request.frameAncestors && request.frameAncestors.pop() || documentUrl; + let topUrl = type === "sub_frame" && request.frameAncestors && request.frameAncestors.pop() || documentUrl; if (blacklisted) { if (type === "script") { - // abort the request before the response gets fetched - addReportEntry(tabId, url, {url: topUrl, - "blacklisted": [url, blacklistedSite ? `User blacklisted ${site}` : "Blacklisted by user"]}); + // 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 '${blacklistedSite ? 'self' : 'none'}';` + value: `script-src 'none';` }); + return {responseHeaders: request.responseHeaders}; // let's skip the inline script parsing, since we block by CSP } else { - let whitelistedSite = whitelist.contains(site); + 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 ${site}` : "Whitelisted by user"]}); + "whitelisted": [url, whitelistedSite ? `User whitelisted ${whitelistedSite}` : "Whitelisted by user"]}); return ResponseProcessor.ACCEPT; } else { let scriptInfo = await ExternalLicenses.check({url: fullUrl, tabId, frameId, documentUrl}); @@ -2937,16 +3122,55 @@ function doc2HTML(doc) { } /** -* Removes noscript tags with name "librejs-path" leaving the inner content to load. +* Shortcut to create a correctly namespaced DOM HTML elements */ -function remove_noscripts(html_doc){ - for(var i = 0; i < html_doc.getElementsByName("librejs-path").length; i++){ - if(html_doc.getElementsByName("librejs-path")[i].tagName == "NOSCRIPT"){ - html_doc.getElementsByName("librejs-path")[i].outerHTML = html_doc.getElementsByName("librejs-path")[i].innerHTML; - } - } +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; +} - return doc2HTML(html_doc); +/** +* Forces displaying any element having the "data-librejs-display" attribute and +*