Project

General

Profile

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

haketilo / background / policy_injector.js @ 77139a6f

1
/**
2
 * Hachette injecting policy to page using webRequest
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 TYPE_PREFIX
12
 * IMPORT get_storage
13
 * IMPORT browser
14
 * IMPORT is_chrome
15
 * IMPORT gen_unique
16
 * IMPORT gen_nonce
17
 * IMPORT is_privileged_url
18
 * IMPORT url_extract_target
19
 * IMPORT sign_policy
20
 * IMPORT get_query_best
21
 * IMPORT parse_csp
22
 * IMPORTS_END
23
 */
24

    
25
var storage;
26
var query_best;
27

    
28
const csp_header_names = {
29
    "content-security-policy" : true,
30
    "x-webkit-csp" : true,
31
    "x-content-security-policy" : true
32
};
33

    
34
const unwanted_csp_directives = {
35
    "report-to" : true,
36
    "report-uri" : true,
37
    "script-src" : true,
38
    "script-src-elem" : true,
39
    "prefetch-src": true
40
};
41

    
42
const header_name = "content-security-policy";
43

    
44
function not_csp_header(header)
45
{
46
    return !csp_header_names[header.name.toLowerCase()];
47
}
48

    
49
function url_inject(details)
50
{
51
    if (is_privileged_url(details.url))
52
	return;
53

    
54
    const targets = url_extract_target(details.url);
55
    if (targets.current)
56
	return;
57

    
58
    /* Redirect; update policy */
59
    if (targets.policy)
60
	targets.target = "";
61

    
62
    let [pattern, settings] = query_best(targets.base_url);
63
    /* Defaults */
64
    if (!pattern)
65
	settings = {};
66

    
67
    const policy = encodeURIComponent(
68
	JSON.stringify({
69
	    allow: settings.allow,
70
	    nonce: gen_nonce(),
71
	    base_url: targets.base_url
72
	})
73
    );
74

    
75
    return {
76
	redirectUrl: [
77
	    targets.base_url,
78
	    '#', sign_policy(policy, new Date()), policy,
79
	    targets.target,
80
	    targets.target2
81
	].join("")
82
    };
83
}
84

    
85
function headers_inject(details)
86
{
87
    const targets = url_extract_target(details.url);
88
    /* Block mis-/unsigned requests */
89
    if (!targets.current)
90
	return {cancel: true};
91

    
92
    const headers = [];
93
    for (let header of details.responseHeaders) {
94
	if (not_csp_header(header)) {
95
	    /* Retain all non-snitching headers */
96
	    if (header.name.toLowerCase() !==
97
	        'content-security-policy-report-only')
98
		headers.push(header);
99

    
100
	    continue;
101
	}
102

    
103
	const csp = parse_csp(header.value);
104
	const rule = `'nonce-${targets.policy.nonce}'`
105
	
106
	/* TODO: confirm deleting non-existent things is OK everywhere */
107
	/* No snitching or prefetching/rendering */
108
	delete csp['report-to'];
109
	delete csp['report-uri'];
110
	
111
	if (!targets.policy.allow) {
112
	    delete csp['script-src'];
113
	    delete csp['script-src-elem'];
114
	    csp['script-src-attr'] = ["'none'"];
115
	    csp['prefetch-src'] = ["'none'"];
116
	}
117
	
118
	if ('script-src' in csp)
119
	    csp['script-src'].push(rule);
120
	else
121
	    csp['script-src'] = [rule];
122

    
123
	if ('script-src-elem' in csp)
124
	    csp['script-src-elem'].push(rule);
125
	else
126
	    csp['script-src-elem'] = [rule];
127
	
128
	/* TODO: is this safe */
129
	let new_policy = Object.entries(csp).map(
130
	    i => i[0] + ' ' + i[1].join(' ') + ';'
131
	);
132
	
133
	headers.push({name: header.name, value: new_policy.join('')});
134
    }
135

    
136
    return {responseHeaders: headers};
137
}
138

    
139
async function start_policy_injector()
140
{
141
    storage = await get_storage();
142
    query_best = await get_query_best();
143

    
144
    let extra_opts = ["blocking", "responseHeaders"];
145
    if (is_chrome)
146
	extra_opts.push("extraHeaders");
147

    
148
    browser.webRequest.onBeforeRequest.addListener(
149
	url_inject,
150
	{
151
	    urls: ["<all_urls>"],
152
	    types: ["main_frame", "sub_frame"]
153
	},
154
	["blocking"]
155
    );
156

    
157
    browser.webRequest.onHeadersReceived.addListener(
158
	headers_inject,
159
	{
160
	    urls: ["<all_urls>"],
161
	    types: ["main_frame", "sub_frame"]
162
	},
163
	extra_opts
164
    );
165
}
166

    
167
/*
168
 * EXPORTS_START
169
 * EXPORT start_policy_injector
170
 * EXPORTS_END
171
 */
(4-4/7)