/**
* This file is part of Haketilo.
*
* Function: Enforcing script blocking rules on a given page, working from a
* content script.
*
* Copyright (C) 2021,2022 Wojtek Kosior
* Copyright (C) 2021 jahoti
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* As additional permission under GNU GPL version 3 section 7, you
* may distribute forms of that code without the copy of the GNU
* GPL normally required by section 4, provided you include this
* license notice and, in case of non-source distribution, a URL
* through which recipients can access the Corresponding Source.
* If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not
* obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* As a special exception to the GPL, any HTML file which merely
* makes function calls to this code, and for that purpose
* includes it by reference shall be deemed a separate work for
* copyright law purposes. If you modify this code, you may extend
* this exception to your version of the code, but you are not
* obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* I, Wojtek Kosior, thereby promise not to sue for violation of this file's
* license. Although I request that you do not make use of this code in a
* proprietary program, I am not going to enforce this in court.
*/
#FROM common/misc.js IMPORT gen_nonce, csp_header_regex
const html_ns = "http://www.w3.org/1999/xhtml";
const svg_ns = "http://www.w3.org/2000/svg";
document.content_loaded = document.readyState === "complete";
const wait_loaded = e => e.content_loaded ? Promise.resolve() :
new Promise(c => e.addEventListener("DOMContentLoaded", c, {once: true}));
wait_loaded(document).then(() => document.content_loaded = true);
/*
* In the case of HTML documents:
* 1. When injecting some payload we need to sanitize CSP tags before
* they reach the document.
* 2. Only tags inside
are considered valid by the browser and
* need to be considered.
* 3. We want to detach from document, wait until its completes
* loading, sanitize it and re-attach .
* 4. We shall wait for anything to appear in or after and take that as
* a sign has finished loading.
* 5. Otherwise, getting the `DOMContentLoaded' event on the document shall also
* be a sign that is fully loaded.
*/
function make_body_start_observer(DOM_element, waiting) {
const observer = new MutationObserver(() => try_body_started(waiting));
observer.observe(DOM_element, {childList: true});
return observer;
}
function try_body_started(waiting) {
const body = waiting.detached_html.querySelector("body");
if ((body && (body.firstChild || body.nextSibling)) ||
waiting.doc.documentElement.nextSibling) {
finish_waiting(waiting);
return true;
}
if (body && waiting.observers.length < 2)
waiting.observers.push(make_body_start_observer(body, waiting));
}
function finish_waiting(waiting) {
if (waiting.finished)
return;
waiting.finished = true;
waiting.observers.forEach(observer => observer.disconnect());
setTimeout(waiting.callback, 0);
}
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)];
wait_loaded(doc).then(() => finish_waiting(waiting));
}
function wait_for_head(doc, detached_html) {
return new Promise(cb => _wait_for_head(doc, detached_html, cb));
}
const blocked_str = "blocked";
function block_attribute(node, attr, ns=null, replace_with=null) {
const [hasa, geta, seta, rema] = ["has", "get", "set", "remove"]
.map(m => (n, ...args) => typeof ns === "string" ?
n[`${m}AttributeNS`](ns, ...args) : n[`${m}Attribute`](...args));
/*
* Disabling attributes by prepending `blocked-' allows them to still be
* relatively easily accessed in case they contain some useful data.
*/
const construct_name = [attr];
while (hasa(node, construct_name.join("-")))
construct_name.unshift(blocked_str);
while (construct_name.length > 1) {
construct_name.shift();
const name = construct_name.join("-");
seta(node, `${blocked_str}-${name}`, geta(node, name));
}
rema(node, attr);
if (replace_with !== null)
seta(node, attr, replace_with);
}
/*
* Used to disable `