Refactored and improved syncFetchPolicy fallback for file: and ftp: special cases.

This commit is contained in:
hackademix 2020-08-28 21:00:01 +02:00
parent 058bf1b206
commit 3922f234f4
7 changed files with 142 additions and 150 deletions

View File

@ -69,7 +69,11 @@ if ! [ $(date -r "$LIB/tld.js" +'%Y%m%d') -ge $(date +'%Y%m%d') -a "$1" != "tld
git add src/lib/tld.js TLD && git commit -m'Updated TLDs.' git add src/lib/tld.js TLD && git commit -m'Updated TLDs.'
fi fi
./html5_events/html5_events.pl if ./html5_events/html5_events.pl; then
# update full event list as an array in src/content/syncFetchPolicy.js
EVENTS=$(egrep '^on[a-z]+$' html5_events/html5_events_archive.txt | sed "s/^on//;s/.*/'&'/;H;1h;"'$!d;x;s/\n/, /g');
perl -pi -e 's/(\blet eventTypes\s*=\s*)\[.*?\]/$1['"$EVENTS"']/' src/content/syncFetchPolicy.js
fi
rm -rf "$BUILD" "$XPI" rm -rf "$BUILD" "$XPI"
cp -pR "$SRC" "$BUILD" cp -pR "$SRC" "$BUILD"

View File

@ -103,12 +103,12 @@ sub patch
if ($must_replace) { if ($must_replace) {
rename $dst, $src; rename $dst, $src;
print "Patched.\n"; print "Patched.\n";
return 0;
} }
else
{
unlink $dst; unlink $dst;
print "Nothing to do.\n"; print "Nothing to do.\n";
} return 1;
} }
patch($SOURCE_FILE); exit(patch($SOURCE_FILE));

View File

@ -6,13 +6,6 @@ class DocumentCSP {
this.root = document.documentElement; this.root = document.documentElement;
} }
removeEventAttributes() {
console.debug("Removing event attributes"); // DEV_ONLY
let {root} = this;
this.rootAttrs = [...root.attributes].filter(a => a.name.toLowerCase().startsWith("on"));
for (let a of this.rootAttrs) root.removeAttributeNode(a);
}
apply(capabilities, embedding = CSP.isEmbedType(this.document.contentType)) { apply(capabilities, embedding = CSP.isEmbedType(this.document.contentType)) {
let {document} = this; let {document} = this;
if (!capabilities.has("script")) { if (!capabilities.has("script")) {
@ -40,8 +33,6 @@ class DocumentCSP {
meta.setAttribute("http-equiv", header.name); meta.setAttribute("http-equiv", header.name);
meta.setAttribute("content", header.value); meta.setAttribute("content", header.value);
let root = document.documentElement; let root = document.documentElement;
let rootAttrs = [...root.attributes].filter(a => a.name.toLowerCase().startsWith("on"));
for (let a of rootAttrs) root.removeAttributeNode(a);
let {head} = document; let {head} = document;
let parent = head || let parent = head ||
@ -60,13 +51,4 @@ class DocumentCSP {
} }
return true; return true;
} }
restoreEventAttributes() {
if (!this.rootAttrs) return;
console.debug("Restoring event attributes"); // DEV_ONLY
let {root, rootAttrs} = this;
for (let a of rootAttrs) {
root.setAttributeNodeNS(a);
}
}
} }

View File

@ -2,7 +2,6 @@
'use strict'; 'use strict';
let listenersMap = new Map(); let listenersMap = new Map();
let backlog = new Set(); let backlog = new Set();
let documentCSP = new DocumentCSP(document);
let ns = { let ns = {
debug: true, // DEV_ONLY debug: true, // DEV_ONLY
@ -42,8 +41,9 @@
//, document.domain, document.baseURI, window.isSecureContext // DEV_ONLY //, document.domain, document.baseURI, window.isSecureContext // DEV_ONLY
); );
let requireDocumentCSP = /^(?:ftp|file):/.test(url); if (this.syncFetchPolicy) { // ftp: or file: - no CSP headers yet
if (!requireDocumentCSP) { this.syncFetchPolicy();
} else {
// CSP headers have been already provided by webRequest, we are not in a hurry... // CSP headers have been already provided by webRequest, we are not in a hurry...
if (/^(javascript|about):/.test(url)) { if (/^(javascript|about):/.test(url)) {
url = document.readyState === "loading" url = document.readyState === "loading"
@ -68,118 +68,6 @@
asyncFetch(); asyncFetch();
return; return;
} }
// 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...");
documentCSP.removeEventAttributes();
let earlyScripts = [];
let dequeueEarlyScripts = (last = false) => {
if (!(ns.canScript && earlyScripts)) return;
if (earlyScripts.length === 0) {
earlyScripts = null;
return;
}
for (let s; s = earlyScripts.shift(); ) {
debug("Restoring", s);
s.firstChild._replaced = true;
s._original.replaceWith(s);
}
}
let syncFetch = callback => {
browser.runtime.sendSyncMessage(
{id: "fetchPolicy", url, contextUrl: url},
callback);
};
if (UA.isMozilla && document.readyState !== "complete") {
// Mozilla has already parsed the <head> element, we must take extra steps...
debug("Early parsing: preemptively suppressing events and script execution.");
{
let eventTypes = [];
for (let p in document.documentElement) if (p.startsWith("on")) eventTypes.push(p.substring(2));
let eventSuppressor = e => {
try {
debug("Event suppressor called for ", e.type, e.target, earlyScripts, e.target._earlyScript); // DEV_ONLY
if (!earlyScripts || document.readyState === "complete") {
debug("Stopping event suppression");
for (let et of eventTypes) document.removeEventListener(et, eventSuppressor, true);
return;
}
if (!ns.canScript || e.target._earlyScript) {
e.stopImmediatePropagation();
debug(`Suppressing ${e.type} on `, e.target); // DEV_ONLY
}
} catch (e) {
error(e);
}
}
debug("Starting event suppression");
for (let et of eventTypes) document.addEventListener(et, eventSuppressor, true);
ns.on("capabilities", () => {
if (!ns.canScript) {
try {
for (node of document.querySelectorAll("*")) {
let evAttrs = [...node.attributes].filter(a => a.name.toLowerCase().startsWith("on"));
for (let a of evAttrs) {
debug("Reparsing event attribute after CSP", a, node);
node.removeAttributeNode(a);
node.setAttributeNodeNS(a);
}
}
} catch (e) {
error(e);
}
}
});
}
addEventListener("beforescriptexecute", e => {
debug(e.type, e.target);
if (earlyScripts) {
let s = e.target;
if (s._replaced) {
debug("Replaced script found");
dequeueEarlyScripts(true);
return;
}
let replacement = document.createRange().createContextualFragment(s.outerHTML);
replacement._original = s;
s._earlyScript = true;
earlyScripts.push(replacement);
e.preventDefault();
dequeueEarlyScripts(true);
debug("Blocked early script");
}
}, true);
}
let setup = policy => {
debug("Fetched %o, readyState %s", policy, document.readyState); // DEV_ONLY
this.setup(policy);
documentCSP.restoreEventAttributes();
}
for (let attempts = 3; attempts-- > 0;) {
try {
syncFetch(setup);
break;
} catch (e) {
if (!Messages.isMissingEndpoint(e) || document.readyState === "complete") {
error(e);
break;
}
error("Background page not ready yet, retrying to fetch policy...")
}
}
dequeueEarlyScripts();
}, },
setup(policy) { setup(policy) {
@ -196,7 +84,7 @@
} else { } else {
let perms = policy.permissions; let perms = policy.permissions;
this.capabilities = new Set(perms.capabilities); this.capabilities = new Set(perms.capabilities);
documentCSP.apply(this.capabilities, this.embeddingDocument); new DocumentCSP(document).apply(this.capabilities, this.embeddingDocument);
} }
this.canScript = this.allows("script"); this.canScript = this.allows("script");
this.fire("capabilities"); this.fire("capabilities");
@ -209,9 +97,5 @@
}, },
}; };
if (this.ns) { this.ns = this.ns ? Object.assign(this.ns, ns) : ns;
this.ns.merge(ns);
} else {
this.ns = ns;
}
} }

File diff suppressed because one or more lines are too long

View File

@ -244,8 +244,7 @@
console.debug("sendSyncMessage suspending on ", records) console.debug("sendSyncMessage suspending on ", records)
suspend(); suspend();
}); });
domSuspender.observe(document.documentElement, {childList: true}); domSuspender.observe(document, {childList: true, subtree: true});
let finalize = () => { let finalize = () => {
console.debug("sendSyncMessage finalizing"); console.debug("sendSyncMessage finalizing");
domSuspender.disconnect(); domSuspender.disconnect();

View File

@ -73,6 +73,14 @@
"/content/content.css" "/content/content.css"
] ]
}, },
{
"run_at": "document_start",
"matches": ["file://*/*", "ftp://*/*"],
"js": [
"lib/SyncMessage.js",
"content/syncFetchPolicy.js"
]
},
{ {
"run_at": "document_start", "run_at": "document_start",
"matches": ["<all_urls>"], "matches": ["<all_urls>"],
@ -83,7 +91,6 @@
"lib/browser-polyfill.js", "lib/browser-polyfill.js",
"lib/log.js", "lib/log.js",
"lib/uuid.js", "lib/uuid.js",
"lib/SyncMessage.js",
"lib/sha256.js", "lib/sha256.js",
"lib/Messages.js", "lib/Messages.js",
"lib/CSP.js", "lib/CSP.js",