diff --git a/src/bg/LifeCycle.js b/src/bg/LifeCycle.js index a3dd748..6e4d4e2 100644 --- a/src/bg/LifeCycle.js +++ b/src/bg/LifeCycle.js @@ -229,7 +229,7 @@ var LifeCycle = (() => { if (!previousVersion) return; - await include("/lib/Ver.js"); + await include("/nscl/common/Ver.js"); previousVersion = new Ver(previousVersion); let currentVersion = new Ver(browser.runtime.getManifest().version); let upgrading = Ver.is(previousVersion, "<=", currentVersion); @@ -263,7 +263,7 @@ var LifeCycle = (() => { // user doesn't want us to remember temporary settings across updates: bail out return; } - await include("/lib/Ver.js"); + await include("/nscl/common/Ver.js"); if (Ver.is(details.version, "<", browser.runtime.getManifest().version)) { // downgrade: temporary survival might not be supported, and we don't care return; diff --git a/src/bg/Settings.js b/src/bg/Settings.js index 180e5c0..daf1288 100644 --- a/src/bg/Settings.js +++ b/src/bg/Settings.js @@ -141,7 +141,7 @@ var Settings = { ? Object.assign(ns[storage], settings[storage]) : ns[storage] = Object.assign({}, ns.defaults[storage])) )); if (ns.local.debug !== oldDebug) { - await include("/lib/log.js"); + await include("/nscl/common/log.js"); if (oldDebug) debug = () => {}; } diff --git a/src/bg/main.js b/src/bg/main.js index 5b9f77e..2176ead 100644 --- a/src/bg/main.js +++ b/src/bg/main.js @@ -310,7 +310,7 @@ debug("Collected seen", seen); return seen; } catch (e) { - await include("/lib/restricted.js"); + await include("/nscl/common/restricted.js"); if (!isRestrictedURL((await browser.tabs.get(tabId)).url)) { // probably a page where content scripts cannot run, let's open the options instead error(e, "Cannot collect noscript activity data"); diff --git a/src/common/CapsCSP.js b/src/common/CapsCSP.js deleted file mode 100644 index 4ac91f2..0000000 --- a/src/common/CapsCSP.js +++ /dev/null @@ -1,33 +0,0 @@ -"use strict"; - -function CapsCSP(baseCSP = new CSP()) { - return Object.assign(baseCSP, { - types: ["script", "object", "media", "font"], - dataUriTypes: ["font", "media", "object"], - buildFromCapabilities(capabilities, blockHttp = false) { - let forbidData = new Set(this.dataUriTypes.filter(t => !capabilities.has(t))); - let blockedTypes = new Set(this.types.filter(t => !capabilities.has(t))); - if(!capabilities.has("script")) { - blockedTypes.add({name: "script-src-elem"}); - blockedTypes.add({name: "script-src-attr"}); - blockedTypes.add("worker"); - if (!blockedTypes.has("object")) { - // data: URIs loaded in objects may run scripts - blockedTypes.add({type: "object", value: "http:"}); - } - } - - if (!blockHttp) { - // HTTP is blocked in onBeforeRequest, let's allow it only and block - // for instance data: and blob: URIs - for (let type of this.dataUriTypes) { - if (blockedTypes.delete(type)) { - blockedTypes.add({type, value: "http:"}); - } - } - } - - return blockedTypes.size ? this.buildBlocker(...blockedTypes) : null; - } - }); -} diff --git a/src/common/Storage.js b/src/common/Storage.js deleted file mode 100644 index 78c481b..0000000 --- a/src/common/Storage.js +++ /dev/null @@ -1,169 +0,0 @@ -"use strict"; -var Storage = (() => { - - let chunksKey = k => `${k}/CHUNKS`; - - async function safeOp(op, type, keys) { - let sync = type === "sync"; - - try { - if (sync) { - let remove = op === "remove"; - if (remove || op === "get") { - keys = [].concat(keys); // don't touch the passed argument - let mergeResults = {}; - let localFallback = await getLocalFallback(); - if (localFallback.size) { - let localKeys = keys.filter(k => localFallback.has(k)); - if (localKeys.length) { - if (remove) { - await browser.storage.local.remove(localKeys); - for (let k of localKeys) { - localFallback.delete(k); - } - await setLocalFallback(localFallback); - } else { - mergeResults = await browser.storage.local.get(localKeys); - } - keys = keys.filter(k => !localFallback.has(k)); - } - } - - if (keys.length) { // we may not have non-fallback keys anymore - let chunkCounts = Object.entries(await browser.storage.sync.get( - keys.map(chunksKey))) - .map(([k, count]) => [k.split("/")[0], count]); - if (chunkCounts.length) { - let chunkedKeys = []; - for (let [k, count] of chunkCounts) { - // prepare to fetch all the chunks at once - while (count-- > 0) chunkedKeys.push(`${k}/${count}`); - } - if (remove) { - let doomedKeys = keys - .concat(chunkCounts.map(([k, count]) => chunksKey(k))) - .concat(chunkedKeys); - return await browser.storage.sync.remove(doomedKeys); - } else { - let chunks = await browser.storage.sync.get(chunkedKeys); - for (let [k, count] of chunkCounts) { - let orderedChunks = []; - for (let j = 0; j < count; j++) { - orderedChunks.push(chunks[`${k}/${j}`]); - } - let whole = orderedChunks.join(''); - try { - mergeResults[k] = JSON.parse(whole); - keys.splice(keys.indexOf(k), 1); // remove from "main" keys - } catch (e) { - error(e, "Could not parse chunked storage key %s (%s).", k, whole); - } - } - } - } - } - return keys.length ? - Object.assign(mergeResults, await browser.storage.sync[op](keys)) - : mergeResults; - } else if (op === "set") { - keys = Object.assign({}, keys); // don't touch the passed argument - const MAX_ITEM_SIZE = 4096; - // Firefox Sync's max object BYTEs size is 16384, Chrome's 8192. - // Rather than mesuring actual bytes, we play it safe by halving then - // lowest to cope with escapes / multibyte characters. - let removeKeys = []; - for (let k of Object.keys(keys)) { - let s = JSON.stringify(keys[k]); - let chunksCountKey = chunksKey(k); - let oldCount = await browser.storage.sync.get(chunksCountKey)[chunksCountKey] || 0; - let count; - if (s.length > MAX_ITEM_SIZE) { - count = Math.ceil(s.length / MAX_ITEM_SIZE); - let chunks = { - [chunksCountKey]: count - }; - for(let j = 0, o = 0; j < count; ++j, o += MAX_ITEM_SIZE) { - chunks[`${k}/${j}`] = s.substr(o, MAX_ITEM_SIZE); - } - await browser.storage.sync.set(chunks); - keys[k] = "[CHUNKED]"; - } else { - count = 0; - removeKeys.push(chunksCountKey); - } - if (oldCount-- > count) { - do { - removeKeys.push(`${k}${oldCount}`); - } while(oldCount-- > count); - } - } - await browser.storage.sync.remove(removeKeys); - } - } - - let ret = await browser.storage[type][op](keys); - if (sync && op === "set") { - let localFallback = await getLocalFallback(); - let size = localFallback.size; - if (size > 0) { - for (let k of Object.keys(keys)) { - localFallback.delete(k); - } - if (size > localFallback.size) { - await setLocalFallback(localFallback); - } - } - } - return ret; - } catch (e) { - error(e, "%s.%s(%o)", type, op, keys); - if (sync) { - debug("Sync disabled? Falling back to local storage (%s %o)", op, keys); - let localFallback = await getLocalFallback(); - let failedKeys = Array.isArray(keys) ? keys - : typeof keys === "string" ? [keys] : Object.keys(keys); - for (let k of failedKeys) { - localFallback.add(k); - } - await setLocalFallback(localFallback); - } else { - throw e; - } - } - - return await browser.storage.local[op](keys); - } - - const LFK_NAME = "__fallbackKeys"; - async function setLocalFallback(keys) { - return await browser.storage.local.set({[LFK_NAME]: [...keys]}); - } - async function getLocalFallback() { - let keys = (await browser.storage.local.get(LFK_NAME))[LFK_NAME]; - return new Set(Array.isArray(keys) ? keys : []); - } - - return { - async get(type, keys) { - return await safeOp("get", type, keys); - }, - - async set(type, keys) { - return await safeOp("set", type, keys); - }, - - async remove(type, keys) { - return await safeOp("remove", type, keys); - }, - - async hasLocalFallback(key) { - return (await getLocalFallback()).has(key); - }, - - async isChunked(key) { - let ccKey = chunksKey(key); - let data = await browser.storage.sync.get([key, ccKey]); - return data[key] === "[CHUNKED]" && parseInt(data[ccKey]); - } - }; -})() diff --git a/src/common/SyntaxChecker.js b/src/common/SyntaxChecker.js deleted file mode 100644 index 8646901..0000000 --- a/src/common/SyntaxChecker.js +++ /dev/null @@ -1,29 +0,0 @@ -class SyntaxChecker { - constructor() { - this.lastError = null; - this.lastFunction = null; - this.lastScript = ""; - } - check(script) { - this.lastScript = script; - try { - return !!(this.lastFunction = new Function(script)); - } catch(e) { - this.lastError = e; - this.lastFunction = null; - } - return false; - } - unquote(s, q) { - // check that this is really a double or a single quoted string... - if (s.length > 1 && s.startsWith(q) && s.endsWith(q) && - // if nothing is left if you remove all he escapes and all the stuff between quotes - s.replace(/\\./g, '').replace(/^(['"])[^\n\r]*?\1/, '') === '') { - try { - return eval(s); - } catch (e) { - } - } - return null; - } -} diff --git a/src/common/locale.js b/src/common/locale.js deleted file mode 100644 index 60498a2..0000000 --- a/src/common/locale.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; -var _ = browser.i18n.getMessage; -var i18n = (() => { - var i18n = { - // derived from http://github.com/piroor/webextensions-lib-l10n - - updateString(aString) { - return aString.replace(/__MSG_(.+?)__/g, function(aMatched) { - var key = aMatched.slice(6, -2); - return _(key); - }); - }, - updateDOM(rootNode = document) { - var texts = document.evaluate( - 'descendant::text()[contains(self::text(), "__MSG_")]', - rootNode, - null, - XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, - null - ); - for (let i = 0, maxi = texts.snapshotLength; i < maxi; i++) - { - let text = texts.snapshotItem(i); - text.nodeValue = this.updateString(text.nodeValue); - } - - var attributes = document.evaluate( - 'descendant::*/attribute::*[contains(., "__MSG_")]', - rootNode, - null, - XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, - null - ); - for (let i = 0, maxi = attributes.snapshotLength; i < maxi; i++) - { - let attribute = attributes.snapshotItem(i); - debug('apply', attribute); - attribute.value = this.updateString(attribute.value); - } - } - }; - - document.addEventListener('DOMContentLoaded', e => i18n.updateDOM()); - return i18n; -})() diff --git a/src/content/DocumentCSP.js b/src/content/DocumentCSP.js deleted file mode 100644 index a7b5251..0000000 --- a/src/content/DocumentCSP.js +++ /dev/null @@ -1,69 +0,0 @@ -'use strict'; -class DocumentCSP { - constructor(document) { - this.document = document; - this.builder = new CapsCSP(); - } - - apply(capabilities, embedding = CSP.isEmbedType(this.document.contentType)) { - let {document} = this; - if (!capabilities.has("script")) { - // safety net for XML (especially SVG) documents and synchronous scripts running - // while inserting the CSP element. - document.defaultView.addEventListener("beforescriptexecute", e => { - if (!e.isTrusted) return; - e.preventDefault(); - debug("Fallback beforexecutescript listener blocked ", e.target); - }, true); - } - - let csp = this.builder; - let blocker = csp.buildFromCapabilities(capabilities, embedding); - if (!blocker) return null; - - let createHTMLElement = - tagName => document.createElementNS("http://www.w3.org/1999/xhtml", tagName); - - let header = csp.asHeader(blocker); - - let meta = createHTMLElement("meta"); - meta.setAttribute("http-equiv", header.name); - meta.setAttribute("content", header.value); - - let root = document.documentElement; - try { - if (!(document instanceof HTMLDocument)) { - if (!(document instanceof XMLDocument)) { - return null; // nothing to do with ImageDocument, for instance - } - // non-HTML XML documents ignore CSP unless wrapped in - // - on Gecko - // - just on Chromium - console.debug("XML Document: temporary replacing %o with ", root); - let htmlDoc = document.implementation.createHTMLDocument(); - let htmlRoot = document.importNode(htmlDoc.documentElement, true); - document.replaceChild(htmlRoot, root); - } - - let {head} = document; - let parent = head || - document.documentElement.insertBefore(createHTMLElement("head"), - document.documentElement.firstElementChild); - - - parent.insertBefore(meta, parent.firstElementChild); - debug(`Failsafe CSP inserted in %s: "%s"`, document.URL, header.value); - meta.remove(); - if (!head) parent.remove(); - if (document.documentElement !== root) - { - - document.replaceChild(root, document.documentElement); - } - } catch (e) { - error(e, "Error inserting CSP %s in %s", document.URL, header && header.value); - return null; - } - return CSP.normalize(header.value); - } -} diff --git a/src/content/PlaceHolder.js b/src/content/PlaceHolder.js deleted file mode 100644 index e12d56a..0000000 --- a/src/content/PlaceHolder.js +++ /dev/null @@ -1,225 +0,0 @@ -var PlaceHolder = (() => { - const HANDLERS = new Map(); - const CLASS_NAME = "__NoScript_PlaceHolder__"; - const SELECTOR = `a.${CLASS_NAME}`; - let checkStyle = async () => { - checkStyle = () => {}; - if (!ns.embeddingDocument) return; - let replacement = document.querySelector(SELECTOR); - if (!replacement) return; - if (window.getComputedStyle(replacement, null).opacity !== "0.8") { - document.head.appendChild(createHTMLElement("style")).textContent = await - (await fetch(browser.extension.getURL("/content/content.css"))).text(); - } - } - - class Handler { - constructor(type, selector) { - this.type = type; - this.selector = selector; - this.placeHolders = new Map(); - HANDLERS.set(type, this); - } - filter(element, request) { - if (request.embeddingDocument) { - return document.URL === request.url; - } - let url = request.initialUrl || request.url; - return "data" in element ? element.data === url : element.src === url; - } - selectFor(request) { - return [...document.querySelectorAll(this.selector)] - .filter(element => this.filter(element, request)) - } - } - - new Handler("frame", "iframe"); - new Handler("object", "object, embed"); - new Handler("media", "video, audio, source"); - - function cloneStyle(src, dest, - props = ["width", "height", "position", "*", "margin*"]) { - var suffixes = ["Top", "Right", "Bottom", "Left"]; - for (let i = props.length; i-- > 0;) { - let p = props[i]; - if (p.endsWith("*")) { - let prefix = p.substring(0, p.length - 1); - props.splice(i, 1, ... - (suffixes.map(prefix ? (suffix => prefix + suffix) : - suffix => suffix.toLowerCase()))); - } - }; - - let srcStyle = window.getComputedStyle(src, null); - let destStyle = dest.style; - for (let p of props) { - destStyle[p] = srcStyle[p]; - } - for (let size of ["width", "height"]) { - if (/^0(?:\D|$)/.test(destStyle[size])) { - destStyle[size] = ""; - } - } - if (src.offsetTop < 0 && src.offsetTop <= (-src.offsetHeight)) { - destStyle.top = "0"; // fixes video player off-display position on Youtube - } - destStyle.display = srcStyle.display !== "block" ? "inline-block" : "block"; - } - - class PlaceHolder { - - static create(policyType, request) { - return new PlaceHolder(policyType, request); - } - static canReplace(policyType) { - return HANDLERS.has(policyType); - } - static handlerFor(policyType) { - return HANDLERS.get(policyType); - } - - static listen() { - PlaceHolder.listen = () => {}; - window.addEventListener("click", ev => { - if (ev.button === 0 && ev.isTrusted) { - let ph, replacement; - for (let e of document.elementsFromPoint(ev.clientX, ev.clientY)) { - if (ph = e._placeHolderObj) { - replacement = e; - break; - } - if (replacement = e._placeHolderReplacement) { - ph = replacement._placeHolderObj; - break; - } - } - if (ph) { - ev.preventDefault(); - ev.stopPropagation(); - if (ev.target.value === "close") { - ph.close(replacement); - } else { - ph.enable(replacement); - } - } - } - }, true, false); - } - - constructor(policyType, request) { - this.policyType = policyType; - this.request = request; - this.replacements = new Set(); - this.handler = PlaceHolder.handlerFor(policyType); - if (this.handler) { - [...document.querySelectorAll(this.handler.selector)] - .filter(element => this.handler.filter(element, request)) - .forEach(element => this.replace(element)); - }; - if (this.replacements.size) { - PlaceHolder.listen(); - checkStyle(); - } - } - - replace(element) { - if (!element.parentElement) return; - if (element.parentElement instanceof HTMLMediaElement) { - this.replace(element.parentElement); - return; - } - let { - url - } = this.request; - let objUrl = new URL(url) - this.origin = objUrl.origin; - if (this.origin === "null") { - this.origin = objUrl.protocol; - } - let TYPE = `<${this.policyType.toUpperCase()}>`; - - let replacement = createHTMLElement("a"); - replacement.className = CLASS_NAME; - cloneStyle(element, replacement); - if (ns.embeddingDocument) { - replacement.classList.add("__ns__document"); - window.stop(); - } - - replacement.href = url; - replacement.title = `${TYPE}@${url}`; - - let inner = replacement.appendChild(createHTMLElement("span")); - inner.className = replacement.className; - - let button = inner.appendChild(createHTMLElement("button")); - button.className = replacement.className; - button.setAttribute("aria-label", button.title = _("Close")); - button.value = "close"; - button.textContent = "×"; - - let description = inner.appendChild(createHTMLElement("span")); - description.textContent = `${TYPE}@${this.origin}`; - - replacement._placeHolderObj = this; - replacement._placeHolderElement = element; - for (let e of replacement.querySelectorAll("*")) { - e._placeHolderReplacement = replacement; - } - - element.replaceWith(replacement); - - // do our best to bring it to front - for (let p = replacement; p = p.parentElement;) { - p.classList.add("__ns__pop2top"); - }; - - this.replacements.add(replacement); - } - - async enable(replacement) { - debug("Enabling %o", this.request, this.policyType); - let ret = await Messages.send("blockedObjects", { - url: this.request.url, - policyType: this.policyType, - documentUrl: document.URL - }); - debug("Received response", ret); - if (!ret) return; - // bring back ancestors - for (let p = replacement; p = p.parentElement;) { - p.classList.remove("__ns__pop2top"); - }; - if (ret.collapse) { - for (let collapsing of (ret.collapse === "all" ? document.querySelectorAll(SELECTOR) : [replacement])) { - this.replacements.delete(collapsing); - collapsing.remove(); - } - return; - } - if (this.request.embeddingDocument) { - window.location.reload(); - return; - } - try { - let element = replacement._placeHolderElement; - replacement.replaceWith(element.cloneNode(true)); - this.replacements.delete(replacement); - } catch (e) { - error(e, "While replacing"); - } - } - - close(replacement) { - replacement.classList.add("__ns__closing"); - this.replacements.delete(replacement); - window.setTimeout(() => { - for (let p = replacement; p = p.parentElement;) { - p.classList.remove("__ns__pop2top"); - }; - replacement.remove() - }, 500); - } - } - return PlaceHolder; -})(); diff --git a/src/content/media.js b/src/content/media.js deleted file mode 100644 index a75ccef..0000000 --- a/src/content/media.js +++ /dev/null @@ -1,106 +0,0 @@ -if ("MediaSource" in window) { - let mediaBlocker; - let notify = allowed => { - let request = { - id: "noscript-media", - type: "media", - url: document.URL, - documentUrl: document.URL, - embeddingDocument: true, - }; - seen.record({policyType: "media", request, allowed}); - debug("MSE notification", document.URL); // DEV_ONLY - notifyPage(); - return request; - }; - let createPlaceholder = (mediaElement, request) => { - try { - let ph = PlaceHolder.create("media", request); - ph.replace(mediaElement); - PlaceHolder.listen(); - debug("MSE placeholder for %o", mediaElement); // DEV_ONLY - } catch (e) { - error(e); - } - }; - if ("SecurityPolicyViolationEvent" in window) { - // "Modern" browsers - let createPlaceholders = () => { - let request = notify(false); - for (let me of document.querySelectorAll("video,audio")) { - if (!(me.src || me.currentSrc) || me.src.startsWith("blob")) { - createPlaceholder(me, request); - } - } - } - let processedURIs = new Set(); - addEventListener("securitypolicyviolation", e => { - let {blockedURI, violatedDirective, originalPolicy} = e; - if (!(e.isTrusted && violatedDirective === "media-src" && CSP.isMediaBlocker(originalPolicy))) return; - if (mediaBlocker === undefined && /^data\b/.test(blockedURI)) { // Firefox 81 reports just "data" - debug("mediaBlocker set via CSP listener.") - mediaBlocker = true; - e.stopImmediatePropagation(); - return; - } - if (blockedURI.startsWith("blob") && - !processedURIs.has(blockedURI)) { - processedURIs.add(blockedURI); - setTimeout(createPlaceholders, 0); - } - }, true); - } - - if (typeof exportFunction === "function") { - // Fallback: Mozilla does not seem to trigger CSP media-src http: for blob: URIs assigned in MSE - window.wrappedJSObject.document.createElement("video").src = "data:"; // triggers early mediaBlocker initialization via CSP - ns.on("capabilities", e => { - mediaBlocker = !ns.allows("media"); - if (mediaBlocker) debug("mediaBlocker set via fetched policy.") - }); - - let unpatched = new Map(); - function patch(obj, methodName, replacement) { - let methods = unpatched.get(obj) || {}; - methods[methodName] = obj[methodName]; - exportFunction(replacement, obj, {defineAs: methodName}); - unpatched.set(obj, methods); - } - let urlMap = new WeakMap(); - patch(window.URL, "createObjectURL", function(o, ...args) { - let url = unpatched.get(window.URL).createObjectURL.call(this, o, ...args); - if (o instanceof MediaSource) { - let urls = urlMap.get(o); - if (!urls) urlMap.set(o, urls = new Set()); - urls.add(url); - } - return url; - }); - - patch(window.MediaSource.prototype, "addSourceBuffer", function(mime, ...args) { - let ms = this; - let urls = urlMap.get(ms); - let request = notify(!mediaBlocker); - if (mediaBlocker) { - let exposedMime = `${mime} (MSE)`; - setTimeout(() => { - try { - let allMedia = [...document.querySelectorAll("video,audio")]; - let me = allMedia.find(e => e.srcObject === ms || - urls && (urls.has(e.currentSrc) || urls.has(e.src))) || - // throwing may cause src not to be assigned at all: - allMedia.find(e => !(e.src || e.currentSrc || e.srcObject)); - if (me) createPlaceholder(me, request); - } catch (e) { - error(e); - } - }, 0); - let msg = `${exposedMime} blocked by NoScript`; - log(msg); - throw new Error(msg); - } - - return unpatched.get(window.MediaSource.prototype).addSourceBuffer.call(ms, mime, ...args); - }); - } -} diff --git a/src/content/sanitizePaste.js b/src/content/sanitizePaste.js deleted file mode 100644 index bf42755..0000000 --- a/src/content/sanitizePaste.js +++ /dev/null @@ -1,63 +0,0 @@ -'use strict'; -{ - let urlAttributes = ['href', 'to', 'from', 'by', 'values']; - let selector = urlAttributes.map(a => `[${a}]`).join(','); - - for (let evType of ["drop", "paste"]) window.addEventListener(evType, e => { - let container = e.target; - let editing = false; - for (let el = container; el; el = el.parentElement) { - if (el.setRangeText || el.contentEditable) { - editing = true; - break; - } - } - if (!editing) return; - - // we won't touch DOM elements which are already there - let oldNodes = new Set(container.querySelectorAll(selector + ",form")); - window.setTimeout(() => { - // we delay our custom sanitization after the browser performed the paste - // or drop job, rather than replacing it, in order to avoid interferences - // with built-in sanitization - try { - let html = container.innerHTML; - if (sanitizeExtras(container, oldNodes)) { - let t = e.type.toUpperCase(); - console.log(`[NoScript] Sanitized\n<${t}>\n${html}\n\nto\n<${t}>\n${container.innerHTML}\n`, container); - } - } catch(ex) { - console.log(ex); - } - }, 0); - }, true); - - function removeAttribute(node, name, value = node.getAttribute(name)) { - node.setAttribute(`data-noscript-removed-${name}`, value); - node.removeAttribute(name); - } - - function sanitizeExtras(container, oldNodes = []) { - let ret = false; - - // remove attributes from forms - for (let f of container.getElementsByTagName("form")) { - if (oldNodes.has(f)) continue; - for (let a of [...f.attributes]) { - removeAttribute(f, a.name); - } - } - - for (let node of container.querySelectorAll(selector)) { - if (oldNodes.has(node)) continue; - for (let name of urlAttributes) { - let value = node.getAttribute(name); - if (/^\W*(?:(?:javascript|data):|https?:[\s\S]+[[(<])/i.test(unescape(value))) { - removeAttribute(node, name, value); - ret = true; - } - } - } - return ret; - } -} diff --git a/src/content/webglHook.js b/src/content/webglHook.js deleted file mode 100644 index fc1beeb..0000000 --- a/src/content/webglHook.js +++ /dev/null @@ -1,43 +0,0 @@ -// depends on nscl/content/patchWindow.js -"use strict"; -ns.on("capabilities", event => { - debug("WebGL Hook", document.URL, document.documentElement && document.documentElement.innerHTML, ns.capabilities); // DEV_ONLY - if (ns.allows("webgl")) return; - let env = {eventName: `nsWebgl:${uuid()}`}; - window.addEventListener(env.eventName, e => { - let request = { - id: "noscript-webgl", - type: "webgl", - url: document.URL, - documentUrl: document.URL, - embeddingDocument: true, - }; - seen.record({policyType: "webgl", request, allowed: false}); - let canvas = e.target; - if (canvas instanceof HTMLCanvasElement) { - try { - let ph = PlaceHolder.create("webgl", request); - ph.replace(canvas); - PlaceHolder.listen(); - } catch (e) { - error(e); - } - } - notifyPage(); - }, true); - - function modifyGetContext(win, env) { - let proto = win.HTMLCanvasElement.prototype; - let getContext = proto.getContext; - exportFunction(function(type, ...rest) { - if (type && type.toLowerCase().includes("webgl")) { - let target = document.contains(this) ? this : window; - target.dispatchEvent(new Event(env.eventName, {composed: true})); - return null; - } - return getContext.call(this, type, ...rest); - }, proto, {defineAs: "getContext"}); - } - - patchWindow(modifyGetContext, env); -}); diff --git a/src/lib/CSP.js b/src/lib/CSP.js deleted file mode 100644 index 8549047..0000000 --- a/src/lib/CSP.js +++ /dev/null @@ -1,44 +0,0 @@ -"use strict"; - -class CSP { - static isMediaBlocker(csp) { - return /(?:^|[\s;])media-src (?:'none'|http:)(?:;|$)/.test(csp); - } - static normalize(csp) { - return csp.replace(/\s*;\s*/g, ';').replace(/\b(script-src\s+'none'.*?;)(?:script-src-\w+\s+'none';)+/, '$1'); - } - - build(...directives) { - return directives.join(';'); - } - - buildBlocker(...types) { - return this.build(...(types.map(t => `${t.name || `${t.type || t}-src`} ${t.value || "'none'"}`))); - } - - blocks(header, type) { - return `;${header};`.includes(`;${type}-src 'none';`) - } - - asHeader(value) { - return {name: CSP.headerName, value}; - } -} - -CSP.isEmbedType = type => /\b(?:application|video|audio)\b/.test(type) && type !== "application/xhtml+xml"; -CSP.headerName = "content-security-policy"; -CSP.patchDataURI = (uri, blocker) => { - let parts = /^data:(?:[^,;]*ml|unknown-content-type)(;[^,]*)?,/i.exec(uri); - if (!(blocker && parts)) { - // not an interesting data: URI, return as it is - return uri; - } - if (parts[1]) { - // extra encoding info, let's bailout (better safe than sorry) - return "data:"; - } - // It's a HTML/XML page, let's prepend our CSP blocker to the document - let patch = parts[0] + encodeURIComponent( - ``); - return uri.startsWith(patch) ? uri : patch + uri.substring(parts[0].length); -} diff --git a/src/lib/LastListener.js b/src/lib/LastListener.js deleted file mode 100644 index b93edbc..0000000 --- a/src/lib/LastListener.js +++ /dev/null @@ -1,49 +0,0 @@ -/** -* Wrapper around listeners on various WebExtensions -* APIs (e.g. webRequest.on*), as a best effort to -* let them run last by removing and re-adding them -* on each call (swapping 2 copies because -* addListener() calls are asynchronous). -* Note: we rely on implementation details Like -* listeners being called in addition order; also, -* clients should ensure they're not called twice for -* the same event, if that's important. -} -*/ - -class LastListener { - constructor(observed, listener, ...extras) { - this.observed = observed; - this.listener = listener; - this.extras = extras; - let ww = this._wrapped = [listener, listener].map(l => { - let w = (...args) => { - if (this.observed.hasListener(w._other)) { - this.observed.removeListener(w); - } else if (this.installed) { - this.observed.addListener(w._other, ...this.extras); - } - debug("Running listener", w === ww[0] ? 0 : 1, ...args); - return this.installed ? this.listener(...args) - : this.defaultResult; - } - return w; - }); - - ww[0]._other = ww[1]; - ww[1]._other = ww[0]; - this.installed = false; - this.defaultResult = null; - } - - install() { - if (this.installed) return; - this.observed.addListener(this._wrapped[0], ...this.extras); - this.installed = true; - } - - uninstall() { - this.installed = false; - for (let l of this._wrapped) this.observed.removeListener(l); - } -} diff --git a/src/lib/NetCSP.js b/src/lib/NetCSP.js deleted file mode 100644 index 4891900..0000000 --- a/src/lib/NetCSP.js +++ /dev/null @@ -1,27 +0,0 @@ -"use strict"; - -class NetCSP extends CSP { - constructor(start) { - super(); - this.start = start; - } - - isMine(header) { - let {name, value} = header; - return name.toLowerCase() === CSP.headerName && - value.split(/,\s*/).some(v => v.startsWith(this.start)); - } - - unmergeExtras(header) { - let {name, value} = header; - return value.split(/,\s*/).filter(v => !v.startsWith(this.start)) - .map(value => {name, value}); - } - - build(...directives) { - return `${this.start};${super.build(...directives)}`; - } - - cleanup(headers) { - } -} diff --git a/src/lib/TabCache.js b/src/lib/TabCache.js deleted file mode 100644 index 16d6e1f..0000000 --- a/src/lib/TabCache.js +++ /dev/null @@ -1,24 +0,0 @@ -var TabCache = (() => { - - let cache = new Map(); - - browser.tabs.onUpdated.addListener(tab => { - cache.set(tab.id, tab); - }); - - browser.tabs.onRemoved.addListener(tab => { - cache.delete(tab.id); - }); - - (async () => { - for (let tab of await browser.tabs.query({})) { - cache.set(tab.id, tab); - } - })(); - - return { - get(tabId) { - return cache.get(tabId); - } - }; -})(); diff --git a/src/lib/Timing.js b/src/lib/Timing.js deleted file mode 100644 index 7bc00fa..0000000 --- a/src/lib/Timing.js +++ /dev/null @@ -1,49 +0,0 @@ -class Timing { - - constructor(workSlot = 10, longTime = 20000, pauseTime = 20) { - this.workSlot = workSlot; - this.longTime = longTime; - this.pauseTime = pauseTime; - this.interrupted = false; - this.fatalTimeout = false; - this.maxCalls = 1000; - this.reset(); - } - - static sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - - async pause() { - if (this.interrupted) throw new TimingException("Timing: interrupted"); - let now = Date.now(); - this.calls++; - let sinceLastCall = now - this.lastCall; - if (sinceLastCall > this.workSlot && this.calls > 1000) { - // low resolution (100ms) timer? Let's cap approximating by calls number - this.maxCalls = this.calls / sinceLastCall * this.workSlot; - } - this.lastCall = now; - this.elapsed = now - this.timeOrigin; - if (now - this.lastPause > this.workSlot || this.calls > this.maxCalls) { - this.tooLong = this.elapsed >= this.longTime; - if (this.tooLong && this.fatalTimeout) { - throw new TimingException(`Timing: exceeded ${this.longTime}ms timeout`); - } - this.calls = 0; - if (this.pauseTime > 0) await Timing.sleep(this.pauseTime); - this.lastPause = Date.now(); - return true; - } - return false; - } - - reset() { - this.elapsed = 0; - this.calls = 0; - this.timeOrigin = this.lastPause = this.lastCall = Date.now(); - this.tooLong = false; - } -} - -class TimingException extends Error {}; diff --git a/src/lib/UA.js b/src/lib/UA.js deleted file mode 100644 index 57e98ae..0000000 --- a/src/lib/UA.js +++ /dev/null @@ -1,22 +0,0 @@ -{ - let mozWebExtUrl = document.URL.startsWith("moz-"); - let isMozilla = mozWebExtUrl || typeof window.wrappedJSObject === "object"; - let mobile = false; - if (isMozilla) { - if (mozWebExtUrl) { - // help browser-specific UI styling - document.documentElement.classList.add("mozwebext"); - mobile = !("windows" in browser); - } - } else { - // shims for non-Mozilla browsers - if (typeof chrome === "object" && !chrome.tabs) { - // content script shims - } - } - - var UA = { - isMozilla, - mobile, - }; -} diff --git a/src/lib/Ver.js b/src/lib/Ver.js deleted file mode 100644 index 3d86140..0000000 --- a/src/lib/Ver.js +++ /dev/null @@ -1,45 +0,0 @@ -"use strict"; -class Ver { - constructor(version) { - if (version instanceof Ver) { - this.versionString = version.versionString; - this.parts = version.parts; - } else { - this.versionString = version.toString(); - this.parts = this.versionString.split("."); - } - } - toString() { - return this.versionString; - } - compare(other) { - if (!(other instanceof Ver)) other = new Ver(other); - let p1 = this.parts, p2 = other.parts; - let maxParts = Math.max(p1.length, p2.length); - for (let j = 0; j < maxParts; j++) { - let s1 = p1[j] || "0"; - let s2 = p2[j] || "0"; - if (s1 === s2) continue; - let n1 = parseInt(s1); - let n2 = parseInt(s2); - if (n1 > n2) return 1; - if (n1 < n2) return -1; - // if numeric part is the same, an alphabetic suffix decreases value - // so a "pure number" wins - if (!/\D/.test(s1)) return 1; - if (!/\D/.test(s2)) return -1; - // both have an alhpabetic suffix, let's compare lexicographycally - if (s1 > s2) return 1; - if (s1 < s2) return -1; - } - return 0; - } - static is(ver1, op, ver2) { - let res = new Ver(ver1).compare(ver2); - - return op.includes("!=") && res !== 0 || - op.includes("=") && res === 0 || - op.includes("<") && res === -1 || - op.includes(">") && res === 1; - } -} \ No newline at end of file diff --git a/src/lib/browser-polyfill.js b/src/lib/browser-polyfill.js deleted file mode 100644 index 5fef28a..0000000 --- a/src/lib/browser-polyfill.js +++ /dev/null @@ -1,1237 +0,0 @@ -(function (global, factory) { - if (typeof define === "function" && define.amd) { - define("webextension-polyfill", ["module"], factory); - } else if (typeof exports !== "undefined") { - factory(module); - } else { - var mod = { - exports: {} - }; - factory(mod); - global.browser = mod.exports; - } -})(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (module) { - /* webextension-polyfill - v0.7.0 - Tue Nov 10 2020 20:24:04 */ - - /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ - - /* vim: set sts=2 sw=2 et tw=80: */ - - /* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - "use strict"; - - if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.prototype) { - const CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = "The message port closed before a response was received."; - const SEND_RESPONSE_DEPRECATION_WARNING = "Returning a Promise is the preferred way to send a reply from an onMessage/onMessageExternal listener, as the sendResponse will be removed from the specs (See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage)"; // Wrapping the bulk of this polyfill in a one-time-use function is a minor - // optimization for Firefox. Since Spidermonkey does not fully parse the - // contents of a function until the first time it's called, and since it will - // never actually need to be called, this allows the polyfill to be included - // in Firefox nearly for free. - - const wrapAPIs = extensionAPIs => { - // NOTE: apiMetadata is associated to the content of the api-metadata.json file - // at build time by replacing the following "include" with the content of the - // JSON file. - const apiMetadata = { - "alarms": { - "clear": { - "minArgs": 0, - "maxArgs": 1 - }, - "clearAll": { - "minArgs": 0, - "maxArgs": 0 - }, - "get": { - "minArgs": 0, - "maxArgs": 1 - }, - "getAll": { - "minArgs": 0, - "maxArgs": 0 - } - }, - "bookmarks": { - "create": { - "minArgs": 1, - "maxArgs": 1 - }, - "get": { - "minArgs": 1, - "maxArgs": 1 - }, - "getChildren": { - "minArgs": 1, - "maxArgs": 1 - }, - "getRecent": { - "minArgs": 1, - "maxArgs": 1 - }, - "getSubTree": { - "minArgs": 1, - "maxArgs": 1 - }, - "getTree": { - "minArgs": 0, - "maxArgs": 0 - }, - "move": { - "minArgs": 2, - "maxArgs": 2 - }, - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeTree": { - "minArgs": 1, - "maxArgs": 1 - }, - "search": { - "minArgs": 1, - "maxArgs": 1 - }, - "update": { - "minArgs": 2, - "maxArgs": 2 - } - }, - "browserAction": { - "disable": { - "minArgs": 0, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "enable": { - "minArgs": 0, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "getBadgeBackgroundColor": { - "minArgs": 1, - "maxArgs": 1 - }, - "getBadgeText": { - "minArgs": 1, - "maxArgs": 1 - }, - "getPopup": { - "minArgs": 1, - "maxArgs": 1 - }, - "getTitle": { - "minArgs": 1, - "maxArgs": 1 - }, - "openPopup": { - "minArgs": 0, - "maxArgs": 0 - }, - "setBadgeBackgroundColor": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "setBadgeText": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "setIcon": { - "minArgs": 1, - "maxArgs": 1 - }, - "setPopup": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "setTitle": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - } - }, - "browsingData": { - "remove": { - "minArgs": 2, - "maxArgs": 2 - }, - "removeCache": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeCookies": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeDownloads": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeFormData": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeHistory": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeLocalStorage": { - "minArgs": 1, - "maxArgs": 1 - }, - "removePasswords": { - "minArgs": 1, - "maxArgs": 1 - }, - "removePluginData": { - "minArgs": 1, - "maxArgs": 1 - }, - "settings": { - "minArgs": 0, - "maxArgs": 0 - } - }, - "commands": { - "getAll": { - "minArgs": 0, - "maxArgs": 0 - } - }, - "contextMenus": { - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeAll": { - "minArgs": 0, - "maxArgs": 0 - }, - "update": { - "minArgs": 2, - "maxArgs": 2 - } - }, - "cookies": { - "get": { - "minArgs": 1, - "maxArgs": 1 - }, - "getAll": { - "minArgs": 1, - "maxArgs": 1 - }, - "getAllCookieStores": { - "minArgs": 0, - "maxArgs": 0 - }, - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "set": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "devtools": { - "inspectedWindow": { - "eval": { - "minArgs": 1, - "maxArgs": 2, - "singleCallbackArg": false - } - }, - "panels": { - "create": { - "minArgs": 3, - "maxArgs": 3, - "singleCallbackArg": true - }, - "elements": { - "createSidebarPane": { - "minArgs": 1, - "maxArgs": 1 - } - } - } - }, - "downloads": { - "cancel": { - "minArgs": 1, - "maxArgs": 1 - }, - "download": { - "minArgs": 1, - "maxArgs": 1 - }, - "erase": { - "minArgs": 1, - "maxArgs": 1 - }, - "getFileIcon": { - "minArgs": 1, - "maxArgs": 2 - }, - "open": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "pause": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeFile": { - "minArgs": 1, - "maxArgs": 1 - }, - "resume": { - "minArgs": 1, - "maxArgs": 1 - }, - "search": { - "minArgs": 1, - "maxArgs": 1 - }, - "show": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - } - }, - "extension": { - "isAllowedFileSchemeAccess": { - "minArgs": 0, - "maxArgs": 0 - }, - "isAllowedIncognitoAccess": { - "minArgs": 0, - "maxArgs": 0 - } - }, - "history": { - "addUrl": { - "minArgs": 1, - "maxArgs": 1 - }, - "deleteAll": { - "minArgs": 0, - "maxArgs": 0 - }, - "deleteRange": { - "minArgs": 1, - "maxArgs": 1 - }, - "deleteUrl": { - "minArgs": 1, - "maxArgs": 1 - }, - "getVisits": { - "minArgs": 1, - "maxArgs": 1 - }, - "search": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "i18n": { - "detectLanguage": { - "minArgs": 1, - "maxArgs": 1 - }, - "getAcceptLanguages": { - "minArgs": 0, - "maxArgs": 0 - } - }, - "identity": { - "launchWebAuthFlow": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "idle": { - "queryState": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "management": { - "get": { - "minArgs": 1, - "maxArgs": 1 - }, - "getAll": { - "minArgs": 0, - "maxArgs": 0 - }, - "getSelf": { - "minArgs": 0, - "maxArgs": 0 - }, - "setEnabled": { - "minArgs": 2, - "maxArgs": 2 - }, - "uninstallSelf": { - "minArgs": 0, - "maxArgs": 1 - } - }, - "notifications": { - "clear": { - "minArgs": 1, - "maxArgs": 1 - }, - "create": { - "minArgs": 1, - "maxArgs": 2 - }, - "getAll": { - "minArgs": 0, - "maxArgs": 0 - }, - "getPermissionLevel": { - "minArgs": 0, - "maxArgs": 0 - }, - "update": { - "minArgs": 2, - "maxArgs": 2 - } - }, - "pageAction": { - "getPopup": { - "minArgs": 1, - "maxArgs": 1 - }, - "getTitle": { - "minArgs": 1, - "maxArgs": 1 - }, - "hide": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "setIcon": { - "minArgs": 1, - "maxArgs": 1 - }, - "setPopup": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "setTitle": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "show": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - } - }, - "permissions": { - "contains": { - "minArgs": 1, - "maxArgs": 1 - }, - "getAll": { - "minArgs": 0, - "maxArgs": 0 - }, - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "request": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "runtime": { - "getBackgroundPage": { - "minArgs": 0, - "maxArgs": 0 - }, - "getPlatformInfo": { - "minArgs": 0, - "maxArgs": 0 - }, - "openOptionsPage": { - "minArgs": 0, - "maxArgs": 0 - }, - "requestUpdateCheck": { - "minArgs": 0, - "maxArgs": 0 - }, - "sendMessage": { - "minArgs": 1, - "maxArgs": 3 - }, - "sendNativeMessage": { - "minArgs": 2, - "maxArgs": 2 - }, - "setUninstallURL": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "sessions": { - "getDevices": { - "minArgs": 0, - "maxArgs": 1 - }, - "getRecentlyClosed": { - "minArgs": 0, - "maxArgs": 1 - }, - "restore": { - "minArgs": 0, - "maxArgs": 1 - } - }, - "storage": { - "local": { - "clear": { - "minArgs": 0, - "maxArgs": 0 - }, - "get": { - "minArgs": 0, - "maxArgs": 1 - }, - "getBytesInUse": { - "minArgs": 0, - "maxArgs": 1 - }, - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "set": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "managed": { - "get": { - "minArgs": 0, - "maxArgs": 1 - }, - "getBytesInUse": { - "minArgs": 0, - "maxArgs": 1 - } - }, - "sync": { - "clear": { - "minArgs": 0, - "maxArgs": 0 - }, - "get": { - "minArgs": 0, - "maxArgs": 1 - }, - "getBytesInUse": { - "minArgs": 0, - "maxArgs": 1 - }, - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "set": { - "minArgs": 1, - "maxArgs": 1 - } - } - }, - "tabs": { - "captureVisibleTab": { - "minArgs": 0, - "maxArgs": 2 - }, - "create": { - "minArgs": 1, - "maxArgs": 1 - }, - "detectLanguage": { - "minArgs": 0, - "maxArgs": 1 - }, - "discard": { - "minArgs": 0, - "maxArgs": 1 - }, - "duplicate": { - "minArgs": 1, - "maxArgs": 1 - }, - "executeScript": { - "minArgs": 1, - "maxArgs": 2 - }, - "get": { - "minArgs": 1, - "maxArgs": 1 - }, - "getCurrent": { - "minArgs": 0, - "maxArgs": 0 - }, - "getZoom": { - "minArgs": 0, - "maxArgs": 1 - }, - "getZoomSettings": { - "minArgs": 0, - "maxArgs": 1 - }, - "goBack": { - "minArgs": 0, - "maxArgs": 1 - }, - "goForward": { - "minArgs": 0, - "maxArgs": 1 - }, - "highlight": { - "minArgs": 1, - "maxArgs": 1 - }, - "insertCSS": { - "minArgs": 1, - "maxArgs": 2 - }, - "move": { - "minArgs": 2, - "maxArgs": 2 - }, - "query": { - "minArgs": 1, - "maxArgs": 1 - }, - "reload": { - "minArgs": 0, - "maxArgs": 2 - }, - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeCSS": { - "minArgs": 1, - "maxArgs": 2 - }, - "sendMessage": { - "minArgs": 2, - "maxArgs": 3 - }, - "setZoom": { - "minArgs": 1, - "maxArgs": 2 - }, - "setZoomSettings": { - "minArgs": 1, - "maxArgs": 2 - }, - "update": { - "minArgs": 1, - "maxArgs": 2 - } - }, - "topSites": { - "get": { - "minArgs": 0, - "maxArgs": 0 - } - }, - "webNavigation": { - "getAllFrames": { - "minArgs": 1, - "maxArgs": 1 - }, - "getFrame": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "webRequest": { - "handlerBehaviorChanged": { - "minArgs": 0, - "maxArgs": 0 - } - }, - "windows": { - "create": { - "minArgs": 0, - "maxArgs": 1 - }, - "get": { - "minArgs": 1, - "maxArgs": 2 - }, - "getAll": { - "minArgs": 0, - "maxArgs": 1 - }, - "getCurrent": { - "minArgs": 0, - "maxArgs": 1 - }, - "getLastFocused": { - "minArgs": 0, - "maxArgs": 1 - }, - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "update": { - "minArgs": 2, - "maxArgs": 2 - } - } - }; - - if (Object.keys(apiMetadata).length === 0) { - throw new Error("api-metadata.json has not been included in browser-polyfill"); - } - /** - * A WeakMap subclass which creates and stores a value for any key which does - * not exist when accessed, but behaves exactly as an ordinary WeakMap - * otherwise. - * - * @param {function} createItem - * A function which will be called in order to create the value for any - * key which does not exist, the first time it is accessed. The - * function receives, as its only argument, the key being created. - */ - - - class DefaultWeakMap extends WeakMap { - constructor(createItem, items = undefined) { - super(items); - this.createItem = createItem; - } - - get(key) { - if (!this.has(key)) { - this.set(key, this.createItem(key)); - } - - return super.get(key); - } - - } - /** - * Returns true if the given object is an object with a `then` method, and can - * therefore be assumed to behave as a Promise. - * - * @param {*} value The value to test. - * @returns {boolean} True if the value is thenable. - */ - - - const isThenable = value => { - return value && typeof value === "object" && typeof value.then === "function"; - }; - /** - * Creates and returns a function which, when called, will resolve or reject - * the given promise based on how it is called: - * - * - If, when called, `chrome.runtime.lastError` contains a non-null object, - * the promise is rejected with that value. - * - If the function is called with exactly one argument, the promise is - * resolved to that value. - * - Otherwise, the promise is resolved to an array containing all of the - * function's arguments. - * - * @param {object} promise - * An object containing the resolution and rejection functions of a - * promise. - * @param {function} promise.resolve - * The promise's resolution function. - * @param {function} promise.rejection - * The promise's rejection function. - * @param {object} metadata - * Metadata about the wrapped method which has created the callback. - * @param {integer} metadata.maxResolvedArgs - * The maximum number of arguments which may be passed to the - * callback created by the wrapped async function. - * - * @returns {function} - * The generated callback function. - */ - - - const makeCallback = (promise, metadata) => { - return (...callbackArgs) => { - if (extensionAPIs.runtime.lastError) { - promise.reject(extensionAPIs.runtime.lastError); - } else if (metadata.singleCallbackArg || callbackArgs.length <= 1 && metadata.singleCallbackArg !== false) { - promise.resolve(callbackArgs[0]); - } else { - promise.resolve(callbackArgs); - } - }; - }; - - const pluralizeArguments = numArgs => numArgs == 1 ? "argument" : "arguments"; - /** - * Creates a wrapper function for a method with the given name and metadata. - * - * @param {string} name - * The name of the method which is being wrapped. - * @param {object} metadata - * Metadata about the method being wrapped. - * @param {integer} metadata.minArgs - * The minimum number of arguments which must be passed to the - * function. If called with fewer than this number of arguments, the - * wrapper will raise an exception. - * @param {integer} metadata.maxArgs - * The maximum number of arguments which may be passed to the - * function. If called with more than this number of arguments, the - * wrapper will raise an exception. - * @param {integer} metadata.maxResolvedArgs - * The maximum number of arguments which may be passed to the - * callback created by the wrapped async function. - * - * @returns {function(object, ...*)} - * The generated wrapper function. - */ - - - const wrapAsyncFunction = (name, metadata) => { - return function asyncFunctionWrapper(target, ...args) { - if (args.length < metadata.minArgs) { - throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`); - } - - if (args.length > metadata.maxArgs) { - throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`); - } - - return new Promise((resolve, reject) => { - if (metadata.fallbackToNoCallback) { - // This API method has currently no callback on Chrome, but it return a promise on Firefox, - // and so the polyfill will try to call it with a callback first, and it will fallback - // to not passing the callback if the first call fails. - try { - target[name](...args, makeCallback({ - resolve, - reject - }, metadata)); - } catch (cbError) { - console.warn(`${name} API method doesn't seem to support the callback parameter, ` + "falling back to call it without a callback: ", cbError); - target[name](...args); // Update the API method metadata, so that the next API calls will not try to - // use the unsupported callback anymore. - - metadata.fallbackToNoCallback = false; - metadata.noCallback = true; - resolve(); - } - } else if (metadata.noCallback) { - target[name](...args); - resolve(); - } else { - target[name](...args, makeCallback({ - resolve, - reject - }, metadata)); - } - }); - }; - }; - /** - * Wraps an existing method of the target object, so that calls to it are - * intercepted by the given wrapper function. The wrapper function receives, - * as its first argument, the original `target` object, followed by each of - * the arguments passed to the original method. - * - * @param {object} target - * The original target object that the wrapped method belongs to. - * @param {function} method - * The method being wrapped. This is used as the target of the Proxy - * object which is created to wrap the method. - * @param {function} wrapper - * The wrapper function which is called in place of a direct invocation - * of the wrapped method. - * - * @returns {Proxy} - * A Proxy object for the given method, which invokes the given wrapper - * method in its place. - */ - - - const wrapMethod = (target, method, wrapper) => { - return new Proxy(method, { - apply(targetMethod, thisObj, args) { - return wrapper.call(thisObj, target, ...args); - } - - }); - }; - - let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty); - /** - * Wraps an object in a Proxy which intercepts and wraps certain methods - * based on the given `wrappers` and `metadata` objects. - * - * @param {object} target - * The target object to wrap. - * - * @param {object} [wrappers = {}] - * An object tree containing wrapper functions for special cases. Any - * function present in this object tree is called in place of the - * method in the same location in the `target` object tree. These - * wrapper methods are invoked as described in {@see wrapMethod}. - * - * @param {object} [metadata = {}] - * An object tree containing metadata used to automatically generate - * Promise-based wrapper functions for asynchronous. Any function in - * the `target` object tree which has a corresponding metadata object - * in the same location in the `metadata` tree is replaced with an - * automatically-generated wrapper function, as described in - * {@see wrapAsyncFunction} - * - * @returns {Proxy} - */ - - const wrapObject = (target, wrappers = {}, metadata = {}) => { - let cache = Object.create(null); - let handlers = { - has(proxyTarget, prop) { - return prop in target || prop in cache; - }, - - get(proxyTarget, prop, receiver) { - if (prop in cache) { - return cache[prop]; - } - - if (!(prop in target)) { - return undefined; - } - - let value = target[prop]; - - if (typeof value === "function") { - // This is a method on the underlying object. Check if we need to do - // any wrapping. - if (typeof wrappers[prop] === "function") { - // We have a special-case wrapper for this method. - value = wrapMethod(target, target[prop], wrappers[prop]); - } else if (hasOwnProperty(metadata, prop)) { - // This is an async method that we have metadata for. Create a - // Promise wrapper for it. - let wrapper = wrapAsyncFunction(prop, metadata[prop]); - value = wrapMethod(target, target[prop], wrapper); - } else { - // This is a method that we don't know or care about. Return the - // original method, bound to the underlying object. - value = value.bind(target); - } - } else if (typeof value === "object" && value !== null && (hasOwnProperty(wrappers, prop) || hasOwnProperty(metadata, prop))) { - // This is an object that we need to do some wrapping for the children - // of. Create a sub-object wrapper for it with the appropriate child - // metadata. - value = wrapObject(value, wrappers[prop], metadata[prop]); - } else if (hasOwnProperty(metadata, "*")) { - // Wrap all properties in * namespace. - value = wrapObject(value, wrappers[prop], metadata["*"]); - } else { - // We don't need to do any wrapping for this property, - // so just forward all access to the underlying object. - Object.defineProperty(cache, prop, { - configurable: true, - enumerable: true, - - get() { - return target[prop]; - }, - - set(value) { - target[prop] = value; - } - - }); - return value; - } - - cache[prop] = value; - return value; - }, - - set(proxyTarget, prop, value, receiver) { - if (prop in cache) { - cache[prop] = value; - } else { - target[prop] = value; - } - - return true; - }, - - defineProperty(proxyTarget, prop, desc) { - return Reflect.defineProperty(cache, prop, desc); - }, - - deleteProperty(proxyTarget, prop) { - return Reflect.deleteProperty(cache, prop); - } - - }; // Per contract of the Proxy API, the "get" proxy handler must return the - // original value of the target if that value is declared read-only and - // non-configurable. For this reason, we create an object with the - // prototype set to `target` instead of using `target` directly. - // Otherwise we cannot return a custom object for APIs that - // are declared read-only and non-configurable, such as `chrome.devtools`. - // - // The proxy handlers themselves will still use the original `target` - // instead of the `proxyTarget`, so that the methods and properties are - // dereferenced via the original targets. - - let proxyTarget = Object.create(target); - return new Proxy(proxyTarget, handlers); - }; - /** - * Creates a set of wrapper functions for an event object, which handles - * wrapping of listener functions that those messages are passed. - * - * A single wrapper is created for each listener function, and stored in a - * map. Subsequent calls to `addListener`, `hasListener`, or `removeListener` - * retrieve the original wrapper, so that attempts to remove a - * previously-added listener work as expected. - * - * @param {DefaultWeakMap} wrapperMap - * A DefaultWeakMap object which will create the appropriate wrapper - * for a given listener function when one does not exist, and retrieve - * an existing one when it does. - * - * @returns {object} - */ - - - const wrapEvent = wrapperMap => ({ - addListener(target, listener, ...args) { - target.addListener(wrapperMap.get(listener), ...args); - }, - - hasListener(target, listener) { - return target.hasListener(wrapperMap.get(listener)); - }, - - removeListener(target, listener) { - target.removeListener(wrapperMap.get(listener)); - } - - }); // Keep track if the deprecation warning has been logged at least once. - - - let loggedSendResponseDeprecationWarning = false; - const onMessageWrappers = new DefaultWeakMap(listener => { - if (typeof listener !== "function") { - return listener; - } - /** - * Wraps a message listener function so that it may send responses based on - * its return value, rather than by returning a sentinel value and calling a - * callback. If the listener function returns a Promise, the response is - * sent when the promise either resolves or rejects. - * - * @param {*} message - * The message sent by the other end of the channel. - * @param {object} sender - * Details about the sender of the message. - * @param {function(*)} sendResponse - * A callback which, when called with an arbitrary argument, sends - * that value as a response. - * @returns {boolean} - * True if the wrapped listener returned a Promise, which will later - * yield a response. False otherwise. - */ - - - return function onMessage(message, sender, sendResponse) { - let didCallSendResponse = false; - let wrappedSendResponse; - let sendResponsePromise = new Promise(resolve => { - wrappedSendResponse = function (response) { - if (!loggedSendResponseDeprecationWarning) { - console.warn(SEND_RESPONSE_DEPRECATION_WARNING, new Error().stack); - loggedSendResponseDeprecationWarning = true; - } - - didCallSendResponse = true; - resolve(response); - }; - }); - let result; - - try { - result = listener(message, sender, wrappedSendResponse); - } catch (err) { - result = Promise.reject(err); - } - - const isResultThenable = result !== true && isThenable(result); // If the listener didn't returned true or a Promise, or called - // wrappedSendResponse synchronously, we can exit earlier - // because there will be no response sent from this listener. - - if (result !== true && !isResultThenable && !didCallSendResponse) { - return false; - } // A small helper to send the message if the promise resolves - // and an error if the promise rejects (a wrapped sendMessage has - // to translate the message into a resolved promise or a rejected - // promise). - - - const sendPromisedResult = promise => { - promise.then(msg => { - // send the message value. - sendResponse(msg); - }, error => { - // Send a JSON representation of the error if the rejected value - // is an instance of error, or the object itself otherwise. - let message; - - if (error && (error instanceof Error || typeof error.message === "string")) { - message = error.message; - } else { - message = "An unexpected error occurred"; - } - - sendResponse({ - __mozWebExtensionPolyfillReject__: true, - message - }); - }).catch(err => { - // Print an error on the console if unable to send the response. - console.error("Failed to send onMessage rejected reply", err); - }); - }; // If the listener returned a Promise, send the resolved value as a - // result, otherwise wait the promise related to the wrappedSendResponse - // callback to resolve and send it as a response. - - - if (isResultThenable) { - sendPromisedResult(result); - } else { - sendPromisedResult(sendResponsePromise); - } // Let Chrome know that the listener is replying. - - - return true; - }; - }); - - const wrappedSendMessageCallback = ({ - reject, - resolve - }, reply) => { - if (extensionAPIs.runtime.lastError) { - // Detect when none of the listeners replied to the sendMessage call and resolve - // the promise to undefined as in Firefox. - // See https://github.com/mozilla/webextension-polyfill/issues/130 - if (extensionAPIs.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) { - resolve(); - } else { - reject(extensionAPIs.runtime.lastError); - } - } else if (reply && reply.__mozWebExtensionPolyfillReject__) { - // Convert back the JSON representation of the error into - // an Error instance. - reject(new Error(reply.message)); - } else { - resolve(reply); - } - }; - - const wrappedSendMessage = (name, metadata, apiNamespaceObj, ...args) => { - if (args.length < metadata.minArgs) { - throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`); - } - - if (args.length > metadata.maxArgs) { - throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`); - } - - return new Promise((resolve, reject) => { - const wrappedCb = wrappedSendMessageCallback.bind(null, { - resolve, - reject - }); - args.push(wrappedCb); - apiNamespaceObj.sendMessage(...args); - }); - }; - - const staticWrappers = { - runtime: { - onMessage: wrapEvent(onMessageWrappers), - onMessageExternal: wrapEvent(onMessageWrappers), - sendMessage: wrappedSendMessage.bind(null, "sendMessage", { - minArgs: 1, - maxArgs: 3 - }) - }, - tabs: { - sendMessage: wrappedSendMessage.bind(null, "sendMessage", { - minArgs: 2, - maxArgs: 3 - }) - } - }; - const settingMetadata = { - clear: { - minArgs: 1, - maxArgs: 1 - }, - get: { - minArgs: 1, - maxArgs: 1 - }, - set: { - minArgs: 1, - maxArgs: 1 - } - }; - apiMetadata.privacy = { - network: { - "*": settingMetadata - }, - services: { - "*": settingMetadata - }, - websites: { - "*": settingMetadata - } - }; - return wrapObject(extensionAPIs, staticWrappers, apiMetadata); - }; - - if (typeof chrome != "object" || !chrome || !chrome.runtime || !chrome.runtime.id) { - throw new Error("This script should only be loaded in a browser extension."); - } // The build process adds a UMD wrapper around this file, which makes the - // `module` variable available. - - - module.exports = wrapAPIs(chrome); - } else { - module.exports = browser; - } -}); diff --git a/src/lib/include.js b/src/lib/include.js deleted file mode 100644 index 896c6d5..0000000 --- a/src/lib/include.js +++ /dev/null @@ -1,35 +0,0 @@ -var include = (() => -{ - let _inclusions = new Map(); - - function scriptLoader(src) { - let script = document.createElement("script"); - script.src = src; - return script; - } - - function styleLoader(src) { - let style = document.createElement("link"); - style.rel = "stylesheet"; - style.type = "text/css"; - style.href = src; - return style; - } - - return async function include(src) { - if (_inclusions.has(src)) return await _inclusions.get(src); - if (Array.isArray(src)) { - return await Promise.all(src.map(s => include(s))); - } - debug("Including", src); - - let loading = new Promise((resolve, reject) => { - let inc = src.endsWith(".css") ? styleLoader(src) : scriptLoader(src); - inc.onload = () => resolve(inc); - inc.onerror = () => reject(new Error(`Failed to load ${src}`)); - document.head.appendChild(inc); - }); - _inclusions.set(src, loading); - return await (loading); - } -})(); diff --git a/src/lib/log.js b/src/lib/log.js deleted file mode 100644 index a30a5a6..0000000 --- a/src/lib/log.js +++ /dev/null @@ -1,19 +0,0 @@ - -{ - let PREFIX = typeof browser === "object" - ? `[${browser.runtime.getManifest().name}]` : ''; - - let debugCount = 0; - - function log(msg, ...rest) { - console.log(`${PREFIX} ${msg}`, ...rest); - } - - function debug(msg, ...rest) { - console.debug(`${PREFIX}:${debugCount++} ${msg}`, ...rest); - } - - function error(e, msg, ...rest) { - console.error(`${PREFIX} ${msg}`, ...rest, e, e.message, e.stack); - } -} diff --git a/src/lib/restricted.js b/src/lib/restricted.js deleted file mode 100644 index 840ab69..0000000 --- a/src/lib/restricted.js +++ /dev/null @@ -1,31 +0,0 @@ -{ - // see https://bugzilla.mozilla.org/show_bug.cgi?id=1415644 - let domains = UA.isMozilla ? [ - "accounts-static.cdn.mozilla.net", - "accounts.firefox.com", - "addons.cdn.mozilla.net", - "addons.mozilla.org", - "api.accounts.firefox.com", - "content.cdn.mozilla.net", - "content.cdn.mozilla.net", - "discovery.addons.mozilla.org", - "input.mozilla.org", - "install.mozilla.org", - "oauth.accounts.firefox.com", - "profile.accounts.firefox.com", - "support.mozilla.org", - "sync.services.mozilla.com", - "testpilot.firefox.com", - ] : [ "chrome.google.com" ]; - - function isRestrictedURL(u) { - try { - if (typeof u === "string") u = new URL(u); - let {protocol, hostname} = u; - return (!/^(?:https?|file|data):$/.test(protocol)) - || protocol === "https:" && hostname && domains.includes(tld.normalize(hostname)); - } catch (e) { - return false; - } - } -} diff --git a/src/lib/sha256.js b/src/lib/sha256.js deleted file mode 100644 index 14bcf45..0000000 --- a/src/lib/sha256.js +++ /dev/null @@ -1,518 +0,0 @@ -/** - * [js-sha256]{@link https://github.com/emn178/js-sha256} - * - * @version 0.9.0 - * @author Chen, Yi-Cyuan [emn178@gmail.com] - * @copyright Chen, Yi-Cyuan 2014-2017 - * @license MIT - */ -/*jslint bitwise: true */ -(function () { - 'use strict'; - - var ERROR = 'input is invalid type'; - var WINDOW = typeof window === 'object'; - var root = WINDOW ? window : {}; - if (root.JS_SHA256_NO_WINDOW) { - WINDOW = false; - } - var WEB_WORKER = !WINDOW && typeof self === 'object'; - var NODE_JS = !root.JS_SHA256_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node; - if (NODE_JS) { - root = global; - } else if (WEB_WORKER) { - root = self; - } - var COMMON_JS = !root.JS_SHA256_NO_COMMON_JS && typeof module === 'object' && module.exports; - var AMD = typeof define === 'function' && define.amd; - var ARRAY_BUFFER = !root.JS_SHA256_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined'; - var HEX_CHARS = '0123456789abcdef'.split(''); - var EXTRA = [-2147483648, 8388608, 32768, 128]; - var SHIFT = [24, 16, 8, 0]; - var K = [ - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 - ]; - var OUTPUT_TYPES = ['hex', 'array', 'digest', 'arrayBuffer']; - - var blocks = []; - - if (root.JS_SHA256_NO_NODE_JS || !Array.isArray) { - Array.isArray = function (obj) { - return Object.prototype.toString.call(obj) === '[object Array]'; - }; - } - - if (ARRAY_BUFFER && (root.JS_SHA256_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) { - ArrayBuffer.isView = function (obj) { - return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer; - }; - } - - var createOutputMethod = function (outputType, is224) { - return function (message) { - return new Sha256(is224, true).update(message)[outputType](); - }; - }; - - var createMethod = function (is224) { - var method = createOutputMethod('hex', is224); - if (NODE_JS) { - method = nodeWrap(method, is224); - } - method.create = function () { - return new Sha256(is224); - }; - method.update = function (message) { - return method.create().update(message); - }; - for (var i = 0; i < OUTPUT_TYPES.length; ++i) { - var type = OUTPUT_TYPES[i]; - method[type] = createOutputMethod(type, is224); - } - return method; - }; - - var nodeWrap = function (method, is224) { - var crypto = eval("require('crypto')"); - var Buffer = eval("require('buffer').Buffer"); - var algorithm = is224 ? 'sha224' : 'sha256'; - var nodeMethod = function (message) { - if (typeof message === 'string') { - return crypto.createHash(algorithm).update(message, 'utf8').digest('hex'); - } else { - if (message === null || message === undefined) { - throw new Error(ERROR); - } else if (message.constructor === ArrayBuffer) { - message = new Uint8Array(message); - } - } - if (Array.isArray(message) || ArrayBuffer.isView(message) || - message.constructor === Buffer) { - return crypto.createHash(algorithm).update(new Buffer(message)).digest('hex'); - } else { - return method(message); - } - }; - return nodeMethod; - }; - - var createHmacOutputMethod = function (outputType, is224) { - return function (key, message) { - return new HmacSha256(key, is224, true).update(message)[outputType](); - }; - }; - - var createHmacMethod = function (is224) { - var method = createHmacOutputMethod('hex', is224); - method.create = function (key) { - return new HmacSha256(key, is224); - }; - method.update = function (key, message) { - return method.create(key).update(message); - }; - for (var i = 0; i < OUTPUT_TYPES.length; ++i) { - var type = OUTPUT_TYPES[i]; - method[type] = createHmacOutputMethod(type, is224); - } - return method; - }; - - function Sha256(is224, sharedMemory) { - if (sharedMemory) { - blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = - blocks[4] = blocks[5] = blocks[6] = blocks[7] = - blocks[8] = blocks[9] = blocks[10] = blocks[11] = - blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; - this.blocks = blocks; - } else { - this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - } - - if (is224) { - this.h0 = 0xc1059ed8; - this.h1 = 0x367cd507; - this.h2 = 0x3070dd17; - this.h3 = 0xf70e5939; - this.h4 = 0xffc00b31; - this.h5 = 0x68581511; - this.h6 = 0x64f98fa7; - this.h7 = 0xbefa4fa4; - } else { // 256 - this.h0 = 0x6a09e667; - this.h1 = 0xbb67ae85; - this.h2 = 0x3c6ef372; - this.h3 = 0xa54ff53a; - this.h4 = 0x510e527f; - this.h5 = 0x9b05688c; - this.h6 = 0x1f83d9ab; - this.h7 = 0x5be0cd19; - } - - this.block = this.start = this.bytes = this.hBytes = 0; - this.finalized = this.hashed = false; - this.first = true; - this.is224 = is224; - } - - Sha256.prototype.update = function (message) { - if (this.finalized) { - return; - } - var notString, type = typeof message; - if (type !== 'string') { - if (type === 'object') { - if (message === null) { - throw new Error(ERROR); - } else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) { - message = new Uint8Array(message); - } else if (!Array.isArray(message)) { - if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) { - throw new Error(ERROR); - } - } - } else { - throw new Error(ERROR); - } - notString = true; - } - var code, index = 0, i, length = message.length, blocks = this.blocks; - - while (index < length) { - if (this.hashed) { - this.hashed = false; - blocks[0] = this.block; - blocks[16] = blocks[1] = blocks[2] = blocks[3] = - blocks[4] = blocks[5] = blocks[6] = blocks[7] = - blocks[8] = blocks[9] = blocks[10] = blocks[11] = - blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; - } - - if (notString) { - for (i = this.start; index < length && i < 64; ++index) { - blocks[i >> 2] |= message[index] << SHIFT[i++ & 3]; - } - } else { - for (i = this.start; index < length && i < 64; ++index) { - code = message.charCodeAt(index); - if (code < 0x80) { - blocks[i >> 2] |= code << SHIFT[i++ & 3]; - } else if (code < 0x800) { - blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; - } else if (code < 0xd800 || code >= 0xe000) { - blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; - } else { - code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); - blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; - } - } - } - - this.lastByteIndex = i; - this.bytes += i - this.start; - if (i >= 64) { - this.block = blocks[16]; - this.start = i - 64; - this.hash(); - this.hashed = true; - } else { - this.start = i; - } - } - if (this.bytes > 4294967295) { - this.hBytes += this.bytes / 4294967296 << 0; - this.bytes = this.bytes % 4294967296; - } - return this; - }; - - Sha256.prototype.finalize = function () { - if (this.finalized) { - return; - } - this.finalized = true; - var blocks = this.blocks, i = this.lastByteIndex; - blocks[16] = this.block; - blocks[i >> 2] |= EXTRA[i & 3]; - this.block = blocks[16]; - if (i >= 56) { - if (!this.hashed) { - this.hash(); - } - blocks[0] = this.block; - blocks[16] = blocks[1] = blocks[2] = blocks[3] = - blocks[4] = blocks[5] = blocks[6] = blocks[7] = - blocks[8] = blocks[9] = blocks[10] = blocks[11] = - blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; - } - blocks[14] = this.hBytes << 3 | this.bytes >>> 29; - blocks[15] = this.bytes << 3; - this.hash(); - }; - - Sha256.prototype.hash = function () { - var a = this.h0, b = this.h1, c = this.h2, d = this.h3, e = this.h4, f = this.h5, g = this.h6, - h = this.h7, blocks = this.blocks, j, s0, s1, maj, t1, t2, ch, ab, da, cd, bc; - - for (j = 16; j < 64; ++j) { - // rightrotate - t1 = blocks[j - 15]; - s0 = ((t1 >>> 7) | (t1 << 25)) ^ ((t1 >>> 18) | (t1 << 14)) ^ (t1 >>> 3); - t1 = blocks[j - 2]; - s1 = ((t1 >>> 17) | (t1 << 15)) ^ ((t1 >>> 19) | (t1 << 13)) ^ (t1 >>> 10); - blocks[j] = blocks[j - 16] + s0 + blocks[j - 7] + s1 << 0; - } - - bc = b & c; - for (j = 0; j < 64; j += 4) { - if (this.first) { - if (this.is224) { - ab = 300032; - t1 = blocks[0] - 1413257819; - h = t1 - 150054599 << 0; - d = t1 + 24177077 << 0; - } else { - ab = 704751109; - t1 = blocks[0] - 210244248; - h = t1 - 1521486534 << 0; - d = t1 + 143694565 << 0; - } - this.first = false; - } else { - s0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10)); - s1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7)); - ab = a & b; - maj = ab ^ (a & c) ^ bc; - ch = (e & f) ^ (~e & g); - t1 = h + s1 + ch + K[j] + blocks[j]; - t2 = s0 + maj; - h = d + t1 << 0; - d = t1 + t2 << 0; - } - s0 = ((d >>> 2) | (d << 30)) ^ ((d >>> 13) | (d << 19)) ^ ((d >>> 22) | (d << 10)); - s1 = ((h >>> 6) | (h << 26)) ^ ((h >>> 11) | (h << 21)) ^ ((h >>> 25) | (h << 7)); - da = d & a; - maj = da ^ (d & b) ^ ab; - ch = (h & e) ^ (~h & f); - t1 = g + s1 + ch + K[j + 1] + blocks[j + 1]; - t2 = s0 + maj; - g = c + t1 << 0; - c = t1 + t2 << 0; - s0 = ((c >>> 2) | (c << 30)) ^ ((c >>> 13) | (c << 19)) ^ ((c >>> 22) | (c << 10)); - s1 = ((g >>> 6) | (g << 26)) ^ ((g >>> 11) | (g << 21)) ^ ((g >>> 25) | (g << 7)); - cd = c & d; - maj = cd ^ (c & a) ^ da; - ch = (g & h) ^ (~g & e); - t1 = f + s1 + ch + K[j + 2] + blocks[j + 2]; - t2 = s0 + maj; - f = b + t1 << 0; - b = t1 + t2 << 0; - s0 = ((b >>> 2) | (b << 30)) ^ ((b >>> 13) | (b << 19)) ^ ((b >>> 22) | (b << 10)); - s1 = ((f >>> 6) | (f << 26)) ^ ((f >>> 11) | (f << 21)) ^ ((f >>> 25) | (f << 7)); - bc = b & c; - maj = bc ^ (b & d) ^ cd; - ch = (f & g) ^ (~f & h); - t1 = e + s1 + ch + K[j + 3] + blocks[j + 3]; - t2 = s0 + maj; - e = a + t1 << 0; - a = t1 + t2 << 0; - } - - this.h0 = this.h0 + a << 0; - this.h1 = this.h1 + b << 0; - this.h2 = this.h2 + c << 0; - this.h3 = this.h3 + d << 0; - this.h4 = this.h4 + e << 0; - this.h5 = this.h5 + f << 0; - this.h6 = this.h6 + g << 0; - this.h7 = this.h7 + h << 0; - }; - - Sha256.prototype.hex = function () { - this.finalize(); - - var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4, h5 = this.h5, - h6 = this.h6, h7 = this.h7; - - var hex = HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] + - HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] + - HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] + - HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] + - HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] + - HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] + - HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] + - HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] + - HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] + - HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] + - HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] + - HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] + - HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F] + - HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] + - HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] + - HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] + - HEX_CHARS[(h4 >> 28) & 0x0F] + HEX_CHARS[(h4 >> 24) & 0x0F] + - HEX_CHARS[(h4 >> 20) & 0x0F] + HEX_CHARS[(h4 >> 16) & 0x0F] + - HEX_CHARS[(h4 >> 12) & 0x0F] + HEX_CHARS[(h4 >> 8) & 0x0F] + - HEX_CHARS[(h4 >> 4) & 0x0F] + HEX_CHARS[h4 & 0x0F] + - HEX_CHARS[(h5 >> 28) & 0x0F] + HEX_CHARS[(h5 >> 24) & 0x0F] + - HEX_CHARS[(h5 >> 20) & 0x0F] + HEX_CHARS[(h5 >> 16) & 0x0F] + - HEX_CHARS[(h5 >> 12) & 0x0F] + HEX_CHARS[(h5 >> 8) & 0x0F] + - HEX_CHARS[(h5 >> 4) & 0x0F] + HEX_CHARS[h5 & 0x0F] + - HEX_CHARS[(h6 >> 28) & 0x0F] + HEX_CHARS[(h6 >> 24) & 0x0F] + - HEX_CHARS[(h6 >> 20) & 0x0F] + HEX_CHARS[(h6 >> 16) & 0x0F] + - HEX_CHARS[(h6 >> 12) & 0x0F] + HEX_CHARS[(h6 >> 8) & 0x0F] + - HEX_CHARS[(h6 >> 4) & 0x0F] + HEX_CHARS[h6 & 0x0F]; - if (!this.is224) { - hex += HEX_CHARS[(h7 >> 28) & 0x0F] + HEX_CHARS[(h7 >> 24) & 0x0F] + - HEX_CHARS[(h7 >> 20) & 0x0F] + HEX_CHARS[(h7 >> 16) & 0x0F] + - HEX_CHARS[(h7 >> 12) & 0x0F] + HEX_CHARS[(h7 >> 8) & 0x0F] + - HEX_CHARS[(h7 >> 4) & 0x0F] + HEX_CHARS[h7 & 0x0F]; - } - return hex; - }; - - Sha256.prototype.toString = Sha256.prototype.hex; - - Sha256.prototype.digest = function () { - this.finalize(); - - var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4, h5 = this.h5, - h6 = this.h6, h7 = this.h7; - - var arr = [ - (h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, h0 & 0xFF, - (h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, h1 & 0xFF, - (h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, h2 & 0xFF, - (h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, h3 & 0xFF, - (h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, h4 & 0xFF, - (h5 >> 24) & 0xFF, (h5 >> 16) & 0xFF, (h5 >> 8) & 0xFF, h5 & 0xFF, - (h6 >> 24) & 0xFF, (h6 >> 16) & 0xFF, (h6 >> 8) & 0xFF, h6 & 0xFF - ]; - if (!this.is224) { - arr.push((h7 >> 24) & 0xFF, (h7 >> 16) & 0xFF, (h7 >> 8) & 0xFF, h7 & 0xFF); - } - return arr; - }; - - Sha256.prototype.array = Sha256.prototype.digest; - - Sha256.prototype.arrayBuffer = function () { - this.finalize(); - - var buffer = new ArrayBuffer(this.is224 ? 28 : 32); - var dataView = new DataView(buffer); - dataView.setUint32(0, this.h0); - dataView.setUint32(4, this.h1); - dataView.setUint32(8, this.h2); - dataView.setUint32(12, this.h3); - dataView.setUint32(16, this.h4); - dataView.setUint32(20, this.h5); - dataView.setUint32(24, this.h6); - if (!this.is224) { - dataView.setUint32(28, this.h7); - } - return buffer; - }; - - function HmacSha256(key, is224, sharedMemory) { - var i, type = typeof key; - if (type === 'string') { - var bytes = [], length = key.length, index = 0, code; - for (i = 0; i < length; ++i) { - code = key.charCodeAt(i); - if (code < 0x80) { - bytes[index++] = code; - } else if (code < 0x800) { - bytes[index++] = (0xc0 | (code >> 6)); - bytes[index++] = (0x80 | (code & 0x3f)); - } else if (code < 0xd800 || code >= 0xe000) { - bytes[index++] = (0xe0 | (code >> 12)); - bytes[index++] = (0x80 | ((code >> 6) & 0x3f)); - bytes[index++] = (0x80 | (code & 0x3f)); - } else { - code = 0x10000 + (((code & 0x3ff) << 10) | (key.charCodeAt(++i) & 0x3ff)); - bytes[index++] = (0xf0 | (code >> 18)); - bytes[index++] = (0x80 | ((code >> 12) & 0x3f)); - bytes[index++] = (0x80 | ((code >> 6) & 0x3f)); - bytes[index++] = (0x80 | (code & 0x3f)); - } - } - key = bytes; - } else { - if (type === 'object') { - if (key === null) { - throw new Error(ERROR); - } else if (ARRAY_BUFFER && key.constructor === ArrayBuffer) { - key = new Uint8Array(key); - } else if (!Array.isArray(key)) { - if (!ARRAY_BUFFER || !ArrayBuffer.isView(key)) { - throw new Error(ERROR); - } - } - } else { - throw new Error(ERROR); - } - } - - if (key.length > 64) { - key = (new Sha256(is224, true)).update(key).array(); - } - - var oKeyPad = [], iKeyPad = []; - for (i = 0; i < 64; ++i) { - var b = key[i] || 0; - oKeyPad[i] = 0x5c ^ b; - iKeyPad[i] = 0x36 ^ b; - } - - Sha256.call(this, is224, sharedMemory); - - this.update(iKeyPad); - this.oKeyPad = oKeyPad; - this.inner = true; - this.sharedMemory = sharedMemory; - } - HmacSha256.prototype = new Sha256(); - - HmacSha256.prototype.finalize = function () { - Sha256.prototype.finalize.call(this); - if (this.inner) { - this.inner = false; - var innerHash = this.array(); - Sha256.call(this, this.is224, this.sharedMemory); - this.update(this.oKeyPad); - this.update(innerHash); - Sha256.prototype.finalize.call(this); - } - }; - - var exports = createMethod(); - exports.sha256 = exports; - exports.sha224 = createMethod(true); - exports.sha256.hmac = createHmacMethod(); - exports.sha224.hmac = createHmacMethod(true); - - if (COMMON_JS) { - module.exports = exports; - } else { - root.sha256 = exports.sha256; - root.sha224 = exports.sha224; - if (AMD) { - define(function () { - return exports; - }); - } - } -})(); diff --git a/src/lib/uuid.js b/src/lib/uuid.js deleted file mode 100644 index 43f6d50..0000000 --- a/src/lib/uuid.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -function uuid() { - try { - return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, - c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4) - .toString(16)); - } catch (e) { - // fallback, as the Tor Browser seems to fail above - uuid = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } -} diff --git a/src/manifest.json b/src/manifest.json index 93fc234..76cea50 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -34,30 +34,30 @@ "background": { "persistent": true, "scripts": [ - "lib/UA.js", - "lib/browser-polyfill.js", - "lib/uuid.js", - "nscl/common/SyncMessage.js", - "lib/log.js", - "lib/include.js", - "nscl/lib/punycode.js", - "nscl/common/tld.js", - "lib/LastListener.js", - "nscl/common/Messages.js", - "lib/CSP.js", - "lib/NetCSP.js", - "lib/TabCache.js", - "common/CapsCSP.js", + "/nscl/lib/browser-polyfill.js", + "/nscl/lib/punycode.js", + "/nscl/common/UA.js", + "/nscl/common/uuid.js", + "/nscl/common/SyncMessage.js", + "/nscl/common/log.js", + "/nscl/common/tld.js", + "/nscl/common/Messages.js", + "/nscl/common/CSP.js", + "/nscl/common/NetCSP.js", + "/nscl/common/CapsCSP.js", "/nscl/common/RequestKey.js", "/nscl/common/Sites.js", "/nscl/common/Permissions.js", "/nscl/common/Policy.js", - "common/locale.js", - "common/SyntaxChecker.js", - "common/Storage.js", + "/nscl/common/locale.js", + "/nscl/common/SyntaxChecker.js", + "/nscl/common/Storage.js", + "/nscl/common/include.js", + "/nscl/service/LastListener.js", + "/nscl/service/prefetchCSSResources.js", + "/nscl/service/TabCache.js", "ui/Prompts.js", "xss/XSS.js", - "/nscl/service/prefetchCSSResources.js", "bg/ReportingCSP.js", "bg/deferWebTraffic.js", "bg/Defaults.js", @@ -91,28 +91,28 @@ "match_about_blank": true, "all_frames": true, "js": [ - "lib/UA.js", - "lib/browser-polyfill.js", - "nscl/common/SyncMessage.js", - "lib/log.js", - "lib/uuid.js", - "lib/sha256.js", - "nscl/common/Messages.js", - "lib/CSP.js", - "nscl/content/patchWindow.js", - "common/CapsCSP.js", - "nscl/common/RequestKey.js", - "content/DocumentCSP.js", - "nscl/content/NoscriptElements.js", + "/nscl/lib/browser-polyfill.js", + "/nscl/lib/sha256.js", + "/nscl/common/UA.js", + "/nscl/common/uuid.js", + "/nscl/common/log.js", + "/nscl/common/SyncMessage.js", + "/nscl/common/Messages.js", + "/nscl/common/CSP.js", + "/nscl/common/CapsCSP.js", + "/nscl/common/RequestKey.js", + "/nscl/content/patchWindow.js", + "/nscl/content/DocumentCSP.js", + "/nscl/content/NoscriptElements.js", "/nscl/content/prefetchCSSResources.js", + "/nscl/content/PlaceHolder.js", + "/nscl/content/sanitizePaste.js", "content/onScriptDisabled.js", "content/staticNS.js", - "content/PlaceHolder.js", - "content/content.js", + "/nscl/content/media.js", + "/nscl/content/webglHook.js", "content/embeddingDocument.js", - "content/webglHook.js", - "content/media.js", - "content/sanitizePaste.js" + "content/content.js" ] }, diff --git a/src/nscl b/src/nscl index 22cc68f..745b8d0 160000 --- a/src/nscl +++ b/src/nscl @@ -1 +1 @@ -Subproject commit 22cc68f13021f67e546bcba27ebf86a7e3a55881 +Subproject commit 745b8d07daa9cbeab399786969cf2bf761ad228c diff --git a/src/ui/options.html b/src/ui/options.html index 716c275..682efc5 100644 --- a/src/ui/options.html +++ b/src/ui/options.html @@ -8,12 +8,12 @@ - - - - + + + + + - diff --git a/src/ui/popup.html b/src/ui/popup.html index f64a261..9793dea 100644 --- a/src/ui/popup.html +++ b/src/ui/popup.html @@ -6,11 +6,11 @@ NoScript Settings - - - - - + + + + + diff --git a/src/ui/popup.js b/src/ui/popup.js index 1912f30..c276499 100644 --- a/src/ui/popup.js +++ b/src/ui/popup.js @@ -208,7 +208,7 @@ addEventListener("unload", e => { error(e, "Could not run scripts on %s: privileged page?", pageTab.url); } - await include("/lib/restricted.js"); + await include("/nscl/common/restricted.js"); let isRestricted = isRestrictedURL(pageTab.url); if (!isHttp || isRestricted) { showMessage("warning", _("privilegedPage")); diff --git a/src/ui/prompt.html b/src/ui/prompt.html index 7761128..ea70fb0 100644 --- a/src/ui/prompt.html +++ b/src/ui/prompt.html @@ -5,11 +5,11 @@ - - - - - + + + + + diff --git a/src/ui/siteInfo.html b/src/ui/siteInfo.html index c37fb53..a67cee2 100644 --- a/src/ui/siteInfo.html +++ b/src/ui/siteInfo.html @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/src/xss/InjectionCheckWorker.js b/src/xss/InjectionCheckWorker.js index e4d75c6..3ad6472 100644 --- a/src/xss/InjectionCheckWorker.js +++ b/src/xss/InjectionCheckWorker.js @@ -4,7 +4,7 @@ let include = src => { } let XSS = {}; -include("/lib/log.js"); +include("/nscl/common/log.js"); for (let logType of ["log", "debug", "error"]) { this[logType] = (...log) => { diff --git a/src/xss/InjectionChecker.js b/src/xss/InjectionChecker.js index 38f429f..f8ba0b2 100644 --- a/src/xss/InjectionChecker.js +++ b/src/xss/InjectionChecker.js @@ -1,8 +1,8 @@ XSS.InjectionChecker = (async () => { await include([ - "/common/SyntaxChecker.js", - "/lib/Base64.js", - "/lib/Timing.js", + "/nscl/common/SyntaxChecker.js", + "/nscl/common/Base64.js", + "/nscl/common/Timing.js", "/xss/FlashIdiocy.js", "/xss/ASPIdiocy.js", "/lib/he.js"]