/** * This file is part of Haketilo. * * Function: Determining what to do on a given web page. * * Copyright (C) 2021 Wojtek Kosior * * 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. */ #IMPORT common/patterns_query_tree.js AS pqt #FROM common/sha256.js IMPORT sha256 /* * CSP rule that either blocks all scripts or only allows scripts with specified * nonce attached. */ function make_csp(nonce) { const rule = nonce ? `'nonce-${nonce}'` : "'none'"; const csp_list = [ ["prefetch-src", "'none'"], ["script-src-attr", "'none'"], ["script-src", rule, "'unsafe-eval'"], ["script-src-elem", rule] ]; return csp_list.map(words => `${words.join(" ")};`).join(" "); } function decide_policy(patterns_tree, url, default_allow, secret) { const policy = {allow: default_allow}; try { var payloads = pqt.search(patterns_tree, url).next().value; } catch (e) { console.error("Haketilo:", e); policy.allow = false; policy.error = {haketilo_error_type: "deciding_policy"}; } if (payloads !== undefined) { /* * mapping will be either the actual mapping identifier or "~allow" if * we matched a simple script block/allow rule. */ policy.mapping = Object.keys(payloads).sort()[0]; const payload = payloads[policy.mapping]; if (policy.mapping === "~allow") { /* Convert 0/1 back to false/true. */ policy.allow = !!payload; } else /* if (payload.identifier) */ { policy.allow = false; policy.payload = payload; /* * Hash a secret and other values into a string that's unpredictable * to someone who does not know these values. What we produce here * is not a true "nonce" because it might get produced multiple * times given the same url and mapping choice. Nevertheless, this * is reasonably good given the limitations WebExtension APIs and * environments give us. If we were using a true nonce, we'd have no * reliable way of passing it to our content scripts. */ const nonce_source = [ policy.mapping, policy.payload.identifier, url, secret ]; policy.nonce = sha256(nonce_source.join(":")); } } if (!policy.allow) policy.csp = make_csp(policy.nonce); return policy; } #EXPORT decide_policy #EXPORT () => ({allow: false, csp: make_csp()}) AS fallback_policy #IF NEVER /* * Note: the functions below were overeagerly written and are not used now but * might prove useful to once we add more functionalities and are hence kept... */ function relaxed_csp_eval(csp) { const new_csp_list = []; for (const directive of csp.split(";")) { const directive_words = directive.trim().split(" "); if (directive_words[0] === "script-src") directive_words.push("'unsafe-eval'"); new_csp_list.push(directive_words); } new_policy.csp = new_csp_list.map(d => `${d.join(" ")}';`).join(" "); } function relax_policy_eval(policy) { const new_policy = Object.assign({}, policy); return Object.assign(new_policy, {csp: relaxed_csp_eval(policy.csp)}); } #EXPORT relax_policy_eval #ENDIF