Revision 53837634
Added by koszko about 2 years ago
| common/misc.js | ||
|---|---|---|
| 84 | 84 |
window.open(url, "_blank"); |
| 85 | 85 |
} |
| 86 | 86 |
|
| 87 |
/* Check if url corresponds to a browser's special page */ |
|
| 88 |
function is_privileged_url(url) |
|
| 89 |
{
|
|
| 90 |
return !!/^(chrome(-extension)?|moz-extension):\/\/|^about:/i.exec(url); |
|
| 91 |
} |
|
| 87 |
/* |
|
| 88 |
* Check if url corresponds to a browser's special page (or a directory index in |
|
| 89 |
* case of `file://' protocol). |
|
| 90 |
*/ |
|
| 91 |
const privileged_reg = |
|
| 92 |
/^(chrome(-extension)?|moz-extension):\/\/|^about:|^file:\/\/.*\/$/; |
|
| 93 |
const is_privileged_url = url => privileged_reg.test(url); |
|
| 92 | 94 |
|
| 93 | 95 |
/* Parse a CSP header */ |
| 94 | 96 |
function parse_csp(csp) {
|
| common/patterns.js | ||
|---|---|---|
| 5 | 5 |
* Redistribution terms are gathered in the `copyright' file. |
| 6 | 6 |
*/ |
| 7 | 7 |
|
| 8 |
const proto_re = "[a-zA-Z]*:\/\/"; |
|
| 8 |
const proto_regex = /^(\w+):\/\/(.*)$/; |
|
| 9 |
|
|
| 9 | 10 |
const domain_re = "[^/?#]+"; |
| 10 |
const segments_re = "/[^?#]*"; |
|
| 11 |
const query_re = "\\?[^#]*"; |
|
| 12 |
|
|
| 13 |
const url_regex = new RegExp(`\ |
|
| 14 |
^\ |
|
| 15 |
(${proto_re})\
|
|
| 16 |
(${domain_re})\
|
|
| 17 |
(${segments_re})?\
|
|
| 18 |
(${query_re})?\
|
|
| 19 |
#?.*\$\ |
|
| 20 |
`); |
|
| 11 |
const path_re = "[^?#]*"; |
|
| 12 |
const query_re = "\\??[^#]*"; |
|
| 13 |
|
|
| 14 |
const http_regex = new RegExp(`^(${domain_re})(${path_re})(${query_re}).*`);
|
|
| 15 |
|
|
| 16 |
const file_regex = new RegExp(`^(${path_re}).*`);
|
|
| 21 | 17 |
|
| 22 | 18 |
function deconstruct_url(url) |
| 23 | 19 |
{
|
| 24 |
const regex_match = url_regex.exec(url);
|
|
| 25 |
if (regex_match === null)
|
|
| 20 |
const proto_match = proto_regex.exec(url);
|
|
| 21 |
if (proto_match === null)
|
|
| 26 | 22 |
return undefined; |
| 27 | 23 |
|
| 28 |
let [_, proto, domain, path, query] = regex_match;
|
|
| 24 |
const deco = {proto: proto_match[1]};
|
|
| 29 | 25 |
|
| 30 |
domain = domain.split(".");
|
|
| 31 |
let path_trailing_dash = |
|
| 32 |
path && path[path.length - 1] === "/"; |
|
| 33 |
path = (path || "").split("/").filter(s => s !== "");
|
|
| 34 |
path.unshift("");
|
|
| 26 |
if (deco.proto === "file") {
|
|
| 27 |
deco.path = file_regex.exec(proto_match[2])[1]; |
|
| 28 |
} else {
|
|
| 29 |
const http_match = http_regex.exec(proto_match[2]); |
|
| 30 |
if (!http_match) |
|
| 31 |
return undefined; |
|
| 32 |
[deco.domain, deco.path, deco.query] = http_match.slice(1, 4); |
|
| 33 |
deco.domain = deco.domain.split(".");
|
|
| 34 |
} |
|
| 35 | 35 |
|
| 36 |
return {proto, domain, path, query, path_trailing_dash};
|
|
| 36 |
const leading_dash = deco.path[0] === "/"; |
|
| 37 |
deco.trailing_dash = deco.path[deco.path.length - 1] === "/"; |
|
| 38 |
deco.path = deco.path.split("/").filter(s => s !== "");
|
|
| 39 |
if (leading_dash || deco.path.length === 0) |
|
| 40 |
deco.path.unshift("");
|
|
| 41 |
|
|
| 42 |
return deco; |
|
| 37 | 43 |
} |
| 38 | 44 |
|
| 39 | 45 |
/* Be sane: both arguments should be arrays of length >= 2 */ |
| ... | ... | |
| 104 | 110 |
return false |
| 105 | 111 |
} |
| 106 | 112 |
|
| 107 |
if (pattern_deco.proto !== url_deco.proto) |
|
| 108 |
return false; |
|
| 109 |
|
|
| 110 |
return domain_matches(url_deco.domain, pattern_deco.domain) && |
|
| 111 |
path_matches(url_deco.path, url_deco.path_trailing_dash, |
|
| 112 |
pattern_deco.path, pattern_deco.path_trailing_dash); |
|
| 113 |
return pattern_deco.proto === url_deco.proto && |
|
| 114 |
!(pattern_deco.proto === "file" && pattern_deco.trailing_dash) && |
|
| 115 |
!!url_deco.domain === !!pattern_deco.domain && |
|
| 116 |
(!url_deco.domain || |
|
| 117 |
domain_matches(url_deco.domain, pattern_deco.domain)) && |
|
| 118 |
path_matches(url_deco.path, url_deco.trailing_dash, |
|
| 119 |
pattern_deco.path, pattern_deco.trailing_dash); |
|
| 113 | 120 |
} |
| 114 | 121 |
|
| 115 |
/* |
|
| 116 |
* Call callback for every possible pattern that matches url. Return when there |
|
| 117 |
* are no more patterns or callback returns false. |
|
| 118 |
*/ |
|
| 119 |
function for_each_possible_pattern(url, callback) |
|
| 122 |
function* each_domain_pattern(domain_segments) |
|
| 120 | 123 |
{
|
| 121 |
const deco = deconstruct_url(url); |
|
| 122 |
|
|
| 123 |
if (deco === undefined) {
|
|
| 124 |
console.log("bad url format", url);
|
|
| 125 |
return; |
|
| 124 |
for (let slice = 0; slice < domain_segments.length; slice++) {
|
|
| 125 |
const domain_part = domain_segments.slice(slice).join(".");
|
|
| 126 |
const domain_wildcards = []; |
|
| 127 |
if (slice === 0) |
|
| 128 |
yield domain_part; |
|
| 129 |
if (slice === 1) |
|
| 130 |
yield "*." + domain_part; |
|
| 131 |
if (slice > 1) |
|
| 132 |
yield "**." + domain_part; |
|
| 133 |
yield "***." + domain_part; |
|
| 126 | 134 |
} |
| 135 |
} |
|
| 127 | 136 |
|
| 128 |
for (let d_slice = 0; d_slice < deco.domain.length; d_slice++) {
|
|
| 129 |
const domain_part = deco.domain.slice(d_slice).join(".");
|
|
| 130 |
const domain_wildcards = []; |
|
| 131 |
if (d_slice === 0) |
|
| 132 |
domain_wildcards.push("");
|
|
| 133 |
if (d_slice === 1) |
|
| 134 |
domain_wildcards.push("*.");
|
|
| 135 |
if (d_slice > 0) |
|
| 136 |
domain_wildcards.push("**.");
|
|
| 137 |
domain_wildcards.push("***.");
|
|
| 138 |
|
|
| 139 |
for (const domain_wildcard of domain_wildcards) {
|
|
| 140 |
const domain_pattern = domain_wildcard + domain_part; |
|
| 141 |
|
|
| 142 |
for (let s_slice = deco.path.length; s_slice > 0; s_slice--) {
|
|
| 143 |
const path_part = deco.path.slice(0, s_slice).join("/");
|
|
| 144 |
const path_wildcards = []; |
|
| 145 |
if (s_slice === deco.path.length) {
|
|
| 146 |
if (deco.path_trailing_dash) |
|
| 147 |
path_wildcards.push("/");
|
|
| 148 |
path_wildcards.push("");
|
|
| 149 |
} |
|
| 150 |
if (s_slice === deco.path.length - 1 && |
|
| 151 |
deco.path[s_slice] !== "*") |
|
| 152 |
path_wildcards.push("/*");
|
|
| 153 |
if (s_slice < deco.path.length && |
|
| 154 |
(deco.path[s_slice] !== "**" || |
|
| 155 |
s_slice < deco.path.length - 1)) |
|
| 156 |
path_wildcards.push("/**");
|
|
| 157 |
if (deco.path[s_slice] !== "***" || s_slice < deco.path.length) |
|
| 158 |
path_wildcards.push("/***");
|
|
| 159 |
|
|
| 160 |
for (const path_wildcard of path_wildcards) {
|
|
| 161 |
const path_pattern = path_part + path_wildcard; |
|
| 162 |
|
|
| 163 |
const pattern = deco.proto + domain_pattern + path_pattern; |
|
| 164 |
|
|
| 165 |
if (callback(pattern) === false) |
|
| 166 |
return; |
|
| 167 |
} |
|
| 168 |
} |
|
| 137 |
function* each_path_pattern(path_segments, trailing_dash) |
|
| 138 |
{
|
|
| 139 |
for (let slice = path_segments.length; slice > 0; slice--) {
|
|
| 140 |
const path_part = path_segments.slice(0, slice).join("/");
|
|
| 141 |
const path_wildcards = []; |
|
| 142 |
if (slice === path_segments.length) {
|
|
| 143 |
if (trailing_dash) |
|
| 144 |
yield path_part + "/"; |
|
| 145 |
yield path_part; |
|
| 169 | 146 |
} |
| 147 |
if (slice === path_segments.length - 1 && path_segments[slice] !== "*") |
|
| 148 |
yield path_part + "/*"; |
|
| 149 |
if (slice < path_segments.length - 1) |
|
| 150 |
yield path_part + "/**"; |
|
| 151 |
if (slice < path_segments.length - 1 || |
|
| 152 |
path_segments[path_segments.length - 1] !== "***") |
|
| 153 |
yield path_part + "/***"; |
|
| 170 | 154 |
} |
| 171 | 155 |
} |
| 172 | 156 |
|
| 173 |
function possible_patterns(url) |
|
| 157 |
/* Generate every possible pattern that matches url. */ |
|
| 158 |
function* each_url_pattern(url) |
|
| 174 | 159 |
{
|
| 175 |
const patterns = []; |
|
| 176 |
for_each_possible_pattern(url, patterns.push); |
|
| 160 |
const deco = deconstruct_url(url); |
|
| 177 | 161 |
|
| 178 |
return patterns; |
|
| 162 |
if (deco === undefined) {
|
|
| 163 |
console.log("bad url format", url);
|
|
| 164 |
return false; |
|
| 165 |
} |
|
| 166 |
|
|
| 167 |
const all_domains = deco.domain ? each_domain_pattern(deco.domain) : [""]; |
|
| 168 |
for (const domain of all_domains) {
|
|
| 169 |
for (const path of each_path_pattern(deco.path, deco.trailing_dash)) |
|
| 170 |
yield `${deco.proto}://${domain}${path}`;
|
|
| 171 |
} |
|
| 179 | 172 |
} |
| 180 | 173 |
|
| 181 | 174 |
/* |
| 182 | 175 |
* EXPORTS_START |
| 183 | 176 |
* EXPORT url_matches |
| 184 |
* EXPORT for_each_possible_pattern |
|
| 185 |
* EXPORT possible_patterns |
|
| 177 |
* EXPORT each_url_pattern |
|
| 186 | 178 |
* EXPORTS_END |
| 187 | 179 |
*/ |
| common/settings_query.js | ||
|---|---|---|
| 8 | 8 |
/* |
| 9 | 9 |
* IMPORTS_START |
| 10 | 10 |
* IMPORT TYPE_PREFIX |
| 11 |
* IMPORT for_each_possible_pattern
|
|
| 11 |
* IMPORT each_url_pattern
|
|
| 12 | 12 |
* IMPORTS_END |
| 13 | 13 |
*/ |
| 14 | 14 |
|
| 15 |
function check_pattern(storage, pattern, multiple, matched) |
|
| 16 |
{
|
|
| 17 |
const settings = storage.get(TYPE_PREFIX.PAGE, pattern); |
|
| 18 |
|
|
| 19 |
if (settings === undefined) |
|
| 20 |
return; |
|
| 21 |
|
|
| 22 |
matched.push([pattern, settings]); |
|
| 23 |
|
|
| 24 |
if (!multiple) |
|
| 25 |
return false; |
|
| 26 |
} |
|
| 27 |
|
|
| 28 | 15 |
function query(storage, url, multiple) |
| 29 | 16 |
{
|
| 30 | 17 |
const matched = []; |
| 31 | 18 |
const cb = p => check_pattern(storage, p, multiple, matched); |
| 32 |
for_each_possible_pattern(url, cb); |
|
| 19 |
for (const pattern of each_url_pattern(url)) {
|
|
| 20 |
const result = [pattern, storage.get(TYPE_PREFIX.PAGE, pattern)]; |
|
| 21 |
if (result[1] === undefined) |
|
| 22 |
continue; |
|
| 23 |
|
|
| 24 |
if (!multiple) |
|
| 25 |
return result; |
|
| 26 |
matched.push(result); |
|
| 27 |
} |
|
| 33 | 28 |
|
| 34 |
return multiple ? matched : (matched[0] || [undefined, undefined]);
|
|
| 29 |
return multiple ? matched : [undefined, undefined];
|
|
| 35 | 30 |
} |
| 36 | 31 |
|
| 37 | 32 |
function query_best(storage, url) |
| content/freezer.js | ||
|---|---|---|
| 49 | 49 |
console.log('Script suppressor has detached.');
|
| 50 | 50 |
return; |
| 51 | 51 |
} |
| 52 |
console.log("script event", e);
|
|
| 52 | 53 |
if (e.isTrusted && !e.target._hachette_payload) {
|
| 53 | 54 |
e.preventDefault(); |
| 54 | 55 |
console.log('Suppressed script', e.target);
|
| content/main.js | ||
|---|---|---|
| 10 | 10 |
* IMPORTS_START |
| 11 | 11 |
* IMPORT handle_page_actions |
| 12 | 12 |
* IMPORT extract_signed |
| 13 |
* IMPORT sign_data |
|
| 13 | 14 |
* IMPORT gen_nonce |
| 14 | 15 |
* IMPORT is_privileged_url |
| 15 | 16 |
* IMPORT mozilla_suppress_scripts |
| ... | ... | |
| 31 | 32 |
parent.hachette_corresponding.appendChild(clone); |
| 32 | 33 |
} |
| 33 | 34 |
|
| 34 |
if (!is_privileged_url(document.URL)) {
|
|
| 35 |
/* Signature valid for half an hour. */ |
|
| 36 |
const min_time = new Date().getTime() - 1800 * 1000; |
|
| 35 |
function extract_cookie_policy(cookie, min_time) |
|
| 36 |
{
|
|
| 37 | 37 |
let best_result = {time: -1};
|
| 38 | 38 |
let policy = null; |
| 39 | 39 |
const extracted_signatures = []; |
| 40 |
for (const match of document.cookie.matchAll(/hachette-(\w*)=([^;]*)/g)) {
|
|
| 40 |
|
|
| 41 |
for (const match of cookie.matchAll(/hachette-(\w*)=([^;]*)/g)) {
|
|
| 41 | 42 |
const new_result = extract_signed(...match.slice(1, 3)); |
| 42 | 43 |
if (new_result.fail) |
| 43 | 44 |
continue; |
| ... | ... | |
| 56 | 57 |
policy = new_policy; |
| 57 | 58 |
} |
| 58 | 59 |
|
| 60 |
return [policy, extracted_signatures]; |
|
| 61 |
} |
|
| 62 |
|
|
| 63 |
function extract_url_policy(url, min_time) |
|
| 64 |
{
|
|
| 65 |
const [base_url, payload, anchor] = |
|
| 66 |
/^([^#]*)#?([^#]*)(#?.*)$/.exec(url).splice(1, 4); |
|
| 67 |
|
|
| 68 |
const match = /^hachette_([^_]+)_(.*)$/.exec(payload); |
|
| 69 |
if (!match) |
|
| 70 |
return [null, url]; |
|
| 71 |
|
|
| 72 |
const result = extract_signed(...match.slice(1, 3)); |
|
| 73 |
if (result.fail) |
|
| 74 |
return [null, url]; |
|
| 75 |
|
|
| 76 |
const original_url = base_url + anchor; |
|
| 77 |
const policy = result.time < min_time ? null : |
|
| 78 |
JSON.parse(decodeURIComponent(result.data)); |
|
| 79 |
|
|
| 80 |
return [policy.url === original_url ? policy : null, original_url]; |
|
| 81 |
} |
|
| 82 |
|
|
| 83 |
function employ_nonhttp_policy(policy) |
|
| 84 |
{
|
|
| 85 |
if (!policy.allow) |
|
| 86 |
return; |
|
| 87 |
|
|
| 88 |
policy.nonce = gen_nonce(); |
|
| 89 |
const [base_url, target] = /^([^#]*)(#?.*)$/.exec(policy.url).slice(1, 3); |
|
| 90 |
const encoded_policy = encodeURIComponent(JSON.stringify(policy)); |
|
| 91 |
const payload = "hachette_" + |
|
| 92 |
sign_data(encoded_policy, new Date().getTime()).join("_");
|
|
| 93 |
const resulting_url = `${base_url}#${payload}${target}`;
|
|
| 94 |
location.href = resulting_url; |
|
| 95 |
location.reload(); |
|
| 96 |
} |
|
| 97 |
|
|
| 98 |
if (!is_privileged_url(document.URL)) {
|
|
| 99 |
let policy_received_callback = () => undefined; |
|
| 100 |
let policy; |
|
| 101 |
|
|
| 102 |
/* Signature valid for half an hour. */ |
|
| 103 |
const min_time = new Date().getTime() - 1800 * 1000; |
|
| 104 |
|
|
| 105 |
if (/^https?:/.test(document.URL)) {
|
|
| 106 |
let signatures; |
|
| 107 |
[policy, signatures] = extract_cookie_policy(document.cookie, min_time); |
|
| 108 |
for (const signature of signatures) |
|
| 109 |
document.cookie = `hachette-${signature}=; Max-Age=-1;`;
|
|
| 110 |
} else {
|
|
| 111 |
const scheme = /^([^:]*)/.exec(document.URL)[1]; |
|
| 112 |
const known_scheme = ["file"].includes(scheme); |
|
| 113 |
|
|
| 114 |
if (!known_scheme) |
|
| 115 |
console.warn(`Unknown url scheme: \`${scheme}'!`);
|
|
| 116 |
|
|
| 117 |
let original_url; |
|
| 118 |
[policy, original_url] = extract_url_policy(document.URL, min_time); |
|
| 119 |
history.replaceState(null, "", original_url); |
|
| 120 |
|
|
| 121 |
if (known_scheme && !policy) |
|
| 122 |
policy_received_callback = employ_nonhttp_policy; |
|
| 123 |
} |
|
| 124 |
|
|
| 59 | 125 |
if (!policy) {
|
| 60 |
console.warn("WARNING! Using default policy!!!");
|
|
| 126 |
console.warn("Using default policy!");
|
|
| 61 | 127 |
policy = {allow: false, nonce: gen_nonce()};
|
| 62 | 128 |
} |
| 63 | 129 |
|
| 64 |
for (const signature of extracted_signatures) |
|
| 65 |
document.cookie = `hachette-${signature}=; Max-Age=-1;`;
|
|
| 66 |
|
|
| 67 |
handle_page_actions(policy.nonce); |
|
| 130 |
handle_page_actions(policy.nonce, policy_received_callback); |
|
| 68 | 131 |
|
| 69 | 132 |
if (!policy.allow) {
|
| 133 |
if (is_mozilla) {
|
|
| 134 |
const script = document.querySelector("script");
|
|
| 135 |
if (script) |
|
| 136 |
script.textContent = "throw 'blocked';\n" + script.textContent; |
|
| 137 |
} |
|
| 70 | 138 |
const old_html = document.documentElement; |
| 71 | 139 |
const new_html = document.createElement("html");
|
| 72 | 140 |
old_html.replaceWith(new_html); |
| content/page_actions.js | ||
|---|---|---|
| 14 | 14 |
* IMPORTS_END |
| 15 | 15 |
*/ |
| 16 | 16 |
|
| 17 |
var port; |
|
| 18 |
var loaded = false; |
|
| 19 |
var scripts_awaiting = []; |
|
| 20 |
var nonce; |
|
| 17 |
let policy_received_callback; |
|
| 18 |
/* Snapshot url early because document.URL can be changed by other code. */ |
|
| 19 |
let url; |
|
| 20 |
let port; |
|
| 21 |
let loaded = false; |
|
| 22 |
let scripts_awaiting = []; |
|
| 23 |
let nonce; |
|
| 21 | 24 |
|
| 22 | 25 |
function handle_message(message) |
| 23 | 26 |
{
|
| ... | ... | |
| 31 | 34 |
scripts_awaiting.push(script_text); |
| 32 | 35 |
} |
| 33 | 36 |
} |
| 34 |
if (action === "settings") |
|
| 37 |
if (action === "settings") {
|
|
| 35 | 38 |
report_settings(data); |
| 39 |
policy_received_callback({url, allow: !!data[1] && data[1].allow});
|
|
| 40 |
} |
|
| 36 | 41 |
} |
| 37 | 42 |
|
| 38 | 43 |
function document_loaded(event) |
| ... | ... | |
| 56 | 61 |
report_script(script_text); |
| 57 | 62 |
} |
| 58 | 63 |
|
| 59 |
function handle_page_actions(script_nonce) {
|
|
| 64 |
function handle_page_actions(script_nonce, policy_received_cb) {
|
|
| 65 |
policy_received_callback = policy_received_cb; |
|
| 66 |
url = document.URL; |
|
| 67 |
|
|
| 60 | 68 |
document.addEventListener("DOMContentLoaded", document_loaded);
|
| 61 | 69 |
port = browser.runtime.connect({name : CONNECTION_TYPE.PAGE_ACTIONS});
|
| 62 | 70 |
port.onMessage.addListener(handle_message); |
| 63 |
port.postMessage({url: document.URL});
|
|
| 71 |
port.postMessage({url});
|
|
| 64 | 72 |
|
| 65 | 73 |
nonce = script_nonce; |
| 66 | 74 |
} |
| html/display-panel.js | ||
|---|---|---|
| 20 | 20 |
* IMPORT TYPE_PREFIX |
| 21 | 21 |
* IMPORT nice_name |
| 22 | 22 |
* IMPORT open_in_settings |
| 23 |
* IMPORT for_each_possible_pattern
|
|
| 23 |
* IMPORT each_url_pattern
|
|
| 24 | 24 |
* IMPORT by_id |
| 25 | 25 |
* IMPORT clone_template |
| 26 | 26 |
* IMPORTS_END |
| ... | ... | |
| 127 | 127 |
|
| 128 | 128 |
function populate_possible_patterns_list(url) |
| 129 | 129 |
{
|
| 130 |
for_each_possible_pattern(url, add_pattern_to_list); |
|
| 130 |
for (const pattern of each_url_pattern(url)) |
|
| 131 |
add_pattern_to_list(pattern); |
|
| 131 | 132 |
|
| 132 | 133 |
for (const [pattern, settings] of query_all(storage, url)) {
|
| 133 | 134 |
set_pattern_li_button_text(ensure_pattern_exists(pattern), |
Also available in: Unified diff
enable whitelisting of `file://' protocol\n\nThis commit additionally also changes the semantics of triple asterisk wildcard in URL path.