diff options
Diffstat (limited to 'common/misc.js')
-rw-r--r-- | common/misc.js | 159 |
1 files changed, 25 insertions, 134 deletions
diff --git a/common/misc.js b/common/misc.js index 3c7dc46..5b0addb 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 @@ -8,9 +10,7 @@ /* * IMPORTS_START - * IMPORT sha256 * IMPORT browser - * IMPORT is_chrome * IMPORT TYPE_NAME * IMPORT TYPE_PREFIX * IMPORTS_END @@ -38,94 +38,27 @@ 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); } -function gen_unique(url) -{ - return sha256(get_secure_salt() + url); -} - -function get_secure_salt() -{ - if (is_chrome) - return browser.runtime.getManifest().key.substring(0, 50); - else - return browser.runtime.getURL("dummy"); -} - -/* - * stripping url from query and target (everything after `#' or `?' - * gets removed) - */ -function url_item(url) -{ - let url_re = /^([^?#]*).*$/; - let match = url_re.exec(url); - return match[1]; -} - -/* - * Assume a url like: - * https://example.com/green?illuminati=confirmed#<injected-policy>#winky - * This function will make it into an object like: - * { - * "base_url": "https://example.com/green?illuminati=confirmed", - * "target": "#<injected-policy>", - * "target2": "#winky", - * "policy": <injected-policy-as-js-object>, - * "current": <boolean-indicating-whether-policy-url-matches> - * } - * In case url doesn't have 2 #'s, target2 and target can be set to undefined. - */ -function url_extract_target(url) -{ - const url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/; - const match = url_re.exec(url); - const targets = { - base_url: match[1], - target: match[3] || "", - target2: match[4] || "" - }; - if (!targets.target) - return targets; - - /* %7B -> { */ - const index = targets.target.indexOf('%7B'); - if (index === -1) - return targets; - - const now = new Date(); - const sig = targets.target.substring(1, index); - const policy = targets.target.substring(index); - if (sig !== sign_policy(policy, now) && - sig !== sign_policy(policy, now, -1)) - return targets; - - try { - targets.policy = JSON.parse(decodeURIComponent(policy)); - targets.current = targets.policy.base_url === targets.base_url; - } catch (e) { - /* This should not be reached - it's our self-produced valid JSON. */ - console.log("Unexpected internal error - invalid JSON smuggled!", e); - } - - return targets; -} - -/* 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) { - let rule = `script-src 'nonce-${nonce}';`; - if (is_chrome) - rule += `script-src-elem 'nonce-${nonce}';`; + 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; } +/* Check if some HTTP header might define CSP rules. */ +const csp_header_regex = + /^\s*(content-security-policy|x-webkit-csp|x-content-security-policy)/i; + /* * Print item together with type, e.g. * nice_name("s", "hello") → "hello (script)" @@ -143,17 +76,13 @@ function open_in_settings(prefix, name) window.open(url, "_blank"); } -/* Check if url corresponds to a browser's special page */ -function is_privileged_url(url) -{ - return !!/^(chrome(-extension)?|moz-extension):\/\/|^about:/i.exec(url); -} - -/* Sign a given policy for a given time */ -function sign_policy(policy, now, hours_offset) { - let time = Math.floor(now / 3600000) + (hours_offset || 0); - return gen_unique(time + policy); -} +/* + * Check if url corresponds to a browser's special page (or a directory index in + * case of `file://' protocol). + */ +const privileged_reg = + /^(chrome(-extension)?|moz-extension):\/\/|^about:|^file:\/\/.*\/$/; +const is_privileged_url = url => privileged_reg.test(url); /* Parse a CSP header */ function parse_csp(csp) { @@ -174,41 +103,7 @@ function parse_csp(csp) { return directives; } -/* Make CSP headers do our bidding, not interfere */ -function sanitize_csp_header(header, rule, block) -{ - const csp = parse_csp(header.value); - - if (block) { - /* 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_policy = Object.entries(csp).map( - i => `${i[0]} ${i[1].join(' ')};` - ); - - return {name: header.name, value: new_policy.join('')}; -} - -/* Regexes and objest to use as/in schemas for parse_json_with_schema(). */ +/* Regexes and objects to use as/in schemas for parse_json_with_schema(). */ const nonempty_string_matcher = /.+/; const matchers = { @@ -223,15 +118,11 @@ const matchers = { /* * EXPORTS_START * EXPORT gen_nonce - * EXPORT gen_unique - * EXPORT url_item - * EXPORT url_extract_target - * EXPORT sign_policy - * EXPORT csp_rule + * EXPORT make_csp_rule + * EXPORT csp_header_regex * EXPORT nice_name * EXPORT open_in_settings * EXPORT is_privileged_url - * EXPORT sanitize_csp_header * EXPORT matchers * EXPORTS_END */ |