aboutsummaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
authorjahoti <jahoti@tilde.team>2021-09-21 00:00:00 +0000
committerjahoti <jahoti@tilde.team>2021-09-21 00:00:00 +0000
commit59fb32a341d42c685b5167c3d8b4d7b87c49fd18 (patch)
tree17143cd40a59eb06b4e698d6fd9ca9d02abaf2b9 /common
parentb1444d9c9ea065d7c97d5809c3ec5259cb01a1da (diff)
parent960363e7dd98a724246320e49c3fbaff9d68d1bd (diff)
downloadbrowser-extension-59fb32a341d42c685b5167c3d8b4d7b87c49fd18.tar.gz
browser-extension-59fb32a341d42c685b5167c3d8b4d7b87c49fd18.zip
Merge branch 'master' into jahoti-update
Diffstat (limited to 'common')
-rw-r--r--common/ajax.js5
-rw-r--r--common/connection_types.js4
-rw-r--r--common/lock.js4
-rw-r--r--common/message_server.js4
-rw-r--r--common/misc.js85
-rw-r--r--common/observable.js5
-rw-r--r--common/once.js5
-rw-r--r--common/patterns.js145
-rw-r--r--common/sanitize_JSON.js5
-rw-r--r--common/settings_query.js4
-rw-r--r--common/signing.js23
-rw-r--r--common/storage_client.js4
-rw-r--r--common/storage_light.js5
-rw-r--r--common/storage_raw.js5
-rw-r--r--common/stored_types.js4
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.