summaryrefslogtreecommitdiff
path: root/content/main.js
blob: 507a7400d2fb63dc12a373c44f72e08dc0b260bd (about) (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
/**
 * Myext main content script run in all frames
 *
 * Copyright (C) 2021 Wojtek Kosior
 *
 * Dual-licensed under:
 *   - 0BSD license
 *   - GPLv3 or (at your option) any later version
 */

"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);
})();