Project

General

Profile

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

haketilo / content / main.js @ dcfc78b0

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 is_chrome
22
 * IMPORT is_mozilla
23
 * IMPORT start_activity_info_server
24
 * IMPORTS_END
25
 */
26

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

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

    
41

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

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

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

    
58
	return true;
59
    }
60

    
61
    return false;
62
}
63

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

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

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

    
96
    sanitize_attributes(node);
97

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

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

    
113
function inject_csp(head)
114
{
115
    console.log('injecting CSP');
116

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

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

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

    
131
    if (is_http()) {
132
	/* rely on CSP injected through webRequest, at the cost of having to fetch a nonce via messaging */
133
	const nonce_capturer = msg => {
134
	    port.onMessage.removeListener(nonce_capturer);
135
	    handle_page_actions(msg[1], port);
136
	};
137
	
138
	port.onMessage.addListener(nonce_capturer);
139
	
140
    } else if (is_whitelisted()) {
141
	/* do not block scripts at all; as a result, there is no need for a green-lighted nonce */
142
	handle_page_actions(null, port);
143
    } else {
144
	nonce = gen_nonce();
145
	handle_page_actions(nonce, port);
146
	block_nodes_recursively(document.documentElement);
147

    
148
	if (is_chrome) {
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
	if (is_mozilla)
158
	    addEventListener('beforescriptexecute', mozilla_suppress_scripts, true);
159
    }
160
}
(3-3/4)