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-21 19:13:11 +01:00
|
|
|
if (details.statusCode === 200) {
|
2022-07-10 07:10:26 +02:00
|
|
|
let initiatorDomain, header;
|
2020-06-25 07:54:17 +02:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
initiatorDomain = helpers.extractDomainFromUrl(details.url, true) || Address.EXAMPLE;
|
2020-05-25 22:53:46 +02:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
// by Jaap (https://gitlab.com/Jaaap)
|
|
|
|
header = details.responseHeaders.find((h) => h.name.toLowerCase() === 'content-type');
|
2020-05-02 13:56:49 +02:00
|
|
|
|
2022-07-10 07:10:26 +02:00
|
|
|
if (header && manipulateDOM.checkHtmlFilterEnabled(initiatorDomain)) {
|
2020-05-02 13:56:49 +02:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
let mimeType, isAllowlisted;
|
2020-05-02 13:56:49 +02:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
mimeType = header.value.replace(/;.*/, '').toLowerCase();
|
|
|
|
isAllowlisted = stateManager._domainIsListed(initiatorDomain);
|
2020-05-02 13:56:49 +02:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
if (!isAllowlisted && mimeType === 'text/html') {
|
2020-05-02 13:56:49 +02:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
let asciiDecoder, decoder, encoder, charset, isFirstData, filter, data;
|
2020-05-19 17:26:16 +02:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
charset = (/charset\s*=/).test(header.value) && header.value.replace(/^.*?charset\s*=\s*/, '').replace(/["']?/g, '');
|
2020-05-22 07:19:01 +02:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
// Check if charset is supported by TextDecoder()
|
2021-11-28 09:28:38 +01:00
|
|
|
if ((/charset\s*=/).test(header.value) && !EncodingTypes[charset.toString().toLowerCase()]) {
|
2021-02-21 19:13:11 +01:00
|
|
|
console.error(`[ LocalCDN ] Unsupported charset: ${charset}`);
|
2021-02-21 19:51:47 +01:00
|
|
|
log.append(details.url, '-', `Unsupported charset: ${charset}`, true);
|
2021-02-21 19:13:11 +01:00
|
|
|
return;
|
|
|
|
}
|
2020-05-22 07:19:01 +02:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
asciiDecoder = new TextDecoder('ASCII');
|
|
|
|
encoder = new TextEncoder();
|
|
|
|
isFirstData = true;
|
|
|
|
filter = browser.webRequest.filterResponseData(details.requestId);
|
|
|
|
data = [];
|
2020-05-19 17:26:16 +02:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
header.value = 'text/html; charset=UTF-8';
|
2020-05-02 13:56:49 +02:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
// NOTE: should work if 'script' string is divided into two chunks
|
|
|
|
filter.ondata = (evt) => {
|
|
|
|
if (isFirstData) {
|
|
|
|
if (!charset) {
|
|
|
|
// content-type has no charset declared
|
|
|
|
let htmlHead, charsetMatch;
|
2021-02-17 07:01:08 +01:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
htmlHead = asciiDecoder.decode(evt.data, {'stream': false});
|
|
|
|
// eslint-disable-next-line no-useless-escape
|
2021-02-28 18:08:07 +01:00
|
|
|
charsetMatch = htmlHead.match(/<meta\s+charset=["']?([^>"'\/]+)["'>\/]/i);
|
|
|
|
if (charsetMatch === null) {
|
|
|
|
// eslint-disable-next-line no-useless-escape
|
2021-04-01 07:04:47 +02:00
|
|
|
charsetMatch = htmlHead.match(/<meta.*charset=["']?([^>"'\/]+)["'].*[>\/]/i) || 'utf8';
|
2021-02-28 18:08:07 +01:00
|
|
|
}
|
|
|
|
|
2023-09-07 06:06:25 +02:00
|
|
|
if (EncodingTypes[charsetMatch[1].toLowerCase().trim()] !== undefined) {
|
|
|
|
charset = charsetMatch[1].trim();
|
2021-02-28 18:08:07 +01:00
|
|
|
} else {
|
2021-07-13 06:26:45 +02:00
|
|
|
// If charset is unclear, then use ASCII by default.
|
|
|
|
// Other charsets are mostly tagged in the header or HTML source code.
|
|
|
|
// https://codeberg.org/nobody/LocalCDN/issues/567
|
|
|
|
charset = 'ASCII';
|
2021-02-28 18:08:07 +01:00
|
|
|
}
|
2021-02-21 19:13:11 +01:00
|
|
|
}
|
|
|
|
decoder = new TextDecoder(charset);
|
2020-05-19 17:26:16 +02:00
|
|
|
}
|
2021-02-21 19:13:11 +01:00
|
|
|
isFirstData = false;
|
2021-01-30 06:45:21 +01:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
data.push(evt.data);
|
|
|
|
};
|
2021-01-30 06:45:21 +01:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
filter.onstop = () => {
|
|
|
|
let str = '';
|
|
|
|
for (let buffer of data) {
|
|
|
|
str += decoder.decode(buffer, {'stream': true});
|
2020-05-19 17:26:16 +02:00
|
|
|
}
|
2021-02-21 19:13:11 +01:00
|
|
|
str += decoder.decode(); // end-of-stream
|
|
|
|
|
2021-10-23 10:08:13 +02:00
|
|
|
// set UTF-8 in document
|
2021-11-24 07:22:11 +01:00
|
|
|
str = manipulateDOM._searchCharset(str, charset);
|
2021-10-23 10:08:13 +02:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
// remove crossorigin and integrity attributes
|
|
|
|
str = str.replace(/<(link|script)[^>]+>/ig, (m) => {
|
|
|
|
// eslint-disable-next-line no-use-before-define
|
|
|
|
if (cdnDomainsRE.test(m)) {
|
2022-02-06 08:06:07 +01:00
|
|
|
return m.replace(/\s+(integrity|crossorigin)(="[^"]*"|='[^']*'|=[^"'`=>\s]+=?|)/ig, '');
|
2021-02-21 19:13:11 +01:00
|
|
|
}
|
|
|
|
return m;
|
|
|
|
});
|
|
|
|
filter.write(encoder.encode(str));
|
|
|
|
filter.close();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return {'responseHeaders': details.responseHeaders};
|
2020-05-02 13:56:49 +02:00
|
|
|
|
2021-02-21 19:13:11 +01:00
|
|
|
}
|
2020-05-02 13:56:49 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-11-24 07:22:11 +01:00
|
|
|
manipulateDOM._searchCharset = function (str, charset) {
|
|
|
|
if (str.indexOf(`charset="${charset}"`) > 0) {
|
|
|
|
return str.replace(`charset="${charset}"`, 'charset="utf8"');
|
2021-12-05 07:30:31 +01:00
|
|
|
}
|
|
|
|
if (str.indexOf(`charset='${charset}'`) > 0) {
|
2021-11-24 07:22:11 +01:00
|
|
|
return str.replace(`charset='${charset}'`, 'charset=\'utf8\'');
|
2021-12-05 07:30:31 +01:00
|
|
|
}
|
|
|
|
if (str.indexOf(`charset=${charset}`) > 0) {
|
2021-11-24 07:22:11 +01:00
|
|
|
return str.replace(`charset=${charset}`, 'charset=utf8');
|
|
|
|
}
|
2021-12-05 07:30:31 +01:00
|
|
|
return str;
|
2021-11-24 07:22:11 +01:00
|
|
|
};
|
2020-05-02 13:56:49 +02:00
|
|
|
|
2022-07-10 07:10:26 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Public Methods
|
|
|
|
*/
|
|
|
|
|
|
|
|
manipulateDOM.checkHtmlFilterEnabled = function (url) {
|
|
|
|
let listedToManipulateDOM, negateHtmlFilter;
|
|
|
|
listedToManipulateDOM = stateManager._domainIsListed(url, 'manipulate-dom');
|
|
|
|
negateHtmlFilter = stateManager.getInvertOption;
|
|
|
|
|
|
|
|
if ((negateHtmlFilter || listedToManipulateDOM) && !(negateHtmlFilter && listedToManipulateDOM)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
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
|
|
|
|
2023-02-09 06:10:56 +01:00
|
|
|
browser.webRequest.onHeadersReceived.addListener(
|
2020-05-02 13:56:49 +02:00
|
|
|
manipulateDOM._removeCrossOriginAndIntegrityAttr,
|
2021-05-03 06:44:52 +02:00
|
|
|
{'types': [WebRequestType.MAIN_FRAME, WebRequestType.SUB_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
|
|
|
);
|