Project

General

Profile

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

haketilo / background / policy_injector.js @ 014f2a2f

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
    console.log("ijnector details", details);
37
    const url = details.url;
38
    if (is_privileged_url(url))
39
	return;
40

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

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

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

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

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

    
64
	hachette_header = header;
65
	break;
66
    }
67

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

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

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

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

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

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

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

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

    
108
    return {responseHeaders: headers};
109
}
110

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

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

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

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