Revision 4c6a2323
Added by koszko over 1 year ago
| Makefile.in | ||
|---|---|---|
| 69 | 69 |
openssl req -x509 -new -nodes -key $< -days 1024 -out $@ \ |
| 70 | 70 |
-subj "/CN=Haketilo Test" |
| 71 | 71 |
|
| 72 |
test: test/certs/rootCA.pem test/certs/site.key |
|
| 72 |
test: test/certs/rootCA.pem test/certs/site.key $(default_target)-build.zip
|
|
| 73 | 73 |
MOZ_HEADLESS=whatever pytest |
| 74 | 74 |
|
| 75 | 75 |
test-environment: test/certs/rootCA.pem test/certs/site.key |
| background/background.js | ||
|---|---|---|
| 1 |
/** |
|
| 2 |
* This file is part of Haketilo. |
|
| 3 |
* |
|
| 4 |
* Function: Background scripts - main script. |
|
| 5 |
* |
|
| 6 |
* Copyright (C) 2022 Wojtek Kosior |
|
| 7 |
* |
|
| 8 |
* This program is free software: you can redistribute it and/or modify |
|
| 9 |
* it under the terms of the GNU General Public License as published by |
|
| 10 |
* the Free Software Foundation, either version 3 of the License, or |
|
| 11 |
* (at your option) any later version. |
|
| 12 |
* |
|
| 13 |
* This program is distributed in the hope that it will be useful, |
|
| 14 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 15 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 16 |
* GNU General Public License for more details. |
|
| 17 |
* |
|
| 18 |
* As additional permission under GNU GPL version 3 section 7, you |
|
| 19 |
* may distribute forms of that code without the copy of the GNU |
|
| 20 |
* GPL normally required by section 4, provided you include this |
|
| 21 |
* license notice and, in case of non-source distribution, a URL |
|
| 22 |
* through which recipients can access the Corresponding Source. |
|
| 23 |
* If you modify file(s) with this exception, you may extend this |
|
| 24 |
* exception to your version of the file(s), but you are not |
|
| 25 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 26 |
* exception statement from your version. |
|
| 27 |
* |
|
| 28 |
* As a special exception to the GPL, any HTML file which merely |
|
| 29 |
* makes function calls to this code, and for that purpose |
|
| 30 |
* includes it by reference shall be deemed a separate work for |
|
| 31 |
* copyright law purposes. If you modify this code, you may extend |
|
| 32 |
* this exception to your version of the code, but you are not |
|
| 33 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 34 |
* exception statement from your version. |
|
| 35 |
* |
|
| 36 |
* You should have received a copy of the GNU General Public License |
|
| 37 |
* along with this program. If not, see <https://www.gnu.org/licenses/>. |
|
| 38 |
* |
|
| 39 |
* I, Wojtek Kosior, thereby promise not to sue for violation of this file's |
|
| 40 |
* license. Although I request that you do not make use of this code in a |
|
| 41 |
* proprietary program, I am not going to enforce this in court. |
|
| 42 |
*/ |
|
| 43 |
|
|
| 44 |
#IMPORT background/patterns_query_manager.js |
|
| 45 |
#IMPORT background/webrequest.js |
|
| 46 |
#IMPORT background/CORS_bypass_server.js |
|
| 47 |
#IMPORT background/broadcast_broker.js |
|
| 48 |
#IMPORT background/indexeddb_files_server.js |
|
| 49 |
|
|
| 50 |
#FROM common/misc.js IMPORT gen_nonce |
|
| 51 |
|
|
| 52 |
function main() {
|
|
| 53 |
const secret = gen_nonce(); |
|
| 54 |
|
|
| 55 |
/* |
|
| 56 |
* Some other services depend on IndexedDB which depende on broadcast |
|
| 57 |
* broker, hence we start it first. |
|
| 58 |
*/ |
|
| 59 |
broadcast_broker.start(); |
|
| 60 |
CORS_bypass_server.start(); |
|
| 61 |
indexeddb_files_server.start(); |
|
| 62 |
|
|
| 63 |
patterns_query_manager.start(secret); |
|
| 64 |
webrequest.start(secret); |
|
| 65 |
} |
|
| 66 |
|
|
| 67 |
main(); |
|
| background/broadcast_broker.js | ||
|---|---|---|
| 42 | 42 |
* proprietary program, I am not going to enforce this in court. |
| 43 | 43 |
*/ |
| 44 | 44 |
|
| 45 |
#IMPORT common/connection_types.js AS CONNECTION_TYPE |
|
| 46 |
|
|
| 47 | 45 |
#FROM common/message_server.js IMPORT listen_for_connection |
| 48 | 46 |
|
| 49 | 47 |
let next_id = 1; |
| ... | ... | |
| 169 | 167 |
|
| 170 | 168 |
function start() |
| 171 | 169 |
{
|
| 172 |
listen_for_connection(CONNECTION_TYPE.BROADCAST_SEND, new_broadcast_sender); |
|
| 173 |
listen_for_connection(CONNECTION_TYPE.BROADCAST_LISTEN, |
|
| 174 |
new_broadcast_listener); |
|
| 170 |
listen_for_connection("broadcast_send", new_broadcast_sender);
|
|
| 171 |
listen_for_connection("broadcast_listen", new_broadcast_listener);
|
|
| 175 | 172 |
} |
| 176 | 173 |
#EXPORT start |
| background/main.js | ||
|---|---|---|
| 1 |
/** |
|
| 2 |
* This file is part of Haketilo. |
|
| 3 |
* |
|
| 4 |
* Function: Main background script. |
|
| 5 |
* |
|
| 6 |
* Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org> |
|
| 7 |
* Copyright (C) 2021 Jahoti <jahoti@envs.net> |
|
| 8 |
* |
|
| 9 |
* This program is free software: you can redistribute it and/or modify |
|
| 10 |
* it under the terms of the GNU General Public License as published by |
|
| 11 |
* the Free Software Foundation, either version 3 of the License, or |
|
| 12 |
* (at your option) any later version. |
|
| 13 |
* |
|
| 14 |
* This program is distributed in the hope that it will be useful, |
|
| 15 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 16 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 17 |
* GNU General Public License for more details. |
|
| 18 |
* |
|
| 19 |
* As additional permission under GNU GPL version 3 section 7, you |
|
| 20 |
* may distribute forms of that code without the copy of the GNU |
|
| 21 |
* GPL normally required by section 4, provided you include this |
|
| 22 |
* license notice and, in case of non-source distribution, a URL |
|
| 23 |
* through which recipients can access the Corresponding Source. |
|
| 24 |
* If you modify file(s) with this exception, you may extend this |
|
| 25 |
* exception to your version of the file(s), but you are not |
|
| 26 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 27 |
* exception statement from your version. |
|
| 28 |
* |
|
| 29 |
* As a special exception to the GPL, any HTML file which merely |
|
| 30 |
* makes function calls to this code, and for that purpose |
|
| 31 |
* includes it by reference shall be deemed a separate work for |
|
| 32 |
* copyright law purposes. If you modify this code, you may extend |
|
| 33 |
* this exception to your version of the code, but you are not |
|
| 34 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 35 |
* exception statement from your version. |
|
| 36 |
* |
|
| 37 |
* You should have received a copy of the GNU General Public License |
|
| 38 |
* along with this program. If not, see <https://www.gnu.org/licenses/>. |
|
| 39 |
* |
|
| 40 |
* I, Wojtek Kosior, thereby promise not to sue for violation of this file's |
|
| 41 |
* license. Although I request that you do not make use of this code in a |
|
| 42 |
* proprietary program, I am not going to enforce this in court. |
|
| 43 |
*/ |
|
| 44 |
|
|
| 45 |
#IMPORT common/storage_light.js AS light_storage |
|
| 46 |
|
|
| 47 |
#IMPORT background/storage_server.js |
|
| 48 |
#IMPORT background/page_actions_server.js |
|
| 49 |
#IMPORT background/stream_filter.js |
|
| 50 |
|
|
| 51 |
#FROM common/browser.js IMPORT browser |
|
| 52 |
#FROM common/stored_types.js IMPORT TYPE_PREFIX |
|
| 53 |
#FROM background/storage.js IMPORT get_storage |
|
| 54 |
#FROM common/misc.js IMPORT is_privileged_url |
|
| 55 |
#FROM common/settings_query.js IMPORT query_best |
|
| 56 |
#FROM background/policy_injector.js IMPORT inject_csp_headers |
|
| 57 |
|
|
| 58 |
const initial_data = ( |
|
| 59 |
#INCLUDE_VERBATIM default_settings.json |
|
| 60 |
); |
|
| 61 |
|
|
| 62 |
storage_server.start(); |
|
| 63 |
page_actions_server.start(); |
|
| 64 |
|
|
| 65 |
async function init_ext(install_details) |
|
| 66 |
{
|
|
| 67 |
if (install_details.reason != "install") |
|
| 68 |
return; |
|
| 69 |
|
|
| 70 |
let storage = await get_storage(); |
|
| 71 |
|
|
| 72 |
await storage.clear(); |
|
| 73 |
|
|
| 74 |
/* Below we add sample settings to the extension. */ |
|
| 75 |
for (let setting of initial_data) {
|
|
| 76 |
let [key, value] = Object.entries(setting)[0]; |
|
| 77 |
storage.set(key[0], key.substring(1), value); |
|
| 78 |
} |
|
| 79 |
} |
|
| 80 |
|
|
| 81 |
browser.runtime.onInstalled.addListener(init_ext); |
|
| 82 |
|
|
| 83 |
/* |
|
| 84 |
* The function below implements a more practical interface for what it does by |
|
| 85 |
* wrapping the old query_best() function. |
|
| 86 |
*/ |
|
| 87 |
function decide_policy_for_url(storage, policy_observable, url) |
|
| 88 |
{
|
|
| 89 |
if (storage === undefined) |
|
| 90 |
return {allow: false};
|
|
| 91 |
|
|
| 92 |
const settings = |
|
| 93 |
{allow: policy_observable !== undefined && policy_observable.value};
|
|
| 94 |
|
|
| 95 |
const [pattern, queried_settings] = query_best(storage, url); |
|
| 96 |
|
|
| 97 |
if (queried_settings) {
|
|
| 98 |
settings.payload = queried_settings.components; |
|
| 99 |
settings.allow = !!queried_settings.allow && !settings.payload; |
|
| 100 |
settings.pattern = pattern; |
|
| 101 |
} |
|
| 102 |
|
|
| 103 |
return settings; |
|
| 104 |
} |
|
| 105 |
|
|
| 106 |
let storage; |
|
| 107 |
let policy_observable = {};
|
|
| 108 |
|
|
| 109 |
function sanitize_web_page(details) |
|
| 110 |
{
|
|
| 111 |
const url = details.url; |
|
| 112 |
if (is_privileged_url(details.url)) |
|
| 113 |
return; |
|
| 114 |
|
|
| 115 |
const policy = |
|
| 116 |
decide_policy_for_url(storage, policy_observable, details.url); |
|
| 117 |
|
|
| 118 |
let headers = details.responseHeaders; |
|
| 119 |
|
|
| 120 |
headers = inject_csp_headers(headers, policy); |
|
| 121 |
|
|
| 122 |
let skip = false; |
|
| 123 |
for (const header of headers) {
|
|
| 124 |
if ((header.name.toLowerCase().trim() === "content-disposition" && |
|
| 125 |
/^\s*attachment\s*(;.*)$/i.test(header.value))) |
|
| 126 |
skip = true; |
|
| 127 |
} |
|
| 128 |
skip = skip || (details.statusCode >= 300 && details.statusCode < 400); |
|
| 129 |
|
|
| 130 |
if (!skip) {
|
|
| 131 |
/* Check for API availability. */ |
|
| 132 |
if (browser.webRequest.filterResponseData) |
|
| 133 |
headers = stream_filter.apply(details, headers, policy); |
|
| 134 |
} |
|
| 135 |
|
|
| 136 |
return {responseHeaders: headers};
|
|
| 137 |
} |
|
| 138 |
|
|
| 139 |
const request_url_regex = /^[^?]*\?url=(.*)$/; |
|
| 140 |
const redirect_url_template = browser.runtime.getURL("dummy") + "?settings=";
|
|
| 141 |
|
|
| 142 |
function synchronously_smuggle_policy(details) |
|
| 143 |
{
|
|
| 144 |
/* |
|
| 145 |
* Content script will make a synchronous XmlHttpRequest to extension's |
|
| 146 |
* `dummy` file to query settings for given URL. We smuggle that |
|
| 147 |
* information in query parameter of the URL we redirect to. |
|
| 148 |
* A risk of fingerprinting arises if a page with script execution allowed |
|
| 149 |
* guesses the dummy file URL and makes an AJAX call to it. It is currently |
|
| 150 |
* a problem in ManifestV2 Chromium-family port of Haketilo because Chromium |
|
| 151 |
* uses predictable URLs for web-accessible resources. We plan to fix it in |
|
| 152 |
* the future ManifestV3 port. |
|
| 153 |
*/ |
|
| 154 |
if (details.type !== "xmlhttprequest") |
|
| 155 |
return {cancel: true};
|
|
| 156 |
|
|
| 157 |
console.debug(`Settings queried using XHR for '${details.url}'.`);
|
|
| 158 |
|
|
| 159 |
let policy = {allow: false};
|
|
| 160 |
|
|
| 161 |
try {
|
|
| 162 |
/* |
|
| 163 |
* request_url should be of the following format: |
|
| 164 |
* <url_for_extension's_dummy_file>?url=<valid_urlencoded_url> |
|
| 165 |
*/ |
|
| 166 |
const match = request_url_regex.exec(details.url); |
|
| 167 |
const queried_url = decodeURIComponent(match[1]); |
|
| 168 |
|
|
| 169 |
if (details.initiator && !queried_url.startsWith(details.initiator)) {
|
|
| 170 |
console.warn(`Blocked suspicious query of '${url}' by '${details.initiator}'. This might be the result of page fingerprinting the browser.`);
|
|
| 171 |
return {cancel: true};
|
|
| 172 |
} |
|
| 173 |
|
|
| 174 |
policy = decide_policy_for_url(storage, policy_observable, queried_url); |
|
| 175 |
} catch (e) {
|
|
| 176 |
console.warn(`Bad request! Expected ${browser.runtime.getURL("dummy")}?url=<valid_urlencoded_url>. Got ${request_url}. This might be the result of page fingerprinting the browser.`);
|
|
| 177 |
} |
|
| 178 |
|
|
| 179 |
const encoded_policy = encodeURIComponent(JSON.stringify(policy)); |
|
| 180 |
|
|
| 181 |
return {redirectUrl: redirect_url_template + encoded_policy};
|
|
| 182 |
} |
|
| 183 |
|
|
| 184 |
const all_types = [ |
|
| 185 |
"main_frame", "sub_frame", "stylesheet", "script", "image", "font", |
|
| 186 |
"object", "xmlhttprequest", "ping", "csp_report", "media", "websocket", |
|
| 187 |
"other", "main_frame", "sub_frame" |
|
| 188 |
]; |
|
| 189 |
|
|
| 190 |
async function start_webRequest_operations() |
|
| 191 |
{
|
|
| 192 |
storage = await get_storage(); |
|
| 193 |
|
|
| 194 |
#IF CHROMIUM |
|
| 195 |
const extra_opts = ["blocking", "extraHeaders"]; |
|
| 196 |
#ELSE |
|
| 197 |
const extra_opts = ["blocking"]; |
|
| 198 |
#ENDIF |
|
| 199 |
|
|
| 200 |
browser.webRequest.onHeadersReceived.addListener( |
|
| 201 |
sanitize_web_page, |
|
| 202 |
{urls: ["<all_urls>"], types: ["main_frame", "sub_frame"]},
|
|
| 203 |
extra_opts.concat("responseHeaders")
|
|
| 204 |
); |
|
| 205 |
|
|
| 206 |
const dummy_url_pattern = browser.runtime.getURL("dummy") + "?url=*";
|
|
| 207 |
browser.webRequest.onBeforeRequest.addListener( |
|
| 208 |
synchronously_smuggle_policy, |
|
| 209 |
{urls: [dummy_url_pattern], types: ["xmlhttprequest"]},
|
|
| 210 |
extra_opts |
|
| 211 |
); |
|
| 212 |
|
|
| 213 |
policy_observable = await light_storage.observe_var("default_allow");
|
|
| 214 |
} |
|
| 215 |
|
|
| 216 |
start_webRequest_operations(); |
|
| 217 |
|
|
| 218 |
#IF MOZILLA |
|
| 219 |
const code = `\ |
|
| 220 |
console.warn("Hi, I'm Mr Dynamic!");
|
|
| 221 |
|
|
| 222 |
console.debug("let's see how window.haketilo_exports looks like now");
|
|
| 223 |
|
|
| 224 |
console.log("haketilo_exports", window.haketilo_exports);
|
|
| 225 |
` |
|
| 226 |
|
|
| 227 |
async function test_dynamic_content_scripts() |
|
| 228 |
{
|
|
| 229 |
browser.contentScripts.register({
|
|
| 230 |
"js": [{code}],
|
|
| 231 |
"matches": ["<all_urls>"], |
|
| 232 |
"allFrames": true, |
|
| 233 |
"runAt": "document_start" |
|
| 234 |
}); |
|
| 235 |
} |
|
| 236 |
|
|
| 237 |
test_dynamic_content_scripts(); |
|
| 238 |
#ENDIF |
|
| background/page_actions_server.js | ||
|---|---|---|
| 1 |
/** |
|
| 2 |
* This file is part of Haketilo. |
|
| 3 |
* |
|
| 4 |
* Function: Serving page actions to content scripts. |
|
| 5 |
* |
|
| 6 |
* Copyright (C) 2021 Wojtek Kosior |
|
| 7 |
* |
|
| 8 |
* This program is free software: you can redistribute it and/or modify |
|
| 9 |
* it under the terms of the GNU General Public License as published by |
|
| 10 |
* the Free Software Foundation, either version 3 of the License, or |
|
| 11 |
* (at your option) any later version. |
|
| 12 |
* |
|
| 13 |
* This program is distributed in the hope that it will be useful, |
|
| 14 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 15 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 16 |
* GNU General Public License for more details. |
|
| 17 |
* |
|
| 18 |
* As additional permission under GNU GPL version 3 section 7, you |
|
| 19 |
* may distribute forms of that code without the copy of the GNU |
|
| 20 |
* GPL normally required by section 4, provided you include this |
|
| 21 |
* license notice and, in case of non-source distribution, a URL |
|
| 22 |
* through which recipients can access the Corresponding Source. |
|
| 23 |
* If you modify file(s) with this exception, you may extend this |
|
| 24 |
* exception to your version of the file(s), but you are not |
|
| 25 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 26 |
* exception statement from your version. |
|
| 27 |
* |
|
| 28 |
* As a special exception to the GPL, any HTML file which merely |
|
| 29 |
* makes function calls to this code, and for that purpose |
|
| 30 |
* includes it by reference shall be deemed a separate work for |
|
| 31 |
* copyright law purposes. If you modify this code, you may extend |
|
| 32 |
* this exception to your version of the code, but you are not |
|
| 33 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 34 |
* exception statement from your version. |
|
| 35 |
* |
|
| 36 |
* You should have received a copy of the GNU General Public License |
|
| 37 |
* along with this program. If not, see <https://www.gnu.org/licenses/>. |
|
| 38 |
* |
|
| 39 |
* I, Wojtek Kosior, thereby promise not to sue for violation of this file's |
|
| 40 |
* license. Although I request that you do not make use of this code in a |
|
| 41 |
* proprietary program, I am not going to enforce this in court. |
|
| 42 |
*/ |
|
| 43 |
|
|
| 44 |
#IMPORT common/storage_light.js AS light_storage |
|
| 45 |
#IMPORT common/connection_types.js AS CONNECTION_TYPE |
|
| 46 |
|
|
| 47 |
#FROM common/browser.js IMPORT browser |
|
| 48 |
#FROM common/message_server.js IMPORT listen_for_connection |
|
| 49 |
#FROM background/storage.js IMPORT get_storage |
|
| 50 |
#FROM common/stored_types.js IMPORT TYPE_PREFIX |
|
| 51 |
#FROM common/sha256.js IMPORT sha256 |
|
| 52 |
#FROM common/ajax.js IMPORT make_ajax_request |
|
| 53 |
|
|
| 54 |
var storage; |
|
| 55 |
var handler; |
|
| 56 |
|
|
| 57 |
// TODO: parallelize script fetching |
|
| 58 |
async function send_scripts(components, port, processed_bags) |
|
| 59 |
{
|
|
| 60 |
for (let [prefix, name] of components) {
|
|
| 61 |
if (prefix === TYPE_PREFIX.BAG) {
|
|
| 62 |
if (processed_bags.has(name)) {
|
|
| 63 |
console.log(`preventing recursive inclusion of bag ${name}`);
|
|
| 64 |
continue; |
|
| 65 |
} |
|
| 66 |
|
|
| 67 |
var bag = storage.get(TYPE_PREFIX.BAG, name); |
|
| 68 |
|
|
| 69 |
if (bag === undefined) {
|
|
| 70 |
console.log(`no bag in storage for key ${name}`);
|
|
| 71 |
continue; |
|
| 72 |
} |
|
| 73 |
|
|
| 74 |
processed_bags.add(name); |
|
| 75 |
await send_scripts(bag, port, processed_bags); |
|
| 76 |
|
|
| 77 |
processed_bags.delete(name); |
|
| 78 |
} else {
|
|
| 79 |
let script_text = await get_script_text(name); |
|
| 80 |
if (script_text === undefined) |
|
| 81 |
continue; |
|
| 82 |
|
|
| 83 |
port.postMessage(["inject", [script_text]]); |
|
| 84 |
} |
|
| 85 |
} |
|
| 86 |
} |
|
| 87 |
|
|
| 88 |
async function get_script_text(script_name) |
|
| 89 |
{
|
|
| 90 |
try {
|
|
| 91 |
let script_data = storage.get(TYPE_PREFIX.SCRIPT, script_name); |
|
| 92 |
if (script_data === undefined) {
|
|
| 93 |
console.log(`missing data for ${script_name}`);
|
|
| 94 |
return; |
|
| 95 |
} |
|
| 96 |
let script_text = script_data.text; |
|
| 97 |
if (!script_text) |
|
| 98 |
script_text = await fetch_remote_script(script_data); |
|
| 99 |
return script_text; |
|
| 100 |
} catch (e) {
|
|
| 101 |
console.log(e); |
|
| 102 |
} |
|
| 103 |
} |
|
| 104 |
|
|
| 105 |
async function fetch_remote_script(script_data) |
|
| 106 |
{
|
|
| 107 |
try {
|
|
| 108 |
let xhttp = await make_ajax_request("GET", script_data.url);
|
|
| 109 |
if (xhttp.status === 200) {
|
|
| 110 |
let computed_hash = sha256(xhttp.responseText); |
|
| 111 |
if (computed_hash !== script_data.hash) {
|
|
| 112 |
console.log(`Bad hash for ${script_data.url}\n got ${computed_hash} instead of ${script_data.hash}`);
|
|
| 113 |
return; |
|
| 114 |
} |
|
| 115 |
return xhttp.responseText; |
|
| 116 |
} else {
|
|
| 117 |
console.log("script not fetched: " + script_data.url);
|
|
| 118 |
return; |
|
| 119 |
} |
|
| 120 |
} catch (e) {
|
|
| 121 |
console.log(e); |
|
| 122 |
} |
|
| 123 |
} |
|
| 124 |
|
|
| 125 |
function handle_message(port, message, handler) |
|
| 126 |
{
|
|
| 127 |
port.onMessage.removeListener(handler[0]); |
|
| 128 |
console.debug(`Loading payload '${message.payload}'.`);
|
|
| 129 |
|
|
| 130 |
const processed_bags = new Set(); |
|
| 131 |
|
|
| 132 |
send_scripts([message.payload], port, processed_bags); |
|
| 133 |
} |
|
| 134 |
|
|
| 135 |
function new_connection(port) |
|
| 136 |
{
|
|
| 137 |
console.log("new page actions connection!");
|
|
| 138 |
let handler = []; |
|
| 139 |
handler.push(m => handle_message(port, m, handler)); |
|
| 140 |
port.onMessage.addListener(handler[0]); |
|
| 141 |
} |
|
| 142 |
|
|
| 143 |
async function start() |
|
| 144 |
{
|
|
| 145 |
storage = await get_storage(); |
|
| 146 |
|
|
| 147 |
listen_for_connection(CONNECTION_TYPE.PAGE_ACTIONS, new_connection); |
|
| 148 |
} |
|
| 149 |
#EXPORT start |
|
| background/patterns_query_manager.js | ||
|---|---|---|
| 64 | 64 |
let script_update_occuring = false; |
| 65 | 65 |
let script_update_needed; |
| 66 | 66 |
|
| 67 |
async function update_content_script() |
|
| 68 |
{
|
|
| 67 |
async function update_content_script() {
|
|
| 69 | 68 |
if (script_update_occuring) |
| 70 | 69 |
return; |
| 71 | 70 |
|
| ... | ... | |
| 98 | 97 |
} |
| 99 | 98 |
#ENDIF |
| 100 | 99 |
|
| 101 |
function register(kind, object) |
|
| 102 |
{
|
|
| 100 |
function register(kind, object) {
|
|
| 103 | 101 |
if (kind === "mappings") {
|
| 104 | 102 |
for (const [pattern, resource] of Object.entries(object.payloads)) |
| 105 | 103 |
pqt.register(tree, pattern, object.identifier, resource); |
| ... | ... | |
| 118 | 116 |
#ENDIF |
| 119 | 117 |
} |
| 120 | 118 |
|
| 121 |
function changed(kind, change) |
|
| 122 |
{
|
|
| 119 |
function changed(kind, change) {
|
|
| 123 | 120 |
const old_version = currently_registered.get(change.key); |
| 124 | 121 |
if (old_version !== undefined) {
|
| 125 | 122 |
if (kind === "mappings") {
|
| ... | ... | |
| 143 | 140 |
#ENDIF |
| 144 | 141 |
} |
| 145 | 142 |
|
| 146 |
async function start(secret_) |
|
| 147 |
{
|
|
| 143 |
function setting_changed(change) {
|
|
| 144 |
if (change.key !== "default_allow") |
|
| 145 |
return; |
|
| 146 |
|
|
| 147 |
default_allow.value = (change.new_val || {}).value;
|
|
| 148 |
|
|
| 149 |
#IF MOZILLA || MV3 |
|
| 150 |
script_update_needed = true; |
|
| 151 |
setTimeout(update_content_script, 0); |
|
| 152 |
#ENDIF |
|
| 153 |
} |
|
| 154 |
|
|
| 155 |
async function start(secret_) {
|
|
| 148 | 156 |
secret = secret_; |
| 149 | 157 |
|
| 150 | 158 |
const [mapping_tracking, initial_mappings] = |
| ... | ... | |
| 155 | 163 |
initial_mappings.forEach(m => register("mappings", m));
|
| 156 | 164 |
initial_blocking.forEach(b => register("blocking", b));
|
| 157 | 165 |
|
| 158 |
const set_allow_val = ch => default_allow.value = (ch.new_val || {}).value;
|
|
| 159 | 166 |
const [setting_tracking, initial_settings] = |
| 160 |
await haketilodb.track.settings(set_allow_val); |
|
| 167 |
await haketilodb.track.settings(setting_changed); |
|
| 168 |
|
|
| 161 | 169 |
for (const setting of initial_settings) {
|
| 162 | 170 |
if (setting.name === "default_allow") |
| 163 | 171 |
Object.assign(default_allow, setting); |
| background/policy_injector.js | ||
|---|---|---|
| 1 |
/** |
|
| 2 |
* This file is part of Haketilo. |
|
| 3 |
* |
|
| 4 |
* Function: Injecting policy to page by modifying HTTP headers. |
|
| 5 |
* |
|
| 6 |
* Copyright (C) 2021, Wojtek Kosior |
|
| 7 |
* Copyright (C) 2021, jahoti |
|
| 8 |
* |
|
| 9 |
* This program is free software: you can redistribute it and/or modify |
|
| 10 |
* it under the terms of the GNU General Public License as published by |
|
| 11 |
* the Free Software Foundation, either version 3 of the License, or |
|
| 12 |
* (at your option) any later version. |
|
| 13 |
* |
|
| 14 |
* This program is distributed in the hope that it will be useful, |
|
| 15 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 16 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 17 |
* GNU General Public License for more details. |
|
| 18 |
* |
|
| 19 |
* As additional permission under GNU GPL version 3 section 7, you |
|
| 20 |
* may distribute forms of that code without the copy of the GNU |
|
| 21 |
* GPL normally required by section 4, provided you include this |
|
| 22 |
* license notice and, in case of non-source distribution, a URL |
|
| 23 |
* through which recipients can access the Corresponding Source. |
|
| 24 |
* If you modify file(s) with this exception, you may extend this |
|
| 25 |
* exception to your version of the file(s), but you are not |
|
| 26 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 27 |
* exception statement from your version. |
|
| 28 |
* |
|
| 29 |
* As a special exception to the GPL, any HTML file which merely |
|
| 30 |
* makes function calls to this code, and for that purpose |
|
| 31 |
* includes it by reference shall be deemed a separate work for |
|
| 32 |
* copyright law purposes. If you modify this code, you may extend |
|
| 33 |
* this exception to your version of the code, but you are not |
|
| 34 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 35 |
* exception statement from your version. |
|
| 36 |
* |
|
| 37 |
* You should have received a copy of the GNU General Public License |
|
| 38 |
* along with this program. If not, see <https://www.gnu.org/licenses/>. |
|
| 39 |
* |
|
| 40 |
* |
|
| 41 |
* I, Wojtek Kosior, thereby promise not to sue for violation of this file's |
|
| 42 |
* license. Although I request that you do not make use of this code in a |
|
| 43 |
* proprietary program, I am not going to enforce this in court. |
|
| 44 |
*/ |
|
| 45 |
|
|
| 46 |
#FROM common/misc.js IMPORT csp_header_regex |
|
| 47 |
|
|
| 48 |
/* Re-enable the import below once nonce stuff here is ready */ |
|
| 49 |
#IF NEVER |
|
| 50 |
#FROM common/misc.js IMPORT gen_nonce |
|
| 51 |
#ENDIF |
|
| 52 |
|
|
| 53 |
/* CSP rule that blocks scripts according to policy's needs. */ |
|
| 54 |
function make_csp_rule(policy) |
|
| 55 |
{
|
|
| 56 |
let rule = "prefetch-src 'none'; script-src-attr 'none';"; |
|
| 57 |
const script_src = policy.nonce !== undefined ? |
|
| 58 |
`'nonce-${policy.nonce}'` : "'none'";
|
|
| 59 |
rule += ` script-src ${script_src}; script-src-elem ${script_src};`;
|
|
| 60 |
return rule; |
|
| 61 |
} |
|
| 62 |
|
|
| 63 |
function inject_csp_headers(headers, policy) |
|
| 64 |
{
|
|
| 65 |
let csp_headers; |
|
| 66 |
|
|
| 67 |
if (policy.payload) {
|
|
| 68 |
headers = headers.filter(h => !csp_header_regex.test(h.name)); |
|
| 69 |
|
|
| 70 |
// TODO: make CSP rules with nonces and facilitate passing them to |
|
| 71 |
// content scripts via dynamic content script registration or |
|
| 72 |
// synchronous XHRs |
|
| 73 |
|
|
| 74 |
// policy.nonce = gen_nonce(); |
|
| 75 |
} |
|
| 76 |
|
|
| 77 |
if (!policy.allow && (policy.nonce || !policy.payload)) {
|
|
| 78 |
headers.push({
|
|
| 79 |
name: "content-security-policy", |
|
| 80 |
value: make_csp_rule(policy) |
|
| 81 |
}); |
|
| 82 |
} |
|
| 83 |
|
|
| 84 |
return headers; |
|
| 85 |
} |
|
| 86 |
|
|
| 87 |
#EXPORT inject_csp_headers |
|
| background/storage.js | ||
|---|---|---|
| 1 |
/** |
|
| 2 |
* This file is part of Haketilo. |
|
| 3 |
* |
|
| 4 |
* Function: Storage manager. |
|
| 5 |
* |
|
| 6 |
* Copyright (C) 2021 Wojtek Kosior |
|
| 7 |
* |
|
| 8 |
* This program is free software: you can redistribute it and/or modify |
|
| 9 |
* it under the terms of the GNU General Public License as published by |
|
| 10 |
* the Free Software Foundation, either version 3 of the License, or |
|
| 11 |
* (at your option) any later version. |
|
| 12 |
* |
|
| 13 |
* This program is distributed in the hope that it will be useful, |
|
| 14 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 15 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 16 |
* GNU General Public License for more details. |
|
| 17 |
* |
|
| 18 |
* As additional permission under GNU GPL version 3 section 7, you |
|
| 19 |
* may distribute forms of that code without the copy of the GNU |
|
| 20 |
* GPL normally required by section 4, provided you include this |
|
| 21 |
* license notice and, in case of non-source distribution, a URL |
|
| 22 |
* through which recipients can access the Corresponding Source. |
|
| 23 |
* If you modify file(s) with this exception, you may extend this |
|
| 24 |
* exception to your version of the file(s), but you are not |
|
| 25 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 26 |
* exception statement from your version. |
|
| 27 |
* |
|
| 28 |
* As a special exception to the GPL, any HTML file which merely |
|
| 29 |
* makes function calls to this code, and for that purpose |
|
| 30 |
* includes it by reference shall be deemed a separate work for |
|
| 31 |
* copyright law purposes. If you modify this code, you may extend |
|
| 32 |
* this exception to your version of the code, but you are not |
|
| 33 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 34 |
* exception statement from your version. |
|
| 35 |
* |
|
| 36 |
* You should have received a copy of the GNU General Public License |
|
| 37 |
* along with this program. If not, see <https://www.gnu.org/licenses/>. |
|
| 38 |
* |
|
| 39 |
* I, Wojtek Kosior, thereby promise not to sue for violation of this file's |
|
| 40 |
* license. Although I request that you do not make use of this code in a |
|
| 41 |
* proprietary program, I am not going to enforce this in court. |
|
| 42 |
*/ |
|
| 43 |
|
|
| 44 |
#IMPORT common/storage_raw.js AS raw_storage |
|
| 45 |
#IMPORT common/observables.js |
|
| 46 |
|
|
| 47 |
#FROM common/stored_types.js IMPORT list_prefixes, TYPE_NAME |
|
| 48 |
#FROM common/lock.js IMPORT lock, unlock, make_lock |
|
| 49 |
#FROM common/once.js IMPORT make_once |
|
| 50 |
#FROM common/browser.js IMPORT browser |
|
| 51 |
|
|
| 52 |
var exports = {};
|
|
| 53 |
|
|
| 54 |
/* A special case of persisted variable is one that contains list of items. */ |
|
| 55 |
|
|
| 56 |
async function get_list_var(name) |
|
| 57 |
{
|
|
| 58 |
let list = await raw_storage.get_var(name); |
|
| 59 |
|
|
| 60 |
return list === undefined ? [] : list; |
|
| 61 |
} |
|
| 62 |
|
|
| 63 |
/* We maintain in-memory copies of some stored lists. */ |
|
| 64 |
|
|
| 65 |
async function list(prefix) |
|
| 66 |
{
|
|
| 67 |
let name = TYPE_NAME[prefix] + "s"; /* Make plural. */ |
|
| 68 |
let map = new Map(); |
|
| 69 |
|
|
| 70 |
for (let item of await get_list_var(name)) |
|
| 71 |
map.set(item, await raw_storage.get(prefix + item)); |
|
| 72 |
|
|
| 73 |
return {map, prefix, name, observable: observables.make(),
|
|
| 74 |
lock: make_lock()}; |
|
| 75 |
} |
|
| 76 |
|
|
| 77 |
var list_by_prefix = {};
|
|
| 78 |
|
|
| 79 |
async function init() |
|
| 80 |
{
|
|
| 81 |
for (let prefix of list_prefixes) |
|
| 82 |
list_by_prefix[prefix] = await list(prefix); |
|
| 83 |
|
|
| 84 |
return exports; |
|
| 85 |
} |
|
| 86 |
|
|
| 87 |
/* |
|
| 88 |
* Facilitate listening to changes |
|
| 89 |
*/ |
|
| 90 |
|
|
| 91 |
exports.add_change_listener = function (cb, prefixes=list_prefixes) |
|
| 92 |
{
|
|
| 93 |
if (typeof(prefixes) === "string") |
|
| 94 |
prefixes = [prefixes]; |
|
| 95 |
|
|
| 96 |
for (let prefix of prefixes) |
|
| 97 |
observables.subscribe(list_by_prefix[prefix].observable, cb); |
|
| 98 |
} |
|
| 99 |
|
|
| 100 |
exports.remove_change_listener = function (cb, prefixes=list_prefixes) |
|
| 101 |
{
|
|
| 102 |
if (typeof(prefixes) === "string") |
|
| 103 |
prefixes = [prefixes]; |
|
| 104 |
|
|
| 105 |
for (let prefix of prefixes) |
|
| 106 |
observables.unsubscribe(list_by_prefix[prefix].observable, cb); |
|
| 107 |
} |
|
| 108 |
|
|
| 109 |
/* Prepare some hepler functions to get elements of a list */ |
|
| 110 |
|
|
| 111 |
function list_items_it(list, with_values=false) |
|
| 112 |
{
|
|
| 113 |
return with_values ? list.map.entries() : list.map.keys(); |
|
| 114 |
} |
|
| 115 |
|
|
| 116 |
function list_entries_it(list) |
|
| 117 |
{
|
|
| 118 |
return list_items_it(list, true); |
|
| 119 |
} |
|
| 120 |
|
|
| 121 |
function list_items(list, with_values=false) |
|
| 122 |
{
|
|
| 123 |
let array = []; |
|
| 124 |
|
|
| 125 |
for (let item of list_items_it(list, with_values)) |
|
| 126 |
array.push(item); |
|
| 127 |
|
|
| 128 |
return array; |
|
| 129 |
} |
|
| 130 |
|
|
| 131 |
function list_entries(list) |
|
| 132 |
{
|
|
| 133 |
return list_items(list, true); |
|
| 134 |
} |
|
| 135 |
|
|
| 136 |
/* |
|
| 137 |
* Below we make additional effort to update map of given kind of items |
|
| 138 |
* every time an item is added/removed to keep everything coherent. |
|
| 139 |
*/ |
|
| 140 |
async function set_item(item, value, list) |
|
| 141 |
{
|
|
| 142 |
await lock(list.lock); |
|
| 143 |
let result = await _set_item(...arguments); |
|
| 144 |
unlock(list.lock) |
|
| 145 |
return result; |
|
| 146 |
} |
|
| 147 |
async function _set_item(item, value, list) |
|
| 148 |
{
|
|
| 149 |
const key = list.prefix + item; |
|
| 150 |
const old_val = list.map.get(item); |
|
| 151 |
const set_obj = {[key]: value};
|
|
| 152 |
if (old_val === undefined) {
|
|
| 153 |
const items = list_items(list); |
|
| 154 |
items.push(item); |
|
| 155 |
set_obj["_" + list.name] = items; |
|
| 156 |
} |
|
| 157 |
|
|
| 158 |
await raw_storage.set(set_obj); |
|
| 159 |
list.map.set(item, value); |
|
| 160 |
|
|
| 161 |
const change = {
|
|
| 162 |
prefix : list.prefix, |
|
| 163 |
item, |
|
| 164 |
old_val, |
|
| 165 |
new_val : value |
|
| 166 |
}; |
|
| 167 |
|
|
| 168 |
observables.broadcast(list.observable, change); |
|
| 169 |
|
|
| 170 |
return old_val; |
|
| 171 |
} |
|
| 172 |
|
|
| 173 |
// TODO: The actual idea to set value to undefined is good - this way we can |
|
| 174 |
// also set a new list of items in the same API call. But such key |
|
| 175 |
// is still stored in the storage. We need to somehow remove it later. |
|
| 176 |
// For that, we're going to have to store 1 more list of each kind. |
|
| 177 |
async function remove_item(item, list) |
|
| 178 |
{
|
|
| 179 |
await lock(list.lock); |
|
| 180 |
let result = await _remove_item(...arguments); |
|
| 181 |
unlock(list.lock) |
|
| 182 |
return result; |
|
| 183 |
} |
|
| 184 |
async function _remove_item(item, list) |
|
| 185 |
{
|
|
| 186 |
const old_val = list.map.get(item); |
|
| 187 |
if (old_val === undefined) |
|
| 188 |
return; |
|
| 189 |
|
|
| 190 |
const items = list_items(list); |
|
| 191 |
const index = items.indexOf(item); |
|
| 192 |
items.splice(index, 1); |
|
| 193 |
|
|
| 194 |
await raw_storage.set({
|
|
| 195 |
[list.prefix + item]: undefined, |
|
| 196 |
["_" + list.name]: items |
|
| 197 |
}); |
|
| 198 |
list.map.delete(item); |
|
| 199 |
|
|
| 200 |
const change = {
|
|
| 201 |
prefix : list.prefix, |
|
| 202 |
item, |
|
| 203 |
old_val, |
|
| 204 |
new_val : undefined |
|
| 205 |
}; |
|
| 206 |
|
|
| 207 |
observables.broadcast(list.observable, change); |
|
| 208 |
|
|
| 209 |
return old_val; |
|
| 210 |
} |
|
| 211 |
|
|
| 212 |
// TODO: same as above applies here |
|
| 213 |
async function replace_item(old_item, new_item, list, new_val=undefined) |
|
| 214 |
{
|
|
| 215 |
await lock(list.lock); |
|
| 216 |
let result = await _replace_item(...arguments); |
|
| 217 |
unlock(list.lock) |
|
| 218 |
return result; |
|
| 219 |
} |
|
| 220 |
async function _replace_item(old_item, new_item, list, new_val=undefined) |
|
| 221 |
{
|
|
| 222 |
const old_val = list.map.get(old_item); |
|
| 223 |
if (new_val === undefined) {
|
|
| 224 |
if (old_val === undefined) |
|
| 225 |
return; |
|
| 226 |
new_val = old_val; |
|
| 227 |
} else if (new_val === old_val && new_item === old_item) {
|
|
| 228 |
return old_val; |
|
| 229 |
} |
|
| 230 |
|
|
| 231 |
if (old_item === new_item || old_val === undefined) {
|
|
| 232 |
await _set_item(new_item, new_val, list); |
|
| 233 |
return old_val; |
|
| 234 |
} |
|
| 235 |
|
|
| 236 |
const items = list_items(list); |
|
| 237 |
const index = items.indexOf(old_item); |
|
| 238 |
items[index] = new_item; |
|
| 239 |
|
|
| 240 |
await raw_storage.set({
|
|
| 241 |
[list.prefix + old_item]: undefined, |
|
| 242 |
[list.prefix + new_item]: new_val, |
|
| 243 |
["_" + list.name]: items |
|
| 244 |
}); |
|
| 245 |
list.map.delete(old_item); |
|
| 246 |
|
|
| 247 |
const change = {
|
|
| 248 |
prefix : list.prefix, |
|
| 249 |
item : old_item, |
|
| 250 |
old_val, |
|
| 251 |
new_val : undefined |
|
| 252 |
}; |
|
| 253 |
|
|
| 254 |
observables.broadcast(list.observable, change); |
|
| 255 |
|
|
| 256 |
list.map.set(new_item, new_val); |
|
| 257 |
|
|
| 258 |
change.item = new_item; |
|
| 259 |
change.old_val = undefined; |
|
| 260 |
change.new_val = new_val; |
|
| 261 |
|
|
| 262 |
observables.broadcast(list.observable, change); |
|
| 263 |
|
|
| 264 |
return old_val; |
|
| 265 |
} |
|
| 266 |
|
|
| 267 |
/* |
|
| 268 |
* For scripts, item name is chosen by user, data should be |
|
| 269 |
* an object containing: |
|
| 270 |
* - script's url and hash or |
|
| 271 |
* - script's text or |
|
| 272 |
* - all three |
|
| 273 |
*/ |
|
| 274 |
|
|
| 275 |
/* |
|
| 276 |
* For bags, item name is chosen by user, data is an array of 2-element |
|
| 277 |
* arrays with type prefix and script/bag names. |
|
| 278 |
*/ |
|
| 279 |
|
|
| 280 |
/* |
|
| 281 |
* For pages data argument is an object with properties `allow' |
|
| 282 |
* and `components'. Item name is url. |
|
| 283 |
*/ |
|
| 284 |
|
|
| 285 |
exports.set = async function (prefix, item, data) |
|
| 286 |
{
|
|
| 287 |
return set_item(item, data, list_by_prefix[prefix]); |
|
| 288 |
} |
|
| 289 |
|
|
| 290 |
exports.get = function (prefix, item) |
|
| 291 |
{
|
|
| 292 |
return list_by_prefix[prefix].map.get(item); |
|
| 293 |
} |
|
| 294 |
|
|
| 295 |
exports.remove = async function (prefix, item) |
|
| 296 |
{
|
|
| 297 |
return remove_item(item, list_by_prefix[prefix]); |
|
| 298 |
} |
|
| 299 |
|
|
| 300 |
exports.replace = async function (prefix, old_item, new_item, |
|
| 301 |
new_data=undefined) |
|
| 302 |
{
|
|
| 303 |
return replace_item(old_item, new_item, list_by_prefix[prefix], |
|
| 304 |
new_data); |
|
| 305 |
} |
|
| 306 |
|
|
| 307 |
exports.get_all_names = function (prefix) |
|
| 308 |
{
|
|
| 309 |
return list_items(list_by_prefix[prefix]); |
|
| 310 |
} |
|
| 311 |
|
|
| 312 |
exports.get_all_names_it = function (prefix) |
|
| 313 |
{
|
|
| 314 |
return list_items_it(list_by_prefix[prefix]); |
|
| 315 |
} |
|
| 316 |
|
|
| 317 |
exports.get_all = function (prefix) |
|
| 318 |
{
|
|
| 319 |
return list_entries(list_by_prefix[prefix]); |
|
| 320 |
} |
|
| 321 |
|
|
| 322 |
exports.get_all_it = function (prefix) |
|
| 323 |
{
|
|
| 324 |
return list_entries_it(list_by_prefix[prefix]); |
|
| 325 |
} |
|
| 326 |
|
|
| 327 |
/* Finally, a quick way to wipe all the data. */ |
|
| 328 |
// TODO: maybe delete items in such order that none of them ever references |
|
| 329 |
// an already-deleted one? |
|
| 330 |
exports.clear = async function () |
|
| 331 |
{
|
|
| 332 |
let lists = list_prefixes.map((p) => list_by_prefix[p]); |
|
| 333 |
|
|
| 334 |
for (let list of lists) |
|
| 335 |
await lock(list.lock); |
|
| 336 |
|
|
| 337 |
for (let list of lists) {
|
|
| 338 |
|
|
| 339 |
let change = {
|
|
| 340 |
prefix : list.prefix, |
|
| 341 |
new_val : undefined |
|
| 342 |
}; |
|
| 343 |
|
|
| 344 |
for (let [item, val] of list_entries_it(list)) {
|
|
| 345 |
change.item = item; |
|
| 346 |
change.old_val = val; |
|
| 347 |
observables.broadcast(list.observable, change); |
|
| 348 |
} |
|
| 349 |
|
|
| 350 |
list.map = new Map(); |
|
| 351 |
} |
|
| 352 |
|
|
| 353 |
await browser.storage.local.clear(); |
|
| 354 |
|
|
| 355 |
for (let list of lists) |
|
| 356 |
unlock(list.lock); |
|
| 357 |
} |
|
| 358 |
|
|
| 359 |
#EXPORT make_once(init) AS get_storage |
|
| background/storage_server.js | ||
|---|---|---|
| 1 |
/** |
|
| 2 |
* This file is part of Haketilo. |
|
| 3 |
* |
|
| 4 |
* Function: Storage through messages (server side). |
|
| 5 |
* |
|
| 6 |
* Copyright (C) 2021 Wojtek Kosior |
|
| 7 |
* |
|
| 8 |
* This program is free software: you can redistribute it and/or modify |
|
| 9 |
* it under the terms of the GNU General Public License as published by |
|
| 10 |
* the Free Software Foundation, either version 3 of the License, or |
|
| 11 |
* (at your option) any later version. |
|
| 12 |
* |
|
| 13 |
* This program is distributed in the hope that it will be useful, |
|
| 14 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 15 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 16 |
* GNU General Public License for more details. |
|
| 17 |
* |
|
| 18 |
* As additional permission under GNU GPL version 3 section 7, you |
|
| 19 |
* may distribute forms of that code without the copy of the GNU |
|
| 20 |
* GPL normally required by section 4, provided you include this |
|
| 21 |
* license notice and, in case of non-source distribution, a URL |
|
| 22 |
* through which recipients can access the Corresponding Source. |
|
| 23 |
* If you modify file(s) with this exception, you may extend this |
|
| 24 |
* exception to your version of the file(s), but you are not |
|
| 25 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 26 |
* exception statement from your version. |
|
| 27 |
* |
|
| 28 |
* As a special exception to the GPL, any HTML file which merely |
|
| 29 |
* makes function calls to this code, and for that purpose |
|
| 30 |
* includes it by reference shall be deemed a separate work for |
|
| 31 |
* copyright law purposes. If you modify this code, you may extend |
|
| 32 |
* this exception to your version of the code, but you are not |
|
| 33 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 34 |
* exception statement from your version. |
|
| 35 |
* |
|
| 36 |
* You should have received a copy of the GNU General Public License |
|
| 37 |
* along with this program. If not, see <https://www.gnu.org/licenses/>. |
|
| 38 |
* |
|
| 39 |
* I, Wojtek Kosior, thereby promise not to sue for violation of this file's |
|
| 40 |
* license. Although I request that you do not make use of this code in a |
|
| 41 |
* proprietary program, I am not going to enforce this in court. |
|
| 42 |
*/ |
|
| 43 |
|
|
| 44 |
#IMPORT common/connection_types.js AS CONNECTION_TYPE |
|
| 45 |
|
|
| 46 |
#FROM common/message_server.js IMPORT listen_for_connection |
|
| 47 |
#FROM background/storage.js IMPORT get_storage |
|
| 48 |
#FROM common/stored_types.js IMPORT list_prefixes |
|
| 49 |
|
|
| 50 |
var storage; |
|
| 51 |
|
|
| 52 |
async function handle_remote_call(port, message) |
|
| 53 |
{
|
|
| 54 |
let [call_id, func, args] = message; |
|
| 55 |
|
|
| 56 |
try {
|
|
| 57 |
let result = await Promise.resolve(storage[func](...args)); |
|
| 58 |
port.postMessage({call_id, result});
|
|
| 59 |
} catch (error) {
|
|
| 60 |
error = error + ''; |
|
| 61 |
port.postMessage({call_id, error});
|
|
| 62 |
} |
|
| 63 |
} |
|
| 64 |
|
|
| 65 |
function remove_storage_listener(cb) |
|
| 66 |
{
|
|
| 67 |
storage.remove_change_listener(cb); |
|
| 68 |
} |
|
| 69 |
|
|
| 70 |
function new_connection(port) |
|
| 71 |
{
|
|
| 72 |
console.log("new remote storage connection!");
|
|
| 73 |
|
|
| 74 |
const message = {};
|
|
| 75 |
for (const prefix of list_prefixes) |
|
| 76 |
message[prefix] = storage.get_all(prefix); |
|
| 77 |
|
|
| 78 |
port.postMessage(message); |
|
| 79 |
|
|
| 80 |
let handle_change = change => port.postMessage(change); |
|
| 81 |
|
|
| 82 |
storage.add_change_listener(handle_change); |
|
| 83 |
|
|
| 84 |
port.onMessage.addListener(m => handle_remote_call(port, m)); |
|
| 85 |
port.onDisconnect.addListener(() => |
|
| 86 |
remove_storage_listener(handle_change)); |
|
| 87 |
} |
|
| 88 |
|
|
| 89 |
async function start() |
|
| 90 |
{
|
|
| 91 |
storage = await get_storage(); |
|
| 92 |
|
|
| 93 |
listen_for_connection(CONNECTION_TYPE.REMOTE_STORAGE, new_connection); |
|
| 94 |
} |
|
| 95 |
#EXPORT start |
|
| background/webrequest.js | ||
|---|---|---|
| 172 | 172 |
extra_opts |
| 173 | 173 |
); |
| 174 | 174 |
#ENDIF |
| 175 |
|
|
| 176 |
await track_default_allow(); |
|
| 177 | 175 |
} |
| 178 | 176 |
#EXPORT start |
| common/ajax.js | ||
|---|---|---|
| 1 |
/** |
|
| 2 |
* This file is part of Haketilo. |
|
| 3 |
* |
|
| 4 |
* Function: Wrapping XMLHttpRequest into a Promise. |
|
| 5 |
* |
|
| 6 |
* Copyright (C) 2021 Wojtek Kosior |
|
| 7 |
* |
|
| 8 |
* This program is free software: you can redistribute it and/or modify |
|
| 9 |
* it under the terms of the GNU General Public License as published by |
|
| 10 |
* the Free Software Foundation, either version 3 of the License, or |
|
| 11 |
* (at your option) any later version. |
|
| 12 |
* |
|
| 13 |
* This program is distributed in the hope that it will be useful, |
|
| 14 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 15 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 16 |
* GNU General Public License for more details. |
|
| 17 |
* |
|
| 18 |
* As additional permission under GNU GPL version 3 section 7, you |
|
| 19 |
* may distribute forms of that code without the copy of the GNU |
|
| 20 |
* GPL normally required by section 4, provided you include this |
|
| 21 |
* license notice and, in case of non-source distribution, a URL |
|
| 22 |
* through which recipients can access the Corresponding Source. |
|
| 23 |
* If you modify file(s) with this exception, you may extend this |
|
| 24 |
* exception to your version of the file(s), but you are not |
|
| 25 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 26 |
* exception statement from your version. |
|
| 27 |
* |
|
| 28 |
* As a special exception to the GPL, any HTML file which merely |
|
| 29 |
* makes function calls to this code, and for that purpose |
|
| 30 |
* includes it by reference shall be deemed a separate work for |
|
| 31 |
* copyright law purposes. If you modify this code, you may extend |
|
| 32 |
* this exception to your version of the code, but you are not |
|
| 33 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 34 |
* exception statement from your version. |
|
| 35 |
* |
|
| 36 |
* You should have received a copy of the GNU General Public License |
|
| 37 |
* along with this program. If not, see <https://www.gnu.org/licenses/>. |
|
| 38 |
* |
|
| 39 |
* I, Wojtek Kosior, thereby promise not to sue for violation of this file's |
|
| 40 |
* license. Although I request that you do not make use of this code in a |
|
| 41 |
* proprietary program, I am not going to enforce this in court. |
|
| 42 |
*/ |
|
| 43 |
|
|
| 44 |
function ajax_callback() |
|
| 45 |
{
|
|
| 46 |
if (this.readyState == 4) |
|
| 47 |
this.resolve_callback(this); |
|
| 48 |
} |
|
| 49 |
|
|
| 50 |
function initiate_ajax_request(resolve, reject, method, url) |
|
| 51 |
{
|
|
| 52 |
const xhttp = new XMLHttpRequest(); |
|
| 53 |
xhttp.resolve_callback = resolve; |
|
| 54 |
xhttp.onreadystatechange = ajax_callback; |
|
| 55 |
xhttp.open(method, url, true); |
|
| 56 |
try {
|
|
| 57 |
xhttp.send(); |
|
| 58 |
} catch(e) {
|
|
| 59 |
console.log(e); |
|
| 60 |
setTimeout(reject, 0); |
|
| 61 |
} |
|
| 62 |
} |
|
| 63 |
|
|
| 64 |
function make_ajax_request(method, url) |
|
| 65 |
{
|
|
| 66 |
return new Promise((resolve, reject) => |
|
| 67 |
initiate_ajax_request(resolve, reject, method, url)); |
|
| 68 |
} |
|
| 69 |
|
|
| 70 |
#EXPORT make_ajax_request |
|
| common/broadcast.js | ||
|---|---|---|
| 41 | 41 |
* proprietary program, I am not going to enforce this in court. |
| 42 | 42 |
*/ |
| 43 | 43 |
|
| 44 |
#IMPORT common/connection_types.js AS CONNECTION_TYPE |
|
| 45 |
|
|
| 46 | 44 |
#FROM common/message_server.js IMPORT connect_to_background |
| 47 | 45 |
|
| 48 | 46 |
function sender_connection() |
| 49 | 47 |
{
|
| 50 | 48 |
return {
|
| 51 |
port: connect_to_background(CONNECTION_TYPE.BROADCAST_SEND)
|
|
| 49 |
port: connect_to_background("broadcast_send")
|
|
| 52 | 50 |
}; |
| 53 | 51 |
} |
| 54 | 52 |
#EXPORT sender_connection |
| ... | ... | |
| 94 | 92 |
function listener_connection(cb) |
| 95 | 93 |
{
|
| 96 | 94 |
const conn = {
|
| 97 |
port: connect_to_background(CONNECTION_TYPE.BROADCAST_LISTEN)
|
|
| 95 |
port: connect_to_background("broadcast_listen")
|
|
| 98 | 96 |
}; |
| 99 | 97 |
|
| 100 | 98 |
conn.port.onMessage.addListener(cb); |
| common/connection_types.js | ||
|---|---|---|
| 1 |
/** |
|
| 2 |
* This file is part of Haketilo. |
|
| 3 |
* |
|
| 4 |
* Function: Define an "enum" of message connection types. |
|
| 5 |
* |
|
| 6 |
* Copyright (C) 2021 Wojtek Kosior |
|
| 7 |
* |
|
| 8 |
* This program is free software: you can redistribute it and/or modify |
|
| 9 |
* it under the terms of the GNU General Public License as published by |
|
| 10 |
* the Free Software Foundation, either version 3 of the License, or |
|
| 11 |
* (at your option) any later version. |
|
| 12 |
* |
|
| 13 |
* This program is distributed in the hope that it will be useful, |
|
| 14 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 15 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 16 |
* GNU General Public License for more details. |
|
| 17 |
* |
|
| 18 |
* As additional permission under GNU GPL version 3 section 7, you |
|
| 19 |
* may distribute forms of that code without the copy of the GNU |
|
| 20 |
* GPL normally required by section 4, provided you include this |
|
| 21 |
* license notice and, in case of non-source distribution, a URL |
|
| 22 |
* through which recipients can access the Corresponding Source. |
|
| 23 |
* If you modify file(s) with this exception, you may extend this |
|
| 24 |
* exception to your version of the file(s), but you are not |
|
| 25 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 26 |
* exception statement from your version. |
|
| 27 |
* |
|
| 28 |
* As a special exception to the GPL, any HTML file which merely |
|
| 29 |
* makes function calls to this code, and for that purpose |
|
| 30 |
* includes it by reference shall be deemed a separate work for |
|
| 31 |
* copyright law purposes. If you modify this code, you may extend |
|
| 32 |
* this exception to your version of the code, but you are not |
|
| 33 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 34 |
* exception statement from your version. |
|
| 35 |
* |
|
| 36 |
* You should have received a copy of the GNU General Public License |
|
| 37 |
* along with this program. If not, see <https://www.gnu.org/licenses/>. |
|
| 38 |
* |
|
| 39 |
* I, Wojtek Kosior, thereby promise not to sue for violation of this file's |
|
| 40 |
* license. Although I request that you do not make use of this code in a |
|
| 41 |
* proprietary program, I am not going to enforce this in court. |
|
| 42 |
*/ |
|
| 43 |
|
|
| 44 |
/* |
|
| 45 |
* Those need to be strings so they can be used as 'name' parameter |
|
| 46 |
* to browser.runtime.connect() |
|
| 47 |
*/ |
|
| 48 |
|
|
| 49 |
#EXPORT "0" AS REMOTE_STORAGE |
|
| 50 |
#EXPORT "1" AS PAGE_ACTIONS |
|
| 51 |
#EXPORT "2" AS ACTIVITY_INFO |
|
| 52 |
#EXPORT "3" AS BROADCAST_SEND |
|
| 53 |
#EXPORT "4" AS BROADCAST_LISTEN |
|
| common/indexeddb.js | ||
|---|---|---|
| 48 | 48 |
#IF UNIT_TEST |
| 49 | 49 |
{}
|
| 50 | 50 |
#ELSE |
| 51 |
#INCLUDE_VERBATIM default_settings.json
|
|
| 51 |
#INCLUDE default_settings.json |
|
| 52 | 52 |
#ENDIF |
| 53 | 53 |
); |
| 54 | 54 |
|
| common/lock.js | ||
|---|---|---|
| 1 |
/** |
|
| 2 |
* This file is part of Haketilo. |
|
| 3 |
* |
|
| 4 |
* Function: Implement a lock (aka binary semaphore aka mutex). |
|
| 5 |
* |
|
| 6 |
* Copyright (C) 2021 Wojtek Kosior |
|
| 7 |
* |
|
| 8 |
* This program is free software: you can redistribute it and/or modify |
|
| 9 |
* it under the terms of the GNU General Public License as published by |
|
| 10 |
* the Free Software Foundation, either version 3 of the License, or |
|
| 11 |
* (at your option) any later version. |
|
| 12 |
* |
|
| 13 |
* This program is distributed in the hope that it will be useful, |
|
| 14 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 15 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 16 |
* GNU General Public License for more details. |
|
| 17 |
* |
|
| 18 |
* As additional permission under GNU GPL version 3 section 7, you |
|
| 19 |
* may distribute forms of that code without the copy of the GNU |
|
| 20 |
* GPL normally required by section 4, provided you include this |
|
| 21 |
* license notice and, in case of non-source distribution, a URL |
|
| 22 |
* through which recipients can access the Corresponding Source. |
|
| 23 |
* If you modify file(s) with this exception, you may extend this |
|
| 24 |
* exception to your version of the file(s), but you are not |
|
| 25 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 26 |
* exception statement from your version. |
|
| 27 |
* |
|
| 28 |
* As a special exception to the GPL, any HTML file which merely |
|
| 29 |
* makes function calls to this code, and for that purpose |
|
| 30 |
* includes it by reference shall be deemed a separate work for |
|
| 31 |
* copyright law purposes. If you modify this code, you may extend |
|
| 32 |
* this exception to your version of the code, but you are not |
|
| 33 |
* obligated to do so. If you do not wish to do so, delete this |
|
| 34 |
* exception statement from your version. |
|
| 35 |
* |
|
| 36 |
* You should have received a copy of the GNU General Public License |
|
| 37 |
* along with this program. If not, see <https://www.gnu.org/licenses/>. |
|
| 38 |
* |
|
| 39 |
* I, Wojtek Kosior, thereby promise not to sue for violation of this file's |
|
| 40 |
* license. Although I request that you do not make use of this code in a |
|
| 41 |
* proprietary program, I am not going to enforce this in court. |
|
| 42 |
*/ |
|
| 43 |
|
|
| 44 |
/* |
|
| 45 |
* Javascript runs single-threaded, with an event loop. Because of that, |
|
| 46 |
* explicit synchronization is often not needed. An exception is when we use |
|
| 47 |
* an API function that must wait. Ajax is an example. Callback passed to ajax |
|
| 48 |
* call doesn't get called immediately, but after some time. In the meantime |
|
| 49 |
* some other piece of code might get to execute and modify some variables. |
|
| 50 |
* Access to WebExtension local storage is another situation where this problem |
|
| 51 |
* can occur. |
|
| 52 |
* |
|
| 53 |
* This is a solution. A lock object, that can be used to delay execution of |
|
| 54 |
* some code until other code finishes its critical work. Locking is wrapped |
|
| 55 |
* in a promise. |
|
| 56 |
*/ |
|
| 57 |
|
|
| 58 |
#EXPORT () => ({free: true, queue: []}) AS make_lock
|
|
| 59 |
|
|
| 60 |
function _lock(lock, cb) {
|
|
| 61 |
if (lock.free) {
|
|
| 62 |
lock.free = false; |
|
| 63 |
setTimeout(cb); |
|
| 64 |
} else {
|
|
| 65 |
lock.queue.push(cb); |
|
| 66 |
} |
|
| 67 |
} |
|
| 68 |
|
|
| 69 |
#EXPORT lock => new Promise(resolve => _lock(lock, resolve)) AS lock |
|
| 70 |
|
|
| 71 |
function try_lock(lock) |
|
| 72 |
{
|
|
| 73 |
if (lock.free) {
|
|
| 74 |
lock.free = false; |
|
| 75 |
return true; |
|
| 76 |
} |
|
| 77 |
|
|
| 78 |
return false; |
|
| 79 |
} |
|
| 80 |
#EXPORT try_lock |
|
| 81 |
|
|
| 82 |
function unlock(lock) {
|
|
| 83 |
if (lock.free) |
|
| 84 |
throw new Exception("Attempting to release a free lock");
|
|
| 85 |
|
|
| 86 |
if (lock.queue.length === 0) {
|
|
| 87 |
lock.free = true; |
|
| 88 |
} else {
|
|
| 89 |
let cb = lock.queue[0]; |
|
| 90 |
lock.queue.splice(0, 1); |
|
| 91 |
setTimeout(cb); |
|
| 92 |
} |
|
| 93 |
} |
|
| 94 |
#EXPORT unlock |
|
| common/message_server.js | ||
|---|---|---|
| 97 | 97 |
return browser.runtime.connect({name: magic});
|
| 98 | 98 |
|
| 99 | 99 |
if (!(magic in listeners)) |
| 100 |
throw `no listener for '${magic}'`
|
|
| 100 |
throw `no listener for '${magic}'`;
|
|
| 101 | 101 |
|
| 102 | 102 |
const ports = [new Port(magic), new Port(magic)]; |
| 103 | 103 |
ports[0].other = ports[1]; |
| common/misc.js | ||
|---|---|---|
| 42 | 42 |
* proprietary program, I am not going to enforce this in court. |
| 43 | 43 |
*/ |
| 44 | 44 |
|
| 45 |
#FROM common/browser.js IMPORT browser |
|
| 46 |
#FROM common/stored_types.js IMPORT TYPE_NAME, TYPE_PREFIX |
|
| 47 |
|
|
| 48 | 45 |
/* uint8_to_hex is a separate function used in cryptographic functions. */ |
| 49 | 46 |
const uint8_to_hex = |
| 50 | 47 |
array => [...array].map(b => ("0" + b.toString(16)).slice(-2)).join("");
|
| ... | ... | |
| 83 | 80 |
*/ |
| 84 | 81 |
#EXPORT (prefix, name) => `${name} (${TYPE_NAME[prefix]})` AS nice_name
|
| 85 | 82 |
|
| 86 |
/* Open settings tab with given item's editing already on. */ |
|
| 87 |
function open_in_settings(prefix, name) |
|
| 88 |
{
|
|
| 89 |
name = encodeURIComponent(name); |
|
| 90 |
const url = browser.runtime.getURL("html/options.html#" + prefix + name);
|
|
| 91 |
window.open(url, "_blank"); |
|
| 92 |
} |
|
| 93 |
#EXPORT open_in_settings |
|
| 94 |
|
|
| 95 | 83 |
/* |
| 96 | 84 |
* Check if url corresponds to a browser's special page (or a directory index in |
| 97 | 85 |
* case of `file://' protocol). |
| ... | ... | |
| 102 | 90 |
const priv_reg = /^chrome(-extension)?:\/\/|^about:|^file:\/\/[^?#]*\/([?#]|$)/; |
| 103 | 91 |
#ENDIF |
| 104 | 92 |
#EXPORT url => priv_reg.test(url) AS is_privileged_url |
| 105 |
|
|
| 106 |
/* Parse a CSP header */ |
|
| 107 |
function parse_csp(csp) {
|
|
| 108 |
let directive, directive_array; |
|
| 109 |
let directives = {};
|
|
| 110 |
for (directive of csp.split(';')) {
|
|
| 111 |
directive = directive.trim(); |
|
| 112 |
if (directive === '') |
|
| 113 |
continue; |
|
| 114 |
|
|
| 115 |
directive_array = directive.split(/\s+/); |
|
| 116 |
directive = directive_array.shift(); |
|
| 117 |
/* The "true" case should never occur; nevertheless... */ |
|
| 118 |
directives[directive] = directive in directives ? |
|
| 119 |
directives[directive].concat(directive_array) : |
|
| 120 |
directive_array; |
|
| 121 |
} |
|
| 122 |
return directives; |
|
| 123 |
} |
|
| 124 |
|
|
| 125 |
/* Regexes and objects to use as/in schemas for parse_json_with_schema(). */ |
|
Also available in: Unified diff
make Haketilo buildable again (for Mozilla)
How cool it is to throw away 5755 lines of code...