Project

General

Profile

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

haketilo / content / main.js @ 48f76d70

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 sign_data
14
 * IMPORT gen_nonce
15
 * IMPORT is_privileged_url
16
 * IMPORT mozilla_suppress_scripts
17
 * IMPORT is_chrome
18
 * IMPORT is_mozilla
19
 * IMPORT start_activity_info_server
20
 * IMPORT modify_on_the_fly
21
 * IMPORTS_END
22
 */
23

    
24
function accept_node(node, parent)
25
{
26
    const clone = document.importNode(node, false);
27
    node.hachette_corresponding = clone;
28
    /*
29
     * TODO: Stop page's own issues like "Error parsing a meta element's
30
     * content:" from appearing as extension's errors.
31
     */
32
    parent.hachette_corresponding.appendChild(clone);
33
}
34

    
35
function extract_cookie_policy(cookie, min_time)
36
{
37
    let best_result = {time: -1};
38
    let policy = null;
39
    const extracted_signatures = [];
40

    
41
    for (const match of cookie.matchAll(/hachette-(\w*)=([^;]*)/g)) {
42
	const new_result = extract_signed(...match.slice(1, 3));
43
	if (new_result.fail)
44
	    continue;
45

    
46
	extracted_signatures.push(match[1]);
47

    
48
	if (new_result.time < Math.max(min_time, best_result.time))
49
	    continue;
50

    
51
	/* This should succeed - it's our self-produced valid JSON. */
52
	const new_policy = JSON.parse(decodeURIComponent(new_result.data));
53
	if (new_policy.url !== document.URL)
54
	    continue;
55

    
56
	best_result = new_result;
57
	policy = new_policy;
58
    }
59

    
60
    return [policy, extracted_signatures];
61
}
62

    
63
function extract_url_policy(url, min_time)
64
{
65
    const [base_url, payload, anchor] =
66
	  /^([^#]*)#?([^#]*)(#?.*)$/.exec(url).splice(1, 4);
67

    
68
    const match = /^hachette_([^_]+)_(.*)$/.exec(payload);
69
    if (!match)
70
	return [null, url];
71

    
72
    const result = extract_signed(...match.slice(1, 3));
73
    if (result.fail)
74
	return [null, url];
75

    
76
    const original_url = base_url + anchor;
77
    const policy = result.time < min_time ? null :
78
	  JSON.parse(decodeURIComponent(result.data));
79

    
80
    return [policy.url === original_url ? policy : null, original_url];
81
}
82

    
83
function employ_nonhttp_policy(policy)
84
{
85
    if (!policy.allow)
86
	return;
87

    
88
    policy.nonce = gen_nonce();
89
    const [base_url, target] = /^([^#]*)(#?.*)$/.exec(policy.url).slice(1, 3);
90
    const encoded_policy = encodeURIComponent(JSON.stringify(policy));
91
    const payload = "hachette_" +
92
	  sign_data(encoded_policy, new Date().getTime()).join("_");
93
    const resulting_url = `${base_url}#${payload}${target}`;
94
    location.href = resulting_url;
95
    location.reload();
96
}
97

    
98
if (!is_privileged_url(document.URL)) {
99
    let policy_received_callback = () => undefined;
100
    let policy;
101

    
102
    /* Signature valid for half an hour. */
103
    const min_time = new Date().getTime() - 1800 * 1000;
104

    
105
    if (/^https?:/.test(document.URL)) {
106
	let signatures;
107
	[policy, signatures] = extract_cookie_policy(document.cookie, min_time);
108
	for (const signature of signatures)
109
	    document.cookie = `hachette-${signature}=; Max-Age=-1;`;
110
    } else {
111
	const scheme = /^([^:]*)/.exec(document.URL)[1];
112
	const known_scheme = ["file", "ftp"].includes(scheme);
113

    
114
	if (!known_scheme)
115
	    console.warn(`Unknown url scheme: \`${scheme}'!`);
116

    
117
	let original_url;
118
	[policy, original_url] = extract_url_policy(document.URL, min_time);
119
	history.replaceState(null, "", original_url);
120

    
121
	if (known_scheme && !policy)
122
	    policy_received_callback = employ_nonhttp_policy;
123
    }
124

    
125
    if (!policy) {
126
	console.warn("Using default policy!");
127
	policy = {allow: false, nonce: gen_nonce()};
128
    }
129

    
130
    handle_page_actions(policy.nonce, policy_received_callback);
131

    
132
    if (!policy.allow) {
133
	if (is_mozilla) {
134
	    const script = document.querySelector("script");
135
	    if (script)
136
		script.textContent = "throw 'blocked';\n" + script.textContent;
137
	}
138
	const old_html = document.documentElement;
139
	const new_html = document.createElement("html");
140
	old_html.replaceWith(new_html);
141
	old_html.hachette_corresponding = new_html;
142

    
143
	const modify_end =
144
	      modify_on_the_fly(old_html, policy, {node_eater: accept_node});
145
	document.addEventListener("DOMContentLoaded", modify_end);
146
    }
147

    
148
    start_activity_info_server();
149
}
(3-3/6)