Project

General

Profile

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

haketilo / content / main.js @ b7e2870f

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 url_extract_target
13
 * IMPORT gen_unique
14
 * IMPORT csp_rule
15
 * IMPORT is_privileged_url
16
 * IMPORT sanitize_attributes
17
 * IMPORT script_suppressor
18
 * IMPORT is_chrome
19
 * IMPORT is_mozilla
20
 * IMPORT start_activity_info_server
21
 * IMPORTS_END
22
 */
23

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

    
35
let url = url_item(document.URL);
36
let unique = gen_unique(url);
37

    
38
const suppressor = script_suppressor(unique);
39

    
40

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

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

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

    
57
	return true;
58
    }
59

    
60
    return false;
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
    meta.setAttribute("content", csp_rule(unique));
119

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

    
126
if (!is_privileged_url(document.URL)) {
127
    start_activity_info_server();
128
    handle_page_actions(unique);
129

    
130
    if (is_http()) {
131
	/* rely on CSP injected through webRequest */
132
    } else if (is_whitelisted()) {
133
	/* do not block scripts at all */
134
    } else {
135
	block_nodes_recursively(document.documentElement);
136

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

    
146
	if (is_mozilla)
147
	    addEventListener('beforescriptexecute', suppressor, true);
148
    }
149
}
(3-3/4)