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.