diff --git a/src/bg/RequestGuard.js b/src/bg/RequestGuard.js index dfbfd29..3bf7b91 100644 --- a/src/bg/RequestGuard.js +++ b/src/bg/RequestGuard.js @@ -549,6 +549,40 @@ var RequestGuard = (() => { } return ABORT; } + + async function onNavCommitted(details) { + debug("onNavCommitted", details); + let {url, tabId, frameId} = details; + try { + let policy = ns.computeChildPolicy({url}, {tab: {tabId}, frameId}); + policy.navigationURL = url; + let ret = await browser.tabs.executeScript(details.tabId, { + code: + `{ + let domPolicy = ${JSON.stringify(policy)}; + if (this.ns) { + ns.domPolicy = domPolicy; + if (ns.setup) { + if (ns.syncSetup) ns.syncSetup(domPolicy); + else if (!ns.pendingSyncFetchPolicy) { + ns.setup(domPolicy); + } + } ; + } else { + ns = {domPolicy} + } + console.debug("domPolicy", domPolicy); + } + ns;`, + runAt: "document_start", + frameId, + }); + debug("onNavCommitted return: ", ret); + } catch(e) { + console.error(e); + } + } + const RequestGuard = { async start() { Messages.addHandler(messageHandler); @@ -600,9 +634,11 @@ var RequestGuard = (() => { wr.onBeforeRequest.addListener(onViolationReport, {urls: [csp.reportURI], types: ["csp_report"]}, ["blocking", "requestBody"]); } + browser.webNavigation.onCommitted.addListener(onNavCommitted); TabStatus.probe(); }, stop() { + browser.webNavigation.onCommitted.removeListener(onNavCommitted); let wr = browser.webRequest; for (let [name, listener] of Object.entries(listeners)) { if (typeof listener === "function") { diff --git a/src/bg/main.js b/src/bg/main.js index fe4522f..6b91524 100644 --- a/src/bg/main.js +++ b/src/bg/main.js @@ -158,40 +158,7 @@ async fetchChildPolicy({url, contextUrl}, sender) { await ns.initializing; return (messageHandler.fetchChildPolicy = - messageHandler.fetchChildPolicySync)(...arguments); - }, - fetchChildPolicySync({url, contextUrl}, sender) { - let {tab, frameId} = sender; - let policy = ns.policy; - if (!policy) { - console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing); - return { - permissions: new Permissions(Permissions.DEFAULT).dry(), - unrestricted: false, - cascaded: false, - fallback: true - }; - } - let topUrl = frameId === 0 ? contextUrl : tab && (tab.url || TabCache.get(tab.id)); - if (Sites.isInternal(url) || !ns.isEnforced(tab ? tab.id : -1)) { - policy = null; - } - - let permissions, unrestricted, cascaded; - if (policy) { - let perms = policy.get(url, contextUrl).perms; - cascaded = topUrl && ns.sync.cascadeRestrictions; - if (cascaded) { - perms = policy.cascadeRestrictions(perms, topUrl); - } - permissions = perms.dry(); - } else { - // otherwise either internal URL or unrestricted - permissions = new Permissions(Permissions.ALL).dry(); - unrestricted = true; - cascaded = false; - } - return {permissions, unrestricted, cascaded}; + ns.computeChildPolicy)(...arguments); }, async openStandalonePopup() { @@ -239,6 +206,40 @@ return !this.isEnforced(request.tabId) || this.policy.can(request.url, capability, request.documentURL); }, + computeChildPolicy({url, contextUrl}, sender) { + let {tab, frameId} = sender; + let policy = ns.policy; + if (!policy) { + console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing); + return { + permissions: new Permissions(Permissions.DEFAULT).dry(), + unrestricted: false, + cascaded: false, + fallback: true + }; + } + let topUrl = frameId === 0 ? contextUrl : tab && (tab.url || TabCache.get(tab.id)); + if (Sites.isInternal(url) || !ns.isEnforced(tab ? tab.id : -1)) { + policy = null; + } + + let permissions, unrestricted, cascaded; + if (policy) { + let perms = policy.get(url, contextUrl).perms; + cascaded = topUrl && ns.sync.cascadeRestrictions; + if (cascaded) { + perms = policy.cascadeRestrictions(perms, topUrl); + } + permissions = perms.dry(); + } else { + // otherwise either internal URL or unrestricted + permissions = new Permissions(Permissions.ALL).dry(); + unrestricted = true; + cascaded = false; + } + return {permissions, unrestricted, cascaded}; + }, + start() { if (this.running) return; this.running = true; diff --git a/src/content/staticNS.js b/src/content/staticNS.js index 2ba9fbb..325ce7c 100644 --- a/src/content/staticNS.js +++ b/src/content/staticNS.js @@ -34,6 +34,7 @@ }, fetchPolicy() { + if (this.policy) return; let url = document.URL; debug(`Fetching policy from document %s, readyState %s`, @@ -41,6 +42,7 @@ //, document.domain, document.baseURI, window.isSecureContext // DEV_ONLY ); + if (/^(ftp|file):/.test(url)) { // ftp: or file: - no CSP headers yet if (this.syncFetchPolicy) { this.syncFetchPolicy(); @@ -50,6 +52,15 @@ return; } } else { + if (this.domPolicy) { + debug("File policy set in webNavigation found!"); + try { + this.setup(this.domPolicy); + return; + } catch(e) { + error(e); + } + } // CSP headers have been already provided by webRequest, we are not in a hurry... if (/^(javascript|about):/.test(url)) { url = document.readyState === "loading" @@ -77,7 +88,8 @@ }, setup(policy) { - debug("%s, %s, %o", document.URL, document.readyState, policy); + if (this.policy) return false; + debug("%s, %s, fetched %o", document.URL, document.readyState, policy); if (!policy) { policy = {permissions: {capabilities: []}, localFallback: true}; } @@ -97,6 +109,7 @@ } this.canScript = this.allows("script"); this.fire("capabilities"); + return true; }, policy: null, @@ -105,6 +118,6 @@ return this.capabilities && this.capabilities.has(cap); }, }; - - this.ns = this.ns ? Object.assign(this.ns, ns) : ns; + this.ns = this.ns ? Object.assign(ns, this.ns) : ns; + debug("StaticNS", JSON.stringify(this.ns)); // DEV_ONLY } diff --git a/src/content/syncFetchPolicy.js b/src/content/syncFetchPolicy.js index 9a1f340..0818d69 100644 --- a/src/content/syncFetchPolicy.js +++ b/src/content/syncFetchPolicy.js @@ -6,19 +6,43 @@ // Here we've got no CSP header yet (file: or ftp: URL), we need one // injected in the DOM as soon as possible. - debug("No CSP yet for non-HTTP document load: fetching policy synchronously..."); + debug("No CSP yet for non-HTTP document load: fetching policy synchronously...", ns); + ns.syncSetup = ns.setup.bind(ns); - if (top !== window) { - if (top.wrappedJSObject._noScriptPolicy) { - try { - ns.setup(JSON.parse(top.wrappedJSObject._noScriptPolicy)); - } catch(e) { - error(e); + if (window.wrappedJSObject) { + if (top === window) { + ns.syncSetup = policy => { + if (!ns.setup(policy)) return; + if (top === window && window.wrappedJSObject) { + let persistentPolicy = JSON.stringify(policy); + Object.freeze(persistentPolicy); + try { + Object.defineProperty(window.wrappedJSObject, "_noScriptPolicy", {value: cloneInto(persistentPolicy, window)}); + } catch(e) { + error(e); + } + } + ns.syncSetup = () => {}; + }; + } else try { + if (top.wrappedJSObject._noScriptPolicy) { + debug("Policy set in parent frame found!") + try { + ns.setup(JSON.parse(top.wrappedJSObject._noScriptPolicy)); + return; + } catch(e) { + error(e); + } } - return; + } catch (e) { + // cross-origin accesss violation, ignore } } + if (ns.domPolicy) { + ns.syncSetup(ns.domPolicy); + return; + } let syncFetch = callback => { browser.runtime.sendSyncMessage( {id: "fetchPolicy", url, contextUrl: url}, @@ -175,23 +199,10 @@ } } - let setup = policy => { - debug("Fetched %o, readyState %s", policy, document.readyState); // DEV_ONLY - if (top === window) { - let persistentPolicy = JSON.stringify(policy); - Object.freeze(persistentPolicy); - try { - Object.defineProperty(window.wrappedJSObject, "_noScriptPolicy", {value: cloneInto(persistentPolicy, window)}); - } catch(e) { - error(e); - } - } - ns.setup(policy); - } - for (let attempts = 3; attempts-- > 0;) { try { - syncFetch(setup); + if (ns.policy) break; + syncFetch(ns.syncSetup); break; } catch (e) { if (!Messages.isMissingEndpoint(e) || document.readyState === "complete") {