Project

General

Profile

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

haketilo / content / main.js @ 3d0efa15

1
/**
2
 * Hachette main content script run in all frames
3
 *
4
 * Copyright (C) 2021 Wojtek Kosior
5
 * Copyright (C) 2021 jahoti
6
 * Redistribution terms are gathered in the `copyright' file.
7
 */
8

    
9
/*
10
 * IMPORTS_START
11
 * IMPORT handle_page_actions
12
 * IMPORT extract_signed
13
 * IMPORT gen_nonce
14
 * IMPORT csp_rule
15
 * IMPORT is_privileged_url
16
 * IMPORT sanitize_attributes
17
 * IMPORT mozilla_suppress_scripts
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 only supplemental in HTTP(s) pages where
28
 * blocking of native scripts also happens through CSP header injection but is
29
 * necessary for 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
var nonce = undefined;
36

    
37
function handle_mutation(mutations, observer)
38
{
39
    if (document.readyState === 'complete') {
40
	console.log("mutation handling complete");
41
	observer.disconnect();
42
	return;
43
    }
44
    for (const mutation of mutations) {
45
	for (const node of mutation.addedNodes)
46
	    block_node(node);
47
    }
48
}
49

    
50
function block_nodes_recursively(node)
51
{
52
    block_node(node);
53
    for (const child of node.children)
54
	block_nodes_recursively(child);
55
}
56

    
57
function block_node(node)
58
{
59
    /*
60
     * Modifying <script> element doesn't always prevent its execution in some
61
     * Mozilla browsers. This is Chromium-specific code.
62
     */
63
    if (node.tagName === "SCRIPT") {
64
	block_script(node);
65
	return;
66
    }
67

    
68
    sanitize_attributes(node);
69

    
70
    if (node.tagName === "HEAD")
71
	inject_csp(node);
72
}
73

    
74
function block_script(node)
75
{
76
    /*
77
     * Disabling scripts this way allows them to still be relatively
78
     * easily accessed in case they contain some useful data.
79
     */
80
    if (node.hasAttribute("type"))
81
	node.setAttribute("blocked-type", node.getAttribute("type"));
82
    node.setAttribute("type", "application/json");
83
}
84

    
85
function inject_csp(head)
86
{
87
    let meta = document.createElement("meta");
88
    meta.setAttribute("http-equiv", "Content-Security-Policy");
89
    meta.setAttribute("content", csp_rule(nonce));
90

    
91
    if (head.firstElementChild === null)
92
	head.appendChild(meta);
93
    else
94
	head.insertBefore(meta, head.firstElementChild);
95
}
96

    
97
if (!is_privileged_url(document.URL)) {
98
    const reductor =
99
	  (ac, [_, sig, pol]) => ac[0] && ac || [extract_signed(sig, pol), sig];
100
    const matches = [...document.cookie.matchAll(/hachette-(\w*)=([^;]*)/g)];
101
    let [policy, signature] = matches.reduce(reductor, []);
102

    
103
    if (!policy || policy.url !== document.URL) {
104
	console.log("WARNING! Using default policy!!!");
105
	policy = {allow: false, nonce: gen_nonce()};
106
    }
107

    
108
    if (signature)
109
	document.cookie = `hachette-${signature}=; Max-Age=-1;`;
110

    
111
    handle_page_actions(policy.nonce);
112

    
113
    if (!policy.allow) {
114
	block_nodes_recursively(document.documentElement);
115

    
116
	if (is_chrome) {
117
	    var observer = new MutationObserver(handle_mutation);
118
	    observer.observe(document.documentElement, {
119
		attributes: true,
120
		childList: true,
121
		subtree: true
122
	    });
123
	}
124

    
125
	if (is_mozilla)
126
	    addEventListener('beforescriptexecute', mozilla_suppress_scripts, true);
127
    }
128

    
129
    start_activity_info_server();
130
}
(3-3/5)