/** * This file is part of Haketilo. * * Function: Injecting policy to page by modifying HTTP headers. * * Copyright (C) 2021, 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 this code in a * proprietary program, I am not going to enforce this in court. */ /* * IMPORTS_START * IMPORT sign_data * IMPORT extract_signed * IMPORT make_csp_rule * IMPORT csp_header_regex * IMPORTS_END */ function inject_csp_headers(headers, policy) { let csp_headers; let old_signature; let haketilo_header; for (const header of headers.filter(h => h.name === "x-haketilo")) { /* x-haketilo header has format: _0_ */ const match = /^([^_]+)_(0_.*)$/.exec(header.value); if (!match) continue; const result = extract_signed(...match.slice(1, 3)); if (result.fail) continue; /* This should succeed - it's our self-produced valid JSON. */ const old_data = JSON.parse(decodeURIComponent(result.data)); /* Confirmed- it's the originals, smuggled in! */ csp_headers = old_data.csp_headers; old_signature = old_data.policy_sig; haketilo_header = header; break; } if (policy.has_payload) { csp_headers = []; const non_csp_headers = []; const header_list = h => csp_header_regex.test(h) ? csp_headers : non_csp_headers; headers.forEach(h => header_list(h.name).push(h)); headers = non_csp_headers; } else { headers.push(...csp_headers || []); } if (!haketilo_header) { haketilo_header = {name: "x-haketilo"}; headers.push(haketilo_header); } if (old_signature) headers = headers.filter(h => h.value.search(old_signature) === -1); const policy_str = encodeURIComponent(JSON.stringify(policy)); const signed_policy = sign_data(policy_str, new Date().getTime()); const later_30sec = new Date(new Date().getTime() + 30000).toGMTString(); headers.push({ name: "Set-Cookie", value: `haketilo-${signed_policy.join("=")}; Expires=${later_30sec};` }); /* * Smuggle in the signature and the original CSP headers for future use. * These are signed with a time of 0, as it's not clear there is a limit on * how long Firefox might retain headers in the cache. */ let haketilo_data = {csp_headers, policy_sig: signed_policy[0]}; haketilo_data = encodeURIComponent(JSON.stringify(haketilo_data)); haketilo_header.value = sign_data(haketilo_data, 0).join("_"); if (!policy.allow) { headers.push({ name: "content-security-policy", value: make_csp_rule(policy) }); } return headers; } /* * EXPORTS_START * EXPORT inject_csp_headers * EXPORTS_END */