12 |
12 |
/*
|
13 |
13 |
* IMPORTS_START
|
14 |
14 |
* IMPORT browser
|
|
15 |
* IMPORT is_csp_header_name
|
15 |
16 |
* IMPORTS_END
|
16 |
17 |
*/
|
17 |
18 |
|
... | ... | |
110 |
111 |
return new TextDecoder(charset || "latin1");
|
111 |
112 |
}
|
112 |
113 |
|
|
114 |
function may_define_csp_rules(html)
|
|
115 |
{
|
|
116 |
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
117 |
|
|
118 |
for (const meta of doc.querySelectorAll("head>meta[http-equiv]")) {
|
|
119 |
if (is_csp_header_name(meta.getAttribute("http-equiv"), true) &&
|
|
120 |
meta.content)
|
|
121 |
return true;
|
|
122 |
}
|
|
123 |
|
|
124 |
/*
|
|
125 |
* Even if no naughty `<meta>' tags were found, subsequent chunk of HTML
|
|
126 |
* data could add some. Before we return `false' we need to be sure we
|
|
127 |
* reached the start of `<body>' where `<meta>' tags are no longer valid.
|
|
128 |
*/
|
|
129 |
|
|
130 |
if (doc.documentElement.nextSibling || doc.body.nextSibling ||
|
|
131 |
doc.body.childNodes.length > 1)
|
|
132 |
return false;
|
|
133 |
|
|
134 |
if (!doc.body.firstChild)
|
|
135 |
return true;
|
|
136 |
|
|
137 |
if (doc.body.firstChild.nodeName !== "#text")
|
|
138 |
return false;
|
|
139 |
|
|
140 |
return /^(<\/|&#|.)$/.test(doc.body.firstChild.wholeText);
|
|
141 |
}
|
|
142 |
|
113 |
143 |
function filter_data(properties, event)
|
114 |
144 |
{
|
115 |
145 |
const data = new Uint8Array(event.data);
|
... | ... | |
118 |
148 |
first_chunk = true;
|
119 |
149 |
properties.decoder = create_decoder(properties, data);
|
120 |
150 |
properties.encoder = new TextEncoder();
|
121 |
|
/* Force UTF-8, this is the only encoding we can produce. */
|
122 |
|
properties.filter.write(new Uint8Array(UTF8_BOM));
|
123 |
151 |
}
|
124 |
152 |
|
125 |
153 |
let decoded = properties.decoder.decode(data);
|
126 |
154 |
|
127 |
|
if (first_chunk) {
|
|
155 |
/* Force UTF-8, this is the only encoding we can produce. */
|
|
156 |
if (first_chunk)
|
|
157 |
properties.filter.write(new Uint8Array(UTF8_BOM));
|
|
158 |
|
|
159 |
if (first_chunk && may_define_csp_rules(decoded)) {
|
128 |
160 |
/*
|
129 |
161 |
* HAX! Our content scripts that execute at `document_start' will always
|
130 |
162 |
* run before the first script in the document, but under Mozilla some
|
... | ... | |
133 |
165 |
* will force `document_start' to happen earlier. This way our content
|
134 |
166 |
* scripts will be able to sanitize `http-equiv' tags with CSP rules
|
135 |
167 |
* that would otherwise stop our injected scripts from executing.
|
|
168 |
*
|
|
169 |
* As we want to only process HTML files that happen to have naughty
|
|
170 |
* `<meta>' tags in `<head>', we use a DOMParser-based heuristic in
|
|
171 |
* `may_define_rules()'. We don't do any additional MIME sniffing as it
|
|
172 |
* is too unreliable (and our heuristic will likely mark non-HTML files
|
|
173 |
* as harmless anyway).
|
136 |
174 |
*/
|
|
175 |
|
137 |
176 |
const dummy_script =
|
138 |
177 |
`<script data-hachette-deleteme="${properties.policy.nonce}" nonce="${properties.policy.nonce}">null</script>`;
|
139 |
178 |
const doctype_decl = /^(\s*<!doctype[^<>"']*>)?/i.exec(decoded)[0];
|
... | ... | |
149 |
188 |
|
150 |
189 |
function apply_stream_filter(details, headers, policy)
|
151 |
190 |
{
|
152 |
|
if (policy.allow)
|
|
191 |
if (!policy.has_payload)
|
153 |
192 |
return headers;
|
154 |
193 |
|
155 |
194 |
const properties = properties_from_headers(headers);
|
only apply stream filter modifications when reasonably necessary