aboutsummaryrefslogtreecommitdiff
path: root/content/main.js
blob: 5d88a6d16b084ef43a02cd6816c9120a50457061 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/**
 * Myext main content script run in all frames
 *
 * Copyright (C) 2021 Wojtek Kosior
 * Redistribution terms are gathered in the `copyright' file.
 */

/*
 * IMPORTS_START
 * IMPORT handle_page_actions
 * IMPORT url_item
 * IMPORT url_extract_target
 * IMPORT gen_unique
 * IMPORT csp_rule
 * IMPORT is_privileged_url
 * IMPORT sanitize_attributes
 * IMPORT script_suppressor
 * IMPORT is_chrome
 * IMPORT is_mozilla
 * IMPORT start_activity_info_server
 * IMPORTS_END
 */

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

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

const suppressor = script_suppressor(unique);


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

function is_whitelisted()
{
    const parsed_url = url_extract_target(document.URL);

    if (parsed_url.target !== undefined &&
	parsed_url.target === '#' + unique) {
	if (parsed_url.target2 !== undefined)
	    window.location.href = parsed_url.base_url + parsed_url.target2;
	else
	    history.replaceState(null, "", parsed_url.base_url);

	return true;
    }

    return false;
}

function handle_mutation(mutations, observer)
{
    if (document.readyState === 'complete') {
	console.log("mutation handling complete");
	observer.disconnect();
	return;
    }
    for (const mutation of mutations) {
	for (const node of mutation.addedNodes)
	    block_node(node);
    }
}

function block_nodes_recursively(node)
{
    block_node(node);
    for (const child of node.children)
	block_nodes_recursively(child);
}

function block_node(node)
{
    /*
     * Modifying <script> element doesn't always prevent its
     * execution in some Mozilla browsers. Additional blocking
     * through CSP meta tag injection is required.
     */
    if (node.tagName === "SCRIPT") {
	block_script(node);
	return;
    }

    sanitize_attributes(node);

    if (node.tagName === "HEAD")
	inject_csp(node);
}

function block_script(node)
{
    /*
     * Disabling scripts this way allows them to still be relatively
     * easily accessed in case they contain some useful data.
     */
    if (node.hasAttribute("type"))
	node.setAttribute("blocked-type", node.getAttribute("type"));
    node.setAttribute("type", "application/json");
}

function inject_csp(head)
{
    console.log('injecting CSP');

    let meta = document.createElement("meta");
    meta.setAttribute("http-equiv", "Content-Security-Policy");
    meta.setAttribute("content", csp_rule(unique));

    if (head.firstElementChild === null)
	head.appendChild(meta);
    else
	head.insertBefore(meta, head.firstElementChild);
}

if (!is_privileged_url(document.URL)) {
    start_activity_info_server();
    handle_page_actions(unique);

    if (is_http()) {
	/* rely on CSP injected through webRequest */
    } else if (is_whitelisted()) {
	/* do not block scripts at all */
    } else {
	block_nodes_recursively(document.documentElement);

	if (is_chrome) {
	    var observer = new MutationObserver(handle_mutation);
	    observer.observe(document.documentElement, {
		attributes: true,
		childList: true,
		subtree: true
	    });
	}

	if (is_mozilla)
	    addEventListener('beforescriptexecute', suppressor, true);
    }
}