/******************************************************************************* µMatrix - a Chromium browser extension to black/white list requests. Copyright (C) 2014 Raymond Hill 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://github.com/gorhill/uMatrix */ /* jshint boss: true */ /* global chrome, µMatrix, punycode, publicSuffixList */ /******************************************************************************/ µMatrix.getBytesInUse = function() { var µm = this; var getBytesInUseHandler = function(bytesInUse) { µm.storageUsed = bytesInUse; }; vAPI.storage.getBytesInUse(null, getBytesInUseHandler); }; /******************************************************************************/ µMatrix.saveUserSettings = function() { this.XAL.keyvalSetMany( this.userSettings, this.getBytesInUse.bind(this) ); }; /******************************************************************************/ µMatrix.loadUserSettings = function(callback) { var µm = this; if ( typeof callback !== 'function' ) { callback = this.noopFunc; } var settingsLoaded = function(store) { // console.log('storage.js > loaded user settings'); µm.userSettings = store; // https://github.com/gorhill/httpswitchboard/issues/344 µm.userAgentSpoofer.shuffle(); callback(µm.userSettings); }; vAPI.storage.get(this.userSettings, settingsLoaded); }; /******************************************************************************/ // save white/blacklist µMatrix.saveMatrix = function() { µMatrix.XAL.keyvalSetOne('userMatrix', this.pMatrix.toString()); }; /******************************************************************************/ µMatrix.loadMatrix = function(callback) { if ( typeof callback !== 'function' ) { callback = this.noopFunc; } var µm = this; var onLoaded = function(bin) { if ( bin.hasOwnProperty('userMatrix') ) { µm.pMatrix.fromString(bin.userMatrix); µm.tMatrix.assign(µm.pMatrix); callback(); } }; this.XAL.keyvalGetOne('userMatrix', onLoaded); }; /******************************************************************************/ µMatrix.getAvailableHostsFiles = function(callback) { var availableHostsFiles = {}; var redirections = {}; var µm = this; var fixLocation = function(location) { // https://github.com/chrisaljoudi/uBlock/issues/418 // We now support built-in external filter lists if ( /^https?:/.test(location) === false ) { location = 'assets/thirdparties/' + location; } return location; }; // selected lists var onSelectedHostsFilesLoaded = function(store) { var lists = store.liveHostsFiles; var locations = Object.keys(lists); var oldLocation, newLocation; var availableEntry, storedEntry; while ( oldLocation = locations.pop() ) { newLocation = redirections[oldLocation] || oldLocation; availableEntry = availableHostsFiles[newLocation]; if ( availableEntry === undefined ) { continue; } storedEntry = lists[oldLocation] || {}; availableEntry.off = storedEntry.off || false; µm.assets.setHomeURL(newLocation, availableEntry.homeURL); if ( storedEntry.entryCount !== undefined ) { availableEntry.entryCount = storedEntry.entryCount; } if ( storedEntry.entryUsedCount !== undefined ) { availableEntry.entryUsedCount = storedEntry.entryUsedCount; } // This may happen if the list name was pulled from the list content if ( availableEntry.title === '' && storedEntry.title !== undefined ) { availableEntry.title = storedEntry.title; } } callback(availableHostsFiles); }; // built-in lists var onBuiltinHostsFilesLoaded = function(details) { var location, locations; try { locations = JSON.parse(details.content); } catch (e) { locations = {}; } var hostsFileEntry; for ( location in locations ) { if ( locations.hasOwnProperty(location) === false ) { continue; } hostsFileEntry = locations[location]; availableHostsFiles[fixLocation(location)] = hostsFileEntry; } // Now get user's selection of lists vAPI.storage.get( { 'liveHostsFiles': availableHostsFiles }, onSelectedHostsFilesLoaded ); }; // permanent hosts files var location; var lists = this.permanentHostsFiles; for ( location in lists ) { if ( lists.hasOwnProperty(location) === false ) { continue; } availableHostsFiles[location] = lists[location]; } // custom lists var c; var locations = this.userSettings.externalHostsFiles.split('\n'); for ( var i = 0; i < locations.length; i++ ) { location = locations[i].trim(); c = location.charAt(0); if ( location === '' || c === '!' || c === '#' ) { continue; } // Coarse validation if ( /[^0-9A-Za-z!*'();:@&=+$,\/?%#\[\]_.~-]/.test(location) ) { continue; } availableHostsFiles[location] = { title: location, external: true }; } // get built-in block lists. this.assets.get('assets/umatrix/hosts-files.json', onBuiltinHostsFilesLoaded); }; /******************************************************************************/ µMatrix.loadHostsFiles = function(callback) { var µm = µMatrix; var hostsFileLoadCount; if ( typeof callback !== 'function' ) { callback = this.noopFunc; } var loadHostsFilesEnd = function() { µm.ubiquitousBlacklist.freeze(); vAPI.storage.set({ 'liveHostsFiles': µm.liveHostsFiles }); vAPI.messaging.broadcast({ what: 'loadHostsFilesCompleted' }); µm.getBytesInUse(); callback(); }; var mergeHostsFile = function(details) { µm.mergeHostsFile(details); hostsFileLoadCount -= 1; if ( hostsFileLoadCount === 0 ) { loadHostsFilesEnd(); } }; var loadHostsFilesStart = function(hostsFiles) { µm.liveHostsFiles = hostsFiles; µm.ubiquitousBlacklist.reset(); var locations = Object.keys(hostsFiles); hostsFileLoadCount = locations.length; // Load all hosts file which are not disabled. var location; while ( location = locations.pop() ) { if ( hostsFiles[location].off ) { hostsFileLoadCount -= 1; continue; } µm.assets.get(location, mergeHostsFile); } // https://github.com/gorhill/uMatrix/issues/2 if ( hostsFileLoadCount === 0 ) { loadHostsFilesEnd(); return; } }; this.getAvailableHostsFiles(loadHostsFilesStart); }; /******************************************************************************/ µMatrix.mergeHostsFile = function(details) { //console.log('storage.js > mergeHostsFile from "%s": "%s..."', details.path, details.content.slice(0, 40)); var usedCount = this.ubiquitousBlacklist.count; var duplicateCount = this.ubiquitousBlacklist.duplicateCount; this.mergeHostsFileContent(details.content); usedCount = this.ubiquitousBlacklist.count - usedCount; duplicateCount = this.ubiquitousBlacklist.duplicateCount - duplicateCount; var hostsFilesMeta = this.liveHostsFiles[details.path]; hostsFilesMeta.entryCount = usedCount + duplicateCount; hostsFilesMeta.entryUsedCount = usedCount; }; /******************************************************************************/ µMatrix.mergeHostsFileContent = function(rawText) { //console.log('storage.js > mergeHostsFileContent from "%s": "%s..."', details.path, details.content.slice(0, 40)); var rawEnd = rawText.length; var ubiquitousBlacklist = this.ubiquitousBlacklist; var reLocalhost = /(^|\s)(localhost\.localdomain|localhost|local|broadcasthost|0\.0\.0\.0|127\.0\.0\.1|::1|fe80::1%lo0)(?=\s|$)/g; var reAsciiSegment = /^[\x21-\x7e]+$/; var matches; var lineBeg = 0, lineEnd; var line; while ( lineBeg < rawEnd ) { lineEnd = rawText.indexOf('\n', lineBeg); if ( lineEnd < 0 ) { lineEnd = rawText.indexOf('\r', lineBeg); if ( lineEnd < 0 ) { lineEnd = rawEnd; } } // rhill 2014-04-18: The trim is important here, as without it there // could be a lingering `\r` which would cause problems in the // following parsing code. line = rawText.slice(lineBeg, lineEnd).trim(); lineBeg = lineEnd + 1; // https://github.com/gorhill/httpswitchboard/issues/15 // Ensure localhost et al. don't end up in the ubiquitous blacklist. line = line .replace(/#.*$/, '') .toLowerCase() .replace(reLocalhost, '') .trim(); // The filter is whatever sequence of printable ascii character without // whitespaces matches = reAsciiSegment.exec(line); if ( !matches || matches.length === 0 ) { continue; } // Bypass anomalies // For example, when a filter contains whitespace characters, or // whatever else outside the range of printable ascii characters. if ( matches[0] !== line ) { //console.error('"%s": "%s" !== "%s"', details.path, matches[0], line); continue; } line = matches[0]; if ( line === '' ) { continue; } ubiquitousBlacklist.add(line); } }; /******************************************************************************/ // `switches` contains the filter lists for which the switch must be revisited. µMatrix.selectHostsFiles = function(switches) { switches = switches || {}; // Only the lists referenced by the switches are touched. var liveHostsFiles = this.liveHostsFiles; var entry, state, location; var i = switches.length; while ( i-- ) { entry = switches[i]; state = entry.off === true; location = entry.location; if ( liveHostsFiles.hasOwnProperty(location) === false ) { if ( state !== true ) { liveHostsFiles[location] = { off: state }; } continue; } if ( liveHostsFiles[location].off === state ) { continue; } liveHostsFiles[location].off = state; } vAPI.storage.set({ 'liveHostsFiles': liveHostsFiles }); }; /******************************************************************************/ // `switches` contains the preset blacklists for which the switch must be // revisited. µMatrix.reloadHostsFiles = function() { var µm = this; // We are just reloading the filter lists: we do not want assets to update. this.assets.autoUpdate = false; var onHostsFilesReady = function() { µm.assets.autoUpdate = µm.userSettings.autoUpdate; }; this.loadHostsFiles(onHostsFilesReady); }; /******************************************************************************/ µMatrix.loadPublicSuffixList = function(callback) { if ( typeof callback !== 'function' ) { callback = this.noopFunc; } var applyPublicSuffixList = function(details) { if ( !details.error ) { publicSuffixList.parse(details.content, punycode.toASCII); } callback(); }; this.assets.get(this.pslPath, applyPublicSuffixList); }; /******************************************************************************/ µMatrix.updateStartHandler = function(callback) { var µm = this; var onListsReady = function(lists) { var assets = {}; for ( var location in lists ) { if ( lists.hasOwnProperty(location) === false ) { continue; } if ( lists[location].off ) { continue; } assets[location] = true; } assets[µm.pslPath] = true; callback(assets); }; this.getAvailableHostsFiles(onListsReady); }; /******************************************************************************/ µMatrix.assetUpdatedHandler = function(details) { var path = details.path || ''; if ( path !== '' ) { this.logger.writeOne('', 'info', vAPI.i18n('loggerEntryAssetUpdated').replace('{{value}}', path)); } if ( this.liveHostsFiles.hasOwnProperty(path) === false ) { return; } var entry = this.liveHostsFiles[path]; if ( entry.off ) { return; } // Compile the list while we have the raw version in memory //console.debug('µMatrix.getCompiledFilterList/onRawListLoaded: compiling "%s"', path); //this.assets.put( // this.getCompiledFilterListPath(path), // this.compileFilters(details.content) //); }; /******************************************************************************/ µMatrix.updateCompleteHandler = function(details) { var µm = this; var updatedCount = details.updatedCount; if ( updatedCount === 0 ) { return; } // Assets are supposed to have been all updated, prevent fetching from // remote servers. µm.assets.remoteFetchBarrier += 1; var onFiltersReady = function() { µm.assets.remoteFetchBarrier -= 1; }; var onPSLReady = function() { if ( updatedCount !== 0 ) { //console.debug('storage.js > µMatrix.updateCompleteHandler: reloading filter lists'); µm.loadHostsFiles(onFiltersReady); } else { onFiltersReady(); } }; if ( details.hasOwnProperty(this.pslPath) ) { //console.debug('storage.js > µMatrix.updateCompleteHandler: reloading PSL'); this.loadPublicSuffixList(onPSLReady); updatedCount -= 1; } else { onPSLReady(); } }; /******************************************************************************/ µMatrix.assetCacheRemovedHandler = (function() { var barrier = false; var handler = function(paths) { if ( barrier ) { return; } barrier = true; var i = paths.length; var path; while ( i-- ) { path = paths[i]; if ( this.liveHostsFiles.hasOwnProperty(path) ) { //console.debug('µMatrix.assetCacheRemovedHandler: decompiling "%s"', path); //this.purgeCompiledFilterList(path); continue; } if ( path === this.pslPath ) { //console.debug('µMatrix.assetCacheRemovedHandler: decompiling "%s"', path); //this.assets.purge('cache://compiled-publicsuffixlist'); continue; } } //this.destroySelfie(); barrier = false; }; return handler; })();