aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWojtek Kosior <koszko@koszko.org>2021-09-06 20:45:50 +0200
committerWojtek Kosior <koszko@koszko.org>2021-09-06 20:45:50 +0200
commit704f2da0673dc714f72b9bb82f6bf648795d4335 (patch)
tree15d4819bef4c984b6494bbf4d188d42d285352cb
parented08ef1a6df1713a0e00ccd656f4bb4ed44647a4 (diff)
downloadbrowser-extension-704f2da0673dc714f72b9bb82f6bf648795d4335.tar.gz
browser-extension-704f2da0673dc714f72b9bb82f6bf648795d4335.zip
re-enable sanitizing of data: URLs and also sanitize intrinsics on non-HTML pages where CSP doesn't work
-rw-r--r--content/freezer.js64
-rw-r--r--content/main.js78
-rw-r--r--copyright5
3 files changed, 65 insertions, 82 deletions
diff --git a/content/freezer.js b/content/freezer.js
deleted file mode 100644
index 0ea362e..0000000
--- a/content/freezer.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * Helper functions for blocking scripts in pages, based off NoScript's lib/DocumentFreezer.js
- *
- * Copyright (C) 2005-2021 Giorgio Maone - https://maone.net
- * Copyright (C) 2021 jahoti
- * Redistribution terms are gathered in the `copyright' file.
- */
-
-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 || [];
-
- 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;
- }
- }
- 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 (fa.length)
- element._frozenAttributes = fa;
- element._frozen = true;
-}
-
-function mozilla_suppress_scripts(e) {
- if (document.readyState === 'complete') {
- removeEventListener('beforescriptexecute', blockExecute, true);
- console.log('Script suppressor has detached.');
- return;
- }
- console.log("script event", e);
- if (e.isTrusted && !e.target._hachette_payload) {
- e.preventDefault();
- console.log('Suppressed script', e.target);
- }
-};
-
-/*
- * EXPORTS_START
- * EXPORT mozilla_suppress_scripts
- * EXPORT sanitize_attributes
- * EXPORTS_END
- */
diff --git a/content/main.js b/content/main.js
index b2cc9ed..a183913 100644
--- a/content/main.js
+++ b/content/main.js
@@ -13,7 +13,6 @@
* IMPORT sign_data
* IMPORT gen_nonce
* IMPORT is_privileged_url
- * IMPORT mozilla_suppress_scripts
* IMPORT is_chrome
* IMPORT is_mozilla
* IMPORT start_activity_info_server
@@ -132,10 +131,18 @@ function finish_waiting(waiting)
function _wait_for_head(doc, detached_html, callback)
{
const waiting = {doc, detached_html, callback, observers: []};
- if (try_body_started(waiting))
- return;
- waiting.observers = [make_body_start_observer(detached_html, waiting)];
+ /*
+ * For XML and SVG documents, instead of waiting for `<head>', we wait
+ * for the entire document to finish loading.
+ */
+ if (doc instanceof HTMLDocument) {
+ if (try_body_started(waiting))
+ return;
+
+ waiting.observers = [make_body_start_observer(detached_html, waiting)];
+ }
+
waiting.loaded_cb = () => finish_waiting(waiting);
doc.addEventListener("DOMContentLoaded", waiting.loaded_cb);
}
@@ -200,32 +207,72 @@ function desanitize_script(script, policy)
delete script.hachette_blocked_type;
}
-function apply_hachette_csp_rules(doc, policy)
+function apply_hachette_csp_rules(doc, head, policy)
{
const meta = doc.createElement("meta");
meta.setAttribute("http-equiv", "Content-Security-Policy");
meta.setAttribute("content", csp_rule(policy.nonce));
- doc.head.append(meta);
+ head.append(meta);
/* CSP is already in effect, we can remove the <meta> now. */
meta.remove();
}
+function sanitize_urls(element)
+{
+ for (const attribute of [...element.attributes]) {
+ if (/^(href|src|data)$/i.test(attribute.localName) &&
+ /^data:([^,;]*ml|unknown-content-type)/i.test(attribute.value))
+ block_attribute(element, attribute.localName);
+ }
+}
+
+function start_data_urls_sanitizing(doc)
+{
+ doc.querySelectorAll("*[href], *[src], *[data]").forEach(sanitize_urls);
+ const mutation_handler = m => m.addedNodes.forEach(sanitize_urls);
+ const mo = new MutationObserver(ms => ms.forEach(mutation_handler));
+ mo.observe(doc, {childList: true, subtree: true});
+}
+
+function apply_intrinsics_sanitizing(root_element)
+{
+ for (const subelem of root_element.querySelectorAll("*")) {
+ [...subelem.attributes]
+ .filter(a => /^on/i.test(a.localName))
+ .filter(a => /^javascript:/i.test(a.value))
+ .forEach(a => block_attribute(subelem, a.localName));
+ }
+}
+
async function sanitize_document(doc, policy)
{
/*
+ * Blocking of scripts that are in the DOM from the beginning. Needed for
+ * Mozilla, harmless on Chromium.
+ * Note that at least in SVG documents the `src' attr on `<script>'s seems
+ * to be ignored by Firefox, so we don't need to sanitize it.
+ */
+ for (const script of document.getElementsByTagName("script")) {
+ const old_children = [...script.childNodes];
+ script.innerHTML = "";
+ setTimeout(() => old_children.forEach(c => script.append(c)), 0);
+ }
+
+ /*
* Ensure our CSP rules are employed from the beginning. This CSP injection
* method is, when possible, going to be applied together with CSP rules
* injected using webRequest.
+ * For non-HTML documents this is just a dummy operation of adding and
+ * removing `head'.
*/
- const has_own_head = doc.head;
- if (!has_own_head)
- doc.documentElement.prepend(doc.createElement("head"));
+ let added_head = doc.createElement("head");
+ if (!doc.head)
+ doc.documentElement.prepend(added_head);
- apply_hachette_csp_rules(doc, policy);
+ apply_hachette_csp_rules(doc, added_head, policy);
- /* Probably not needed, but...: proceed with DOM in its initial state. */
- if (!has_own_head)
- doc.head.remove();
+ /* Proceed with DOM in its initial state. */
+ added_head.remove();
/*
* <html> node gets hijacked now, to be re-attached after <head> is loaded
@@ -243,10 +290,15 @@ async function sanitize_document(doc, policy)
for (const script of old_html.querySelectorAll("script"))
sanitize_script(script, policy);
+ if (!(doc instanceof HTMLDocument))
+ apply_intrinsics_sanitizing(old_html);
+
new_html.replaceWith(old_html);
for (const script of old_html.querySelectorAll("script"))
desanitize_script(script, policy);
+
+ start_data_urls_sanitizing(doc);
}
if (!is_privileged_url(document.URL)) {
diff --git a/copyright b/copyright
index 58993a6..4c37eb3 100644
--- a/copyright
+++ b/copyright
@@ -67,11 +67,6 @@ License: Expat
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-Files: content/freezer.js
-Copyright: 2005-2021 Giorgio Maone - https://maone.net
- 2021 jahoti <jahoti@tilde.team>
-License: GPL-2+
-
Files: licenses/*
Copyright: 2001, 2002, 2011-2013 Creative Commons
License: CC-BY-4.0