13 |
13 |
* IMPORT sign_data
|
14 |
14 |
* IMPORT gen_nonce
|
15 |
15 |
* IMPORT is_privileged_url
|
16 |
|
* IMPORT mozilla_suppress_scripts
|
17 |
16 |
* IMPORT is_chrome
|
18 |
17 |
* IMPORT is_mozilla
|
19 |
18 |
* IMPORT start_activity_info_server
|
... | ... | |
132 |
131 |
function _wait_for_head(doc, detached_html, callback)
|
133 |
132 |
{
|
134 |
133 |
const waiting = {doc, detached_html, callback, observers: []};
|
135 |
|
if (try_body_started(waiting))
|
136 |
|
return;
|
137 |
134 |
|
138 |
|
waiting.observers = [make_body_start_observer(detached_html, waiting)];
|
|
135 |
/*
|
|
136 |
* For XML and SVG documents, instead of waiting for `<head>', we wait
|
|
137 |
* for the entire document to finish loading.
|
|
138 |
*/
|
|
139 |
if (doc instanceof HTMLDocument) {
|
|
140 |
if (try_body_started(waiting))
|
|
141 |
return;
|
|
142 |
|
|
143 |
waiting.observers = [make_body_start_observer(detached_html, waiting)];
|
|
144 |
}
|
|
145 |
|
139 |
146 |
waiting.loaded_cb = () => finish_waiting(waiting);
|
140 |
147 |
doc.addEventListener("DOMContentLoaded", waiting.loaded_cb);
|
141 |
148 |
}
|
... | ... | |
200 |
207 |
delete script.hachette_blocked_type;
|
201 |
208 |
}
|
202 |
209 |
|
203 |
|
function apply_hachette_csp_rules(doc, policy)
|
|
210 |
function apply_hachette_csp_rules(doc, head, policy)
|
204 |
211 |
{
|
205 |
212 |
const meta = doc.createElement("meta");
|
206 |
213 |
meta.setAttribute("http-equiv", "Content-Security-Policy");
|
207 |
214 |
meta.setAttribute("content", csp_rule(policy.nonce));
|
208 |
|
doc.head.append(meta);
|
|
215 |
head.append(meta);
|
209 |
216 |
/* CSP is already in effect, we can remove the <meta> now. */
|
210 |
217 |
meta.remove();
|
211 |
218 |
}
|
212 |
219 |
|
|
220 |
function sanitize_urls(element)
|
|
221 |
{
|
|
222 |
for (const attribute of [...element.attributes]) {
|
|
223 |
if (/^(href|src|data)$/i.test(attribute.localName) &&
|
|
224 |
/^data:([^,;]*ml|unknown-content-type)/i.test(attribute.value))
|
|
225 |
block_attribute(element, attribute.localName);
|
|
226 |
}
|
|
227 |
}
|
|
228 |
|
|
229 |
function start_data_urls_sanitizing(doc)
|
|
230 |
{
|
|
231 |
doc.querySelectorAll("*[href], *[src], *[data]").forEach(sanitize_urls);
|
|
232 |
const mutation_handler = m => m.addedNodes.forEach(sanitize_urls);
|
|
233 |
const mo = new MutationObserver(ms => ms.forEach(mutation_handler));
|
|
234 |
mo.observe(doc, {childList: true, subtree: true});
|
|
235 |
}
|
|
236 |
|
|
237 |
function apply_intrinsics_sanitizing(root_element)
|
|
238 |
{
|
|
239 |
for (const subelem of root_element.querySelectorAll("*")) {
|
|
240 |
[...subelem.attributes]
|
|
241 |
.filter(a => /^on/i.test(a.localName))
|
|
242 |
.filter(a => /^javascript:/i.test(a.value))
|
|
243 |
.forEach(a => block_attribute(subelem, a.localName));
|
|
244 |
}
|
|
245 |
}
|
|
246 |
|
213 |
247 |
async function sanitize_document(doc, policy)
|
214 |
248 |
{
|
|
249 |
/*
|
|
250 |
* Blocking of scripts that are in the DOM from the beginning. Needed for
|
|
251 |
* Mozilla, harmless on Chromium.
|
|
252 |
* Note that at least in SVG documents the `src' attr on `<script>'s seems
|
|
253 |
* to be ignored by Firefox, so we don't need to sanitize it.
|
|
254 |
*/
|
|
255 |
for (const script of document.getElementsByTagName("script")) {
|
|
256 |
const old_children = [...script.childNodes];
|
|
257 |
script.innerHTML = "";
|
|
258 |
setTimeout(() => old_children.forEach(c => script.append(c)), 0);
|
|
259 |
}
|
|
260 |
|
215 |
261 |
/*
|
216 |
262 |
* Ensure our CSP rules are employed from the beginning. This CSP injection
|
217 |
263 |
* method is, when possible, going to be applied together with CSP rules
|
218 |
264 |
* injected using webRequest.
|
|
265 |
* For non-HTML documents this is just a dummy operation of adding and
|
|
266 |
* removing `head'.
|
219 |
267 |
*/
|
220 |
|
const has_own_head = doc.head;
|
221 |
|
if (!has_own_head)
|
222 |
|
doc.documentElement.prepend(doc.createElement("head"));
|
|
268 |
let added_head = doc.createElement("head");
|
|
269 |
if (!doc.head)
|
|
270 |
doc.documentElement.prepend(added_head);
|
223 |
271 |
|
224 |
|
apply_hachette_csp_rules(doc, policy);
|
|
272 |
apply_hachette_csp_rules(doc, added_head, policy);
|
225 |
273 |
|
226 |
|
/* Probably not needed, but...: proceed with DOM in its initial state. */
|
227 |
|
if (!has_own_head)
|
228 |
|
doc.head.remove();
|
|
274 |
/* Proceed with DOM in its initial state. */
|
|
275 |
added_head.remove();
|
229 |
276 |
|
230 |
277 |
/*
|
231 |
278 |
* <html> node gets hijacked now, to be re-attached after <head> is loaded
|
... | ... | |
243 |
290 |
for (const script of old_html.querySelectorAll("script"))
|
244 |
291 |
sanitize_script(script, policy);
|
245 |
292 |
|
|
293 |
if (!(doc instanceof HTMLDocument))
|
|
294 |
apply_intrinsics_sanitizing(old_html);
|
|
295 |
|
246 |
296 |
new_html.replaceWith(old_html);
|
247 |
297 |
|
248 |
298 |
for (const script of old_html.querySelectorAll("script"))
|
249 |
299 |
desanitize_script(script, policy);
|
|
300 |
|
|
301 |
start_data_urls_sanitizing(doc);
|
250 |
302 |
}
|
251 |
303 |
|
252 |
304 |
if (!is_privileged_url(document.URL)) {
|
re-enable sanitizing of data: URLs and also sanitize intrinsics on non-HTML pages where CSP doesn't work