/******************************************************************************* ηMatrix - a browser extension to black/white list requests. Copyright (C) 2014-2019 Raymond Hill Copyright (C) 2019-2020 Alessio Vanni This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see {http://www.gnu.org/licenses/}. Home: https://libregit.spks.xyz/heckyel/ematrix uMatrix Home: https://github.com/gorhill/uMatrix */ 'use strict'; /******************************************************************************/ /******************************************************************************/ // Default handler (function() { Cu.import('chrome://ematrix/content/lib/UriTools.jsm'); var ηm = ηMatrix; /******************************************************************************/ // Default is for commonly used message. function onMessage(request, sender, callback) { // Async switch ( request.what ) { case 'getAssetContent': ηm.assets.get(request.url, { dontCache: true }, callback); return; case 'selectHostsFiles': ηm.selectHostsFiles(request, callback); return; default: break; } // Sync var response; switch ( request.what ) { case 'forceReloadTab': ηm.forceReload(request.tabId, request.bypassCache); break; case 'forceUpdateAssets': ηm.scheduleAssetUpdater(0); ηm.assets.updateStart({ delay: 2000 }); break; case 'getUserSettings': response = { userSettings: ηm.userSettings, matrixSwitches: { 'https-strict': ηm.pMatrix.evaluateSwitch('https-strict', '*') === 1, 'referrer-spoof': ηm.pMatrix.evaluateSwitch('referrer-spoof', '*') === 1, 'noscript-spoof': ηm.pMatrix.evaluateSwitch('noscript-spoof', '*') === 1 } }; break; case 'gotoExtensionURL': ηm.gotoExtensionURL(request); break; case 'gotoURL': ηm.gotoURL(request); break; case 'mustBlock': response = ηm.mustBlock( request.scope, request.hostname, request.type ); break; case 'readRawSettings': response = ηm.stringFromRawSettings(); break; case 'reloadHostsFiles': ηm.reloadHostsFiles(); break; case 'setMatrixSwitch': ηm.tMatrix.setSwitch(request.switchName, '*', request.state); if ( ηm.pMatrix.setSwitch(request.switchName, '*', request.state) ) { ηm.saveMatrix(); } break; case 'userSettings': if ( request.hasOwnProperty('value') === false ) { request.value = undefined; } response = ηm.changeUserSettings(request.name, request.value); break; case 'writeRawSettings': ηm.rawSettingsFromString(request.content); break; default: return vAPI.messaging.UNHANDLED; } callback(response); } /******************************************************************************/ vAPI.messaging.setup(onMessage); /******************************************************************************/ })(); /******************************************************************************/ /******************************************************************************/ (function() { // popup.js var ηm = ηMatrix; /******************************************************************************/ // Constructor is faster than object literal var RowSnapshot = function(srcHostname, desHostname, desDomain) { this.domain = desDomain; this.temporary = ηm.tMatrix.evaluateRowZXY(srcHostname, desHostname); this.permanent = ηm.pMatrix.evaluateRowZXY(srcHostname, desHostname); this.counts = RowSnapshot.counts.slice(); this.totals = RowSnapshot.counts.slice(); }; RowSnapshot.counts = (function() { var aa = []; for ( var i = 0, n = ηm.Matrix.columnHeaderIndices.size; i < n; i++ ) { aa[i] = 0; } return aa; })(); /******************************************************************************/ var matrixSnapshot = function(pageStore, details) { var ηmuser = ηm.userSettings; var headerIndices = ηm.Matrix.columnHeaderIndices; var r = { appVersion: vAPI.app.version, blockedCount: pageStore.requestStats.blocked.all, collapseAllDomains: ηmuser.popupCollapseAllDomains, collapseBlacklistedDomains: ηmuser.popupCollapseBlacklistedDomains, diff: [], domain: pageStore.pageDomain, has3pReferrer: pageStore.has3pReferrer, hasMixedContent: pageStore.hasMixedContent, hasNoscriptTags: pageStore.hasNoscriptTags, hasWebWorkers: pageStore.hasWebWorkers, headerIndices: Array.from(headerIndices), hostname: pageStore.pageHostname, mtxContentModified: pageStore.mtxContentModifiedTime !== details.mtxContentModifiedTime, mtxCountModified: pageStore.mtxCountModifiedTime !== details.mtxCountModifiedTime, mtxContentModifiedTime: pageStore.mtxContentModifiedTime, mtxCountModifiedTime: pageStore.mtxCountModifiedTime, pMatrixModified: ηm.pMatrix.modifiedTime !== details.pMatrixModifiedTime, pMatrixModifiedTime: ηm.pMatrix.modifiedTime, pSwitches: {}, rows: {}, rowCount: 0, scope: '*', tabId: pageStore.tabId, tMatrixModified: ηm.tMatrix.modifiedTime !== details.tMatrixModifiedTime, tMatrixModifiedTime: ηm.tMatrix.modifiedTime, tSwitches: {}, url: pageStore.pageUrl, userSettings: { colorBlindFriendly: ηmuser.colorBlindFriendly, displayTextSize: ηmuser.displayTextSize, popupScopeLevel: ηmuser.popupScopeLevel } }; if ( typeof details.scope === 'string' ) { r.scope = details.scope; } else if ( ηmuser.popupScopeLevel === 'site' ) { r.scope = r.hostname; } else if ( ηmuser.popupScopeLevel === 'domain' ) { r.scope = r.domain; } for ( var switchName of ηm.Matrix.switchNames ) { r.tSwitches[switchName] = ηm.tMatrix.evaluateSwitchZ(switchName, r.scope); r.pSwitches[switchName] = ηm.pMatrix.evaluateSwitchZ(switchName, r.scope); } // These rows always exist r.rows['*'] = new RowSnapshot(r.scope, '*', '*'); r.rows['1st-party'] = new RowSnapshot(r.scope, '1st-party', '1st-party'); r.rowCount += 1; var reqType, reqHostname, reqDomain; var desHostname; var row, typeIndex; var anyIndex = headerIndices.get('*'); var pos, count; for ( var entry of pageStore.hostnameTypeCells ) { pos = entry[0].indexOf(' '); reqHostname = entry[0].slice(0, pos); reqType = entry[0].slice(pos + 1); // rhill 2013-10-23: hostname can be empty if the request is a data url // https://github.com/gorhill/httpswitchboard/issues/26 if ( reqHostname === '' ) { reqHostname = pageStore.pageHostname; } reqDomain = UriTools.domainFromHostname(reqHostname) || reqHostname; // We want rows of self and ancestors desHostname = reqHostname; for (;;) { // If row exists, ancestors exist if ( r.rows.hasOwnProperty(desHostname) !== false ) { break; } r.rows[desHostname] = new RowSnapshot(r.scope, desHostname, reqDomain); r.rowCount += 1; if ( desHostname === reqDomain ) { break; } pos = desHostname.indexOf('.'); if ( pos === -1 ) { break; } desHostname = desHostname.slice(pos + 1); } count = entry[1].size; typeIndex = headerIndices.get(reqType); row = r.rows[reqHostname]; row.counts[typeIndex] += count; row.counts[anyIndex] += count; row = r.rows[reqDomain]; row.totals[typeIndex] += count; row.totals[anyIndex] += count; row = r.rows['*']; row.totals[typeIndex] += count; row.totals[anyIndex] += count; } r.diff = ηm.tMatrix.diff(ηm.pMatrix, r.hostname, Object.keys(r.rows)); return r; }; /******************************************************************************/ var matrixSnapshotFromTabId = function(details, callback) { var matrixSnapshotIf = function(tabId, details) { var pageStore = ηm.pageStoreFromTabId(tabId); if ( pageStore === null ) { callback('ENOTFOUND'); return; } // First verify whether we must return data or not. if ( ηm.tMatrix.modifiedTime === details.tMatrixModifiedTime && ηm.pMatrix.modifiedTime === details.pMatrixModifiedTime && pageStore.mtxContentModifiedTime === details.mtxContentModifiedTime && pageStore.mtxCountModifiedTime === details.mtxCountModifiedTime ) { callback('ENOCHANGE'); return ; } callback(matrixSnapshot(pageStore, details)); }; // Specific tab id requested? if ( details.tabId ) { matrixSnapshotIf(details.tabId, details); return; } // Fall back to currently active tab var onTabReady = function(tab) { if ( tab instanceof Object === false ) { callback('ENOTFOUND'); return; } // Allow examination of behind-the-scene requests var tabId = tab.url.lastIndexOf(vAPI.getURL('dashboard.html'), 0) !== 0 ? tab.id : vAPI.noTabId; matrixSnapshotIf(tabId, details); }; vAPI.tabs.get(null, onTabReady); }; /******************************************************************************/ var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { case 'matrixSnapshot': matrixSnapshotFromTabId(request, callback); return; default: break; } // Sync var response; switch ( request.what ) { case 'toggleMatrixSwitch': ηm.tMatrix.setSwitchZ( request.switchName, request.srcHostname, ηm.tMatrix.evaluateSwitchZ(request.switchName, request.srcHostname) === false ); break; case 'blacklistMatrixCell': ηm.tMatrix.blacklistCell( request.srcHostname, request.desHostname, request.type ); break; case 'whitelistMatrixCell': ηm.tMatrix.whitelistCell( request.srcHostname, request.desHostname, request.type ); break; case 'graylistMatrixCell': ηm.tMatrix.graylistCell( request.srcHostname, request.desHostname, request.type ); break; case 'applyDiffToPermanentMatrix': // aka "persist" if ( ηm.pMatrix.applyDiff(request.diff, ηm.tMatrix) ) { ηm.saveMatrix(); } break; case 'applyDiffToTemporaryMatrix': // aka "revert" ηm.tMatrix.applyDiff(request.diff, ηm.pMatrix); break; case 'revertTemporaryMatrix': ηm.tMatrix.assign(ηm.pMatrix); break; default: return vAPI.messaging.UNHANDLED; } callback(response); }; vAPI.messaging.listen('popup.js', onMessage); })(); /******************************************************************************/ /******************************************************************************/ // content scripts (function() { var ηm = ηMatrix; /******************************************************************************/ var foundInlineCode = function(tabId, pageStore, details, type) { if ( pageStore === null ) { return; } let pageHostname = pageStore.pageHostname; let ηmuri = UriTools.set(details.documentURI); let frameURL = UriTools.normalizedURI(); var blocked = details.blocked; if ( blocked === undefined ) { blocked = ηm.mustBlock(pageHostname, ηmuri.hostname, type); } var mapTo = { css: 'style', script: 'script' }; // https://github.com/gorhill/httpswitchboard/issues/333 // Look-up here whether inline scripting is blocked for the frame. var url = frameURL + '{inline_' + mapTo[type] + '}'; pageStore.recordRequest(type, url, blocked); ηm.logger.writeOne(tabId, 'net', pageHostname, url, type, blocked); }; /******************************************************************************/ var contentScriptLocalStorageHandler = function(tabId, originURL) { var tabContext = ηm.tabContextManager.lookup(tabId); if ( tabContext === null ) { return; } var blocked = ηm.mustBlock( tabContext.rootHostname, UriTools.hostnameFromURI(originURL), 'cookie' ); var pageStore = ηm.pageStoreFromTabId(tabId); if ( pageStore !== null ) { var requestURL = originURL + '/{localStorage}'; pageStore.recordRequest('cookie', requestURL, blocked); ηm.logger.writeOne(tabId, 'net', tabContext.rootHostname, requestURL, 'cookie', blocked); } var removeStorage = blocked && ηm.userSettings.deleteLocalStorage; if ( removeStorage ) { ηm.localStorageRemovedCounter++; } return removeStorage; }; /******************************************************************************/ // Evaluate many URLs against the matrix. var lookupBlockedCollapsibles = function(tabId, requests) { if ( placeholdersReadTime < ηm.rawSettingsWriteTime ) { placeholders = undefined; } if ( placeholders === undefined ) { placeholders = { frame: ηm.rawSettings.framePlaceholder, image: ηm.rawSettings.imagePlaceholder }; if ( placeholders.frame ) { placeholders.frameDocument = ηm.rawSettings.framePlaceholderDocument.replace( '{{bg}}', ηm.rawSettings.framePlaceholderBackground !== 'default' ? ηm.rawSettings.framePlaceholderBackground : ηm.rawSettings.placeholderBackground ); } if ( placeholders.image ) { placeholders.imageBorder = ηm.rawSettings.imagePlaceholderBorder !== 'default' ? ηm.rawSettings.imagePlaceholderBorder : ηm.rawSettings.placeholderBorder; placeholders.imageBackground = ηm.rawSettings.imagePlaceholderBackground !== 'default' ? ηm.rawSettings.imagePlaceholderBackground : ηm.rawSettings.placeholderBackground; } placeholdersReadTime = Date.now(); } var response = { blockedResources: [], hash: requests.hash, id: requests.id, placeholders: placeholders }; var tabContext = ηm.tabContextManager.lookup(tabId); if ( tabContext === null ) { return response; } var pageStore = ηm.pageStoreFromTabId(tabId); if ( pageStore !== null ) { pageStore.lookupBlockedCollapsibles(requests, response); } return response; }; var placeholders, placeholdersReadTime = 0; /******************************************************************************/ var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { default: break; } var tabId = sender && sender.tab ? sender.tab.id || 0 : 0, tabContext = ηm.tabContextManager.lookup(tabId), rootHostname = tabContext && tabContext.rootHostname, pageStore = ηm.pageStoreFromTabId(tabId); // Sync var response; switch ( request.what ) { case 'contentScriptHasLocalStorage': response = contentScriptLocalStorageHandler(tabId, request.originURL); break; case 'lookupBlockedCollapsibles': response = lookupBlockedCollapsibles(tabId, request); break; case 'mustRenderNoscriptTags?': if ( tabContext === null ) { break; } response = ηm.tMatrix.mustBlock(rootHostname, rootHostname, 'script') && ηm.tMatrix.evaluateSwitchZ('noscript-spoof', rootHostname); if ( pageStore !== null ) { pageStore.hasNoscriptTags = true; } // https://github.com/gorhill/uMatrix/issues/225 // A good place to force an update of the page title, as at // this point the DOM has been loaded. ηm.updateTitle(tabId); break; case 'securityPolicyViolation': if ( request.directive === 'worker-src' ) { var url = UriTools.hostnameFromURI(request.blockedURI) !== '' ? request.blockedURI : request.documentURI; if ( pageStore !== null ) { pageStore.hasWebWorkers = true; pageStore.recordRequest('script', url, request.blocked); } if ( tabContext !== null ) { ηm.logger.writeOne(tabId, 'net', rootHostname, url, 'worker', request.blocked); } } else if ( request.directive === 'script-src' ) { foundInlineCode(tabId, pageStore, request, 'script'); } else if ( request.directive === 'style-src' ) { foundInlineCode(tabId, pageStore, request, 'css'); } break; case 'shutdown?': if ( tabContext !== null ) { response = ηm.tMatrix.evaluateSwitchZ('matrix-off', rootHostname); } break; default: return vAPI.messaging.UNHANDLED; } callback(response); }; vAPI.messaging.listen('contentscript.js', onMessage); /******************************************************************************/ })(); /******************************************************************************/ /******************************************************************************/ // cloud-ui.js (function() { /******************************************************************************/ var ηm = ηMatrix; /******************************************************************************/ var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { case 'cloudGetOptions': vAPI.cloud.getOptions(function(options) { options.enabled = ηm.userSettings.cloudStorageEnabled === true; callback(options); }); return; case 'cloudSetOptions': vAPI.cloud.setOptions(request.options, callback); return; case 'cloudPull': return vAPI.cloud.pull(request.datakey, callback); case 'cloudPush': return vAPI.cloud.push(request.datakey, request.data, callback); default: break; } // Sync var response; switch ( request.what ) { // For when cloud storage is disabled. case 'cloudPull': // fallthrough case 'cloudPush': break; default: return vAPI.messaging.UNHANDLED; } callback(response); }; /******************************************************************************/ vAPI.messaging.listen('cloud-ui.js', onMessage); })(); /******************************************************************************/ /******************************************************************************/ // user-rules.js (function() { var ηm = ηMatrix; /******************************************************************************/ var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { default: break; } // Sync var response; switch ( request.what ) { case 'getUserRules': response = { temporaryRules: ηm.tMatrix.toString(), permanentRules: ηm.pMatrix.toString() }; break; case 'setUserRules': if ( typeof request.temporaryRules === 'string' ) { ηm.tMatrix.fromString(request.temporaryRules); } if ( typeof request.permanentRules === 'string' ) { ηm.pMatrix.fromString(request.permanentRules); ηm.saveMatrix(); } response = { temporaryRules: ηm.tMatrix.toString(), permanentRules: ηm.pMatrix.toString() }; break; default: return vAPI.messaging.UNHANDLED; } callback(response); }; vAPI.messaging.listen('user-rules.js', onMessage); })(); /******************************************************************************/ /******************************************************************************/ // hosts-files.js (function() { var ηm = ηMatrix; /******************************************************************************/ var prepEntries = function(entries) { var ηmuri = UriTools; var entry; for ( var k in entries ) { if ( entries.hasOwnProperty(k) === false ) { continue; } entry = entries[k]; if ( typeof entry.homeURL === 'string' ) { entry.homeHostname = ηmuri.hostnameFromURI(entry.homeURL); entry.homeDomain = ηmuri.domainFromHostname(entry.homeHostname); } } }; /******************************************************************************/ var getLists = function(callback) { var r = { autoUpdate: ηm.userSettings.autoUpdate, available: null, cache: null, current: ηm.liveHostsFiles, blockedHostnameCount: ηm.ubiquitousBlacklist.count }; var onMetadataReady = function(entries) { r.cache = entries; prepEntries(r.cache); callback(r); }; var onAvailableHostsFilesReady = function(lists) { r.available = lists; prepEntries(r.available); ηm.assets.metadata(onMetadataReady); }; ηm.getAvailableHostsFiles(onAvailableHostsFilesReady); }; /******************************************************************************/ var onMessage = function(request, sender, callback) { var ηm = ηMatrix; // Async switch ( request.what ) { case 'getLists': return getLists(callback); default: break; } // Sync var response; switch ( request.what ) { case 'purgeCache': ηm.assets.purge(request.assetKey); ηm.assets.remove('compiled/' + request.assetKey); break; case 'purgeAllCaches': if ( request.hard ) { ηm.assets.remove(/./); } else { ηm.assets.purge(/./, 'public_suffix_list.dat'); } break; default: return vAPI.messaging.UNHANDLED; } callback(response); }; vAPI.messaging.listen('hosts-files.js', onMessage); })(); /******************************************************************************/ /******************************************************************************/ // about.js (function() { var ηm = ηMatrix; /******************************************************************************/ var restoreUserData = function(userData) { var countdown = 4; var onCountdown = function() { countdown -= 1; if ( countdown === 0 ) { vAPI.app.restart(); } }; var onAllRemoved = function() { vAPI.storage.set(userData.settings, onCountdown); vAPI.storage.set({ userMatrix: userData.rules }, onCountdown); vAPI.storage.set({ liveHostsFiles: userData.hostsFiles }, onCountdown); if ( userData.rawSettings instanceof Object ) { ηMatrix.saveRawSettings(userData.rawSettings, onCountdown); } }; // If we are going to restore all, might as well wipe out clean local // storage ηm.XAL.keyvalRemoveAll(onAllRemoved); }; /******************************************************************************/ var resetUserData = function() { var onAllRemoved = function() { vAPI.app.restart(); }; ηm.XAL.keyvalRemoveAll(onAllRemoved); }; /******************************************************************************/ var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { default: break; } // Sync var response; switch ( request.what ) { case 'getAllUserData': response = { app: vAPI.app.name, version: vAPI.app.version, when: Date.now(), settings: ηm.userSettings, rules: ηm.pMatrix.toString(), hostsFiles: ηm.liveHostsFiles, rawSettings: ηm.rawSettings }; break; case 'getSomeStats': response = { version: vAPI.app.version, storageUsed: ηm.storageUsed }; break; case 'restoreAllUserData': restoreUserData(request.userData); break; case 'resetAllUserData': resetUserData(); break; default: return vAPI.messaging.UNHANDLED; } callback(response); }; vAPI.messaging.listen('about.js', onMessage); /******************************************************************************/ /******************************************************************************/ // logger-ui.js (function() { /******************************************************************************/ var ηm = ηMatrix, loggerURL = vAPI.getURL('logger-ui.html'); /******************************************************************************/ var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { default: break; } // Sync var response; switch ( request.what ) { case 'readMany': if ( ηm.logger.ownerId !== undefined && request.ownerId !== ηm.logger.ownerId ) { response = { unavailable: true }; break; } var tabIds = {}; for ( var tabId in ηm.pageStores ) { var pageStore = ηm.pageStoreFromTabId(tabId); if ( pageStore === null ) { continue; } if ( pageStore.rawUrl.startsWith(loggerURL) ) { continue; } tabIds[tabId] = pageStore.title || pageStore.rawUrl; } response = { colorBlind: false, entries: ηm.logger.readAll(request.ownerId), maxLoggedRequests: ηm.userSettings.maxLoggedRequests, noTabId: vAPI.noTabId, tabIds: tabIds, tabIdsToken: ηm.pageStoresToken }; break; case 'releaseView': if ( request.ownerId === ηm.logger.ownerId ) { ηm.logger.ownerId = undefined; } break; default: return vAPI.messaging.UNHANDLED; } callback(response); }; vAPI.messaging.listen('logger-ui.js', onMessage); /******************************************************************************/ })(); /******************************************************************************/ /******************************************************************************/ })(); /******************************************************************************/