aboutsummaryrefslogtreecommitdiff
path: root/common/misc.js
blob: 036eb45559ca66c68005d1c2d676ee74912d9fb2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
 * Myext miscellaneous operations refactored to a separate file
 *
 * Copyright (C) 2021 Wojtek Kosior
 * Copyright (C) 2021 jahoti
 * Redistribution terms are gathered in the `copyright' file.
 */

/*
 * IMPORTS_START
 * IMPORT sha256
 * IMPORT browser
 * IMPORT is_chrome
 * IMPORT TYPE_NAME
 * IMPORTS_END
 */

/* Generate a random base64-encoded 128-bit sequence */
function gen_nonce()
{
    let randomData = new Uint8Array(16);
    crypto.getRandomValues(randomData);
    return btoa(String.fromCharCode.apply(null, randomData));
}

/*
 * generating unique, per-site value that can be computed synchronously
 * and is impossible to guess for a malicious website
 */
function gen_unique(url)
{
    return sha256(get_secure_salt() + url);
}

function get_secure_salt()
{
    if (is_chrome)
	return browser.runtime.getManifest().key.substring(0, 50);
    else
	return browser.runtime.getURL("dummy");
}

/*
 * stripping url from query and target (everything after `#' or `?'
 * gets removed)
 */
function url_item(url)
{
    let url_re = /^([^?#]*).*$/;
    let match = url_re.exec(url);
    return match[1];
}

/*
 * Assume a url like: https://example.com/green?illuminati=confirmed#tinky#winky
 * This function will make it into an object like:
 * {
 *     "base_url" : "https://example.com/green?illuminati=confirmed",
 *     "target" : "#tinky",
 *     "target2" : "#winky"
 * }
 * In case url doesn't have 2 #'s, target2 and target can be set to undefined.
 */
function url_extract_target(url)
{
    let url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/;
    let match = url_re.exec(url);
    return {
	base_url : match[1],
	target : match[3],
	target2 : match[4]
    };
}

/* csp rule that blocks all scripts except for those injected by us */
function csp_rule(nonce)
{
    let rule = `script-src 'nonce-${nonce}';`;
    if (is_chrome)
	rule += `script-src-elem 'nonce-${nonce}';`;
    return rule;
}

/*
 * Print item together with type, e.g.
 * nice_name("s", "hello") → "hello (script)"
 */
function nice_name(prefix, name)
{
    return `${name} (${TYPE_NAME[prefix]})`;
}

/* Open settings tab with given item's editing already on. */
function open_in_settings(prefix, name)
{
    name = encodeURIComponent(name);
    const url = browser.runtime.getURL("html/options.html#" + prefix + name);
    window.open(url, "_blank");
}

/* Check if url corresponds to a browser's special page */
function is_privileged_url(url)
{
    return !!/^(chrome(-extension)?|moz-extension):\/\/|^about:/i.exec(url);
}

/* Extract any policy present in the URL */
function url_extract_policy(url)
{
    var policy_string;
    const targets = url_extract_target(url);
    
    try {
	policy_string = targets.target.substring(65);
	targets.policy = JSON.parse(decodeURIComponent(policy_string));
    } catch (e) {
	/* TODO what should happen here? */
    }
    
    if (targets.policy) {
	const sig = gen_unique(policy_string + targets.base_url);
	targets.valid_sig = targets.target.substring(1, 65) === sig;
    }

    return targets;
}

/*
 * EXPORTS_START
 * EXPORT gen_nonce
 * EXPORT gen_unique
 * EXPORT url_item
 * EXPORT url_extract_target
 * EXPORT url_extract_policy
 * EXPORT csp_rule
 * EXPORT nice_name
 * EXPORT open_in_settings
 * EXPORT is_privileged_url
 * EXPORTS_END
 */