diff options
Diffstat (limited to 'content/main.js')
-rw-r--r-- | content/main.js | 221 |
1 files changed, 122 insertions, 99 deletions
diff --git a/content/main.js b/content/main.js index 2a46c7e..d55ee2e 100644 --- a/content/main.js +++ b/content/main.js @@ -5,113 +5,133 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; +/* + * IMPORTS_START + * IMPORT handle_page_actions + * IMPORT url_item + * IMPORT gen_unique + * IMPORT sanitize_attributes + * IMPORT script_suppressor + * IMPORT is_chrome + * IMPORT is_mozilla + * IMPORTS_END + */ -(() => { - const handle_page_actions = window.handle_page_actions; - const url_item = window.url_item; - const gen_unique = window.gen_unique; - const sanitize_attributes = window.sanitize_attributes; +/* + * Due to some technical limitations the chosen method of whitelisting sites + * is to smuggle whitelist indicator in page's url as a "magical" string + * after '#'. Right now this is not needed in HTTP(s) pages where native + * script blocking happens through CSP header injection but is needed for + * protocols like ftp:// and file://. + * + * The code that actually injects the magical string into ftp:// and file:// + * urls has not yet been added to the extension. + */ - /* - * Due to some technical limitations the chosen method of whitelisting sites - * is to smuggle whitelist indicator in page's url as a "magical" string - * after '#'. Right now this is not needed in HTTP(s) pages where native - * script blocking happens through CSP header injection but is needed for - * protocols like ftp:// and file://. - * - * The code that actually injects the magical string into ftp:// and file:// - * urls has not yet been added to the extension. - */ +let url = url_item(document.URL); +let unique = gen_unique(url); +let nonce = unique.substring(1); - let url = url_item(document.URL); - let unique = gen_unique(url); - let nonce = unique.substring(1); - - const scriptSuppressor = window.scriptSuppressor(nonce); - - function needs_blocking() - { - if (url.startsWith("https://") || url.startsWith("http://")) - return false; - - let url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/; - let match = url_re.exec(document.URL); - let base_url = match[1]; - let first_target = match[3]; - let second_target = match[4]; - - if (first_target !== undefined && - first_target === unique) { - if (second_target !== undefined) - window.location.href = base_url + second_target; - else - history.replaceState(null, "", base_url); - - console.log(["allowing whitelisted", document.URL]); - return false; - } - - console.log(["disallowing", document.URL]); - return true; - } +const suppressor = script_suppressor(nonce); + +function needs_blocking() +{ + if (url.startsWith("https://") || url.startsWith("http://")) + return false; - function handle_mutation(mutations, observer) - { - if (document.readyState === 'complete') { - console.log("complete"); - observer.disconnect(); - return; - } - for (let mutation of mutations) { - for (let node of mutation.addedNodes) { - /* - * Modifying <script> element doesn't always prevent its - * execution in some Mozilla browsers. Additional blocking - * through CSP meta tag injection is required. - */ - if (node.tagName === "SCRIPT") { - block_script(node); - continue; - } - - sanitize_attributes(node); - - if (node.tagName === "HEAD") - inject_csp(node); - } - } + let url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/; + let match = url_re.exec(document.URL); + let base_url = match[1]; + let first_target = match[3]; + let second_target = match[4]; + + if (first_target !== undefined && + first_target === unique) { + if (second_target !== undefined) + window.location.href = base_url + second_target; + else + history.replaceState(null, "", base_url); + + console.log(["allowing whitelisted", document.URL]); + return false; } - function block_script(node) - { - console.log(node); - - /* - * Disabling scripts this way allows them to still be relatively - * easily accessed in case they contain some useful data. - */ - if (node.hasAttribute("type")) - node.setAttribute("blocked-type", node.getAttribute("type")); - node.setAttribute("type", "application/json"); + console.log(["disallowing", document.URL]); + return true; +} + +function handle_mutation(mutations, observer) +{ + if (document.readyState === 'complete') { + console.log("mutation handling complete"); + observer.disconnect(); + return; } + for (const mutation of mutations) { + for (const node of mutation.addedNodes) + block_node(node); + } +} + +function block_nodes_recursively(node) +{ + block_node(node); + for (const child of node.children) + block_nodes_recursively(child); +} - function inject_csp(node) - { - console.log('injecting CSP'); - let meta = document.createElement("meta"); - meta.setAttribute("http-equiv", "Content-Security-Policy"); - meta.setAttribute("content", `\ -script-src 'nonce-${nonce}'; \ -script-src-elem 'nonce-${nonce}';\ -`); - node.appendChild(meta); +function block_node(node) +{ + /* + * Modifying <script> element doesn't always prevent its + * execution in some Mozilla browsers. Additional blocking + * through CSP meta tag injection is required. + */ + if (node.tagName === "SCRIPT") { + block_script(node); + return; } - if (needs_blocking()) { - // Script blocking for Gecko - addEventListener('beforescriptexecute', scriptSuppressor, true); - + sanitize_attributes(node); + + if (node.tagName === "HEAD") + inject_csp(node); +} + +function block_script(node) +{ + /* + * Disabling scripts this way allows them to still be relatively + * easily accessed in case they contain some useful data. + */ + if (node.hasAttribute("type")) + node.setAttribute("blocked-type", node.getAttribute("type")); + node.setAttribute("type", "application/json"); +} + +function inject_csp(head) +{ + console.log('injecting CSP'); + + let meta = document.createElement("meta"); + meta.setAttribute("http-equiv", "Content-Security-Policy"); + + let rule = `script-src 'nonce-${nonce}'; `; + if (is_chrome) + rule += `script-src-elem 'nonce-${nonce}';`; + + meta.setAttribute("content", rule); + + if (head.firstElementChild === null) + head.appendChild(meta); + else + head.insertBefore(meta, head.firstElementChild); +} + +if (needs_blocking()) { + block_nodes_recursively(document.documentElement); + + if (is_chrome) { var observer = new MutationObserver(handle_mutation); observer.observe(document.documentElement, { attributes: true, @@ -120,5 +140,8 @@ script-src-elem 'nonce-${nonce}';\ }); } - handle_page_actions(nonce); -})(); + if (is_mozilla) + addEventListener('beforescriptexecute', suppressor, true); +} + +handle_page_actions(nonce); |