aboutsummaryrefslogtreecommitdiff
path: root/content/main.js
blob: 23f7f66c8542de5335f61036ef695cdc6c28975b (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
/**
 * Myext main content script run in all frames
 *
 * Copyright (C) 2021 Wojtek Kosior
 *
 * This code is dual-licensed under:
 * - Asshole license 1.0,
 * - GPLv3 or (at your option) any later version
 *
 * "dual-licensed" means you can choose the license you prefer.
 *
 * This code is released under a permissive license because I disapprove of
 * copyright and wouldn't be willing to sue a violator. Despite not putting
 * this code under copyleft (which is also kind of copyright), I do not want
 * it to be made proprietary. Hence, the permissive alternative to GPL is the
 * Asshole license 1.0 that allows me to call you an asshole if you use it.
 * This means you're legally ok regardless of how you utilize this code but if
 * you make it into something nonfree, you're an asshole.
 *
 * You should have received a copy of both GPLv3 and Asshole license 1.0
 * together with this code. If not, please see:
 * - https://www.gnu.org/licenses/gpl-3.0.en.html
 * - https://koszko.org/asshole-license.txt
 */

"use strict";

(() => {
    const handle_page_actions = window.handle_page_actions;
    const url_item = window.url_item;
    const gen_unique = window.gen_unique;

    var url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/;
    var match = url_re.exec(document.URL);
    var base_url = match[1];
    var first_target = match[3];
    var second_target = match[4];

    // TODO: can be refactored *a little bit* with policy_smuggler.js
    let url = url_item(document.URL);
    let unique = gen_unique(url);

    let nonce = unique.substring(1);

    var block = true;
    if (first_target !== undefined &&
	first_target === unique) {
	block = false;
	console.log(["allowing", document.URL]);
	if (second_target !== undefined)
	    window.location.href = base_url + second_target;
	else
	    history.replaceState(null, "", base_url);
    } else {
	console.log(["not allowing", document.URL]);
    }

    function handle_mutation(mutations, observer)
    {
	if (document.readyState === 'complete') {
	    console.log("complete");
	    observer.disconnect();
	    return;
	}
	for (let mutation of mutations) {
	    for (let node of mutation.addedNodes) {
		/*
		 * 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);
		    continue;
		}

		sanitize_attributes(node);

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

    function block_script(node)
    {
	console.log(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(node)
    {
	console.log('injecting CSP');
	let meta = document.createElement("meta");
	meta.setAttribute("http-equiv", "Content-Security-Policy");
	meta.setAttribute("content", `\
script-src 'nonce-${nonce}'; \
script-src-elem 'nonce-${nonce}';\
`);
	node.appendChild(meta);
    }

    function sanitize_attributes(node)
    {
	if (node.attributes === undefined)
	    return;

	/*
	 * We have to do it in 2 loops, removing attribute modifies
	 * our iterator
	 */
	let attr_names = [];
	for (let attr of node.attributes) {
	    let attr_name = attr.localName;
	    if (attr_name.startsWith("on"))
		attr_names.push(attr_name);
	}

	for (let attr_name of attr_names) {
	    node.removeAttribute(attr_name);
	    console.log("sanitized", attr_name);
	}
    }

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

    handle_page_actions(nonce);
})();