Project

General

Profile

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

haketilo / content / main.js @ 7ee7889a

1
/**
2
 * Myext main content script run in all frames
3
 *
4
 * Copyright (C) 2021 Wojtek Kosior
5
 *
6
 * This code is dual-licensed under:
7
 * - Asshole license 1.0,
8
 * - GPLv3 or (at your option) any later version
9
 *
10
 * "dual-licensed" means you can choose the license you prefer.
11
 *
12
 * This code is released under a permissive license because I disapprove of
13
 * copyright and wouldn't be willing to sue a violator. Despite not putting
14
 * this code under copyleft (which is also kind of copyright), I do not want
15
 * it to be made proprietary. Hence, the permissive alternative to GPL is the
16
 * Asshole license 1.0 that allows me to call you an asshole if you use it.
17
 * This means you're legally ok regardless of how you utilize this code but if
18
 * you make it into something nonfree, you're an asshole.
19
 *
20
 * You should have received a copy of both GPLv3 and Asshole license 1.0
21
 * together with this code. If not, please see:
22
 * - https://www.gnu.org/licenses/gpl-3.0.en.html
23
 * - https://koszko.org/asshole-license.txt
24
 */
25

    
26
"use strict";
27

    
28
(() => {
29
    const handle_page_actions = window.handle_page_actions;
30
    const url_item = window.url_item;
31
    const gen_unique = window.gen_unique;
32

    
33
    /*
34
     * Due to some technical limitations the chosen method of whitelisting sites
35
     * is to smuggle whitelist indicator in page's url as a "magical" string
36
     * after '#'. Right now this is not needed in HTTP(s) pages where native
37
     * script blocking happens through CSP header injection but is needed for
38
     * protocols like ftp:// and file://.
39
     *
40
     * The code that actually injects the magical string into ftp:// and file://
41
     * urls has not yet been added to the extension.
42
     */
43

    
44
    let url = url_item(document.URL);
45
    let unique = gen_unique(url);
46
    let nonce = unique.substring(1);
47

    
48
    function needs_blocking()
49
    {
50
	if (url.startsWith("https://") || url.startsWith("http://"))
51
	    return false;
52

    
53
	let url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/;
54
	let match = url_re.exec(document.URL);
55
	let base_url = match[1];
56
	let first_target = match[3];
57
	let second_target = match[4];
58

    
59
	if (first_target !== undefined &&
60
	    first_target === unique) {
61
	    if (second_target !== undefined)
62
		window.location.href = base_url + second_target;
63
	    else
64
		history.replaceState(null, "", base_url);
65

    
66
	    console.log(["allowing whitelisted", document.URL]);
67
	    return false;
68
	}
69

    
70
	console.log(["disallowing", document.URL]);
71
	return true;
72
    }
73

    
74
    function handle_mutation(mutations, observer)
75
    {
76
	if (document.readyState === 'complete') {
77
	    console.log("complete");
78
	    observer.disconnect();
79
	    return;
80
	}
81
	for (let mutation of mutations) {
82
	    for (let node of mutation.addedNodes) {
83
		/*
84
		 * Modifying <script> element doesn't always prevent its
85
		 * execution in some Mozilla browsers. Additional blocking
86
		 * through CSP meta tag injection is required.
87
		 */
88
		if (node.tagName === "SCRIPT") {
89
		    block_script(node);
90
		    continue;
91
		}
92

    
93
		sanitize_attributes(node);
94

    
95
		if (node.tagName === "HEAD")
96
		    inject_csp(node);
97
	    }
98
	}
99
    }
100

    
101
    function block_script(node)
102
    {
103
	console.log(node);
104

    
105
	/*
106
	 * Disabling scripts this way allows them to still be relatively
107
	 * easily accessed in case they contain some useful data.
108
	 */
109
	if (node.hasAttribute("type"))
110
	    node.setAttribute("blocked-type", node.getAttribute("type"));
111
	node.setAttribute("type", "application/json");
112
    }
113

    
114
    function inject_csp(node)
115
    {
116
	console.log('injecting CSP');
117
	let meta = document.createElement("meta");
118
	meta.setAttribute("http-equiv", "Content-Security-Policy");
119
	meta.setAttribute("content", `\
120
script-src 'nonce-${nonce}'; \
121
script-src-elem 'nonce-${nonce}';\
122
`);
123
	node.appendChild(meta);
124
    }
125

    
126
    function sanitize_attributes(node)
127
    {
128
	if (node.attributes === undefined)
129
	    return;
130

    
131
	/*
132
	 * We have to do it in 2 loops, removing attribute modifies
133
	 * our iterator
134
	 */
135
	let attr_names = [];
136
	for (let attr of node.attributes) {
137
	    let attr_name = attr.localName;
138
	    if (attr_name.startsWith("on"))
139
		attr_names.push(attr_name);
140
	}
141

    
142
	for (let attr_name of attr_names) {
143
	    node.removeAttribute(attr_name);
144
	    console.log("sanitized", attr_name);
145
	}
146
    }
147

    
148
    if (needs_blocking()) {
149
	var observer = new MutationObserver(handle_mutation);
150
	observer.observe(document.documentElement, {
151
	    attributes: true,
152
	    childList: true,
153
	    subtree: true
154
	});
155
    }
156

    
157
    handle_page_actions(nonce);
158
})();
(1-1/2)