aboutsummaryrefslogtreecommitdiff
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
parentb1444d9c9ea065d7c97d5809c3ec5259cb01a1da (diff)
parent960363e7dd98a724246320e49c3fbaff9d68d1bd (diff)
downloadbrowser-extension-59fb32a341d42c685b5167c3d8b4d7b87c49fd18.tar.gz
browser-extension-59fb32a341d42c685b5167c3d8b4d7b87c49fd18.zip
Merge branch 'master' into jahoti-update
-rw-r--r--README.txt6
-rw-r--r--background/cookie_filter.js17
-rw-r--r--background/main.js4
-rw-r--r--background/page_actions_server.js18
-rw-r--r--background/policy_injector.js76
-rw-r--r--background/storage.js4
-rw-r--r--background/storage_server.js4
-rw-r--r--background/stream_filter.js11
-rwxr-xr-xbuild.sh23
-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
-rw-r--r--content/activity_info_server.js13
-rw-r--r--content/freezer.js64
-rw-r--r--content/main.js228
-rw-r--r--content/page_actions.js12
-rw-r--r--content/repo_query.js5
-rw-r--r--copyright9
-rw-r--r--default_settings.json51
-rw-r--r--html/DOM_helpers.js4
-rw-r--r--html/MOZILLA_scrollbar_fix.css6
-rw-r--r--html/back_button.css5
-rw-r--r--html/base.css4
-rw-r--r--html/default_blocking_policy.js5
-rw-r--r--html/display-panel.html8
-rw-r--r--html/display-panel.js36
-rw-r--r--html/import_frame.js4
-rw-r--r--html/options.html30
-rw-r--r--html/options_main.js7
-rw-r--r--icons/haketilo.svg (renamed from icons/hachette.svg)0
-rw-r--r--icons/haketilo128.png (renamed from icons/hachette128.png)bin6031 -> 6031 bytes
-rw-r--r--icons/haketilo16.png (renamed from icons/hachette16.png)bin752 -> 752 bytes
-rw-r--r--icons/haketilo32.png (renamed from icons/hachette32.png)bin1358 -> 1358 bytes
-rw-r--r--icons/haketilo48.png (renamed from icons/hachette48.png)bin2154 -> 2154 bytes
-rw-r--r--icons/haketilo64.png (renamed from icons/hachette64.png)bin2908 -> 2908 bytes
-rw-r--r--manifest.json32
-rwxr-xr-xre-generate_icons.sh2
49 files changed, 490 insertions, 505 deletions
diff --git a/README.txt b/README.txt
index ad640b0..1aec0ba 100644
--- a/README.txt
+++ b/README.txt
@@ -1,4 +1,4 @@
-# Hachette - Make The Web Great Again! #
+# Haketilo - Make The Web Great Again! #
This extension's goal is to allow replacing javascript served by websites
with scripts specified by user. Something like NoScript and Greasemonkey
@@ -9,7 +9,7 @@ Currently, the target browsers for this extension are Ungoogled Chromium
and various forks of Firefox (version 60+).
This extension is still in an early stage. Also see
-`https://hachettebugs.koszko.org/projects/hachette/wiki/' for documentation in
+`https://hydrillabugs.koszko.org/projects/haketilo/wiki/' for documentation in
development.
## Installation ##
@@ -28,6 +28,6 @@ various additional licenses and permissions for particular files.
## Contributing ##
Get the code from: https://git.koszko.org/browser-extension/
-Come to: https://hachettebugs.koszko.org/projects/hachette
+Come to: https://hydrillabugs.koszko.org/projects/haketilo
Optionally, write to $(echo a29zemtvQGtvc3prby5vcmcK | base64 -d)
diff --git a/background/cookie_filter.js b/background/cookie_filter.js
index fea2d23..64d18b2 100644
--- a/background/cookie_filter.js
+++ b/background/cookie_filter.js
@@ -1,7 +1,8 @@
/**
- * part of Hachette
- * Filtering request headers to remove hachette cookies that might have slipped
- * through.
+ * This file is part of Haketilo.
+ *
+ * Function: Filtering request headers to remove haketilo cookies that might
+ * have slipped through.
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
@@ -13,29 +14,29 @@
* IMPORTS_END
*/
-function is_valid_hachette_cookie(cookie)
+function is_valid_haketilo_cookie(cookie)
{
- const match = /^hachette-(\w*)=(.*)$/.exec(cookie);
+ const match = /^haketilo-(\w*)=(.*)$/.exec(cookie);
if (!match)
return false;
return !extract_signed(match.slice(1, 3)).fail;
}
-function remove_hachette_cookies(header)
+function remove_haketilo_cookies(header)
{
if (header.name !== "Cookie")
return header;
const cookies = header.value.split("; ");
- const value = cookies.filter(c => !is_valid_hachette_cookie(c)).join("; ");
+ const value = cookies.filter(c => !is_valid_haketilo_cookie(c)).join("; ");
return value ? {name: "Cookie", value} : null;
}
function filter_cookie_headers(headers)
{
- return headers.map(remove_hachette_cookies).filter(h => h);
+ return headers.map(remove_haketilo_cookies).filter(h => h);
}
/*
diff --git a/background/main.js b/background/main.js
index 03cd5d7..40b3a9e 100644
--- a/background/main.js
+++ b/background/main.js
@@ -1,5 +1,7 @@
/**
- * Hachette main background script
+ * This file is part of Haketilo.
+ *
+ * Function: Main background script.
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
diff --git a/background/page_actions_server.js b/background/page_actions_server.js
index b0db5f5..156a79f 100644
--- a/background/page_actions_server.js
+++ b/background/page_actions_server.js
@@ -1,5 +1,7 @@
/**
- * Hachette serving of page actions to content scripts
+ * This file is part of Haketilo.
+ *
+ * Function: Serving page actions to content scripts.
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
@@ -25,15 +27,19 @@ let policy_observable;
function send_actions(url, port)
{
- let [pattern, settings] = query_best(storage, url);
- if (!settings)
- settings = {allow: policy_observable && policy_observable.value};
+ const [pattern, queried_settings] = query_best(storage, url);
+
+ const settings = {allow: policy_observable && policy_observable.value};
+ Object.assign(settings, queried_settings);
+ if (settings.components)
+ settings.allow = false;
+
const repos = storage.get_all(TYPE_PREFIX.REPO);
port.postMessage(["settings", [pattern, settings, repos]]);
- let components = settings.components;
- let processed_bags = new Set();
+ const components = settings.components;
+ const processed_bags = new Set();
if (components !== undefined)
send_scripts([components], port, processed_bags);
diff --git a/background/policy_injector.js b/background/policy_injector.js
index 318190b..881595b 100644
--- a/background/policy_injector.js
+++ b/background/policy_injector.js
@@ -1,5 +1,7 @@
/**
- * Hachette injecting policy to page using webRequest
+ * This file is part of Haketilo.
+ *
+ * Function: Injecting policy to page by modifying HTTP headers.
*
* Copyright (C) 2021 Wojtek Kosior
* Copyright (C) 2021 jahoti
@@ -10,33 +12,81 @@
* IMPORTS_START
* IMPORT sign_data
* IMPORT extract_signed
- * IMPORT sanitize_csp_header
* IMPORT make_csp_rule
- * IMPORT is_csp_header_name
+ * IMPORT csp_header_regex
* IMPORTS_END
*/
function inject_csp_headers(headers, policy)
{
- if (!policy.allow || policy.has_payload) {
- /* Remove report-only CSP headers that snitch on us. */
- headers = headers.filter(h => !is_csp_header_name(h.name, true));
+ let csp_headers;
+ let old_signature;
+ let haketilo_header;
- /* Add our own CSP header */
- headers.push({
- name: "content-security-policy",
- value: make_csp_rule(policy)
- });
+ for (const header of headers.filter(h => h.name === "x-haketilo")) {
+ /* x-haketilo header has format: <signature>_0_<data> */
+ const match = /^([^_]+)_(0_.*)$/.exec(header.value);
+ if (!match)
+ continue;
+
+ const result = extract_signed(...match.slice(1, 3));
+ if (result.fail)
+ continue;
+
+ /* This should succeed - it's our self-produced valid JSON. */
+ const old_data = JSON.parse(decodeURIComponent(result.data));
+
+ /* Confirmed- it's the originals, smuggled in! */
+ csp_headers = old_data.csp_headers;
+ old_signature = old_data.policy_sig;
+
+ haketilo_header = header;
+ break;
+ }
+
+ if (policy.has_payload) {
+ csp_headers = [];
+ const non_csp_headers = [];
+ const header_list =
+ h => csp_header_regex.test(h) ? csp_headers : non_csp_headers;
+ headers.forEach(h => header_list(h.name).push(h));
+ headers = non_csp_headers;
+ } else {
+ headers.push(...csp_headers || []);
}
-
+
+ if (!haketilo_header) {
+ haketilo_header = {name: "x-haketilo"};
+ headers.push(haketilo_header);
+ }
+
+ if (old_signature)
+ headers = headers.filter(h => h.value.search(old_signature) === -1);
+
const policy_str = encodeURIComponent(JSON.stringify(policy));
const signed_policy = sign_data(policy_str, new Date().getTime());
const later_30sec = new Date(new Date().getTime() + 30000).toGMTString();
headers.push({
name: "Set-Cookie",
- value: `hachette-${signed_policy.join("=")}; Expires=${later_30sec};`
+ value: `haketilo-${signed_policy.join("=")}; Expires=${later_30sec};`
});
+ /*
+ * Smuggle in the signature and the original CSP headers for future use.
+ * These are signed with a time of 0, as it's not clear there is a limit on
+ * how long Firefox might retain headers in the cache.
+ */
+ let haketilo_data = {csp_headers, policy_sig: signed_policy[0]};
+ haketilo_data = encodeURIComponent(JSON.stringify(haketilo_data));
+ haketilo_header.value = sign_data(haketilo_data, 0).join("_");
+
+ if (!policy.allow) {
+ headers.push({
+ name: "content-security-policy",
+ value: make_csp_rule(policy)
+ });
+ }
+
return headers;
}
diff --git a/background/storage.js b/background/storage.js
index 12c0c61..a4e626a 100644
--- a/background/storage.js
+++ b/background/storage.js
@@ -1,5 +1,7 @@
/**
- * Hachette storage manager
+ * This file is part of Haketilo.
+ *
+ * Function: Storage manager.
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
diff --git a/background/storage_server.js b/background/storage_server.js
index 2252eb5..73126d4 100644
--- a/background/storage_server.js
+++ b/background/storage_server.js
@@ -1,5 +1,7 @@
/**
- * Hachette storage through connection (server side)
+ * This file is part of Haketilo.
+ *
+ * Function: Storage through messages (server side).
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
diff --git a/background/stream_filter.js b/background/stream_filter.js
index 96b6132..e5e0827 100644
--- a/background/stream_filter.js
+++ b/background/stream_filter.js
@@ -1,5 +1,7 @@
/**
- * Hachette modifying a web page using the StreamFilter API
+ * This file is part of Haketilo.
+ *
+ * Function: Modifying a web page using the StreamFilter API.
*
* Copyright (C) 2018 Giorgio Maone <giorgio@maone.net>
* Copyright (C) 2021 Wojtek Kosior
@@ -12,7 +14,7 @@
/*
* IMPORTS_START
* IMPORT browser
- * IMPORT is_csp_header_name
+ * IMPORT csp_header_regex
* IMPORTS_END
*/
@@ -116,8 +118,7 @@ function may_define_csp_rules(html)
const doc = new DOMParser().parseFromString(html, "text/html");
for (const meta of doc.querySelectorAll("head>meta[http-equiv]")) {
- if (is_csp_header_name(meta.getAttribute("http-equiv"), true) &&
- meta.content)
+ if (csp_header_regex.test(meta.httpEquiv) && meta.content)
return true;
}
@@ -174,7 +175,7 @@ function filter_data(properties, event)
*/
const dummy_script =
- `<script data-hachette-deleteme="${properties.policy.nonce}" nonce="${properties.policy.nonce}">null</script>`;
+ `<script data-haketilo-deleteme="${properties.policy.nonce}" nonce="${properties.policy.nonce}">null</script>`;
const doctype_decl = /^(\s*<!doctype[^<>"']*>)?/i.exec(decoded)[0];
decoded = doctype_decl + dummy_script +
decoded.substring(doctype_decl.length);
diff --git a/build.sh b/build.sh
index 0659ed1..478ca04 100755
--- a/build.sh
+++ b/build.sh
@@ -200,19 +200,18 @@ main() {
GECKO_APPLICATIONS=''
if [ "$BROWSER" = "chromium" ]; then
+ CHROMIUM_KEY="$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64)"
+ CHROMIUM_KEY=$(echo chromium-key-dummy-file-$CHROMIUM_KEY | tr / -)
+ touch $BUILDDIR/$CHROMIUM_KEY
+
CHROMIUM_KEY="\n\
-\n\
- // WARNING!!!\n\
- // EACH USER SHOULD REPLACE \"key\" WITH A UNIQUE VALUE!!!\n\
- // OTHERWISE, SECURITY CAN BE TRIVIALLY COMPROMISED!\n\
- //\n\
- // A unique key can be generated with:\n\
- // $ ssh-keygen -f /path/to/new/key.pem -t rsa -b 1024\n\
- //\n\
- // Only relevant to users of chrome-based browsers.\n\
- // Users of Firefox forks are safe.\n\
-\n\
- \"key\": \"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcnNhAAAAAwEAAQAAAIEA+0GT5WNmRRo8e5tL9+BmNtY6aBPwLIgbPnLShYBMSR40iYwLTsccrkwBXb3bs1o4p6q5WJugI8Lsia+GXZc/XHGFkq7D1aWiTxlJLs8z0JC2TQ2/yatYmBMchogYGeeUfP7aI7JJZwpATts+VhIvgga/4FYj+DijMIEpwdckqFEAAAII4Dh7HOA4exwAAAAHc3NoLXJzYQAAAIEA+0GT5WNmRRo8e5tL9+BmNtY6aBPwLIgbPnLShYBMSR40iYwLTsccrkwBXb3bs1o4p6q5WJugI8Lsia+GXZc/XHGFkq7D1aWiTxlJLs8z0JC2TQ2/yatYmBMchogYGeeUfP7aI7JJZwpATts+VhIvgga/4FYj+DijMIEpwdckqFEAAAADAQABAAAAgEHB5/MhEKMFOs8e1cMJ97ZiWubiUPlWpcqyQmauLUj1nspg3JTBh8AWJEVkaxuFgU5gYCHQmRjC6yUdywyziOEkFA4r/WpX4WmbIe+GQHRHhitLN0dgF8N6/fVNOoa5StTdfZqyl23pVXyepoDNjrJFKyupqPMmpwfH5lGr9RwBAAAAQG76HflB/5j8P2YgIYX6dQT4Ei0SqiIjNVy7jFJUQDKSJg/PYkedE02JZJBJPcMYxEJUxXtMgq+upamNILfkmY0AAABBAP4v0O5dqjy16xDDFzb4DPNAcw5Za9KJaXKVkUuKXMNZOKTR0RC/upjNTmttY980RKdIx5zA25dO8cx563bSDIsAAABBAP0MaOpBiai/eRmLqhlthHODa+Mur6W3uc9PyhWhgDBjLNMR/doaYeyfVKxtIiN3a+HkN++G+vbokRweQv++bhMAAAANdXJ6QGxvY2FsaG9zdAECAwQFBg==\","
+ // WARNING!!!\n\
+ // EACH USER SHOULD REPLACE DUMMY FILE's VALUE WITH A UNIQUE ONE!!!\n\
+ // OTHERWISE, SECURITY CAN BE TRIVIALLY COMPROMISED!\n\
+ // Only relevant to users of chrome-based browsers.\n\
+ // Users of Firefox forks are safe.\n\
+ \"$CHROMIUM_KEY\"\
+"
else
GECKO_APPLICATIONS="\n\
\"applications\": {\n\
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.
diff --git a/content/activity_info_server.js b/content/activity_info_server.js
index beecb1a..d1dfe36 100644
--- a/content/activity_info_server.js
+++ b/content/activity_info_server.js
@@ -1,7 +1,8 @@
/**
- * part of Hachette
- * Informing about activities performed by content script (script injection,
- * script blocking).
+ * This file is part of Haketilo.
+ *
+ * Function: Informing the popup about what happens in the content script
+ * (script injection, script blocking, etc.).
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
@@ -44,9 +45,9 @@ function report_settings(settings)
report_activity("settings", settings);
}
-function report_content_type(content_type)
+function report_document_type(is_html)
{
- report_activity("content_type", content_type);
+ report_activity("is_html", is_html);
}
function report_repo_query_action(update, port)
@@ -96,6 +97,6 @@ function start_activity_info_server()
* EXPORT start_activity_info_server
* EXPORT report_script
* EXPORT report_settings
- * EXPORT report_content_type
+ * EXPORT report_document_type
* EXPORTS_END
*/
diff --git a/content/freezer.js b/content/freezer.js
deleted file mode 100644
index 0ea362e..0000000
--- a/content/freezer.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * Helper functions for blocking scripts in pages, based off NoScript's lib/DocumentFreezer.js
- *
- * Copyright (C) 2005-2021 Giorgio Maone - https://maone.net
- * Copyright (C) 2021 jahoti
- * Redistribution terms are gathered in the `copyright' file.
- */
-
-const loaderAttributes = ["href", "src", "data"];
-const jsOrDataUrlRx = /^(?:data:(?:[^,;]*ml|unknown-content-type)|javascript:)/i;
-
-function sanitize_attributes(element) {
- if (element._frozen)
- return;
- let fa = [];
- let loaders = [];
- let attributes = element.attributes || [];
-
- for (let a of attributes) {
- let name = a.localName.toLowerCase();
- if (loaderAttributes.includes(name))
- if (jsOrDataUrlRx.test(a.value))
- loaders.push(a);
-
- else if (name.startsWith("on")) {
- console.debug("Removing", a, element.outerHTML);
- fa.push(a.cloneNode());
- a.value = "";
- element[name] = null;
- }
- }
- if (loaders.length) {
- for (let a of loaders) {
- fa.push(a.cloneNode());
- a.value = "javascript://frozen";
- }
- if ("contentWindow" in element)
- element.replaceWith(element = element.cloneNode(true));
-
- }
- if (fa.length)
- element._frozenAttributes = fa;
- element._frozen = true;
-}
-
-function mozilla_suppress_scripts(e) {
- if (document.readyState === 'complete') {
- removeEventListener('beforescriptexecute', blockExecute, true);
- console.log('Script suppressor has detached.');
- return;
- }
- console.log("script event", e);
- if (e.isTrusted && !e.target._hachette_payload) {
- e.preventDefault();
- console.log('Suppressed script', e.target);
- }
-};
-
-/*
- * EXPORTS_START
- * EXPORT mozilla_suppress_scripts
- * EXPORT sanitize_attributes
- * EXPORTS_END
- */
diff --git a/content/main.js b/content/main.js
index 3ebf093..cec9943 100644
--- a/content/main.js
+++ b/content/main.js
@@ -1,5 +1,7 @@
/**
- * Hachette main content script run in all frames
+ * This file is part of Haketilo.
+ *
+ * Function: Main content script that runs in all frames.
*
* Copyright (C) 2021 Wojtek Kosior
* Copyright (C) 2021 jahoti
@@ -13,23 +15,27 @@
* IMPORT sign_data
* IMPORT gen_nonce
* IMPORT is_privileged_url
- * IMPORT mozilla_suppress_scripts
* IMPORT is_chrome
* IMPORT is_mozilla
* IMPORT start_activity_info_server
* IMPORT make_csp_rule
- * IMPORT is_csp_header_name
- * IMPORT sanitize_csp_header
+ * IMPORT csp_header_regex
* IMPORTS_END
*/
+document.content_loaded = document.readyState === "complete";
+const wait_loaded = e => e.content_loaded ? Promise.resolve() :
+ new Promise(c => e.addEventListener("DOMContentLoaded", c, {once: true}));
+
+wait_loaded(document).then(() => document.content_loaded = true);
+
function extract_cookie_policy(cookie, min_time)
{
let best_result = {time: -1};
let policy = null;
const extracted_signatures = [];
- for (const match of cookie.matchAll(/hachette-(\w*)=([^;]*)/g)) {
+ for (const match of cookie.matchAll(/haketilo-(\w*)=([^;]*)/g)) {
const new_result = extract_signed(...match.slice(1, 3));
if (new_result.fail)
continue;
@@ -56,7 +62,7 @@ function extract_url_policy(url, min_time)
const [base_url, payload, anchor] =
/^([^#]*)#?([^#]*)(#?.*)$/.exec(url).splice(1, 4);
- const match = /^hachette_([^_]+)_(.*)$/.exec(payload);
+ const match = /^haketilo_([^_]+)_(.*)$/.exec(payload);
if (!match)
return [null, url];
@@ -79,7 +85,7 @@ function employ_nonhttp_policy(policy)
policy.nonce = gen_nonce();
const [base_url, target] = /^([^#]*)(#?.*)$/.exec(policy.url).slice(1, 3);
const encoded_policy = encodeURIComponent(JSON.stringify(policy));
- const payload = "hachette_" +
+ const payload = "haketilo_" +
sign_data(encoded_policy, new Date().getTime()).join("_");
const resulting_url = `${base_url}#${payload}${target}`;
location.href = resulting_url;
@@ -87,18 +93,17 @@ function employ_nonhttp_policy(policy)
}
/*
+ * In the case of HTML documents:
* 1. When injecting some payload we need to sanitize <meta> CSP tags before
* they reach the document.
* 2. Only <meta> tags inside <head> are considered valid by the browser and
* need to be considered.
* 3. We want to detach <html> from document, wait until its <head> completes
* loading, sanitize it and re-attach <html>.
- * 4. Browsers are eager to add <meta>'s that appear after `</head>' but before
- * `<body>'. Due to this behavior the `DOMContentLoaded' event is considered
- * unreliable (although it could still work properly, it is just problematic
- * to verify).
- * 5. We shall wait for anything to appear in or after <body> and take that as
- * a sign <head> has _really_ finished loading.
+ * 4. We shall wait for anything to appear in or after <body> and take that as
+ * a sign <head> has finished loading.
+ * 5. Otherwise, getting the `DOMContentLoaded' event on the document shall also
+ * be a sign that <head> is fully loaded.
*/
function make_body_start_observer(DOM_element, waiting)
@@ -124,20 +129,23 @@ function try_body_started(waiting)
function finish_waiting(waiting)
{
+ if (waiting.finished)
+ return;
+ waiting.finished = true;
waiting.observers.forEach(observer => observer.disconnect());
- waiting.doc.removeEventListener("DOMContentLoaded", waiting.loaded_cb);
setTimeout(waiting.callback, 0);
}
function _wait_for_head(doc, detached_html, callback)
{
const waiting = {doc, detached_html, callback, observers: []};
+
if (try_body_started(waiting))
return;
waiting.observers = [make_body_start_observer(detached_html, waiting)];
- waiting.loaded_cb = () => finish_waiting(waiting);
- doc.addEventListener("DOMContentLoaded", waiting.loaded_cb);
+
+ wait_loaded(doc).then(() => finish_waiting(waiting));
}
function wait_for_head(doc, detached_html)
@@ -147,105 +155,176 @@ function wait_for_head(doc, detached_html)
const blocked_str = "blocked";
-function block_attribute(node, attr)
+function block_attribute(node, attr, ns=null)
{
+ const [hasa, geta, seta, rema] = ["has", "get", "set", "remove"]
+ .map(m => (n, ...args) => typeof ns === "string" ?
+ n[`${m}AttributeNS`](ns, ...args) : n[`${m}Attribute`](...args));
/*
- * Disabling attributes this way allows them to still be relatively
- * easily accessed in case they contain some useful data.
+ * Disabling attributes by prepending `-blocked' allows them to still be
+ * relatively easily accessed in case they contain some useful data.
*/
const construct_name = [attr];
- while (node.hasAttribute(construct_name.join("")))
+ while (hasa(node, construct_name.join("")))
construct_name.unshift(blocked_str);
while (construct_name.length > 1) {
construct_name.shift();
const name = construct_name.join("");
- node.setAttribute(`${blocked_str}-${name}`, node.getAttribute(name));
+ seta(node, `${blocked_str}-${name}`, geta(node, name));
}
- node.removeAttribute(attr);
+ rema(node, attr);
}
-function sanitize_meta(meta, policy)
+/*
+ * Used to disable `<script>'s and `<meta>'s that have not yet been added to
+ * live DOM (doesn't work for those already added).
+ */
+function sanitize_meta(meta)
{
- const http_equiv = meta.getAttribute("http-equiv");
- const value = meta.content;
-
- if (!value || !is_csp_header_name(http_equiv, true))
- return;
-
- block_attribute(meta, "content");
+ if (csp_header_regex.test(meta.httpEquiv) && meta.content)
+ block_attribute(meta, "content");
}
function sanitize_script(script)
{
- script.hachette_blocked_type = script.type;
+ script.haketilo_blocked_type = script.getAttribute("type");
script.type = "text/plain";
}
/*
- * Executed after script has been connected to the DOM, when it is no longer
- * eligible for being executed by the browser
+ * Executed after `<script>' has been connected to the DOM, when it is no longer
+ * eligible for being executed by the browser.
*/
-function desanitize_script(script, policy)
+function desanitize_script(script)
{
- script.setAttribute("type", script.hachette_blocked_type);
+ script.setAttribute("type", script.haketilo_blocked_type);
- if (script.hachette_blocked_type === undefined)
+ if ([null, undefined].includes(script.haketilo_blocked_type))
script.removeAttribute("type");
- delete script.hachette_blocked_type;
+ delete script.haketilo_blocked_type;
+}
+
+const bad_url_reg = /^data:([^,;]*ml|unknown-content-type)/i;
+function sanitize_urls(element)
+{
+ for (const attr of [...element.attributes || []]
+ .filter(attr => /^(href|src|data)$/i.test(attr.localName))
+ .filter(attr => bad_url_reg.test(attr.value)))
+ block_attribute(element, attr.localName, attr.namespaceURI);
+}
+
+function start_data_urls_sanitizing(doc)
+{
+ doc.querySelectorAll("*[href], *[src], *[data]").forEach(sanitize_urls);
+ if (!doc.content_loaded) {
+ const mutation_handler = m => m.addedNodes.forEach(sanitize_urls);
+ const mo = new MutationObserver(ms => ms.forEach(mutation_handler));
+ mo.observe(doc, {childList: true, subtree: true});
+ wait_loaded(doc).then(() => mo.disconnect());
+ }
+}
+
+/*
+ * Normally, we block scripts with CSP. However, Mozilla does optimizations that
+ * cause part of the DOM to be loaded when our content scripts get to run. Thus,
+ * before the CSP rules we inject (for non-HTTP pages) become effective, we need
+ * to somehow block the execution of `<script>'s and intrinsics that were
+ * already there. Additionally, some browsers (IceCat 60) seem to have problems
+ * applying this CSP to non-inline `<scripts>' in certain scenarios.
+ */
+function prevent_script_execution(event)
+{
+ if (!event.target.haketilo_payload)
+ event.preventDefault();
}
-function apply_hachette_csp_rules(doc, policy)
+function mozilla_initial_block(doc)
{
- const meta = doc.createElement("meta");
- meta.setAttribute("http-equiv", "Content-Security-Policy");
- meta.setAttribute("content", make_csp_rule(policy));
- doc.head.append(meta);
- /* CSP is already in effect, we can remove the <meta> now. */
- meta.remove();
+ doc.addEventListener("beforescriptexecute", prevent_script_execution);
+
+ for (const elem of doc.querySelectorAll("*")) {
+ [...elem.attributes].map(attr => attr.localName)
+ .filter(attr => /^on/.test(attr) && elem.wrappedJSObject[attr])
+ .forEach(attr => elem.wrappedJSObject[attr] = null);
+ }
}
+/*
+ * Here we block all scripts of a document which might be either and
+ * HTMLDocument or an XMLDocument. Modifying an XML document might disrupt
+ * Mozilla's XML preview. This is an unfortunate thing we have to accept for
+ * now. XML documents *have to* be sanitized as well because they might
+ * contain `<script>' tags (or on* attributes) with namespace declared as
+ * "http://www.w3.org/1999/xhtml" or "http://www.w3.org/2000/svg" which allows
+ * javascript execution.
+ */
async function sanitize_document(doc, policy)
{
/*
+ * Blocking of scripts that are in the DOM from the beginning. Needed for
+ * Mozilla.
+ */
+ if (is_mozilla)
+ mozilla_initial_block(doc);
+
+ /*
* Ensure our CSP rules are employed from the beginning. This CSP injection
* method is, when possible, going to be applied together with CSP rules
* injected using webRequest.
+ * Using elements namespaced as HTML makes this CSP injection also work for
+ * non-HTML documents.
*/
- const has_own_head = doc.head;
- if (!has_own_head)
- doc.documentElement.prepend(doc.createElement("head"));
-
- apply_hachette_csp_rules(doc, policy);
-
- /* Probably not needed, but...: proceed with DOM in its initial state. */
- if (!has_own_head)
- doc.head.remove();
+ const html = new DOMParser().parseFromString(`<html><head><meta \
+http-equiv="Content-Security-Policy" content="${make_csp_rule(policy)}"\
+/></head><body>Loading...</body></html>`, "text/html").documentElement;
/*
- * <html> node gets hijacked now, to be re-attached after <head> is loaded
+ * Root node gets hijacked now, to be re-attached after <head> is loaded
* and sanitized.
*/
- const old_html = doc.documentElement;
- const new_html = doc.createElement("html");
- old_html.replaceWith(new_html);
+ const root = doc.documentElement;
+ root.replaceWith(html);
- await wait_for_head(doc, old_html);
+ /*
+ * When we don't inject payload, we neither block document's CSP `<meta>'
+ * tags nor wait for `<head>' to be parsed.
+ */
+ if (policy.has_payload) {
+ await wait_for_head(doc, root);
- for (const meta of old_html.querySelectorAll("head meta"))
- sanitize_meta(meta, policy);
+ root.querySelectorAll("head meta")
+ .forEach(m => sanitize_meta(m, policy));
+ }
- if (!policy.allow)
- for (const script of old_html.querySelectorAll("script"))
- sanitize_script(script, policy);
+ root.querySelectorAll("script").forEach(s => sanitize_script(s, policy));
+ html.replaceWith(root);
+ root.querySelectorAll("script").forEach(s => desanitize_script(s, policy));
- new_html.replaceWith(old_html);
+ start_data_urls_sanitizing(doc);
+}
- if (!policy.allow)
- for (const script of old_html.querySelectorAll("script"))
- desanitize_script(script, policy);
+async function disable_service_workers()
+{
+ if (!navigator.serviceWorker)
+ return;
+
+ const registrations = await navigator.serviceWorker.getRegistrations();
+ if (registrations.length === 0)
+ return;
+
+ console.warn("Service Workers detected on this page! Unregistering and reloading");
+
+ try {
+ await Promise.all(registrations.map(r => r.unregister()));
+ } finally {
+ location.reload();
+ }
+
+ /* Never actually return! */
+ return new Promise(() => 0);
}
if (!is_privileged_url(document.URL)) {
@@ -259,7 +338,7 @@ if (!is_privileged_url(document.URL)) {
let signatures;
[policy, signatures] = extract_cookie_policy(document.cookie, min_time);
for (const signature of signatures)
- document.cookie = `hachette-${signature}=; Max-Age=-1;`;
+ document.cookie = `haketilo-${signature}=; Max-Age=-1;`;
} else {
const scheme = /^([^:]*)/.exec(document.URL)[1];
const known_scheme = ["file", "ftp"].includes(scheme);
@@ -276,14 +355,19 @@ if (!is_privileged_url(document.URL)) {
}
if (!policy) {
- console.warn("Using fallback policy!");
+ console.debug("Using fallback policy!");
policy = {allow: false, nonce: gen_nonce()};
}
+ if (!(document instanceof HTMLDocument))
+ policy.has_payload = false;
+
+ console.debug("current policy", policy);
+
const doc_ready = Promise.all([
- (policy.allow && !policy.has_payload) ? Promise.resolve : sanitize_document(document, policy),
- new Promise(cb => document.addEventListener("DOMContentLoaded",
- cb, {once: true}))
+ policy.allow ? Promise.resolve() : sanitize_document(document, policy),
+ policy.allow ? Promise.resolve() : disable_service_workers(),
+ wait_loaded(document)
]);
handle_page_actions(policy.nonce, policy_received_callback, doc_ready);
diff --git a/content/page_actions.js b/content/page_actions.js
index 8057541..db7c352 100644
--- a/content/page_actions.js
+++ b/content/page_actions.js
@@ -1,5 +1,7 @@
/**
- * Hachette handling of page actions in content scripts
+ * This file is part of Haketilo.
+ *
+ * Function: Handle page actions in a content script.
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
@@ -11,7 +13,7 @@
* IMPORT browser
* IMPORT report_script
* IMPORT report_settings
- * IMPORT report_content_type
+ * IMPORT report_document_type
* IMPORTS_END
*/
@@ -60,7 +62,7 @@ function add_script(script_text)
let script = document.createElement("script");
script.textContent = script_text;
script.setAttribute("nonce", nonce);
- script._hachette_payload = true;
+ script.haketilo_payload = true;
document.body.appendChild(script);
report_script(script_text);
@@ -70,8 +72,8 @@ function handle_page_actions(script_nonce, policy_received_cb,
doc_ready_promise) {
policy_received_callback = policy_received_cb;
url = document.URL;
- is_html = /html/.test(document.contentType);
- report_content_type(document.contentType);
+ is_html = document instanceof HTMLDocument;
+ report_document_type(is_html);
doc_ready_promise.then(document_ready);
diff --git a/content/repo_query.js b/content/repo_query.js
index 3708108..637282c 100644
--- a/content/repo_query.js
+++ b/content/repo_query.js
@@ -1,6 +1,7 @@
/**
- * part of Hachette
- * Getting available content for site from remote repositories.
+ * This file is part of Haketilo.
+ *
+ * Function: Getting available content for site from remote repositories.
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
diff --git a/copyright b/copyright
index 253864c..c176e61 100644
--- a/copyright
+++ b/copyright
@@ -1,12 +1,12 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: Hachette
+Upstream-Name: Haketilo
Source: https://git.koszko.org/browser-extension/
Files: *
Copyright: 2021 Wojtek Kosior <koszko@koszko.org>
License: GPL-3+-javascript or Alicense-1.0
-Files: *.sh
+Files: *.sh default_settings.json
Copyright: 2021 Wojtek Kosior <koszko@koszko.org>
2021 jahoti <jahoti@tilde.team>
License: CC0
@@ -67,11 +67,6 @@ License: Expat
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-Files: content/freezer.js
-Copyright: 2005-2021 Giorgio Maone - https://maone.net
- 2021 jahoti <jahoti@tilde.team>
-License: GPL-2+
-
Files: test/*
Copyright: 2021 jahoti <jahoti@tilde.team>
2021 jahoti <jahoti@tilde.team>
diff --git a/default_settings.json b/default_settings.json
index 44fbca0..e7ea2c3 100644
--- a/default_settings.json
+++ b/default_settings.json
@@ -1,50 +1 @@
-[
- {
- "sbandcamp": {
- "text": "/*\n\tCopyright © 2021 jahoti (jahoti@tilde.team)\n\t\n\tLicensed under the Apache License, Version 2.0 (the \"License\");\n\tyou may not use this file except in compliance with the License.\n\tYou may obtain a copy of the License at\n\t\n\t http://www.apache.org/licenses/LICENSE-2.0\n\t\n\tUnless required by applicable law or agreed to in writing, software\n\tdistributed under the License is distributed on an \"AS IS\" BASIS,\n\tWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n\tSee the License for the specific language governing permissions and\n\tlimitations under the License.\n*/\n\nvar div, player, playerBox = document.querySelector('.inline_player');\nplayerBox.innerHTML = '';\n\nfor (var track of JSON.parse(document.querySelector('[data-tralbum]').dataset.tralbum).trackinfo) {\n\tdiv = document.createElement('div');\n\tplayer = document.createElement('audio');\n\t\n\tdiv.innerText = track.title + ': ';\n\tplayer.src = track.file['mp3-128']; // Is this always available?\n\tdiv.append(player);\n\tplayerBox.append(div);\n}"
- }
- },
- {
- "sopencores": {
- "text":"let data = JSON.parse(document.getElementById(\"__NEXT_DATA__\").textContent);\nlet sections = {};\nfor (let h1 of document.getElementsByClassName(\"cMJCrc\")) {\n let ul = document.createElement(\"ul\");\n if (h1.nextElementSibling !== null)\n\th1.parentNode.insertBefore(ul, h1.nextElementSibling);\n else\n\th1.parentNode.appendChild(ul);\n\n sections[h1.children[1].firstChild.textContent] = ul;\n}\n\nfor (let prop of data.props.pageProps.list) {\n let ul = sections[prop.category];\n if (ul === undefined) {\n\tconsole.log(`unknown category \"${prop.category}\" for project \"${prop.title}\"`);\n\tcontinue;\n }\n\n let li = document.createElement(\"li\");\n let a = document.createElement(\"a\");\n a.setAttribute(\"href\", \"/projects/\" + prop.slug);\n a.textContent = prop.title;\n\n li.appendChild(a);\n ul.appendChild(li);\n}\n"
- }
- },
- {
- "ssumofus (sign petition)": {
- "text": "/*\n\tCopyright © 2021 jahoti (jahoti@tilde.team)\n\t\n\tLicensed under the Apache License, Version 2.0 (the \"License\");\n\tyou may not use this file except in compliance with the License.\n\tYou may obtain a copy of the License at\n\t\n\t http://www.apache.org/licenses/LICENSE-2.0\n\t\n\tUnless required by applicable law or agreed to in writing, software\n\tdistributed under the License is distributed on an \"AS IS\" BASIS,\n\tWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n\tSee the License for the specific language governing permissions and\n\tlimitations under the License.\n*/\n\nfunction submitFormItem() {\n\tvar name, val, queryString = '', xhr = new content.XMLHttpRequest();\n\tfor (var formItem of this.querySelectorAll('select, input:not([type=\"radio\"]):not([type=\"checkbox\"])' +\n\t\t':not([type=\"submit\"]):not([type=\"reset\"])')) {\n\t\tqueryString += (queryString && '&') + formItem.name + '=' + encodeURIComponent(formItem.value);\n\t}\n\t\n\txhr.onreadystatechange = function () {\n\t\tif (this.readyState === 4) {\n\t\t\tif (this.status === 200) location.href = JSON.parse(this.responseText).follow_up_url;\n\t\t\telse if (this.status === 422) {\n\t\t\t\tvar failMessage = [], response = JSON.parse(this.responseText);\n\t\t\t\tfor (field in response.errors) for (error of response.errors[field]) {\n\t\t\t\t\tfailMessage.push('Field \"' + field + '\" ' + error);\n\t\t\t\t}\n\t\t\t\talert(failMessage.join('\\n'));\n\t\t\t}\n\t\t\telse alert('Submission failed: response code ' + this.status);\n\t\t}\n\t}\n\t\n\txhr.open('POST', this.action, true); // Manually add the domain, as it's not properly handled in extensions\n\txhr.setRequestHeader('X-CSRF-Token', csrf);\n\txhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n\txhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');\n\txhr.send(queryString);\n\treturn false;\n}\n\n// Apply CSS as necessary\nif (notice = document.querySelector('#petition-bar-main > span')) notice.style.display = 'none'; // Hide the totally mistaken (even without this extension) anti-anti-JS warning\ndocument.querySelector('.script-dependent').style.display = 'block';\ndocument.querySelector('.button-wrapper').style.position = 'static'; // Stop the \"submit\" button obscuring the form\n\n\n\ncsrf = document.querySelector('meta[name=\"csrf-token\"]').content\nfor (var button of document.querySelectorAll('button[type=\"submit\"].button.action-form__submit-button')) button.form.onsubmit = submitFormItem;"
- }
- },
- {
- "sworldcat (library holdings)": {
- "text": "/*\n\tCopyright © 2021 jahoti (jahoti@tilde.team)\n\t\n\tLicensed under the Apache License, Version 2.0 (the \"License\");\n\tyou may not use this file except in compliance with the License.\n\tYou may obtain a copy of the License at\n\t\n\t http://www.apache.org/licenses/LICENSE-2.0\n\t\n\tUnless required by applicable law or agreed to in writing, software\n\tdistributed under the License is distributed on an \"AS IS\" BASIS,\n\tWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n\tSee the License for the specific language governing permissions and\n\tlimitations under the License.\n*/\n\nvar pathParts = location.pathname.split('/'), itemRef = pathParts[pathParts.length - 1];\n\n// Generate a function which, when invoked, loads the catalog holdings starting at i (one-indexed) focused on loc\nfunction generateGoTo(i, set_loc) {\n\treturn function () {\n\t\t; // If this is a new search, \"set_loc\" won't be set; set it\n\t\tvar xhr = new content.XMLHttpRequest(), loc = set_loc || encodeURIComponent(locInput.value);\n\t\txhr.onreadystatechange = function () {\n\t\t\tif (this.readyState === 4) {\n\t\t\t\tif (this.status === 200) {\n\t\t\t\t\tretrieved.innerHTML = this.responseText;\n\t\t\t\t\t\n\t\t\t\t\tvar i, node = document.getElementById('libslocator');\n\t\t\t\t\tnode.parentNode.removeChild(node);\n\t\t\t\t\tfor (node of retrieved.querySelectorAll('a[href^=\"javascript:findLibs(\\'\\', \"]')) {\n\t\t\t\t\t\ti = parseInt(node.href.split(',', 2)[1]);\n\t\t\t\t\t\tnode.onclick = generateGoTo(i, loc);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse alert('Search failed: response code ' + this.status);\n\t\t\t}\n\t\t}\n\t\t\n\t\txhr.open('GET', 'https://www.worldcat.org/wcpa/servlet/org.oclc.lac.ui.ajax.ServiceServlet?wcoclcnum=' + itemRef + '&start_holding='\n\t\t\t\t+ i + '&serviceCommand=holdingsdata&loc=' + loc, true);\n\t\txhr.send();\n\t\treturn false; // Make sure the browser doesn't try to submit any holding form\n\t};\n}\n\n\nvar retriever = document.querySelector('.retrieving'), retrieved = document.getElementById('donelocator');\n\nvar locForm = document.createElement('form'), locLabel = document.createElement('label'), locInput = document.createElement('input'),\n\tlocSubmit = document.createElement('input');\n\nlocForm.appendChild(locLabel);\nlocForm.appendChild(locInput);\nlocForm.appendChild(locSubmit);\n\nlocInput.name = locLabel.htmlFor = 'cat_location';\nlocInput.type = 'text';\nlocInput.required = 'yes';\nlocLabel.innerText = 'Find copies closest to: ';\nlocSubmit.value = 'Go';\nlocSubmit.type = 'submit';\nlocForm.onsubmit = generateGoTo(1);\n\nretriever.parentNode.replaceChild(locForm, retriever);"
- }
- },
- {
- "phttps://*.bandcamp.com/track/*": {
- "components": ["s", "bandcamp"]
- }
- },
- {
- "phttps://opencores.org/projects": {
- "components": ["s", "opencores"]
- }
- },
- {
- "phttps://actions.sumofus.org/a/*": {
- "components": ["s", "sumofus (sign petition)"],
- }
- },
- {
- "phttps://worldcat.org/title/**": {
- "components": ["s", "worldcat (library holdings)"]
- }
- },
- {
- "phttps://www.worldcat.org/title/**": {
- "components": ["s", "worldcat (library holdings)"]
- }
- },
- {
- "rhttps://api-demo.hachette-hydrilla.org": {}
- }
-]
+[{"shaketilo demo script":{"url":"","hash":"","text":"/**\n * Haketilo demo script.\n *\n * Copyright (C) Wojtek Kosior\n * Available under the terms of Creative Commons Zero\n * <https://creativecommons.org/publicdomain/zero/1.0/legalcode>\n */\n\nconst banner = document.createElement(\"h2\");\n\nbanner.textContent = \"Hoooray! Haketilo works :D\"\n\nbanner.setAttribute(\"style\", `\\\nmargin: 1em; \\\nborder-radius: 1em 0px; \\\nbackground-color: #474; \\\npadding: 10px 20px; \\\ncolor: #eee; \\\nbox-shadow: 0 6px 8px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19); \\\ndisplay: inline-block;\\\n`);\n\ndocument.body.prepend(banner);"}},{"bhaketilo demo bag":[["s","haketilo demo script"]]},{"phttps://hachette-hydrilla.org":{"components":["b","haketilo demo bag"],"allow":false}},{"rhttps://api-demo.hachette-hydrilla.org":{}}]
diff --git a/html/DOM_helpers.js b/html/DOM_helpers.js
index 01e2be9..4fe118d 100644
--- a/html/DOM_helpers.js
+++ b/html/DOM_helpers.js
@@ -1,5 +1,7 @@
/**
- * Hachette operations on DOM elements
+ * This file is part of Haketilo.
+ *
+ * Function: Operations on DOM elements.
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
diff --git a/html/MOZILLA_scrollbar_fix.css b/html/MOZILLA_scrollbar_fix.css
index 5feb7c3..cdbd5c6 100644
--- a/html/MOZILLA_scrollbar_fix.css
+++ b/html/MOZILLA_scrollbar_fix.css
@@ -1,6 +1,8 @@
/**
- * Hachette
- * Hacky fix for vertical scrollbar width being included in child's width.
+ * This file is part of Haketilo.
+ *
+ * Function: Hacky fix for vertical scrollbar width being included in child's
+ * width.
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
diff --git a/html/back_button.css b/html/back_button.css
index 1ddc5da..b83e834 100644
--- a/html/back_button.css
+++ b/html/back_button.css
@@ -1,6 +1,7 @@
/**
- * part of Hachette
- * Style for a "back" button with a CSS arrow image.
+ * This file is part of Haketilo.
+ *
+ * Function: Style for a "back" button with a CSS arrow image.
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
diff --git a/html/base.css b/html/base.css
index df52f7c..517a5c1 100644
--- a/html/base.css
+++ b/html/base.css
@@ -1,5 +1,7 @@
/**
- * Hachette base styles
+ * This file is part of Haketilo.
+ *
+ * Function: Base styles.
*
* Copyright (C) 2021 Wojtek Kosior
* Copyright (C) 2021 Nicholas Johnson
diff --git a/html/default_blocking_policy.js b/html/default_blocking_policy.js
index 2f49bac..b6458f3 100644
--- a/html/default_blocking_policy.js
+++ b/html/default_blocking_policy.js
@@ -1,6 +1,7 @@
/**
- * part of Hachette
- * Default policy dialog logic.
+ * This file is part of Haketilo.
+ *
+ * Function: Logic for the dialog of default policy selection.
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
diff --git a/html/display-panel.html b/html/display-panel.html
index 3ed1b7a..ee9e767 100644
--- a/html/display-panel.html
+++ b/html/display-panel.html
@@ -1,12 +1,16 @@
<!doctype html>
<!--
+ This file is part of Haketilo.
+
+ Function: Extension's popup page.
+
Copyright (C) 2021 Wojtek Kosior
Redistribution terms are gathered in the `copyright' file.
-->
<html>
<head>
<meta charset="utf-8"/>
- <title>Hachette - page settings</title>
+ <title>Haketilo - page settings</title>
<link type="text/css" rel="stylesheet" href="reset.css" />
<link type="text/css" rel="stylesheet" href="base.css" />
<link type="text/css" rel="stylesheet" href="back_button.css" />
@@ -331,7 +335,7 @@
<div class="footer padding_inline has_upper_thin_line">
<button id="settings_but" type="button">
- Open Hachette settings
+ Open Haketilo settings
</button>
</div>
</div>
diff --git a/html/display-panel.js b/html/display-panel.js
index 7d801c9..c078850 100644
--- a/html/display-panel.js
+++ b/html/display-panel.js
@@ -1,5 +1,7 @@
/**
- * Hachette display panel logic
+ * This file is part of Haketilo.
+ *
+ * Function: Popup logic.
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
@@ -21,7 +23,6 @@
* IMPORT TYPE_PREFIX
* IMPORT nice_name
* IMPORT open_in_settings
- * IMPORT url_matches
* IMPORT each_url_pattern
* IMPORT by_id
* IMPORT clone_template
@@ -114,36 +115,21 @@ function add_pattern_to_list(pattern)
return template;
}
-function ensure_pattern_exists(pattern)
-{
- let entry_object = known_patterns.get(pattern);
- /*
- * As long as pattern computation works well, we should never get into this
- * conditional block. This is just a safety measure. To be removed as part
- * of a bigger rework when we start taking iframes into account.
- */
- if (entry_object === undefined) {
- console.log(`unknown pattern: ${pattern}`);
- entry_object = add_pattern_to_list(pattern);
- }
-
- return entry_object;
-}
-
function style_possible_pattern_entry(pattern, exists_in_settings)
{
const [text, class_action] = exists_in_settings ?
["Edit", "add"] : ["Add", "remove"];
- const entry_object = ensure_pattern_exists(pattern);
+ const entry_object = known_patterns.get(pattern);
- entry_object.button.textContent = `${text} setting`;
- entry_object.entry.classList[class_action]("matched_pattern");
+ if (entry_object) {
+ entry_object.button.textContent = `${text} setting`;
+ entry_object.entry.classList[class_action]("matched_pattern");
+ }
}
function handle_page_change(change)
{
- if (url_matches(tab_url, change.item))
- style_possible_pattern_entry(change.item, change.new_val !== undefined);
+ style_possible_pattern_entry(change.item, change.new_val !== undefined);
}
function populate_possible_patterns_list(url)
@@ -276,8 +262,8 @@ function handle_activity_report(message)
template.script_contents.textContent = data;
container_for_injected.appendChild(template.div);
}
- if (type === "content_type") {
- if (!/html/.test(data))
+ if (type === "is_html") {
+ if (!data)
content_type_cell.classList.remove("hide");
}
if (type === "repo_query_action") {
diff --git a/html/import_frame.js b/html/import_frame.js
index c0eb2f0..ae6fab4 100644
--- a/html/import_frame.js
+++ b/html/import_frame.js
@@ -1,5 +1,7 @@
/**
- * Hachette HTML import frame script
+ * This file is part of Haketilo.
+ *
+ * Function: Logic for the settings import frame.
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
diff --git a/html/options.html b/html/options.html
index f2a75e1..2e8317c 100644
--- a/html/options.html
+++ b/html/options.html
@@ -1,12 +1,16 @@
<!doctype html>
<!--
+ This file is part of Haketilo.
+
+ Function: Extension's settings page.
+
Copyright (C) 2021 Wojtek Kosior
Redistribution terms are gathered in the `copyright' file.
-->
<html>
<head>
<meta charset="utf-8"/>
- <title>Hachette options</title>
+ <title>Haketilo options</title>
<link type="text/css" rel="stylesheet" href="reset.css" />
<link type="text/css" rel="stylesheet" href="base.css" />
<link type="text/css" rel="stylesheet" href="table.css" />
@@ -152,6 +156,22 @@
min-width: 70vw;
resize: none;
}
+
+ .form_disabled>* {
+ opacity: 0.5;
+ pointer-events: none;
+ }
+
+ .form_disabled_msg {
+ display: none;
+ font-style: italic;
+ }
+
+ .form_disabled .form_disabled_msg {
+ opacity: initial;
+ pointer-events: initial;
+ display: initial;
+ }
</style>
</head>
<body>
@@ -226,15 +246,18 @@
<label for="page_url_field">URL: </label>
<input id="page_url_field"></input>
<label>Payload: </label>
- <span>
+ <span class="nowrap">
<span id="page_payload"></span>
<button id="select_page_components_but">
Choose payload
</button>
</span>
- <div>
+ <div id="allow_native_scripts_container" class="nowrap">
<input id="page_allow_chbx" type="checkbox" style="display: inline;"></input>
<label for="page_allow_chbx">Allow native scripts</label>
+ <span class="form_disabled_msg">
+ (only possible when no payload is used)
+ </span>
</div>
<div>
<button id="save_page_but" type="button"> Save </button>
@@ -248,6 +271,7 @@
</div>
</div>
<button id="add_page_but" type="button"> Add page </button>
+ <br/>
<IMPORT html/default_blocking_policy.html />
</div>
<div id="bags" class="tab">
diff --git a/html/options_main.js b/html/options_main.js
index 2f4f154..f8faf9b 100644
--- a/html/options_main.js
+++ b/html/options_main.js
@@ -1,5 +1,7 @@
/**
- * Hachette HTML options page main script
+ * This file is part of Haketilo.
+ *
+ * Function: Settings page logic.
*
* Copyright (C) 2021 Wojtek Kosior
* Redistribution terms are gathered in the `copyright' file.
@@ -157,6 +159,7 @@ function work_repo_li_data(ul)
return [ul.work_name_input.value, {}];
}
+const allow_native_scripts_container = by_id("allow_native_scripts_container");
const page_payload_span = by_id("page_payload");
function set_page_components(components)
@@ -164,12 +167,14 @@ function set_page_components(components)
if (components === undefined) {
page_payload_span.setAttribute("data-payload", "no");
page_payload_span.textContent = "(None)";
+ allow_native_scripts_container.classList.remove("form_disabled");
} else {
page_payload_span.setAttribute("data-payload", "yes");
let [prefix, name] = components;
page_payload_span.setAttribute("data-prefix", prefix);
page_payload_span.setAttribute("data-name", name);
page_payload_span.textContent = nice_name(prefix, name);
+ allow_native_scripts_container.classList.add("form_disabled");
}
}
diff --git a/icons/hachette.svg b/icons/haketilo.svg
index 6e8948d..6e8948d 100644
--- a/icons/hachette.svg
+++ b/icons/haketilo.svg
diff --git a/icons/hachette128.png b/icons/haketilo128.png
index 18816e9..18816e9 100644
--- a/icons/hachette128.png
+++ b/icons/haketilo128.png
Binary files differ
diff --git a/icons/hachette16.png b/icons/haketilo16.png
index 182ede5..182ede5 100644
--- a/icons/hachette16.png
+++ b/icons/haketilo16.png
Binary files differ
diff --git a/icons/hachette32.png b/icons/haketilo32.png
index ffaa84b..ffaa84b 100644
--- a/icons/hachette32.png
+++ b/icons/haketilo32.png
Binary files differ
diff --git a/icons/hachette48.png b/icons/haketilo48.png
index 1ffcd38..1ffcd38 100644
--- a/icons/hachette48.png
+++ b/icons/haketilo48.png
Binary files differ
diff --git a/icons/hachette64.png b/icons/haketilo64.png
index a02abb0..a02abb0 100644
--- a/icons/hachette64.png
+++ b/icons/haketilo64.png
Binary files differ
diff --git a/manifest.json b/manifest.json
index bd963fe..3caa6af 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,18 +1,20 @@
+// This is the manifest file of Haketilo.
+//
// Copyright (C) 2021 Wojtek Kosior
// Redistribution terms are gathered in the `copyright' file.
{
"manifest_version": 2,
- "name": "Hachette",
- "short_name": "Hachette",
- "version": "0.0.1",_CHROMIUM_KEY_
+ "name": "Haketilo",
+ "short_name": "Haketilo",
+ "version": "0.1",
"author": "various",
"description": "Control your \"Web\" browsing.",_GECKO_APPLICATIONS_
"icons":{
- "128": "icons/hachette128.png",
- "64": "icons/hachette64.png",
- "48": "icons/hachette48.png",
- "32": "icons/hachette32.png",
- "16": "icons/hachette16.png"
+ "128": "icons/haketilo128.png",
+ "64": "icons/haketilo64.png",
+ "48": "icons/haketilo48.png",
+ "32": "icons/haketilo32.png",
+ "16": "icons/haketilo16.png"
},
"permissions": [
"contextMenus",
@@ -29,20 +31,20 @@
"browser_action": {
"browser_style": true,
"default_icon": {
- "128": "icons/hachette128.png",
- "64": "icons/hachette64.png",
- "48": "icons/hachette48.png",
- "32": "icons/hachette32.png",
- "16": "icons/hachette16.png"
+ "128": "icons/haketilo128.png",
+ "64": "icons/haketilo64.png",
+ "48": "icons/haketilo48.png",
+ "32": "icons/haketilo32.png",
+ "16": "icons/haketilo16.png"
},
- "default_title": "Hachette",
+ "default_title": "Haketilo",
"default_popup": "html/display-panel.html"
},
"options_ui": {
"page": "html/options.html",
"open_in_tab": true
},
- "web_accessible_resources": [
+ "web_accessible_resources": [_CHROMIUM_KEY_
],
"background": {
"persistent": true,
diff --git a/re-generate_icons.sh b/re-generate_icons.sh
index ba0c28a..e557ad0 100755
--- a/re-generate_icons.sh
+++ b/re-generate_icons.sh
@@ -4,5 +4,5 @@
# Redistribution terms are gathered in the `copyright' file.
for SIZE in 128 64 48 32 16; do
- inkscape -z -e icons/hachette$SIZE.png -w $SIZE -h $SIZE icons/hachette.svg
+ inkscape -z -e icons/haketilo$SIZE.png -w $SIZE -h $SIZE icons/haketilo.svg
done