LocalCDN-Firefox-Chrome-Brave/core/manipulate-dom.js

143 lines
4.6 KiB
JavaScript
Raw Normal View History

2020-05-02 13:56:49 +02:00
/**
* Remove integrity checks from embedded CSS and JavaScript files
* Belongs to LocalCDN.
*
2020-06-30 18:41:58 +02:00
* @author nobody
2020-05-02 13:56:49 +02:00
* @since 2020-02-27
*
* @license MPL 2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
'use strict';
/**
* Manipulate DOM
*/
var manipulateDOM = {};
/**
* Private Methods
*/
manipulateDOM._removeCrossOriginAndIntegrityAttr = function (details) {
2021-02-17 07:01:08 +01:00
if (!BrowserType.FIREFOX) {
// Chromium (and other) browsers do not support webRequest.filterResponseData
// https://bugs.chromium.org/p/chromium/issues/detail?id=487422
console.warn('[ LocalCDN ] browser.webRequest.filterResponseData not supported by your browser.');
return;
}
2021-02-17 07:01:08 +01:00
let initiatorDomain, listedToManipulateDOM, negateHtmlFilter, filtering, header;
initiatorDomain = helpers.extractDomainFromUrl(details.url, true) || Address.EXAMPLE;
2021-02-17 07:01:08 +01:00
listedToManipulateDOM = stateManager._domainIsListed(initiatorDomain, 'manipulate-dom');
negateHtmlFilter = stateManager.getInvertOption;
2021-02-17 07:01:08 +01:00
if ((negateHtmlFilter || listedToManipulateDOM) && !(negateHtmlFilter && listedToManipulateDOM)) {
filtering = true;
} else {
filtering = false;
}
2020-05-02 13:56:49 +02:00
// by Jaap (https://gitlab.com/Jaaap)
2021-02-17 07:01:08 +01:00
header = details.responseHeaders.find((h) => h.name.toLowerCase() === 'content-type');
2020-05-02 13:56:49 +02:00
if (header && filtering) {
2020-05-02 13:56:49 +02:00
2020-10-17 07:09:30 +02:00
let mimeType, isAllowlisted;
2020-05-02 13:56:49 +02:00
mimeType = header.value.replace(/;.*/, '').toLowerCase();
2020-10-17 07:09:30 +02:00
isAllowlisted = stateManager._domainIsListed(initiatorDomain);
2020-05-02 13:56:49 +02:00
2020-10-17 07:09:30 +02:00
if (!isAllowlisted && mimeType === 'text/html') {
2020-05-02 13:56:49 +02:00
2021-02-17 07:01:08 +01:00
let asciiDecoder, decoder, encoder, charset, isFirstData, filter, data;
2020-05-19 17:26:16 +02:00
2021-02-17 07:01:08 +01:00
charset = (/charset\s*=/).test(header.value) && header.value.replace(/^.*?charset\s*=\s*/, '').replace(/["']?/g, '');
// Check if charset is supported by TextDecoder()
2021-02-17 07:01:08 +01:00
if (/charset\s*=/.test(header.value) && !EncodingTypes[charset.toString().toLowerCase()]) {
console.error(`[ LocalCDN ] Unsupported charset: ${charset}`);
2020-05-23 16:56:16 +02:00
return;
}
2020-05-23 16:56:16 +02:00
asciiDecoder = new TextDecoder('ASCII');
encoder = new TextEncoder();
2020-05-19 17:26:16 +02:00
isFirstData = true;
filter = browser.webRequest.filterResponseData(details.requestId);
2021-02-17 07:01:08 +01:00
data = [];
2020-05-19 17:26:16 +02:00
2020-05-02 13:56:49 +02:00
header.value = 'text/html; charset=UTF-8';
2021-02-17 07:01:08 +01:00
// NOTE: should work if 'script' string is divided into two chunks
filter.ondata = (evt) => {
2020-05-19 17:26:16 +02:00
if (isFirstData) {
if (!charset) {
2021-02-17 07:01:08 +01:00
// content-type has no charset declared
let htmlHead, charsetMatch;
htmlHead = asciiDecoder.decode(evt.data, {'stream': false});
// eslint-disable-next-line no-useless-escape
charsetMatch = htmlHead.match(/<meta.*charset=["']?([^>"'\/]+)["'].*[>\/]/i);
charset = charsetMatch ? charsetMatch[1] : 'UTF-8';
2020-05-19 17:26:16 +02:00
}
decoder = new TextDecoder(charset);
}
2021-01-30 06:45:21 +01:00
isFirstData = false;
data.push(evt.data);
2021-02-17 07:01:08 +01:00
};
2021-01-30 06:45:21 +01:00
2021-02-17 07:01:08 +01:00
filter.onstop = () => {
2021-01-30 06:45:21 +01:00
let str = '';
for (let buffer of data) {
2021-02-17 07:01:08 +01:00
str += decoder.decode(buffer, {'stream': true});
2021-01-30 06:45:21 +01:00
}
str += decoder.decode(); // end-of-stream
2021-02-17 07:01:08 +01:00
// remove crossorigin and integrity attributes
str = str.replace(/<(link|script)[^>]+>/ig, (m) => {
// eslint-disable-next-line no-use-before-define
2020-05-19 17:26:16 +02:00
if (cdnDomainsRE.test(m)) {
2020-12-06 07:52:25 +01:00
return m.replace(/\s+(integrity|crossorigin)(="[^"]*"|='[^']*'|=[^"'`=>\s]+|)/ig, '');
2020-05-19 17:26:16 +02:00
}
return m;
});
2020-05-02 13:56:49 +02:00
filter.write(encoder.encode(str));
filter.close();
2021-02-17 07:01:08 +01:00
};
2020-05-02 13:56:49 +02:00
}
2021-02-17 07:01:08 +01:00
return {'responseHeaders': details.responseHeaders};
2020-05-02 13:56:49 +02:00
}
};
/**
* Initializations
*/
2021-02-17 07:01:08 +01:00
/* eslint-disable one-var */
let cdnDomainsRE = new RegExp(`//(${Object.keys(mappings.cdn).map((m) => m.replace(/\W/g, '\\$&')).join('|')})/`);
/* eslint-enable one-var */
2020-05-02 13:56:49 +02:00
/**
2021-02-17 07:01:08 +01:00
* Event Handlers
*/
2020-05-02 13:56:49 +02:00
chrome.webRequest.onHeadersReceived.addListener(
manipulateDOM._removeCrossOriginAndIntegrityAttr,
{'types': [WebRequestType.MAIN_FRAME], 'urls': [Address.ANY]},
2020-05-19 17:26:16 +02:00
[WebRequest.BLOCKING, WebRequest.RESPONSE_HEADERS]
2020-05-02 13:56:49 +02:00
);