Removed all references to RequestUtil.js and dependancies.

This commit is contained in:
hackademix 2018-08-18 03:16:54 +02:00
parent e959accb70
commit 2f9c5299af
14 changed files with 96 additions and 253 deletions

View File

@ -58,8 +58,9 @@
var ChildPolicies = {
async storeTabInfo(tabId, info) {
try {
let preamble = info ? `${marker} + ${JSON.stringify(JSON.stringify([info]))} + ${marker} + "," + ` : "";
await browser.tabs.executeScript(tabId, {
code: `window.name = ${marker} + ${JSON.stringify(JSON.stringify([info]))} + ${marker} + "," + window.name;`,
code: `window.name = ${preamble}window.name.split(${marker} + ",").pop();`,
allFrames: false,
matchAboutBlank: true,
runAt: "document_start",

View File

@ -0,0 +1,52 @@
var ContentScriptOnce = (() => {
"use strict";
let requestMap = new Map();
{
let cleanup = r => {
let {requestId} = r;
let scripts = requestMap.get(requestId);
if (scripts) {
window.setTimeout(() => {
requestMap.delete(requestId);
for (let s of scripts) s.unregister();
}, 0);
}
}
let filter = {
urls: ["<all_urls>"],
types: ["main_frame", "sub_frame", "object"]
};
let wr = browser.webRequest;
for (let event of ["onCompleted", "onErrorOccurred"]) {
wr[event].addListener(cleanup, filter);
}
}
return {
async execute(request, options) {
let {requestId, url} = request;
let scripts = requestMap.get(requestId);
if (!scripts) requestMap.set(requestId, scripts = new Set());
try {
let urlObj = new URL(url);
if (urlObj.port) {
urlObj.port = "";
url = urlObj.toString();
}
} catch (e) {}
let defOpts = {
runAt: "document_start",
matchAboutBlank: true,
matches: [url],
allFrames: true,
};
scripts.add(await browser.contentScripts.register(
Object.assign(defOpts, options)
));
}
}
})();

View File

@ -1,169 +0,0 @@
'use strict';
{
let xmlFeedOrImage = /^(?:(?:application|text)\/(?:(?:r(?:ss|df)|atom)\+)xml(;|$))|image\//i;
let rawXml = /^(?:application|text)\/xml;/i;
let brokenXMLOnLoad;
(async () => brokenXMLOnLoad = parseInt((await browser.runtime.getBrowserInfo()).version) < 61)()
let pendingScripts = new Map();
let NOP = () => {};
let reloadingTabs = new Map();
let tabKey = (tabId, url) => `${tabId}:${url}`;
let cleanup = r => {
pendingScripts.delete(r.requestId);
let key = tabKey(r.tabId, r.url);
if (reloadingTabs.get(key) === false) {
reloadingTabs.delete(key);
}
};
let executeAll = async request => {
let {url, tabId, frameId, requestId, type} = request;
let scripts = pendingScripts.get(requestId);
if (!scripts) return -1;
pendingScripts.delete(requestId);
let count = 0;
let run = async details => {
details = Object.assign({
runAt: "document_start",
matchAboutBlank: true,
frameId
}, details);
try {
let res;
for (let attempts = 10; attempts-- > 0;) {
try {
res = await browser.tabs.executeScript(tabId, details);
break;
} catch(e) {
if (!/No matching message handler/.test(e.message)) throw e;
debug("Couldn't inject script into %s: too early? Retrying up to %s times...", url, attempts);
}
}
count++;
debug("Execute on start OK, result=%o", res, url, details);
} catch (e) {
error(e, "Execute on start failed", url, details);
}
};
await Promise.all([...scripts.values()].map(run));
return count;
};
{
let filter = {
urls: ["<all_urls>"],
types: ["main_frame", "sub_frame"]
};
let wr = browser.webRequest;
for (let event of ["onCompleted", "onErrorOccurred"]) {
wr[event].addListener(cleanup, filter);
}
wr.onResponseStarted.addListener(r => {
let scripts = pendingScripts.get(r.requestId);
if (scripts) scripts.runAndFlush();
}, filter);
}
var RequestUtil = {
getResponseMetaData(request) {
return request.response || (request.response = new ResponseMetaData(request));
},
executeOnStart(request, details) {
let {requestId, url, tabId, frameId, statusCode, type} = request;
if (statusCode >= 300 && statusCode < 400 || type === "object") return;
if (frameId === 0) {
let key = tabKey(tabId, url);
debug("Checking whether %s is a reloading tab...", key);
if (reloadingTabs.get(key)) {
reloadingTabs.set(key, false); // doom it for removal in cleanup
return;
}
}
let response = this.getResponseMetaData(request);
let {contentType, contentDisposition} = response;
if (contentDisposition ||
xmlFeedOrImage.test(contentType) && !/\/svg\b/i.test(contentType)) {
debug("Skipping execute on start of %s %o.", url, response);
return;
}
debug("Injecting script on start in %s (%o).", url, response);
let scripts = pendingScripts.get(requestId);
let scriptKey = JSON.stringify(details);
if (!scripts) {
pendingScripts.set(requestId, scripts = new Map());
scripts.set(scriptKey, details);
} else {
scripts.set(scriptKey, details);
return;
}
if (/^(?:application|text)\//.test(contentType)
&& !/[^;]+\b(html|xml)\b/i.test(contentType)) {
debug("Not HTML: defer script to onResponseStarted for %s (%o)", url, response);
return;
}
let mustCheckFeed = brokenXMLOnLoad && frameId === 0 && rawXml.test(contentType);
debug("mustCheckFeed = %s, brokenXMLOnLoad = %s", mustCheckFeed, brokenXMLOnLoad);
let filter = browser.webRequest.filterResponseData(requestId);
let buffer = [];
let responseCompleted = false;
let mustReload = false;
scripts.runAndFlush = async () => {
scripts.runAndFlush = NOP;
if (responseCompleted && buffer && !buffer.length) {
filter.disconnect();
}
let scriptsRan = await executeAll(request);
if (mustCheckFeed && !scriptsRan) {
mustReload = true;
debug(`Marking as "must reload"`, tabId, url);
reloadingTabs.set(tabKey(tabId, url), true);
}
if (buffer) {
debug("Flushing %s buffer chunks on %s", buffer.length, url);
for (let chunk of buffer) {
filter.write(chunk);
}
buffer = null;
}
filter.disconnect();
if (responseCompleted) {
filter.onstop(null);
}
};
filter.ondata = event => {
scripts.runAndFlush();
if (buffer) {
debug("buffering", url);
buffer.push(event.data);
return;
}
debug("ondata", url);
filter.write(event.data);
filter.disconnect();
};
filter.onstop = async event => {
responseCompleted = true;
await scripts.runAndFlush();
if (mustReload && !buffer) {
mustReload = false;
browser.tabs.update(tabId, {url});
}
}
}
}
}

View File

@ -90,7 +90,7 @@ var Settings = {
if (typeof unrestrictedTab === "boolean") {
ns.unrestrictedTabs[unrestrictedTab ? "add" : "delete"](tabId);
ChildPolicies.storeTabInfo(tabId, {unrestricted: unrestrictedTab});
ChildPolicies.storeTabInfo(tabId, unrestrictedTab && {unrestricted: true});
}
if (reloadAffected) {
browser.tabs.reload(tabId);

View File

@ -34,16 +34,20 @@ function deferWebTraffic(promiseToWaitFor, next) {
if (frameId !== 0) {
documentUrl = request.frameAncestors.pop().url;
}
reloadTab(tabId);
if (tabId !== -1) {
reloadTab(tabId);
} else {
debug("No tab to reload for %s %s from %s", type, url, documentUrl);
}
}
}
debug("Deferring ", url, type);
debug("Deferring %s %s from %s", type, url, documentUrl);
try {
await promiseToWaitFor;
} catch (e) {
error(e);
}
debug("Green light to ", url, type);
debug("Green light to %s %s from %s", type, url, documentUrl);
}
function spyTabs(request) {

View File

@ -36,7 +36,7 @@
await include("/bg/defaults.js");
await ns.defaults;
await include(["/bg/RequestGuard.js", "/bg/RequestUtil.js"]);
await include("/bg/RequestGuard.js");
await RequestGuard.start();
await XSS.start(); // we must start it anyway to initialize sub-objects
if (!ns.sync.xss) {

View File

@ -3,23 +3,30 @@
// debug = () => {}; // REL_ONLY
{
let listenersMap = new Map();
let backlog = new Set();
var ns = {
on(eventName, listener) {
let listeners = listenersMap.get(eventName);
if (!listeners) listenersMap.set(eventName, listeners = new Set());
listeners.add(listener);
if (backlog.has(eventName)) this.fire(eventName, listener);
},
detach(eventName, listener) {
let listeners = listenersMap.get(eventName);
if (listeners) listeners.delete(listener);
},
fire(eventName) {
fire(eventName, listener = null) {
if (listener) {
listener({type:eventName, source: this});
return;
}
let listeners = listenersMap.get(eventName);
if (listeners) {
for (let l of listeners) {
l(this);
this.fire(eventName, l);
}
}
backlog.add(eventName);
},
setup(DEFAULT, MARKER) {
this.perms.DEFAULT = DEFAULT;
@ -36,10 +43,10 @@
let tabInfoRx = new RegExp(`^${MARKER}\\[([^]*?)\\]${MARKER},`);
if (top === window) { // wrap to hide
Reflect.defineProperty(top.wrappedJSObject, "name", {
get: exportFunction(() => _name.replace(eraseTabInfoRx, ""), top.wrappedJSObject),
get: exportFunction(() => top.name.replace(eraseTabInfoRx, ""), top.wrappedJSObject),
set: exportFunction(value => {
let preamble = _name.match(tabInfoRx);
_name = `${preamble && preamble[0] || ""}${value}`;
let preamble = top.name.match(tabInfoRx);
top.name = `${preamble && preamble[0] || ""}${value}`;
return value;
}, top.wrappedJSObject)
});
@ -57,14 +64,14 @@
}
ns.fire("perms");
},
storeTabInfo(info) {
let {MARKER} = this.perms;
window.name = `${MARKER}${JSON.stringify([info])}${MARKER},${window.name.split(marker).pop()}`;
},
perms: { DEFAULT: null, CURRENT: null, tabInfo: {} },
perms: { DEFAULT: null, CURRENT: null, tabInfo: {}, MARKER: "" },
allows(cap) {
let perms = this.perms.CURRENT;
return perms && perms.capabilities.includes(cap);
},
getWindowName() {
return top !== window || !this.perms.MARKER ? window.name
: window.name.split(this.perms.MARKER + ",").pop();
}
}
}

View File

@ -1,4 +1,4 @@
ns.on("perms", ns => {
ns.on("perms", event => {
debug("Media Hook", document.URL, document.documentElement && document.documentElement.innerHTML, ns.perms.CURRENT); // DEV_ONLY
let mediaBlocker = !ns.allows("media");
let unpatched = new Map();
@ -54,6 +54,4 @@ ns.on("perms", ns => {
return unpatched.get(window.MediaSource.prototype).addSourceBuffer.call(ms, mime, ...args);
});
})();
document.URL;
});

View File

@ -1,4 +1,4 @@
ns.on("perms", ns => {
ns.on("perms", event => {
debug("WebGL Hook", document.URL, document.documentElement && document.documentElement.innerHTML, ns.perms.CURRENT); // DEV_ONLY
if (ns.allows("webgl")) return;
let proto = HTMLCanvasElement.prototype;
@ -26,4 +26,3 @@ ns.on("perms", ns => {
return getContext.call(this, type, ...rest);
}, proto, {defineAs: "getContext"});
});
document.URL;

View File

@ -1,54 +0,0 @@
class ResponseMetaData {
constructor(request) {
let {responseHeaders} = request;
this.headers = {};
this.contentType = this.contentDisposition = null;
for (let h of responseHeaders) {
if (/^\s*Content-(Type|Disposition)\s*$/i.test(h.name)) {
let propertyName = RegExp.$1;
propertyName = `content${propertyName.charAt(0).toUpperCase()}${propertyName.substring(1).toLowerCase()}`;
this[propertyName] = h.value;
this.headers[propertyName] = h;
}
}
this.forcedUTF8 = false;
}
get charset() {
let charset = "";
if (this.contentType) {
let m = this.contentType.match(/;\s*charset\s*=\s*(\S+)/);
if (m) {
charset = m[1];
}
}
Object.defineProperty(this, "charset", { value: charset, writable: false, configurable: true });
return charset;
}
get isUTF8() {
return /^utf-?8$/i.test(this.charset);
}
forceUTF8() {
if (!(this.forcedUTF8 || this.isUTF8)) {
let h = this.headers.contentType;
if (h) {
h.value = h.value.replace(/;\s*charset\s*=.*|$/, "; charset=utf8");
this.forcedUTF8 = true;
} // if the header doesn't exist the browser should default to UTF-8 anyway
}
return this.forcedUTF8;
}
createDecoder() {
if (this.charset) {
try {
return new TextDecoder(this.charset);
} catch (e) {
console.error(e);
}
}
return new TextDecoder("utf-8");
}
};

View File

@ -39,7 +39,6 @@
"lib/include.js",
"lib/punycode.js",
"lib/tld.js",
"lib/ResponseMetaData.js",
"lib/LastListener.js",
"common/Policy.js",
"common/locale.js",

View File

@ -741,7 +741,7 @@ XSS.InjectionChecker = (async () => {
checkJS: function(s, unescapedUni) {
this.log(s);
if (/\?name\b[\s\S]*:|[^&?]\bname\b/.test(s)) {
if (/[=\(](?:[\s\S]*(?:\?name\b[\s\S]*:|[^&?]\bname\b)|name\b)/.test(s)) {
this.nameAssignment = true;
}

View File

@ -61,8 +61,9 @@ var XSS = (() => {
let block = !!(reasons.urlInjection || reasons.postInjection)
if (reasons.protectName) {
RequestUtil.executeOnStart(request, {
file: "/xss/sanitizeName.js",
await include("bg/COntentScriptOnce.js");
await ContentScriptOnce.execute(request, {
js: [{file: "/xss/sanitizeName.js"}],
});
if (!block) return ALLOW;
}

View File

@ -1,4 +1,9 @@
if (/[<"'\`(=:]/.test(window.name)) {
console.log(`NoScript XSS filter sanitizing suspicious window.name "%s" on %s`, window.name, document.URL);
window.name = "";
}
ns.on("perms", event => {
if (ns.allows("script")) {
let name = ns.getWindowName();
if (/[<"'\`(=:]/.test(name)) {
console.log(`NoScript XSS filter sanitizing suspicious window.name "%s" on %s`, name, document.URL);
window.name = window.name.substring(0, window.name.length - name.length);
}
}
});