Project

General

Profile

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

haketilo / content / main.js @ 261548ff

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
/*
9
 * IMPORTS_START
10
 * IMPORT handle_page_actions
11
 * IMPORT url_item
12
 * IMPORT gen_unique
13
 * IMPORT sanitize_attributes
14
 * IMPORT script_suppressor
15
 * IMPORT is_chrome
16
 * IMPORT is_mozilla
17
 * IMPORTS_END
18
 */
19

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

    
31
let url = url_item(document.URL);
32
let unique = gen_unique(url);
33
let nonce = unique.substring(1);
34

    
35
const suppressor = script_suppressor(nonce);
36

    
37
function needs_blocking()
38
{
39
    if (url.startsWith("https://") || url.startsWith("http://"))
40
	return false;
41

    
42
    let url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/;
43
    let match = url_re.exec(document.URL);
44
    let base_url = match[1];
45
    let first_target = match[3];
46
    let second_target = match[4];
47

    
48
    if (first_target !== undefined &&
49
	first_target === unique) {
50
	if (second_target !== undefined)
51
	    window.location.href = base_url + second_target;
52
	else
53
	    history.replaceState(null, "", base_url);
54

    
55
	console.log(["allowing whitelisted", document.URL]);
56
	return false;
57
    }
58

    
59
    console.log(["disallowing", document.URL]);
60
    return true;
61
}
62

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

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

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

    
95
    sanitize_attributes(node);
96

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

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

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

    
116
    let meta = document.createElement("meta");
117
    meta.setAttribute("http-equiv", "Content-Security-Policy");
118

    
119
    let rule = `script-src 'nonce-${nonce}'; `;
120
    if (is_chrome)
121
	rule += `script-src-elem 'nonce-${nonce}';`;
122

    
123
    meta.setAttribute("content", rule);
124

    
125
    if (head.firstElementChild === null)
126
	head.appendChild(meta);
127
    else
128
	head.insertBefore(meta, head.firstElementChild);
129
}
130

    
131
if (needs_blocking()) {
132
    block_nodes_recursively(document.documentElement);
133

    
134
    if (is_chrome) {
135
	var observer = new MutationObserver(handle_mutation);
136
	observer.observe(document.documentElement, {
137
	    attributes: true,
138
	    childList: true,
139
	    subtree: true
140
	});
141
    }
142

    
143
    if (is_mozilla)
144
	addEventListener('beforescriptexecute', suppressor, true);
145
}
146

    
147
handle_page_actions(nonce);
(2-2/3)