diff options
Diffstat (limited to 'content')
-rw-r--r-- | content/freezer.js | 98 | ||||
-rw-r--r-- | content/main.js | 221 | ||||
-rw-r--r-- | content/page_actions.js | 92 |
3 files changed, 218 insertions, 193 deletions
diff --git a/content/freezer.js b/content/freezer.js index cdd0709..1696f53 100644 --- a/content/freezer.js +++ b/content/freezer.js @@ -6,58 +6,60 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; +const loaderAttributes = ["href", "src", "data"]; +const jsOrDataUrlRx = /^(?:data:(?:[^,;]*ml|unknown-content-type)|javascript:)/i; -(() => { - const loaderAttributes = ["href", "src", "data"]; - const jsOrDataUrlRx = /^(?:data:(?:[^,;]*ml|unknown-content-type)|javascript:)/i; +function sanitize_attributes(element) { + if (element._frozen) + return; + let fa = []; + let loaders = []; + let attributes = element.attributes || []; - function sanitizeAttributes(element) { - if (element._frozen) - return; - let fa = []; - let loaders = []; - for (let a of element.attributes) { - let name = a.localName.toLowerCase(); - if (loaderAttributes.includes(name)) - if (jsOrDataUrlRx.test(a.value)) - loaders.push(a); + for (let a of attributes) { + let name = a.localName.toLowerCase(); + if (loaderAttributes.includes(name)) + if (jsOrDataUrlRx.test(a.value)) + loaders.push(a); - else if (name.startsWith("on")) { - console.debug("Removing", a, element.outerHTML); - fa.push(a.cloneNode()); - a.value = ""; - element[name] = null; - } + else if (name.startsWith("on")) { + console.debug("Removing", a, element.outerHTML); + fa.push(a.cloneNode()); + a.value = ""; + element[name] = null; } - if (loaders.length) { - for (let a of loaders) { - fa.push(a.cloneNode()); - a.value = "javascript://frozen"; - } - if ("contentWindow" in element) - element.replaceWith(element = element.cloneNode(true)); - + } + if (loaders.length) { + for (let a of loaders) { + fa.push(a.cloneNode()); + a.value = "javascript://frozen"; } - if (fa.length) - element._frozenAttributes = fa; - element._frozen = true; + if ("contentWindow" in element) + element.replaceWith(element = element.cloneNode(true)); + } - - function scriptSuppressor(nonce) { - const blockExecute = e => { - if (document.readyState === 'complete') { - removeEventListener('beforescriptexecute', blockExecute, true); - return; - } - else if (e.isTrusted && e.target.getAttribute('nonce') !== nonce) { // Prevent blocking of injected scripts - e.preventDefault(); - console.log('Suppressed script', e.target); - } - }; - return blockExecute; + if (fa.length) + element._frozenAttributes = fa; + element._frozen = true; +} + +function script_suppressor(nonce) { + const blockExecute = e => { + if (document.readyState === 'complete') { + removeEventListener('beforescriptexecute', blockExecute, true); + return; + } + else if (e.isTrusted && e.target.getAttribute('nonce') !== nonce) { // Prevent blocking of injected scripts + e.preventDefault(); + console.log('Suppressed script', e.target); + } }; - - window.scriptSuppressor = scriptSuppressor; - window.sanitize_attributes = sanitizeAttributes; -})(); + return blockExecute; +}; + +/* + * EXPORTS_START + * EXPORT script_suppressor + * EXPORT sanitize_attributes + * EXPORTS_END + */ 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); diff --git a/content/page_actions.js b/content/page_actions.js index 88332a8..bc65449 100644 --- a/content/page_actions.js +++ b/content/page_actions.js @@ -5,60 +5,60 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - -(() => { - const CONNECTION_TYPE = window.CONNECTION_TYPE; - const browser = window.browser; - - var port; - var loaded = false; - var scripts_awaiting = []; - var nonce; +/* + * IMPORTS_START + * IMPORT CONNECTION_TYPE + * IMPORT browser + * IMPORTS_END + */ - function handle_message(message) - { - console.log(["message", message]); +var port; +var loaded = false; +var scripts_awaiting = []; +var nonce; - if (message.inject === undefined) - return; +function handle_message(message) +{ + if (message.inject === undefined) + return; - for (let script_text of message.inject) { - if (loaded) - add_script(script_text); - else - scripts_awaiting.push(script_text); - } + for (let script_text of message.inject) { + if (loaded) + add_script(script_text); + else + scripts_awaiting.push(script_text); } +} - function document_loaded(event) - { - console.log("loaded"); - - loaded = true; +function document_loaded(event) +{ + loaded = true; - for (let script_text of scripts_awaiting) - add_script(script_text); + for (let script_text of scripts_awaiting) + add_script(script_text); - scripts_awaiting = undefined; - } + scripts_awaiting = undefined; +} - function add_script(script_text) - { - let script = document.createElement("script"); - script.textContent = script_text; - script.setAttribute("nonce", nonce); - document.body.appendChild(script); - } +function add_script(script_text) +{ + let script = document.createElement("script"); + script.textContent = script_text; + script.setAttribute("nonce", nonce); + document.body.appendChild(script); +} - function handle_page_actions(script_nonce) { - document.addEventListener("DOMContentLoaded", document_loaded); - port = browser.runtime.connect({name : CONNECTION_TYPE.PAGE_ACTIONS}); - port.onMessage.addListener(handle_message); - port.postMessage({url: document.URL}); +function handle_page_actions(script_nonce) { + document.addEventListener("DOMContentLoaded", document_loaded); + port = browser.runtime.connect({name : CONNECTION_TYPE.PAGE_ACTIONS}); + port.onMessage.addListener(handle_message); + port.postMessage({url: document.URL}); - nonce = script_nonce; - } + nonce = script_nonce; +} - window.handle_page_actions = handle_page_actions; -})(); +/* + * EXPORTS_START + * EXPORT handle_page_actions + * EXPORTS_END + */ |