Project

General

Profile

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

haketilo / content / main.js @ b93f26bf

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

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

    
26
    let url = url_item(document.URL);
27
    let unique = gen_unique(url);
28
    let nonce = unique.substring(1);
29

    
30
    function needs_blocking()
31
    {
32
	if (url.startsWith("https://") || url.startsWith("http://"))
33
	    return false;
34

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

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

    
48
	    console.log(["allowing whitelisted", document.URL]);
49
	    return false;
50
	}
51

    
52
	console.log(["disallowing", document.URL]);
53
	return true;
54
    }
55

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

    
75
		sanitize_attributes(node);
76

    
77
		if (node.tagName === "HEAD")
78
		    inject_csp(node);
79
	    }
80
	}
81
    }
82

    
83
    function block_script(node)
84
    {
85
	console.log(node);
86

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

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

    
108
    function sanitize_attributes(node)
109
    {
110
	if (node.attributes === undefined)
111
	    return;
112

    
113
	/*
114
	 * We have to do it in 2 loops, removing attribute modifies
115
	 * our iterator
116
	 */
117
	let attr_names = [];
118
	for (let attr of node.attributes) {
119
	    let attr_name = attr.localName;
120
	    if (attr_name.startsWith("on"))
121
		attr_names.push(attr_name);
122
	}
123

    
124
	for (let attr_name of attr_names) {
125
	    node.removeAttribute(attr_name);
126
	    console.log("sanitized", attr_name);
127
	}
128
    }
129

    
130
    if (needs_blocking()) {
131
	var observer = new MutationObserver(handle_mutation);
132
	observer.observe(document.documentElement, {
133
	    attributes: true,
134
	    childList: true,
135
	    subtree: true
136
	});
137
    }
138

    
139
    handle_page_actions(nonce);
140
})();
(1-1/2)