Project

General

Profile

Download (3.25 KB) Statistics
| Branch: | Tag: | Revision:

haketilo / content / main.js @ edbbe400

1
/**
2
 * Myext main content script run in all frames
3
 *
4
 * Copyright (C) 2021 Wojtek Kosior
5
 * Redistribution terms are gathered in the `copyright' file.
6
 */
7

    
8
"use strict";
9

    
10
(() => {
11
    const handle_page_actions = window.handle_page_actions;
12
    const url_item = window.url_item;
13
    const gen_unique = window.gen_unique;
14
    const sanitize_attributes = window.sanitize_attributes;
15

    
16
    /*
17
     * Due to some technical limitations the chosen method of whitelisting sites
18
     * is to smuggle whitelist indicator in page's url as a "magical" string
19
     * after '#'. Right now this is not needed in HTTP(s) pages where native
20
     * script blocking happens through CSP header injection but is needed for
21
     * protocols like ftp:// and file://.
22
     *
23
     * The code that actually injects the magical string into ftp:// and file://
24
     * urls has not yet been added to the extension.
25
     */
26

    
27
    let url = url_item(document.URL);
28
    let unique = gen_unique(url);
29
    let nonce = unique.substring(1);
30
    
31
    const scriptSuppressor = window.scriptSuppressor(nonce);
32

    
33
    function needs_blocking()
34
    {
35
	if (url.startsWith("https://") || url.startsWith("http://"))
36
	    return false;
37

    
38
	let url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/;
39
	let match = url_re.exec(document.URL);
40
	let base_url = match[1];
41
	let first_target = match[3];
42
	let second_target = match[4];
43

    
44
	if (first_target !== undefined &&
45
	    first_target === unique) {
46
	    if (second_target !== undefined)
47
		window.location.href = base_url + second_target;
48
	    else
49
		history.replaceState(null, "", base_url);
50

    
51
	    console.log(["allowing whitelisted", document.URL]);
52
	    return false;
53
	}
54

    
55
	console.log(["disallowing", document.URL]);
56
	return true;
57
    }
58

    
59
    function handle_mutation(mutations, observer)
60
    {
61
	if (document.readyState === 'complete') {
62
	    console.log("complete");
63
	    observer.disconnect();
64
	    return;
65
	}
66
	for (let mutation of mutations) {
67
	    for (let node of mutation.addedNodes) {
68
		/*
69
		 * Modifying <script> element doesn't always prevent its
70
		 * execution in some Mozilla browsers. Additional blocking
71
		 * through CSP meta tag injection is required.
72
		 */
73
		if (node.tagName === "SCRIPT") {
74
		    block_script(node);
75
		    continue;
76
		}
77

    
78
		sanitize_attributes(node);
79

    
80
		if (node.tagName === "HEAD")
81
		    inject_csp(node);
82
	    }
83
	}
84
    }
85

    
86
    function block_script(node)
87
    {
88
	console.log(node);
89

    
90
	/*
91
	 * Disabling scripts this way allows them to still be relatively
92
	 * easily accessed in case they contain some useful data.
93
	 */
94
	if (node.hasAttribute("type"))
95
	    node.setAttribute("blocked-type", node.getAttribute("type"));
96
	node.setAttribute("type", "application/json");
97
    }
98

    
99
    function inject_csp(node)
100
    {
101
	console.log('injecting CSP');
102
	let meta = document.createElement("meta");
103
	meta.setAttribute("http-equiv", "Content-Security-Policy");
104
	meta.setAttribute("content", `\
105
script-src 'nonce-${nonce}'; \
106
script-src-elem 'nonce-${nonce}';\
107
`);
108
	node.appendChild(meta);
109
    }
110

    
111
    if (needs_blocking()) {
112
	// Script blocking for Gecko
113
	addEventListener('beforescriptexecute', scriptSuppressor, true);
114
	
115
	var observer = new MutationObserver(handle_mutation);
116
	observer.observe(document.documentElement, {
117
	    attributes: true,
118
	    childList: true,
119
	    subtree: true
120
	});
121
    }
122

    
123
    handle_page_actions(nonce);
124
})();
(1-1/2)