Project

General

Profile

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

haketilo / background / policy_injector.js @ 3d0efa15

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 get_storage
12
 * IMPORT browser
13
 * IMPORT is_chrome
14
 * IMPORT gen_nonce
15
 * IMPORT is_privileged_url
16
 * IMPORT sign_data
17
 * IMPORT extract_signed
18
 * IMPORT query_best
19
 * IMPORT sanitize_csp_header
20
 * IMPORT csp_rule
21
 * IMPORTS_END
22
 */
23

    
24
var storage;
25

    
26
const csp_header_names = new Set([
27
    "content-security-policy",
28
    "x-webkit-csp",
29
    "x-content-security-policy"
30
]);
31

    
32
const report_only = "content-security-policy-report-only";
33

    
34
function headers_inject(details)
35
{
36
    const url = details.url;
37
    if (is_privileged_url(url))
38
	return;
39

    
40
    const [pattern, settings] = query_best(storage, url);
41
    const allow = !!(settings && settings.allow);
42
    const nonce = gen_nonce();
43
    const rule = `'nonce-${nonce}'`;
44

    
45
    let orig_csp_headers;
46
    let old_signature;
47
    let hachette_header;
48
    let headers = details.responseHeaders;
49

    
50
    for (const header of headers.filter(h => h.name === "x-hachette")) {
51
	const match = /^([^%])(%.*)$/.exec(header.value);
52
	if (!match)
53
	    continue;
54

    
55
	const old_data = extract_signed(...match.splice(1, 2), [[0]]);
56
	if (!old_data || old_data.url !== url)
57
	    continue;
58

    
59
	/* Confirmed- it's the originals, smuggled in! */
60
	orig_csp_headers = old_data.csp_headers;
61
	old_signature = old_data.policy_signature;
62

    
63
	hachette_header = header;
64
	break;
65
    }
66

    
67
    if (!hachette_header) {
68
	hachette_header = {name: "x-hachette"};
69
	headers.push(hachette_header);
70
    }
71

    
72
    orig_csp_headers = orig_csp_headers ||
73
	headers.filter(h => csp_header_names.has(h.name.toLowerCase()));
74
    headers = headers.filter(h => !csp_header_names.has(h.name.toLowerCase()));
75

    
76
    /* Remove headers that only snitch on us */
77
    if (!allow)
78
	headers = headers.filter(h => h.name.toLowerCase() !== report_only);
79

    
80
    if (old_signature)
81
	headers = headers.filter(h => h.name.search(old_signature) === -1);
82

    
83
    const sanitizer = h => sanitize_csp_header(h, rule, allow);
84
    headers.push(...orig_csp_headers.map(sanitizer));
85

    
86
    const policy = encodeURIComponent(JSON.stringify({allow, nonce, url}));
87
    const policy_signature = sign_data(policy, new Date());
88
    const later_30sec = new Date(new Date().getTime() + 30000).toGMTString();
89
    headers.push({
90
	name: "Set-Cookie",
91
	value: `hachette-${policy_signature}=${policy}; Expires=${later_30sec};`
92
    });
93

    
94
    /*
95
     * Smuggle in the signature and the original CSP headers for future use.
96
     * These are signed with a time of 0, as it's not clear there is a limit on
97
     * how long Firefox might retain headers in the cache.
98
     */
99
    let hachette_data = {csp_headers: orig_csp_headers, policy_signature, url};
100
    hachette_data = encodeURIComponent(JSON.stringify(hachette_data));
101
    hachette_header.value = sign_data(hachette_data, 0) + hachette_data;
102

    
103
    /* To ensure there is a CSP header if required */
104
    if (!allow)
105
	headers.push({name: "content-security-policy", value: csp_rule(nonce)});
106

    
107
    return {responseHeaders: headers};
108
}
109

    
110
async function start_policy_injector()
111
{
112
    storage = await get_storage();
113

    
114
    let extra_opts = ["blocking", "responseHeaders"];
115
    if (is_chrome)
116
	extra_opts.push("extraHeaders");
117

    
118
    browser.webRequest.onHeadersReceived.addListener(
119
	headers_inject,
120
	{
121
	    urls: ["<all_urls>"],
122
	    types: ["main_frame", "sub_frame"]
123
	},
124
	extra_opts
125
    );
126
}
127

    
128
/*
129
 * EXPORTS_START
130
 * EXPORT start_policy_injector
131
 * EXPORTS_END
132
 */
(3-3/5)