summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorhackademix2018-08-15 16:41:55 +0200
committerhackademix2018-08-16 23:43:36 +0200
commit57d883d63e9b8232e275dac7eec24f84c040dd24 (patch)
treea4a9f05f0744ccef23e31643f80c400abe0dab37 /src
parent2c75eedadddd8bda0522cddaf119e1e5c621c7c2 (diff)
downloadnoscript-57d883d63e9b8232e275dac7eec24f84c040dd24.tar.gz
noscript-57d883d63e9b8232e275dac7eec24f84c040dd24.tar.xz
noscript-57d883d63e9b8232e275dac7eec24f84c040dd24.zip
Policy serialization using the contentScripts API.
Diffstat (limited to 'src')
-rw-r--r--src/bg/ChildPolicies.js111
-rw-r--r--src/bg/RequestGuard.js17
-rw-r--r--src/bg/main.js5
-rw-r--r--src/content/content.js30
-rw-r--r--src/content/media.js103
-rw-r--r--src/content/webglHook.js11
-rw-r--r--src/manifest.json5
7 files changed, 205 insertions, 77 deletions
diff --git a/src/bg/ChildPolicies.js b/src/bg/ChildPolicies.js
new file mode 100644
index 0000000..fd790c9
--- /dev/null
+++ b/src/bg/ChildPolicies.js
@@ -0,0 +1,111 @@
+"use script";
+{
+ let Scripts = {
+ references: new Set(),
+ opts: {
+ js: [{}],
+ allFrames: true,
+ matchAboutBlank: true,
+ runAt: "document_start"
+ },
+ forget() {
+ for (let script of [...this.references]) {
+ script.unregister();
+ this.references.delete(script);
+ }
+ },
+ async register(code, matches, excludeMatches) {
+ debug("Registering child policy.", code, matches, excludeMatches);
+ if (!matches.length) return;
+ try {
+ this.opts.js[0].code = code;
+ this.opts.matches = matches;
+ if (excludeMatches && excludeMatches.length) {
+ this.opts.excludeMatches = excludeMatches;
+ } else {
+ delete this.opts.excludeMatches;
+ }
+ this.references.add(await browser.contentScripts.register(this.opts));
+ } catch (e) {
+ error(e);
+ }
+ }
+ };
+
+ let flatten = arr => arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
+
+ let protocolRx = /^(https?):/i;
+ let pathRx = /[^:/]\//;
+ let portRx = /:\d+(?=\/|$)/;
+ let validMatchPatternRx = /^(?:https?|\*):\/\/(?:\*\.)?(?:[\w\u0100-\uf000][\w\u0100-\uf000.-]*)?[\w\u0100-\uf000]\/(\*|[^*]*)$/;
+
+ let siteKey2MatchPattern = site => {
+ let hasProtocol = site.match(protocolRx);
+ let protocol = hasProtocol ? ''
+ : Sites.isSecureDomainKey(site) ? "https://" : "*://";
+ let hostname = Sites.toggleSecureDomainKey(site, false)
+ .replace(portRx, '');
+ if (!hasProtocol) hostname = `*.${hostname}`;
+ let path = pathRx.test(hostname) ? "" : "/*";
+ let mp = `${protocol}${hostname}${path}`;
+ return validMatchPatternRx.test(mp) && (path ? mp : [mp, `${mp}?*`, `${mp}#*`]);
+ };
+
+ let siteKeys2MatchPatterns = keys => keys && flatten(keys.map(siteKey2MatchPattern)).filter(p => !!p) || [];
+
+ var ChildPolicies = {
+ async update(policy) {
+ 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("/"))
+ ));
+ }
+
+ Scripts.forget();
+ // register new content scripts
+ for (let [perms, keys] of [...permsMap]) {
+ await Scripts.register(`ns.perms.CURRENT = ${perms};`, siteKeys2MatchPatterns(keys), excludeMap.get(perms));
+ }
+ await Scripts.register(
+ `ns.perms.DEFAULT = ${JSON.stringify(serialized.DEFAULT)};
+ if(!ns.perms.CURRENT) ns.perms.CURRENT = ns.perms.DEFAULT;
+ ns.fire("perms");`,
+ ["<all_urls>"]);
+ }
+ }
+}
diff --git a/src/bg/RequestGuard.js b/src/bg/RequestGuard.js
index fcd6fa7..703623f 100644
--- a/src/bg/RequestGuard.js
+++ b/src/bg/RequestGuard.js
@@ -437,22 +437,7 @@ var RequestGuard = (() => {
blocker = CSP.createBlocker(...blockedTypes);
}
- if (canScript && !isObject) {
- if (!capabilities.has("webgl")) {
- RequestUtil.executeOnStart(request, {
- file: "/content/webglHook.js"
- });
- }
- if (!capabilities.has("media")) {
- RequestUtil.executeOnStart(request, {
- code: "window.mediaBlocker = true;"
- });
- }
-
- RequestUtil.executeOnStart(request, {
- file: "/content/media.js"
- });
- } else if (request.type === "main_frame" && !TabStatus.map.has(tabId)) {
+ if (request.type === "main_frame" && !TabStatus.map.has(tabId)) {
debug("No TabStatus data yet for noscriptFrame", tabId);
TabStatus.record(request, "noscriptFrame", true);
}
diff --git a/src/bg/main.js b/src/bg/main.js
index 47ee89e..2eb2896 100644
--- a/src/bg/main.js
+++ b/src/bg/main.js
@@ -26,12 +26,14 @@
let policyData = (await Storage.get("sync", "policy")).policy;
if (policyData && policyData.DEFAULT) {
ns.policy = new Policy(policyData);
+ await ChildPolicies.update(policyData);
} else {
await include("/legacy/Legacy.js");
ns.policy = await Legacy.createOrMigratePolicy();
ns.savePolicy();
}
-
+
+
await include("/bg/defaults.js");
await ns.defaults;
await include(["/bg/RequestGuard.js", "/bg/RequestUtil.js"]);
@@ -226,6 +228,7 @@
async savePolicy() {
if (this.policy) {
+ await ChildPolicies.update(this.policy);
await Storage.set("sync", {
policy: this.policy.dry()
});
diff --git a/src/content/content.js b/src/content/content.js
index 886c363..37d0d32 100644
--- a/src/content/content.js
+++ b/src/content/content.js
@@ -1,7 +1,34 @@
'use strict';
// debug = () => {}; // REL_ONLY
-
+{
+ let listenersMap = new Map();
+ var ns = {
+ on(eventName, listener) {
+ let listeners = listenersMap.get(eventName);
+ if (!listeners) listenersMap.set(eventName, listeners = new Set());
+ listeners.add(listener);
+ },
+ detach(eventName, listener) {
+ let listeners = listenersMap.get(eventName);
+ if (listeners) listeners.delete(listener);
+ },
+ fire(eventName) {
+ let listeners = listenersMap.get(eventName);
+ if (listeners) {
+ for (let l of listeners) {
+ l(this);
+ }
+ }
+ },
+ perms: { DEFAULT: null, CURRENT: null },
+ allows(cap) {
+ let perms = this.perms.CURRENT;
+ return perms && perms.capabilities.includes(cap);
+ }
+ }
+}
+
var canScript = true, shouldScript = false;
let now = () => performance.now() + performance.timeOrigin;
@@ -24,7 +51,6 @@ function probe() {
var _ = browser.i18n.getMessage;
-
var embeddingDocument = false;
var seen = {
diff --git a/src/content/media.js b/src/content/media.js
index ffa67e0..5da239f 100644
--- a/src/content/media.js
+++ b/src/content/media.js
@@ -1,60 +1,59 @@
-{
- debug("Media Hook (blocked %s)", !!window.mediaBlocker, document.URL, document.documentElement && document.documentElement.innerHTML); // DEV_ONLY
- (() => {
- 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);
+ns.on("perms", ns => {
+ debug("Media Hook", document.URL, document.documentElement && document.documentElement.innerHTML, ns.perms.CURRENT); // DEV_ONLY
+ let mediaBlocker = !ns.allows("media");
+ 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);
}
- 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;
- });
+ return url;
+ });
- patch(window.MediaSource.prototype, "addSourceBuffer", function(mime, ...args) {
- let ms = this;
- let urls = urlMap.get(ms);
+ patch(window.MediaSource.prototype, "addSourceBuffer", function(mime, ...args) {
+ let ms = this;
+ let urls = urlMap.get(ms);
- let request = {
- id: "noscript-media",
- type: "media",
- url: document.URL,
- documentUrl: document.URL,
- embeddingDocument: true,
- };
- seen.record({policyType: "media", request, allowed: false});
- notifyPage();
+ let request = {
+ id: "noscript-media",
+ type: "media",
+ url: document.URL,
+ documentUrl: document.URL,
+ embeddingDocument: true,
+ };
+ seen.record({policyType: "media", request, allowed: false});
+ notifyPage();
- if (window.mediaBlocker) {
- (async () => {
- let me = Array.from(document.querySelectorAll("video,audio"))
- .find(e => e.srcObject === ms || urls && urls.has(e.src));
+ if (mediaBlocker) {
+ (async () => {
+ let me = Array.from(document.querySelectorAll("video,audio"))
+ .find(e => e.srcObject === ms || urls && urls.has(e.src));
- if (!me) return;
- let exposedMime = `${mime} (MSE)`;
+ if (!me) return;
+ let exposedMime = `${mime} (MSE)`;
- try {
- let ph = PlaceHolder.create("media", request);
- ph.replace(me);
- PlaceHolder.listen();
- } catch (e) {
- error(e);
- }
- })();
- throw new Error(`${exposedMime} blocked by NoScript`);
- }
+ try {
+ let ph = PlaceHolder.create("media", request);
+ ph.replace(me);
+ PlaceHolder.listen();
+ } catch (e) {
+ error(e);
+ }
+ })();
+ throw new Error(`${exposedMime} blocked by NoScript`);
+ }
- return unpatched.get(window.MediaSource.prototype).addSourceBuffer.call(ms, mime, ...args);
- });
+ return unpatched.get(window.MediaSource.prototype).addSourceBuffer.call(ms, mime, ...args);
+ });
- })();
- document.URL;
-}
+})();
+document.URL;
diff --git a/src/content/webglHook.js b/src/content/webglHook.js
index d4c064a..67f31eb 100644
--- a/src/content/webglHook.js
+++ b/src/content/webglHook.js
@@ -1,5 +1,6 @@
-{
- debug("WebGL Hook", document.URL, document.documentElement && document.documentElement.innerHTML); // DEV_ONLY
+ns.on("perms", ns => {
+ debug("WebGL Hook", document.URL, document.documentElement && document.documentElement.innerHTML, ns.perms.CURRENT); // DEV_ONLY
+ if (ns.allows("webgl")) return;
let proto = HTMLCanvasElement.prototype;
let getContext = proto.getContext;
exportFunction(function(type, ...rest) {
@@ -23,6 +24,6 @@
return {};
}
return getContext.call(this, type, ...rest);
- }, proto, {defineAs: "getContext"});
- document.URL;
-}
+ }, proto, {defineAs: "getContext"});
+});
+document.URL;
diff --git a/src/manifest.json b/src/manifest.json
index 61f618b..fd0e027 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -49,6 +49,7 @@
"ui/Prompts.js",
"xss/XSS.js",
"bg/deferWebTraffic.js",
+ "bg/ChildPolicies.js",
"bg/main.js"
]
},
@@ -63,7 +64,9 @@
"lib/log.js",
"content/onScriptDisabled.js",
"content/content.js",
- "content/PlaceHolder.js"
+ "content/webglHook.js",
+ "content/PlaceHolder.js",
+ "content/media.js"
]
},
{