From 4c6a2323d90e9321ec2b78e226167b3013ea69ab Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Sat, 29 Jan 2022 00:03:51 +0100 Subject: make Haketilo buildable again (for Mozilla) How cool it is to throw away 5755 lines of code... --- background/background.js | 67 +++++++ background/broadcast_broker.js | 7 +- background/main.js | 238 ----------------------- background/page_actions_server.js | 149 --------------- background/patterns_query_manager.js | 28 ++- background/policy_injector.js | 87 --------- background/storage.js | 359 ----------------------------------- background/storage_server.js | 95 --------- background/webrequest.js | 2 - 9 files changed, 87 insertions(+), 945 deletions(-) create mode 100644 background/background.js delete mode 100644 background/main.js delete mode 100644 background/page_actions_server.js delete mode 100644 background/policy_injector.js delete mode 100644 background/storage.js delete mode 100644 background/storage_server.js (limited to 'background') diff --git a/background/background.js b/background/background.js new file mode 100644 index 0000000..90826ef --- /dev/null +++ b/background/background.js @@ -0,0 +1,67 @@ +/** + * This file is part of Haketilo. + * + * Function: Background scripts - main script. + * + * Copyright (C) 2022 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 background/patterns_query_manager.js +#IMPORT background/webrequest.js +#IMPORT background/CORS_bypass_server.js +#IMPORT background/broadcast_broker.js +#IMPORT background/indexeddb_files_server.js + +#FROM common/misc.js IMPORT gen_nonce + +function main() { + const secret = gen_nonce(); + + /* + * Some other services depend on IndexedDB which depende on broadcast + * broker, hence we start it first. + */ + broadcast_broker.start(); + CORS_bypass_server.start(); + indexeddb_files_server.start(); + + patterns_query_manager.start(secret); + webrequest.start(secret); +} + +main(); diff --git a/background/broadcast_broker.js b/background/broadcast_broker.js index 79a1bb7..d8a0e28 100644 --- a/background/broadcast_broker.js +++ b/background/broadcast_broker.js @@ -42,8 +42,6 @@ * proprietary program, I am not going to enforce this in court. */ -#IMPORT common/connection_types.js AS CONNECTION_TYPE - #FROM common/message_server.js IMPORT listen_for_connection let next_id = 1; @@ -169,8 +167,7 @@ function remove_broadcast_sender(sender_ctx) function start() { - listen_for_connection(CONNECTION_TYPE.BROADCAST_SEND, new_broadcast_sender); - listen_for_connection(CONNECTION_TYPE.BROADCAST_LISTEN, - new_broadcast_listener); + listen_for_connection("broadcast_send", new_broadcast_sender); + listen_for_connection("broadcast_listen", new_broadcast_listener); } #EXPORT start diff --git a/background/main.js b/background/main.js deleted file mode 100644 index 61c96ac..0000000 --- a/background/main.js +++ /dev/null @@ -1,238 +0,0 @@ -/** - * This file is part of Haketilo. - * - * Function: Main background script. - * - * 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 of this code in a - * proprietary program, I am not going to enforce this in court. - */ - -#IMPORT common/storage_light.js AS light_storage - -#IMPORT background/storage_server.js -#IMPORT background/page_actions_server.js -#IMPORT background/stream_filter.js - -#FROM common/browser.js IMPORT browser -#FROM common/stored_types.js IMPORT TYPE_PREFIX -#FROM background/storage.js IMPORT get_storage -#FROM common/misc.js IMPORT is_privileged_url -#FROM common/settings_query.js IMPORT query_best -#FROM background/policy_injector.js IMPORT inject_csp_headers - -const initial_data = ( -#INCLUDE_VERBATIM default_settings.json -); - -storage_server.start(); -page_actions_server.start(); - -async function init_ext(install_details) -{ - if (install_details.reason != "install") - return; - - let storage = await get_storage(); - - await storage.clear(); - - /* Below we add sample settings to the extension. */ - for (let setting of initial_data) { - let [key, value] = Object.entries(setting)[0]; - storage.set(key[0], key.substring(1), value); - } -} - -browser.runtime.onInstalled.addListener(init_ext); - -/* - * The function below implements a more practical interface for what it does by - * wrapping the old query_best() function. - */ -function decide_policy_for_url(storage, policy_observable, url) -{ - if (storage === undefined) - return {allow: false}; - - const settings = - {allow: policy_observable !== undefined && policy_observable.value}; - - const [pattern, queried_settings] = query_best(storage, url); - - if (queried_settings) { - settings.payload = queried_settings.components; - settings.allow = !!queried_settings.allow && !settings.payload; - settings.pattern = pattern; - } - - return settings; -} - -let storage; -let policy_observable = {}; - -function sanitize_web_page(details) -{ - const url = details.url; - if (is_privileged_url(details.url)) - return; - - const policy = - decide_policy_for_url(storage, policy_observable, details.url); - - let headers = details.responseHeaders; - - headers = inject_csp_headers(headers, policy); - - let skip = false; - for (const header of headers) { - if ((header.name.toLowerCase().trim() === "content-disposition" && - /^\s*attachment\s*(;.*)$/i.test(header.value))) - skip = true; - } - skip = skip || (details.statusCode >= 300 && details.statusCode < 400); - - if (!skip) { - /* Check for API availability. */ - if (browser.webRequest.filterResponseData) - headers = stream_filter.apply(details, headers, policy); - } - - return {responseHeaders: headers}; -} - -const request_url_regex = /^[^?]*\?url=(.*)$/; -const redirect_url_template = browser.runtime.getURL("dummy") + "?settings="; - -function synchronously_smuggle_policy(details) -{ - /* - * Content script will make a synchronous XmlHttpRequest to extension's - * `dummy` file to query settings for given URL. We smuggle that - * information in query parameter of the URL we redirect to. - * A risk of fingerprinting arises if a page with script execution allowed - * guesses the dummy file URL and makes an AJAX call to it. It is currently - * a problem in ManifestV2 Chromium-family port of Haketilo because Chromium - * uses predictable URLs for web-accessible resources. We plan to fix it in - * the future ManifestV3 port. - */ - if (details.type !== "xmlhttprequest") - return {cancel: true}; - - console.debug(`Settings queried using XHR for '${details.url}'.`); - - let policy = {allow: false}; - - try { - /* - * request_url should be of the following format: - * ?url= - */ - const match = request_url_regex.exec(details.url); - const queried_url = decodeURIComponent(match[1]); - - if (details.initiator && !queried_url.startsWith(details.initiator)) { - console.warn(`Blocked suspicious query of '${url}' by '${details.initiator}'. This might be the result of page fingerprinting the browser.`); - return {cancel: true}; - } - - policy = decide_policy_for_url(storage, policy_observable, queried_url); - } catch (e) { - console.warn(`Bad request! Expected ${browser.runtime.getURL("dummy")}?url=. Got ${request_url}. This might be the result of page fingerprinting the browser.`); - } - - const encoded_policy = encodeURIComponent(JSON.stringify(policy)); - - return {redirectUrl: redirect_url_template + encoded_policy}; -} - -const all_types = [ - "main_frame", "sub_frame", "stylesheet", "script", "image", "font", - "object", "xmlhttprequest", "ping", "csp_report", "media", "websocket", - "other", "main_frame", "sub_frame" -]; - -async function start_webRequest_operations() -{ - storage = await get_storage(); - -#IF CHROMIUM - const extra_opts = ["blocking", "extraHeaders"]; -#ELSE - const extra_opts = ["blocking"]; -#ENDIF - - browser.webRequest.onHeadersReceived.addListener( - sanitize_web_page, - {urls: [""], types: ["main_frame", "sub_frame"]}, - extra_opts.concat("responseHeaders") - ); - - const dummy_url_pattern = browser.runtime.getURL("dummy") + "?url=*"; - browser.webRequest.onBeforeRequest.addListener( - synchronously_smuggle_policy, - {urls: [dummy_url_pattern], types: ["xmlhttprequest"]}, - extra_opts - ); - - policy_observable = await light_storage.observe_var("default_allow"); -} - -start_webRequest_operations(); - -#IF MOZILLA -const code = `\ -console.warn("Hi, I'm Mr Dynamic!"); - -console.debug("let's see how window.haketilo_exports looks like now"); - -console.log("haketilo_exports", window.haketilo_exports); -` - -async function test_dynamic_content_scripts() -{ - browser.contentScripts.register({ - "js": [{code}], - "matches": [""], - "allFrames": true, - "runAt": "document_start" -}); -} - -test_dynamic_content_scripts(); -#ENDIF diff --git a/background/page_actions_server.js b/background/page_actions_server.js deleted file mode 100644 index 67c9b9e..0000000 --- a/background/page_actions_server.js +++ /dev/null @@ -1,149 +0,0 @@ -/** - * This file is part of Haketilo. - * - * Function: Serving page actions to content scripts. - * - * 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/storage_light.js AS light_storage -#IMPORT common/connection_types.js AS CONNECTION_TYPE - -#FROM common/browser.js IMPORT browser -#FROM common/message_server.js IMPORT listen_for_connection -#FROM background/storage.js IMPORT get_storage -#FROM common/stored_types.js IMPORT TYPE_PREFIX -#FROM common/sha256.js IMPORT sha256 -#FROM common/ajax.js IMPORT make_ajax_request - -var storage; -var handler; - -// TODO: parallelize script fetching -async function send_scripts(components, port, processed_bags) -{ - for (let [prefix, name] of components) { - if (prefix === TYPE_PREFIX.BAG) { - if (processed_bags.has(name)) { - console.log(`preventing recursive inclusion of bag ${name}`); - continue; - } - - var bag = storage.get(TYPE_PREFIX.BAG, name); - - if (bag === undefined) { - console.log(`no bag in storage for key ${name}`); - continue; - } - - processed_bags.add(name); - await send_scripts(bag, port, processed_bags); - - processed_bags.delete(name); - } else { - let script_text = await get_script_text(name); - if (script_text === undefined) - continue; - - port.postMessage(["inject", [script_text]]); - } - } -} - -async function get_script_text(script_name) -{ - try { - let script_data = storage.get(TYPE_PREFIX.SCRIPT, script_name); - if (script_data === undefined) { - console.log(`missing data for ${script_name}`); - return; - } - let script_text = script_data.text; - if (!script_text) - script_text = await fetch_remote_script(script_data); - return script_text; - } catch (e) { - console.log(e); - } -} - -async function fetch_remote_script(script_data) -{ - try { - let xhttp = await make_ajax_request("GET", script_data.url); - if (xhttp.status === 200) { - let computed_hash = sha256(xhttp.responseText); - if (computed_hash !== script_data.hash) { - console.log(`Bad hash for ${script_data.url}\n got ${computed_hash} instead of ${script_data.hash}`); - return; - } - return xhttp.responseText; - } else { - console.log("script not fetched: " + script_data.url); - return; - } - } catch (e) { - console.log(e); - } -} - -function handle_message(port, message, handler) -{ - port.onMessage.removeListener(handler[0]); - console.debug(`Loading payload '${message.payload}'.`); - - const processed_bags = new Set(); - - send_scripts([message.payload], port, processed_bags); -} - -function new_connection(port) -{ - console.log("new page actions connection!"); - let handler = []; - handler.push(m => handle_message(port, m, handler)); - port.onMessage.addListener(handler[0]); -} - -async function start() -{ - storage = await get_storage(); - - listen_for_connection(CONNECTION_TYPE.PAGE_ACTIONS, new_connection); -} -#EXPORT start diff --git a/background/patterns_query_manager.js b/background/patterns_query_manager.js index 3b74ee9..9de9d35 100644 --- a/background/patterns_query_manager.js +++ b/background/patterns_query_manager.js @@ -64,8 +64,7 @@ let registered_script = null; let script_update_occuring = false; let script_update_needed; -async function update_content_script() -{ +async function update_content_script() { if (script_update_occuring) return; @@ -98,8 +97,7 @@ if (this.haketilo_content_script_main) } #ENDIF -function register(kind, object) -{ +function register(kind, object) { if (kind === "mappings") { for (const [pattern, resource] of Object.entries(object.payloads)) pqt.register(tree, pattern, object.identifier, resource); @@ -118,8 +116,7 @@ function register(kind, object) #ENDIF } -function changed(kind, change) -{ +function changed(kind, change) { const old_version = currently_registered.get(change.key); if (old_version !== undefined) { if (kind === "mappings") { @@ -143,8 +140,19 @@ function changed(kind, change) #ENDIF } -async function start(secret_) -{ +function setting_changed(change) { + if (change.key !== "default_allow") + return; + + default_allow.value = (change.new_val || {}).value; + +#IF MOZILLA || MV3 + script_update_needed = true; + setTimeout(update_content_script, 0); +#ENDIF +} + +async function start(secret_) { secret = secret_; const [mapping_tracking, initial_mappings] = @@ -155,9 +163,9 @@ async function start(secret_) initial_mappings.forEach(m => register("mappings", m)); initial_blocking.forEach(b => register("blocking", b)); - const set_allow_val = ch => default_allow.value = (ch.new_val || {}).value; const [setting_tracking, initial_settings] = - await haketilodb.track.settings(set_allow_val); + await haketilodb.track.settings(setting_changed); + for (const setting of initial_settings) { if (setting.name === "default_allow") Object.assign(default_allow, setting); diff --git a/background/policy_injector.js b/background/policy_injector.js deleted file mode 100644 index 36c950e..0000000 --- a/background/policy_injector.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * 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 of this code in a - * proprietary program, I am not going to enforce this in court. - */ - -#FROM common/misc.js IMPORT csp_header_regex - -/* Re-enable the import below once nonce stuff here is ready */ -#IF NEVER -#FROM common/misc.js IMPORT gen_nonce -#ENDIF - -/* CSP rule that blocks scripts according to policy's needs. */ -function make_csp_rule(policy) -{ - let rule = "prefetch-src 'none'; script-src-attr 'none';"; - const script_src = policy.nonce !== undefined ? - `'nonce-${policy.nonce}'` : "'none'"; - rule += ` script-src ${script_src}; script-src-elem ${script_src};`; - return rule; -} - -function inject_csp_headers(headers, policy) -{ - let csp_headers; - - if (policy.payload) { - headers = headers.filter(h => !csp_header_regex.test(h.name)); - - // TODO: make CSP rules with nonces and facilitate passing them to - // content scripts via dynamic content script registration or - // synchronous XHRs - - // policy.nonce = gen_nonce(); - } - - if (!policy.allow && (policy.nonce || !policy.payload)) { - headers.push({ - name: "content-security-policy", - value: make_csp_rule(policy) - }); - } - - return headers; -} - -#EXPORT inject_csp_headers diff --git a/background/storage.js b/background/storage.js deleted file mode 100644 index fbd4a7e..0000000 --- a/background/storage.js +++ /dev/null @@ -1,359 +0,0 @@ -/** - * This file is part of Haketilo. - * - * Function: Storage manager. - * - * 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/storage_raw.js AS raw_storage -#IMPORT common/observables.js - -#FROM common/stored_types.js IMPORT list_prefixes, TYPE_NAME -#FROM common/lock.js IMPORT lock, unlock, make_lock -#FROM common/once.js IMPORT make_once -#FROM common/browser.js IMPORT browser - -var exports = {}; - -/* A special case of persisted variable is one that contains list of items. */ - -async function get_list_var(name) -{ - let list = await raw_storage.get_var(name); - - return list === undefined ? [] : list; -} - -/* We maintain in-memory copies of some stored lists. */ - -async function list(prefix) -{ - let name = TYPE_NAME[prefix] + "s"; /* Make plural. */ - let map = new Map(); - - for (let item of await get_list_var(name)) - map.set(item, await raw_storage.get(prefix + item)); - - return {map, prefix, name, observable: observables.make(), - lock: make_lock()}; -} - -var list_by_prefix = {}; - -async function init() -{ - for (let prefix of list_prefixes) - list_by_prefix[prefix] = await list(prefix); - - return exports; -} - -/* - * Facilitate listening to changes - */ - -exports.add_change_listener = function (cb, prefixes=list_prefixes) -{ - if (typeof(prefixes) === "string") - prefixes = [prefixes]; - - for (let prefix of prefixes) - observables.subscribe(list_by_prefix[prefix].observable, cb); -} - -exports.remove_change_listener = function (cb, prefixes=list_prefixes) -{ - if (typeof(prefixes) === "string") - prefixes = [prefixes]; - - for (let prefix of prefixes) - observables.unsubscribe(list_by_prefix[prefix].observable, cb); -} - -/* Prepare some hepler functions to get elements of a list */ - -function list_items_it(list, with_values=false) -{ - return with_values ? list.map.entries() : list.map.keys(); -} - -function list_entries_it(list) -{ - return list_items_it(list, true); -} - -function list_items(list, with_values=false) -{ - let array = []; - - for (let item of list_items_it(list, with_values)) - array.push(item); - - return array; -} - -function list_entries(list) -{ - return list_items(list, true); -} - -/* - * Below we make additional effort to update map of given kind of items - * every time an item is added/removed to keep everything coherent. - */ -async function set_item(item, value, list) -{ - await lock(list.lock); - let result = await _set_item(...arguments); - unlock(list.lock) - return result; -} -async function _set_item(item, value, list) -{ - const key = list.prefix + item; - const old_val = list.map.get(item); - const set_obj = {[key]: value}; - if (old_val === undefined) { - const items = list_items(list); - items.push(item); - set_obj["_" + list.name] = items; - } - - await raw_storage.set(set_obj); - list.map.set(item, value); - - const change = { - prefix : list.prefix, - item, - old_val, - new_val : value - }; - - observables.broadcast(list.observable, change); - - return old_val; -} - -// TODO: The actual idea to set value to undefined is good - this way we can -// also set a new list of items in the same API call. But such key -// is still stored in the storage. We need to somehow remove it later. -// For that, we're going to have to store 1 more list of each kind. -async function remove_item(item, list) -{ - await lock(list.lock); - let result = await _remove_item(...arguments); - unlock(list.lock) - return result; -} -async function _remove_item(item, list) -{ - const old_val = list.map.get(item); - if (old_val === undefined) - return; - - const items = list_items(list); - const index = items.indexOf(item); - items.splice(index, 1); - - await raw_storage.set({ - [list.prefix + item]: undefined, - ["_" + list.name]: items - }); - list.map.delete(item); - - const change = { - prefix : list.prefix, - item, - old_val, - new_val : undefined - }; - - observables.broadcast(list.observable, change); - - return old_val; -} - -// TODO: same as above applies here -async function replace_item(old_item, new_item, list, new_val=undefined) -{ - await lock(list.lock); - let result = await _replace_item(...arguments); - unlock(list.lock) - return result; -} -async function _replace_item(old_item, new_item, list, new_val=undefined) -{ - const old_val = list.map.get(old_item); - if (new_val === undefined) { - if (old_val === undefined) - return; - new_val = old_val; - } else if (new_val === old_val && new_item === old_item) { - return old_val; - } - - if (old_item === new_item || old_val === undefined) { - await _set_item(new_item, new_val, list); - return old_val; - } - - const items = list_items(list); - const index = items.indexOf(old_item); - items[index] = new_item; - - await raw_storage.set({ - [list.prefix + old_item]: undefined, - [list.prefix + new_item]: new_val, - ["_" + list.name]: items - }); - list.map.delete(old_item); - - const change = { - prefix : list.prefix, - item : old_item, - old_val, - new_val : undefined - }; - - observables.broadcast(list.observable, change); - - list.map.set(new_item, new_val); - - change.item = new_item; - change.old_val = undefined; - change.new_val = new_val; - - observables.broadcast(list.observable, change); - - return old_val; -} - -/* - * For scripts, item name is chosen by user, data should be - * an object containing: - * - script's url and hash or - * - script's text or - * - all three - */ - -/* - * For bags, item name is chosen by user, data is an array of 2-element - * arrays with type prefix and script/bag names. - */ - -/* - * For pages data argument is an object with properties `allow' - * and `components'. Item name is url. - */ - -exports.set = async function (prefix, item, data) -{ - return set_item(item, data, list_by_prefix[prefix]); -} - -exports.get = function (prefix, item) -{ - return list_by_prefix[prefix].map.get(item); -} - -exports.remove = async function (prefix, item) -{ - return remove_item(item, list_by_prefix[prefix]); -} - -exports.replace = async function (prefix, old_item, new_item, - new_data=undefined) -{ - return replace_item(old_item, new_item, list_by_prefix[prefix], - new_data); -} - -exports.get_all_names = function (prefix) -{ - return list_items(list_by_prefix[prefix]); -} - -exports.get_all_names_it = function (prefix) -{ - return list_items_it(list_by_prefix[prefix]); -} - -exports.get_all = function (prefix) -{ - return list_entries(list_by_prefix[prefix]); -} - -exports.get_all_it = function (prefix) -{ - return list_entries_it(list_by_prefix[prefix]); -} - -/* Finally, a quick way to wipe all the data. */ -// TODO: maybe delete items in such order that none of them ever references -// an already-deleted one? -exports.clear = async function () -{ - let lists = list_prefixes.map((p) => list_by_prefix[p]); - - for (let list of lists) - await lock(list.lock); - - for (let list of lists) { - - let change = { - prefix : list.prefix, - new_val : undefined - }; - - for (let [item, val] of list_entries_it(list)) { - change.item = item; - change.old_val = val; - observables.broadcast(list.observable, change); - } - - list.map = new Map(); - } - - await browser.storage.local.clear(); - - for (let list of lists) - unlock(list.lock); -} - -#EXPORT make_once(init) AS get_storage diff --git a/background/storage_server.js b/background/storage_server.js deleted file mode 100644 index 5aa07bf..0000000 --- a/background/storage_server.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * This file is part of Haketilo. - * - * Function: Storage through messages (server side). - * - * 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/connection_types.js AS CONNECTION_TYPE - -#FROM common/message_server.js IMPORT listen_for_connection -#FROM background/storage.js IMPORT get_storage -#FROM common/stored_types.js IMPORT list_prefixes - -var storage; - -async function handle_remote_call(port, message) -{ - let [call_id, func, args] = message; - - try { - let result = await Promise.resolve(storage[func](...args)); - port.postMessage({call_id, result}); - } catch (error) { - error = error + ''; - port.postMessage({call_id, error}); - } -} - -function remove_storage_listener(cb) -{ - storage.remove_change_listener(cb); -} - -function new_connection(port) -{ - console.log("new remote storage connection!"); - - const message = {}; - for (const prefix of list_prefixes) - message[prefix] = storage.get_all(prefix); - - port.postMessage(message); - - let handle_change = change => port.postMessage(change); - - storage.add_change_listener(handle_change); - - port.onMessage.addListener(m => handle_remote_call(port, m)); - port.onDisconnect.addListener(() => - remove_storage_listener(handle_change)); -} - -async function start() -{ - storage = await get_storage(); - - listen_for_connection(CONNECTION_TYPE.REMOTE_STORAGE, new_connection); -} -#EXPORT start diff --git a/background/webrequest.js b/background/webrequest.js index bd091dc..cb89a3d 100644 --- a/background/webrequest.js +++ b/background/webrequest.js @@ -172,7 +172,5 @@ async function start(secret_) extra_opts ); #ENDIF - - await track_default_allow(); } #EXPORT start -- cgit v1.2.3