Policy serialization using the contentScripts API.
This commit is contained in:
parent
2c75eedadd
commit
57d883d63e
|
@ -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>"]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
});
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue