diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/ajax.js | 5 | ||||
-rw-r--r-- | common/connection_types.js | 4 | ||||
-rw-r--r-- | common/lock.js | 4 | ||||
-rw-r--r-- | common/message_server.js | 4 | ||||
-rw-r--r-- | common/misc.js | 85 | ||||
-rw-r--r-- | common/observable.js | 5 | ||||
-rw-r--r-- | common/once.js | 5 | ||||
-rw-r--r-- | common/patterns.js | 145 | ||||
-rw-r--r-- | common/sanitize_JSON.js | 5 | ||||
-rw-r--r-- | common/settings_query.js | 4 | ||||
-rw-r--r-- | common/signing.js | 23 | ||||
-rw-r--r-- | common/storage_client.js | 4 | ||||
-rw-r--r-- | common/storage_light.js | 5 | ||||
-rw-r--r-- | common/storage_raw.js | 5 | ||||
-rw-r--r-- | common/stored_types.js | 4 |
15 files changed, 114 insertions, 193 deletions
diff --git a/common/ajax.js b/common/ajax.js index 8082bbe..7269a8a 100644 --- a/common/ajax.js +++ b/common/ajax.js @@ -1,6 +1,7 @@ /** - * part of Hachette - * Wrapping XMLHttpRequest into a Promise. + * This file is part of Haketilo. + * + * Function: Wrapping XMLHttpRequest into a Promise. * * Copyright (C) 2021 Wojtek Kosior * Redistribution terms are gathered in the `copyright' file. diff --git a/common/connection_types.js b/common/connection_types.js index 88c6964..3e9df56 100644 --- a/common/connection_types.js +++ b/common/connection_types.js @@ -1,5 +1,7 @@ /** - * Hachette background scripts message connection types "enum" + * This file is part of Haketilo. + * + * Function: Define an "enum" of message connection types. * * Copyright (C) 2021 Wojtek Kosior * Redistribution terms are gathered in the `copyright' file. diff --git a/common/lock.js b/common/lock.js index 822ad1b..6cf0835 100644 --- a/common/lock.js +++ b/common/lock.js @@ -1,5 +1,7 @@ /** - * Hachette lock (aka binary semaphore aka mutex) + * This file is part of Haketilo. + * + * Function: Implement a lock (aka binary semaphore aka mutex). * * Copyright (C) 2021 Wojtek Kosior * Redistribution terms are gathered in the `copyright' file. diff --git a/common/message_server.js b/common/message_server.js index ea40487..c8c6696 100644 --- a/common/message_server.js +++ b/common/message_server.js @@ -1,5 +1,7 @@ /** - * Hachette message server + * This file is part of Haketilo. + * + * Function: Message server. * * Copyright (C) 2021 Wojtek Kosior * Redistribution terms are gathered in the `copyright' file. diff --git a/common/misc.js b/common/misc.js index 97fc2dc..9ffb7ff 100644 --- a/common/misc.js +++ b/common/misc.js @@ -1,5 +1,7 @@ /** - * Hachette miscellaneous operations refactored to a separate file + * This file is part of Haketilo. + * + * Function: Miscellaneous operations refactored to a separate file. * * Copyright (C) 2021 Wojtek Kosior * Copyright (C) 2021 jahoti @@ -36,36 +38,26 @@ function Uint8toHex(data) return returnValue; } -function gen_nonce(length) // Default 16 +function gen_nonce(length=16) { - let randomData = new Uint8Array(length || 16); + let randomData = new Uint8Array(length); crypto.getRandomValues(randomData); return Uint8toHex(randomData); } -/* csp rule that blocks all scripts except for those injected by us */ -function csp_rule(nonce) +/* CSP rule that blocks scripts according to policy's needs. */ +function make_csp_rule(policy) { - const rule = `'nonce-${nonce}'`; - return `script-src ${rule}; script-src-elem ${rule}; script-src-attr 'none'; prefetch-src 'none';`; + let rule = "prefetch-src 'none'; script-src-attr 'none';"; + const script_src = policy.has_payload ? + `'nonce-${policy.nonce}'` : "'none'"; + rule += ` script-src ${script_src}; script-src-elem ${script_src};`; + return rule; } /* Check if some HTTP header might define CSP rules. */ -const csp_header_names = new Set([ - "content-security-policy", - "x-webkit-csp", - "x-content-security-policy" -]); - -const report_only_header_name = "content-security-policy-report-only"; - -function is_csp_header_name(string, include_report_only) -{ - string = string && string.toLowerCase().trim() || ""; - - return (include_report_only && string === report_only_header_name) || - csp_header_names.has(string); -} +const csp_header_regex = + /^\s*(content-security-policy|x-webkit-csp|x-content-security-policy)/i; /* * Print item together with type, e.g. @@ -111,52 +103,6 @@ function parse_csp(csp) { return directives; } -/* Make CSP headers do our bidding, not interfere */ -function sanitize_csp_header(header, policy) -{ - const rule = `'nonce-${policy.nonce}'`; - const csp = parse_csp(header.value); - - if (!policy.allow) { - /* No snitching */ - delete csp['report-to']; - delete csp['report-uri']; - - delete csp['script-src']; - delete csp['script-src-elem']; - - csp['script-src-attr'] = ["'none'"]; - csp['prefetch-src'] = ["'none'"]; - } - - if ('script-src' in csp) - csp['script-src'].push(rule); - else - csp['script-src'] = [rule]; - - if ('script-src-elem' in csp) - csp['script-src-elem'].push(rule); - else - csp['script-src-elem'] = [rule]; - - const new_csp = Object.entries(csp).map( - i => `${i[0]} ${i[1].join(' ')};` - ); - - return {name: header.name, value: new_csp.join('')}; -} - -/* csp rule that blocks all scripts except for those injected by us */ -function make_csp_rule(policy) -{ - let rule = "prefetch-src 'none'; ", nonce = `'nonce-${policy.nonce}'`; - if (!policy.allow) { - rule += `script-src ${nonce}; script-src-elem ${nonce}; ` + - "script-src-attr 'none'; "; - } - return rule; -} - /* Regexes and objects to use as/in schemas for parse_json_with_schema(). */ const nonempty_string_matcher = /.+/; @@ -173,11 +119,10 @@ const matchers = { * EXPORTS_START * EXPORT gen_nonce * EXPORT make_csp_rule - * EXPORT is_csp_header_name + * EXPORT csp_header_regex * EXPORT nice_name * EXPORT open_in_settings * EXPORT is_privileged_url - * EXPORT sanitize_csp_header * EXPORT matchers * EXPORTS_END */ diff --git a/common/observable.js b/common/observable.js index 02f1c1b..ab3b444 100644 --- a/common/observable.js +++ b/common/observable.js @@ -1,6 +1,7 @@ /** - * part of Hachette - * Facilitate listening to events + * This file is part of Haketilo. + * + * Function: Facilitate listening to (internal, self-generated) events. * * Copyright (C) 2021 Wojtek Kosior * Redistribution terms are gathered in the `copyright' file. diff --git a/common/once.js b/common/once.js index 098b43f..93e842f 100644 --- a/common/once.js +++ b/common/once.js @@ -1,5 +1,8 @@ /** - * Hachette feature initialization promise + * This file is part of Haketilo. + * + * Function: Wrap APIs that depend on some asynchronous initialization into + * promises. * * Copyright (C) 2021 Wojtek Kosior * Redistribution terms are gathered in the `copyright' file. diff --git a/common/patterns.js b/common/patterns.js index ebb55ab..625be05 100644 --- a/common/patterns.js +++ b/common/patterns.js @@ -1,10 +1,17 @@ /** - * Hachette operations on page url patterns + * This file is part of Haketilo. + * + * Function: Operations on page URL patterns. * * Copyright (C) 2021 Wojtek Kosior * Redistribution terms are gathered in the `copyright' file. */ +const MAX_URL_PATH_LEN = 12; +const MAX_URL_PATH_CHARS = 255; +const MAX_DOMAIN_LEN = 7; +const MAX_DOMAIN_CHARS = 100; + const proto_regex = /^(\w+):\/\/(.*)$/; const user_re = "[^/?#@]+@" @@ -37,103 +44,51 @@ function deconstruct_url(url) [deco.domain, deco.path, deco.query] = http_match.slice(1, 4); } - if (deco.domain) - deco.domain = deco.domain.split("."); - const leading_dash = deco.path[0] === "/"; deco.trailing_dash = deco.path[deco.path.length - 1] === "/"; - deco.path = deco.path.split("/").filter(s => s !== ""); - if (leading_dash || deco.path.length === 0) - deco.path.unshift(""); - return deco; -} + if (deco.domain) { + if (deco.domain.length > MAX_DOMAIN_CHARS) { + const idx = deco.domain.indexOf(".", deco.domain.length - + MAX_DOMAIN_CHARS); + if (idx === -1) + deco.domain = []; + else + deco.domain = deco.domain.substring(idx + 1); -/* Be sane: both arguments should be arrays of length >= 2 */ -function domain_matches(url_domain, pattern_domain) -{ - const length_difference = url_domain.length - pattern_domain.length; - - for (let i = 1; i <= url_domain.length; i++) { - const url_part = url_domain[url_domain.length - i]; - const pattern_part = pattern_domain[pattern_domain.length - i]; - - if (pattern_domain.length === i) { - if (pattern_part === "*") - return length_difference === 0; - if (pattern_part === "**") - return length_difference > 0; - if (pattern_part === "***") - return true; - return length_difference === 0 && pattern_part === url_part; + deco.domain_truncated = true; } - if (pattern_part !== url_part) - return false; - } - - return pattern_domain.length === url_domain.length + 1 && - pattern_domain[0] === "***"; -} - -function path_matches(url_path, url_trailing_dash, - pattern_path, pattern_trailing_dash) -{ - const dashes_ok = !(pattern_trailing_dash && !url_trailing_dash); - - if (pattern_path.length === 0) - return url_path.length === 0 && dashes_ok; - - const length_difference = url_path.length - pattern_path.length; - - for (let i = 0; i < url_path.length; i++) { - if (pattern_path.length === i + 1) { - if (pattern_path[i] === "*") - return length_difference === 0; - if (pattern_path[i] === "**") { - return length_difference > 0 || - (url_path[i] === "**" && dashes_ok); - } - if (pattern_path[i] === "***") - return length_difference >= 0; - return length_difference === 0 && - pattern_path[i] === url_path[i] && dashes_ok; + if (deco.path.length > MAX_URL_PATH_CHARS) { + deco.path = deco.path.substring(0, deco.path.lastIndexOf("/")); + deco.path_truncated = true; } - - if (pattern_path[i] !== url_path[i]) - return false; } - return false; -} - -function url_matches(url, pattern) -{ - const url_deco = deconstruct_url(url); - const pattern_deco = deconstruct_url(pattern); - - if (url_deco === undefined || pattern_deco === undefined) { - console.log(`bad comparison: ${url} and ${pattern}`); - return false + if (typeof deco.domain === "string") { + deco.domain = deco.domain.split("."); + if (deco.domain.splice(0, deco.domain.length - MAX_DOMAIN_LEN).length + > 0) + deco.domain_truncated = true; } - return pattern_deco.proto === url_deco.proto && - !(pattern_deco.proto === "file" && pattern_deco.trailing_dash) && - !!url_deco.domain === !!pattern_deco.domain && - (!url_deco.domain || - domain_matches(url_deco.domain, pattern_deco.domain)) && - path_matches(url_deco.path, url_deco.trailing_dash, - pattern_deco.path, pattern_deco.trailing_dash); + deco.path = deco.path.split("/").filter(s => s !== ""); + if (deco.domain && deco.path.splice(MAX_URL_PATH_LEN).length > 0) + deco.path_truncated = true; + if (leading_dash || deco.path.length === 0) + deco.path.unshift(""); + + return deco; } -function* each_domain_pattern(domain_segments) +function* each_domain_pattern(deco) { - for (let slice = 0; slice < domain_segments.length; slice++) { - const domain_part = domain_segments.slice(slice).join("."); + for (let slice = 0; slice < deco.domain.length - 1; slice++) { + const domain_part = deco.domain.slice(slice).join("."); const domain_wildcards = []; - if (slice === 0) + if (slice === 0 && !deco.domain_truncated) yield domain_part; - if (slice === 1) + if (slice === 1 && !deco.domain_truncated) yield "*." + domain_part; if (slice > 1) yield "**." + domain_part; @@ -141,22 +96,23 @@ function* each_domain_pattern(domain_segments) } } -function* each_path_pattern(path_segments, trailing_dash) +function* each_path_pattern(deco) { - for (let slice = path_segments.length; slice > 0; slice--) { - const path_part = path_segments.slice(0, slice).join("/"); + for (let slice = deco.path.length; slice > 0; slice--) { + const path_part = deco.path.slice(0, slice).join("/"); const path_wildcards = []; - if (slice === path_segments.length) { - if (trailing_dash) + if (slice === deco.path.length && !deco.path_truncated) { + if (deco.trailing_dash) yield path_part + "/"; yield path_part; } - if (slice === path_segments.length - 1 && path_segments[slice] !== "*") + if (slice === deco.path.length - 1 && !deco.path_truncated && + deco.path[slice] !== "*") yield path_part + "/*"; - if (slice < path_segments.length - 1) + if (slice < deco.path.length - 1) yield path_part + "/**"; - if (slice < path_segments.length - 1 || - path_segments[path_segments.length - 1] !== "***") + if (slice !== deco.path.length - 1 || deco.path_truncated || + deco.path[slice] !== "***") yield path_part + "/***"; } } @@ -167,20 +123,19 @@ function* each_url_pattern(url) const deco = deconstruct_url(url); if (deco === undefined) { - console.log("bad url format", url); + console.error("bad url format", url); return false; } - const all_domains = deco.domain ? each_domain_pattern(deco.domain) : [""]; + const all_domains = deco.domain ? each_domain_pattern(deco) : [""]; for (const domain of all_domains) { - for (const path of each_path_pattern(deco.path, deco.trailing_dash)) + for (const path of each_path_pattern(deco)) yield `${deco.proto}://${domain}${path}`; } } /* * EXPORTS_START - * EXPORT url_matches * EXPORT each_url_pattern * EXPORTS_END */ diff --git a/common/sanitize_JSON.js b/common/sanitize_JSON.js index 8b86d2d..4cf1ef4 100644 --- a/common/sanitize_JSON.js +++ b/common/sanitize_JSON.js @@ -1,6 +1,7 @@ /** - * part of Hachette - * Powerful, full-blown format enforcer for externally-obtained JSON + * This file is part of Haketilo. + * + * Function: Powerful, full-blown format enforcer for externally-obtained JSON. * * Copyright (C) 2021 Wojtek Kosior * Redistribution terms are gathered in the `copyright' file. diff --git a/common/settings_query.js b/common/settings_query.js index b54e580..7e1315e 100644 --- a/common/settings_query.js +++ b/common/settings_query.js @@ -1,5 +1,7 @@ /** - * Hachette querying page settings with regard to wildcard records + * This file is part of Haketilo. + * + * Function: Querying page settings. * * Copyright (C) 2021 Wojtek Kosior * Redistribution terms are gathered in the `copyright' file. diff --git a/common/signing.js b/common/signing.js index 2171714..11cd442 100644 --- a/common/signing.js +++ b/common/signing.js @@ -1,6 +1,7 @@ /** - * part of Hachette - * Functions related to "signing" of data, refactored to a separate file. + * This file is part of Haketilo. + * + * Functions: Operations related to "signing" of data. * * Copyright (C) 2021 Wojtek Kosior * Redistribution terms are gathered in the `copyright' file. @@ -10,13 +11,13 @@ * IMPORTS_START * IMPORT sha256 * IMPORT browser - * IMPORT is_chrome + * IMPORT is_mozilla * IMPORTS_END */ /* * In order to make certain data synchronously accessible in certain contexts, - * hachette smuggles it in string form in places like cookies, URLs and headers. + * Haketilo smuggles it in string form in places like cookies, URLs and headers. * When using the smuggled data, we first need to make sure it isn't spoofed. * For that, we use this pseudo-signing mechanism. * @@ -30,18 +31,18 @@ * * The secret shared between execution contexts has to be available * synchronously. Under Mozilla, this is the extension's per-session id. Under - * Chromium, this is the key that resides in the manifest. - * - * An idea to (under Chromium) instead store the secret in a file fetched - * synchronously using XMLHttpRequest is being considered. + * Chromium, this is a dummy web-accessible-resource name that resides in the + * manifest and is supposed to be constructed by each user using a unique value + * (this is done automatically by `build.sh'). */ function get_secret() { - if (is_chrome) - return browser.runtime.getManifest().key.substring(0, 50); - else + if (is_mozilla) return browser.runtime.getURL("dummy"); + + return chrome.runtime.getManifest().web_accessible_resources + .map(r => /^chromium-key-dummy-file-(.*)/.exec(r)).filter(r => r)[0][1]; } function extract_signed(signature, signed_data) diff --git a/common/storage_client.js b/common/storage_client.js index 2b2f495..ef4a0b8 100644 --- a/common/storage_client.js +++ b/common/storage_client.js @@ -1,5 +1,7 @@ /** - * Hachette storage through connection (client side) + * This file is part of Haketilo. + * + * Function: Storage through messages (client side). * * Copyright (C) 2021 Wojtek Kosior * Redistribution terms are gathered in the `copyright' file. diff --git a/common/storage_light.js b/common/storage_light.js index 067bf0c..32e3b1f 100644 --- a/common/storage_light.js +++ b/common/storage_light.js @@ -1,6 +1,7 @@ /** - * part of Hachette - * Storage manager, lighter than the previous one. + * This file is part of Haketilo. + * + * Function: Storage manager, lighter than the previous one. * * Copyright (C) 2021 Wojtek Kosior * Redistribution terms are gathered in the `copyright' file. diff --git a/common/storage_raw.js b/common/storage_raw.js index 4c02ee4..e354b6b 100644 --- a/common/storage_raw.js +++ b/common/storage_raw.js @@ -1,6 +1,7 @@ /** - * part of Hachette - * Basic wrappers for storage API functions. + * This file is part of Haketilo. + * + * Function: Basic wrappers for storage API functions. * * Copyright (C) 2021 Wojtek Kosior * Redistribution terms are gathered in the `copyright' file. diff --git a/common/stored_types.js b/common/stored_types.js index bfceba6..a693b1c 100644 --- a/common/stored_types.js +++ b/common/stored_types.js @@ -1,5 +1,7 @@ /** - * Hachette stored item types "enum" + * This file is part of Haketilo. + * + * Function: Define an "enum" of stored item types. * * Copyright (C) 2021 Wojtek Kosior * Redistribution terms are gathered in the `copyright' file. |