Project

General

Profile

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

haketilo / background / policy_injector.js @ d09b7ee1

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
 * IMPORT is_csp_header_name
22
 * IMPORTS_END
23
 */
24

    
25
var storage;
26

    
27
function headers_inject(details)
28
{
29
    const url = details.url;
30
    if (is_privileged_url(url))
31
	return;
32

    
33
    const [pattern, settings] = query_best(storage, url);
34
    const allow = !!(settings && settings.allow);
35
    const nonce = gen_nonce();
36

    
37
    let orig_csp_headers;
38
    let old_signature;
39
    let hachette_header;
40
    let headers = details.responseHeaders;
41

    
42
    for (const header of headers.filter(h => h.name === "x-hachette")) {
43
	const match = /^([^%])(%.*)$/.exec(header.value);
44
	if (!match)
45
	    continue;
46

    
47
	const old_data = extract_signed(...match.splice(1, 2), [[0]]);
48
	if (!old_data || old_data.url !== url)
49
	    continue;
50

    
51
	/* Confirmed- it's the originals, smuggled in! */
52
	orig_csp_headers = old_data.csp_headers;
53
	old_signature = old_data.policy_signature;
54

    
55
	hachette_header = header;
56
	break;
57
    }
58

    
59
    if (!hachette_header) {
60
	hachette_header = {name: "x-hachette"};
61
	headers.push(hachette_header);
62
    }
63

    
64
    orig_csp_headers = orig_csp_headers ||
65
	headers.filter(h => is_csp_header_name(h.name));
66

    
67
    /* When blocking remove report-only CSP headers that snitch on us. */
68
    headers = headers.filter(h => !is_csp_header_name(h.name, !allow));
69

    
70
    if (old_signature)
71
	headers = headers.filter(h => h.name.search(old_signature) === -1);
72

    
73
    const policy_object = {allow, nonce, url};
74
    const sanitizer = h => sanitize_csp_header(h, policy_object);
75
    headers.push(...orig_csp_headers.map(sanitizer));
76

    
77
    const policy = encodeURIComponent(JSON.stringify(policy_object));
78
    const policy_signature = sign_data(policy, new Date());
79
    const later_30sec = new Date(new Date().getTime() + 30000).toGMTString();
80
    headers.push({
81
	name: "Set-Cookie",
82
	value: `hachette-${policy_signature}=${policy}; Expires=${later_30sec};`
83
    });
84

    
85
    /*
86
     * Smuggle in the signature and the original CSP headers for future use.
87
     * These are signed with a time of 0, as it's not clear there is a limit on
88
     * how long Firefox might retain headers in the cache.
89
     */
90
    let hachette_data = {csp_headers: orig_csp_headers, policy_signature, url};
91
    hachette_data = encodeURIComponent(JSON.stringify(hachette_data));
92
    hachette_header.value = sign_data(hachette_data, 0) + hachette_data;
93

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

    
98
    return {responseHeaders: headers};
99
}
100

    
101
async function start_policy_injector()
102
{
103
    storage = await get_storage();
104

    
105
    let extra_opts = ["blocking", "responseHeaders"];
106
    if (is_chrome)
107
	extra_opts.push("extraHeaders");
108

    
109
    browser.webRequest.onHeadersReceived.addListener(
110
	headers_inject,
111
	{
112
	    urls: ["<all_urls>"],
113
	    types: ["main_frame", "sub_frame"]
114
	},
115
	extra_opts
116
    );
117
}
118

    
119
/*
120
 * EXPORTS_START
121
 * EXPORT start_policy_injector
122
 * EXPORTS_END
123
 */
(3-3/5)