diff options
-rw-r--r-- | src/bg/ChildPolicies.js | 200 | ||||
-rw-r--r-- | src/bg/RequestGuard.js | 21 | ||||
-rw-r--r-- | src/bg/main.js | 35 | ||||
-rw-r--r-- | src/common/Policy.js | 10 | ||||
-rw-r--r-- | src/content/dynamicNS.js | 21 | ||||
-rw-r--r-- | src/manifest.json | 1 |
6 files changed, 43 insertions, 245 deletions
diff --git a/src/bg/ChildPolicies.js b/src/bg/ChildPolicies.js deleted file mode 100644 index 58b18ca..0000000 --- a/src/bg/ChildPolicies.js +++ /dev/null @@ -1,200 +0,0 @@ -"use strict"; -{ - let marker = uuid(); - let allUrls = ["<all_urls>"]; - - let Scripts = { - references: new Set(), - opts: { - js: [{file: "/content/dynamicNS.js"}, {}], - allFrames: true, - matchAboutBlank: true, - runAt: "document_start" - }, - async init() { - this.init = this.forget; - }, - forget() { - for (let script of [...this.references]) { - script.unregister(); - this.references.delete(script); - } - }, - debug: false, - trace(code) { - return this.debug - ? `console.debug("Executing child policy on %s", document.URL, ${JSON.stringify(code)});${code}` - : code - ; - }, - async register(code, matches, excludeMatches) { - debug("Registering child policy.", code, matches, excludeMatches); - if (!matches.length) return; - try { - let opts = Object.assign({}, this.opts); - opts.js[1].code = this.trace(code); - opts.matches = matches; - if (excludeMatches && excludeMatches.length) { - opts.excludeMatches = excludeMatches; - } - this.references.add(await browser.contentScripts.register(opts)); - } catch (e) { - error(e); - } - }, - - buildPerms(perms) { - if (typeof perms !== "string") { - perms = JSON.stringify(perms); - } - return `ns.setup(${perms}, "${marker}");` - } - }; - - let flatten = arr => arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []); - - let protocolRx = /^(\w+):/i; - let portRx = /:\d+(?=\/|$)/; - let validMatchPatternRx = /^(?:\*|(?:http|ws|ftp)s?|file):\/\/(?:\*|(?:\*\.)?[\w\u0100-\uf000][\w\u0100-\uf000.-]*|\[[\w:]+\])?\/(\*|[^*]*)$/; - - let validMatchPattern = mp => validMatchPatternRx.test(mp); - - let siteKey2MatchPattern = site => { - let hasProtocol = site.match(protocolRx); - let mp = site; - if (hasProtocol) { - mp = Sites.cleanUrl(mp); - if (!mp) return false; - } else { - mp = Sites.isSecureDomainKey(site) ? "https://" : "*://"; - let hostname = Sites.toggleSecureDomainKey(site, false).replace(portRx, ''); - if (hostname && hostname !== ".") { - if (tld.isIp(hostname) || hostname.includes("*")) { - mp += hostname; - } else { - if (!tld.preserveFQDNs) hostname = tld.normalize(hostname); - mp += hostname.startsWith(".") ? `*${hostname}` : `*.${hostname}`; - } - } else { - mp += "*"; - } - if (!(hostname && hostname.includes("/"))) mp += "/"; - } - - return validMatchPattern(mp) && - (mp.endsWith("/") ? `${mp}*` : [mp, `${mp}?*`, `${mp}#*`]); - }; - - let withFQDNs = patterns => { - if (tld.preserveFQDNs) return patterns; - let rx = /^(?:\w+|\*):\/\/([^/]*[^.*/])/; - return patterns.concat( - patterns.map(p => p.replace(rx, (m, host) => tld.isIp(host) ? m : m + ".") - ).filter(validMatchPattern) - ); - }; - - let extraProtocols = patterns => patterns.concat( - patterns.filter(p => p.startsWith("*://")) - .map(p => p.replace("*", "ftp"))); - - let siteKeys2MatchPatterns = keys => - keys ? [... new Set( - extraProtocols(withFQDNs(flatten(keys.map(siteKey2MatchPattern)).filter(p => !!p))))] - : []; - - var ChildPolicies = { - addTabInfoCookie(request, info) { - let {tabId, frameId} = request; - let h = { - name: "Set-Cookie", - value: `${marker}_${tabId}_${frameId}=${JSON.stringify(info)}` - }; - let {responseHeaders} = request; - if (responseHeaders.some(({value, name}) => h.value === value && h.name === name)) { - return false; - } - responseHeaders.push(h); - return true; - }, - async update(policy, tracing) { - if (tracing !== "undefined") Scripts.debug = tracing; - let t0 = Date.now(); - await Scripts.init(); - - if (!policy.enforced) { - await Scripts.register(Scripts.buildPerms("null"), allUrls); - return; - } - - let serialized = policy.dry ? policy.dry(true) : policy; - let permsMap = new Map(); - let trusted = JSON.stringify(serialized.TRUSTED); - let untrusted = JSON.stringify(serialized.UNTRUSTED); - let presets = { - trusted, - untrusted, - temp: trusted - }; - // map presets to site keys - for (let [container, perms] of Object.entries(presets)) { - let newKeys = serialized.sites[container]; - if (!(newKeys && newKeys.length)) continue; - let keys = permsMap.get(perms); - if (keys) { - newKeys = keys.concat(newKeys); - } - permsMap.set(perms, newKeys); - } - // map custom permissions to site keys - for (let [key, perms] of Object.entries(serialized.sites.custom)) { - let permsKey = JSON.stringify(perms); - let keys = permsMap.get(permsKey); - if (keys) { - keys.push(key); - } else { - permsMap.set(permsKey, [key]); - } - } - - // compute exclusions - let permsMapEntries = [...permsMap]; - let excludeMap = new Map(); - - for (let [perms, keys] of permsMapEntries) { - excludeMap.set(perms, siteKeys2MatchPatterns(flatten( - permsMapEntries.filter(([other]) => other !== perms) - .map(([otherPerms, otherKeys]) => otherKeys)) - .filter(k => k && k.includes("/") && keys.some(by => Sites.isImplied(k, by))) - )); - } - - // register new content scripts - let registering = []; - let allMatching = []; - for (let [perms, keys] of [...permsMap]) { - let match = siteKeys2MatchPatterns(keys); - allMatching.push(...match); - registering.push(Scripts.register(Scripts.buildPerms(perms), match, excludeMap.get(perms))); - } - registering.push(Scripts.register(Scripts.buildPerms(serialized.DEFAULT), allUrls, allMatching)); - await Promise.all(registering); - if (tracing) { - debug("All the child policies registered in %sms", Date.now() - t0); - } - }, - - getForDocument(policy, url, context = null) { - return { - permissions: policy && policy.get(url, context).perms.dry(), - MARKER: marker - }; - }, - - }; - - if (!browser.contentScripts) { // #chromium fallback - Scripts.register = ChildPolicies.update = () => {}; - } - -} diff --git a/src/bg/RequestGuard.js b/src/bg/RequestGuard.js index f636f23..0de52f4 100644 --- a/src/bg/RequestGuard.js +++ b/src/bg/RequestGuard.js @@ -262,14 +262,11 @@ var RequestGuard = (() => { } function intersectCapabilities(perms, frameAncestors) { - if (frameAncestors && frameAncestors.length > 0 && ns.sync.cascadeRestrictions) { + if (frameAncestors && frameAncestors.length && ns.sync.cascadeRestrictions) { // cascade top document's restrictions to subframes - let topUrl = frameAncestors[frameAncestors.length - 1].url; - let topPerms = ns.policy.get(topUrl, topUrl).perms; - if (topPerms !== perms) { - let topCaps = topPerms.capabilities; - return new Set([...perms.capabilities].filter(c => topCaps.has(c))); - } + perms = policy.cascadeRestrictions(perms, + frameAncestors[frameAncestors.length - 1].url) + .capabilities; } return perms.capabilities; } @@ -347,20 +344,12 @@ var RequestGuard = (() => { if (isMainFrame) { if (policy.autoAllowTop && perms === policy.DEFAULT) { policy.set(Sites.optimalKey(url), perms = policy.TRUSTED.tempTwin); - promises.push(ChildPolicies.update(policy)); } capabilities = perms.capabilities; } else { capabilities = intersectCapabilities(perms, request.frameAncestors); } - } else { - if (isMainFrame || type === "sub_frame") { - let unrestricted = ns.unrestrictedTabs.has(tabId) && {unrestricted: true}; - if (unrestricted) { - headersModified = ChildPolicies.addTabInfoCookie(request, unrestricted); - } - } - } + } // else unrestricted, either globally or per-tab if (isMainFrame && !TabStatus.map.has(tabId)) { debug("No TabStatus data yet for noscriptFrame", tabId); TabStatus.record(request, "noscriptFrame", diff --git a/src/bg/main.js b/src/bg/main.js index 5d4f79f..ed53603 100644 --- a/src/bg/main.js +++ b/src/bg/main.js @@ -28,7 +28,6 @@ let policyData = (await Storage.get("sync", "policy")).policy; if (policyData && policyData.DEFAULT) { ns.policy = new Policy(policyData); - await ChildPolicies.update(policyData, ns.local.debug); } else { await include("/legacy/Legacy.js"); ns.policy = await Legacy.createOrMigratePolicy(); @@ -141,11 +140,25 @@ return await Settings.import(data); }, - async fetchChildPolicy({url, contextUrl}, sender) { - let {tab} = sender; + fetchChildPolicy({url, contextUrl}, sender) { if (!url) url = sender.url; - let policy = !Sites.isInternal(url) && ns.isEnforced(tab.id) ? ns.policy : null; - return ChildPolicies.getForDocument(policy, url, contextUrl || tab.url); + let {tab} = sender; + let tabUrl = tab.url; + if (!contextUrl) contextUrl = tabUrl; + + let policy = !Sites.isInternal(url) && ns.isEnforced(tab.id) + ? ns.policy : null; + + let permissions = Permissions.ALL; + if (policy) { + let perms = policy.get(url, contextUrl).perms; + if (tabUrl && ns.sync.cascadeRestrictions) { + perms = policy.cascadeRestrictions(perms, tabUrl); + } + permissions = perms.dry(); + } // otherwise either internal URL or unrestricted + + return {permissions}; }, async openStandalonePopup() { @@ -170,7 +183,13 @@ }, }; - + function onSyncMessage(msg, sender) { + switch(msg.id) { + case "fetchPolicy": + return messageHandler.fetchChildPolicy(msg, sender); + break; + } + } var ns = { running: false, @@ -190,6 +209,8 @@ if (this.running) return; this.running = true; + browser.runtime.onSyncMessage.addListener(onSyncMessage); + deferWebTraffic(init(), async () => { Commands.install(); @@ -208,6 +229,7 @@ stop() { if (!this.running) return; this.running = false; + browser.runtime.onSyncMessage.removeListener(onSyncMessage); Messages.removeHandler(messageHandler); RequestGuard.stop(); log("STOPPED"); @@ -215,7 +237,6 @@ async savePolicy() { if (this.policy) { - await ChildPolicies.update(this.policy, this.local.debug); await Storage.set("sync", { policy: this.policy.dry() }); diff --git a/src/common/Policy.js b/src/common/Policy.js index d67c24f..674263a 100644 --- a/src/common/Policy.js +++ b/src/common/Policy.js @@ -451,6 +451,16 @@ var {Permissions, Policy, Sites} = (() => { return JSON.stringify(this.dry(true)); } + cascadeRestrictions(perms, topUrl) { + let topPerms = ns.policy.get(topUrl, topUrl).perms; + if (topPerms !== perms) { + let topCaps = topPerms.capabilities; + perms = new Permissions([...perms.capabilities].filter(c => topCaps.has(c)), + perms.temp, perms.contextual); + } + return perms; + } + equals(other) { this.snapshot === other.snapshot; } diff --git a/src/content/dynamicNS.js b/src/content/dynamicNS.js deleted file mode 100644 index 51a2701..0000000 --- a/src/content/dynamicNS.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -// ensure the order which manifest scripts and dynamically registered scripts -// are executed in doesn't matter for initialization, by using a stub. - -if (!this.ns) { - let deferredSetup = null; - let nsStub = this.ns = { - config: {}, - setup(permissions, MARKER) { - deferredSetup = [permissions, MARKER]; - }, - merge: ns => { - ns.config = Object.assign(ns.config, nsStub.config); - this.ns = ns; - if (deferredSetup) { - ns.setup(...deferredSetup); - } - } - } -} diff --git a/src/manifest.json b/src/manifest.json index 0bd9338..55a351b 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -57,7 +57,6 @@ "xss/XSS.js", "bg/ReportingCSP.js", "bg/deferWebTraffic.js", - "bg/ChildPolicies.js", "bg/Defaults.js", "bg/RequestGuard.js", "bg/Settings.js", |