Project

General

Profile

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

haketilo / content / main.js @ 1789f174

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

    
9
/*
10
 * IMPORTS_START
11
 * IMPORT CONNECTION_TYPE
12
 * IMPORT handle_page_actions
13
 * IMPORT url_item
14
 * IMPORT url_extract_target
15
 * IMPORT gen_unique
16
 * IMPORT gen_nonce
17
 * IMPORT csp_rule
18
 * IMPORT is_privileged_url
19
 * IMPORT sanitize_attributes
20
 * IMPORT mozilla_suppress_scripts
21
 * IMPORT browser
22
 * IMPORT is_chrome
23
 * IMPORT is_mozilla
24
 * IMPORT start_activity_info_server
25
 * IMPORTS_END
26
 */
27

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

    
39
let url = url_item(document.URL);
40
let unique = gen_unique(url);
41

    
42

    
43
function is_http()
44
{
45
    return !!/^https?:\/\//i.exec(document.URL);
46
}
47

    
48
function is_whitelisted()
49
{
50
    const parsed_url = url_extract_target(document.URL);
51

    
52
    if (parsed_url.target !== undefined &&
53
	parsed_url.target === '#' + unique) {
54
	if (parsed_url.target2 !== undefined)
55
	    window.location.href = parsed_url.base_url + parsed_url.target2;
56
	else
57
	    history.replaceState(null, "", parsed_url.base_url);
58

    
59
	return true;
60
    }
61

    
62
    return false;
63
}
64

    
65
function handle_mutation(mutations, observer)
66
{
67
    if (document.readyState === 'complete') {
68
	console.log("mutation handling complete");
69
	observer.disconnect();
70
	return;
71
    }
72
    for (const mutation of mutations) {
73
	for (const node of mutation.addedNodes)
74
	    block_node(node);
75
    }
76
}
77

    
78
function block_nodes_recursively(node)
79
{
80
    block_node(node);
81
    for (const child of node.children)
82
	block_nodes_recursively(child);
83
}
84

    
85
function block_node(node)
86
{
87
    /*
88
     * Modifying <script> element doesn't always prevent its
89
     * execution in some Mozilla browsers. Additional blocking
90
     * through CSP meta tag injection is required.
91
     */
92
    if (node.tagName === "SCRIPT") {
93
	block_script(node);
94
	return;
95
    }
96

    
97
    sanitize_attributes(node);
98

    
99
    if (node.tagName === "HEAD")
100
	inject_csp(node);
101
}
102

    
103
function block_script(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(head)
115
{
116
    console.log('injecting CSP');
117

    
118
    let meta = document.createElement("meta");
119
    meta.setAttribute("http-equiv", "Content-Security-Policy");
120
    meta.setAttribute("content", csp_rule(nonce));
121

    
122
    if (head.firstElementChild === null)
123
	head.appendChild(meta);
124
    else
125
	head.insertBefore(meta, head.firstElementChild);
126
}
127

    
128
if (!is_privileged_url(document.URL)) {
129
    start_activity_info_server();
130
    var nonce, port = browser.runtime.connect({name : CONNECTION_TYPE.PAGE_ACTIONS});
131

    
132
    if (is_http()) {
133
	/* rely on CSP injected through webRequest, at the cost of having to fetch a nonce via messaging */
134
	const nonce_capturer = msg => {
135
	    port.onMessage.removeListener(nonce_capturer);
136
	    handle_page_actions(msg[1], port);
137
	};
138

    
139
	port.onMessage.addListener(nonce_capturer);
140

    
141
    } else if (is_whitelisted()) {
142
	/* do not block scripts at all; as a result, there is no need for a green-lighted nonce */
143
	handle_page_actions(null, port);
144
    } else {
145
	nonce = gen_nonce();
146
	handle_page_actions(nonce, port);
147
	block_nodes_recursively(document.documentElement);
148

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

    
158
	if (is_mozilla)
159
	    addEventListener('beforescriptexecute', mozilla_suppress_scripts, true);
160
    }
161
}
(3-3/4)