uBlock/src/js/storage.js

1094 lines
36 KiB
JavaScript
Raw Normal View History

2014-06-24 00:42:43 +02:00
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2014-2016 Raymond Hill
2014-06-24 00:42:43 +02:00
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/uBlock
*/
/* global YaMD5, punycode, publicSuffixList */
'use strict';
2014-06-24 00:42:43 +02:00
/******************************************************************************/
2015-03-07 05:36:09 +01:00
µBlock.getBytesInUse = function(callback) {
if ( typeof callback !== 'function' ) {
callback = this.noopFunc;
}
2014-06-24 00:42:43 +02:00
var getBytesInUseHandler = function(bytesInUse) {
µBlock.storageUsed = bytesInUse;
2015-03-07 05:36:09 +01:00
callback(bytesInUse);
2014-06-24 00:42:43 +02:00
};
// Not all platforms implement this method.
if ( vAPI.storage.getBytesInUse instanceof Function ) {
vAPI.storage.getBytesInUse(null, getBytesInUseHandler);
} else {
callback();
}
2014-06-24 00:42:43 +02:00
};
/******************************************************************************/
2015-04-10 08:17:12 +02:00
µBlock.keyvalSetOne = function(key, val, callback) {
var bin = {};
bin[key] = val;
vAPI.storage.set(bin, callback || this.noopFunc);
};
/******************************************************************************/
2015-11-29 23:06:58 +01:00
µBlock.saveLocalSettings = (function() {
var saveAfter = 4 * 60 * 1000;
2014-06-24 00:42:43 +02:00
2015-11-29 23:06:58 +01:00
var save = function() {
2016-10-08 16:15:31 +02:00
this.localSettingsLastSaved = Date.now();
2015-11-29 23:06:58 +01:00
vAPI.storage.set(this.localSettings);
2015-11-29 23:57:55 +01:00
};
2014-06-24 00:42:43 +02:00
2015-11-29 23:06:58 +01:00
var onTimeout = function() {
var µb = µBlock;
2016-10-08 16:15:31 +02:00
if ( µb.localSettingsLastModified > µb.localSettingsLastSaved ) {
2015-11-29 23:57:55 +01:00
save.call(µb);
2015-11-29 23:06:58 +01:00
}
vAPI.setTimeout(onTimeout, saveAfter);
};
2014-06-24 00:42:43 +02:00
2015-11-29 23:06:58 +01:00
vAPI.setTimeout(onTimeout, saveAfter);
return save;
})();
2014-06-24 00:42:43 +02:00
/******************************************************************************/
µBlock.saveUserSettings = function() {
vAPI.storage.set(this.userSettings);
2014-06-24 00:42:43 +02:00
};
/******************************************************************************/
µBlock.savePermanentFirewallRules = function() {
2015-04-10 08:17:12 +02:00
this.keyvalSetOne('dynamicFilteringString', this.permanentFirewall.toString());
2015-03-27 18:00:55 +01:00
};
/******************************************************************************/
2015-05-21 20:15:17 +02:00
µBlock.savePermanentURLFilteringRules = function() {
this.keyvalSetOne('urlFilteringString', this.permanentURLFiltering.toString());
};
/******************************************************************************/
2015-03-27 18:00:55 +01:00
µBlock.saveHostnameSwitches = function() {
2015-04-10 08:17:12 +02:00
this.keyvalSetOne('hostnameSwitchesString', this.hnSwitches.toString());
};
/******************************************************************************/
µBlock.saveWhitelist = function() {
2015-08-11 21:29:14 +02:00
this.keyvalSetOne('netWhitelist', this.stringFromWhitelist(this.netWhitelist));
this.netWhitelistModifyTime = Date.now();
};
/******************************************************************************/
// This will remove all unused filter list entries from
// µBlock.remoteBlacklists`. This helps reduce the size of backup files.
2015-04-16 18:14:43 +02:00
µBlock.extractSelectedFilterLists = function(callback) {
var µb = this;
2015-04-16 18:14:43 +02:00
var onBuiltinListsLoaded = function(details) {
var builtin;
try {
builtin = JSON.parse(details.content);
} catch (e) {
builtin = {};
}
2015-04-16 18:14:43 +02:00
var result = JSON.parse(JSON.stringify(µb.remoteBlacklists));
2015-06-03 15:51:24 +02:00
var entry, builtinPath, defaultState;
2015-04-16 18:14:43 +02:00
for ( var path in result ) {
if ( result.hasOwnProperty(path) === false ) {
continue;
}
2015-06-03 15:51:24 +02:00
entry = result[path];
// https://github.com/gorhill/uBlock/issues/277
// uBlock's filter lists are always enabled by default, so we
// have to include in backup only those which are turned off.
if ( path.startsWith('assets/ublock/') ) {
2015-06-03 15:51:24 +02:00
if ( entry.off !== true ) {
delete result[path];
}
continue;
}
2015-04-16 18:14:43 +02:00
builtinPath = path.replace(/^assets\/thirdparties\//, '');
defaultState = builtin.hasOwnProperty(builtinPath) === false ||
builtin[builtinPath].off === true;
2015-06-03 15:51:24 +02:00
if ( entry.off === true && entry.off === defaultState ) {
2015-04-16 18:14:43 +02:00
delete result[path];
}
}
2015-04-16 18:14:43 +02:00
callback(result);
};
// https://github.com/gorhill/uBlock/issues/63
// Get built-in block lists: this will help us determine whether a
// specific list must be included in the result.
this.loadAndPatchStockFilterLists(onBuiltinListsLoaded);
};
/******************************************************************************/
2014-07-13 02:32:44 +02:00
µBlock.saveUserFilters = function(content, callback) {
2015-12-07 14:59:22 +01:00
// https://github.com/gorhill/uBlock/issues/1022
// Be sure to end with an empty line.
content = content.trim();
if ( content !== '' ) {
content += '\n';
}
2015-08-11 21:29:14 +02:00
this.assets.put(this.userFiltersPath, content, callback);
2014-07-13 02:32:44 +02:00
};
/******************************************************************************/
µBlock.loadUserFilters = function(callback) {
return this.assets.get(this.userFiltersPath, callback);
};
/******************************************************************************/
2015-04-27 00:31:51 +02:00
µBlock.appendUserFilters = function(filters) {
if ( filters.length === 0 ) {
2015-02-24 00:31:29 +01:00
return;
}
2014-09-08 23:46:58 +02:00
var µb = this;
2015-04-27 02:33:49 +02:00
var onSaved = function() {
2015-04-27 00:31:51 +02:00
var compiledFilters = µb.compileFilters(filters);
2015-02-24 00:31:29 +01:00
var snfe = µb.staticNetFilteringEngine;
var cfe = µb.cosmeticFilteringEngine;
var acceptedCount = snfe.acceptedCount + cfe.acceptedCount;
2016-03-17 18:56:21 +01:00
var discardedCount = snfe.discardedCount + cfe.discardedCount;
2016-02-17 15:28:20 +01:00
µb.applyCompiledFilters(compiledFilters, true);
2015-02-24 00:31:29 +01:00
var entry = µb.remoteBlacklists[µb.userFiltersPath];
2015-04-27 00:31:51 +02:00
var deltaEntryCount = snfe.acceptedCount + cfe.acceptedCount - acceptedCount;
2016-03-17 18:56:21 +01:00
var deltaEntryUsedCount = deltaEntryCount - (snfe.discardedCount + cfe.discardedCount - discardedCount);
2015-04-27 00:31:51 +02:00
entry.entryCount += deltaEntryCount;
entry.entryUsedCount += deltaEntryUsedCount;
vAPI.storage.set({ 'remoteBlacklists': µb.remoteBlacklists });
2015-02-24 00:31:29 +01:00
µb.staticNetFilteringEngine.freeze();
µb.redirectEngine.freeze();
2015-02-24 00:31:29 +01:00
µb.cosmeticFilteringEngine.freeze();
2015-11-29 23:06:58 +01:00
µb.selfieManager.create();
2015-02-24 00:31:29 +01:00
};
2014-07-13 02:32:44 +02:00
var onLoaded = function(details) {
if ( details.error ) {
return;
}
2015-04-07 03:26:05 +02:00
// https://github.com/chrisaljoudi/uBlock/issues/976
2015-03-10 04:00:52 +01:00
// If we reached this point, the filter quite probably needs to be
// added for sure: do not try to be too smart, trying to avoid
// duplicates at this point may lead to more issues.
2015-04-27 00:31:51 +02:00
µb.saveUserFilters(details.content.trim() + '\n\n' + filters.trim(), onSaved);
2014-07-13 02:32:44 +02:00
};
2014-09-08 23:46:58 +02:00
2015-02-24 00:31:29 +01:00
this.loadUserFilters(onLoaded);
2014-07-13 02:32:44 +02:00
};
/******************************************************************************/
2014-07-25 22:12:20 +02:00
µBlock.getAvailableLists = function(callback) {
var availableLists = {};
var relocationMap = {};
2014-06-24 00:42:43 +02:00
2015-02-25 22:51:04 +01:00
var fixLocation = function(location) {
2015-04-07 03:26:05 +02:00
// https://github.com/chrisaljoudi/uBlock/issues/418
2015-02-25 22:51:04 +01:00
// We now support built-in external filter lists
if ( /^https?:/.test(location) === false ) {
location = 'assets/thirdparties/' + location;
}
return location;
};
2014-07-25 22:12:20 +02:00
// selected lists
var onSelectedListsLoaded = function(store) {
var µb = µBlock;
2014-07-25 22:12:20 +02:00
var lists = store.remoteBlacklists;
var locations = Object.keys(lists);
var location, availableEntry, storedEntry;
var off;
2014-07-25 22:12:20 +02:00
while ( (location = locations.pop()) ) {
storedEntry = lists[location];
off = storedEntry.off === true;
// New location?
if ( relocationMap.hasOwnProperty(location) ) {
µb.purgeFilterList(location);
location = relocationMap[location];
if ( off && lists.hasOwnProperty(location) ) {
off = lists[location].off === true;
}
}
availableEntry = availableLists[location];
if ( availableEntry === undefined ) {
µb.purgeFilterList(location);
2014-07-25 22:12:20 +02:00
continue;
}
availableEntry.off = off;
if ( typeof availableEntry.homeURL === 'string' ) {
µb.assets.setHomeURL(location, availableEntry.homeURL);
}
if ( storedEntry.entryCount !== undefined ) {
availableEntry.entryCount = storedEntry.entryCount;
}
if ( storedEntry.entryUsedCount !== undefined ) {
availableEntry.entryUsedCount = storedEntry.entryUsedCount;
2014-07-25 22:12:20 +02:00
}
2015-03-11 04:46:18 +01:00
// This may happen if the list name was pulled from the list
// content.
2015-04-07 03:26:05 +02:00
// https://github.com/chrisaljoudi/uBlock/issues/982
2015-03-11 04:46:18 +01:00
// There is no guarantee the title was successfully extracted from
// the list content.
if ( availableEntry.title === '' &&
typeof storedEntry.title === 'string' &&
storedEntry.title !== ''
) {
availableEntry.title = storedEntry.title;
}
2014-06-24 00:42:43 +02:00
}
2016-01-03 19:58:25 +01:00
// https://github.com/gorhill/uBlock/issues/747
if ( µb.firstInstall ) {
µb.autoSelectFilterLists(availableLists);
}
2014-07-25 22:12:20 +02:00
callback(availableLists);
2014-06-24 00:42:43 +02:00
};
2014-07-25 22:12:20 +02:00
// built-in lists
var onBuiltinListsLoaded = function(details) {
var location, locations;
try {
locations = JSON.parse(details.content);
} catch (e) {
locations = {};
}
var entry;
2014-07-25 22:12:20 +02:00
for ( location in locations ) {
if ( locations.hasOwnProperty(location) === false ) {
continue;
}
entry = locations[location];
2015-02-25 22:51:04 +01:00
location = fixLocation(location);
// Migrate obsolete location to new location, if any
if ( typeof entry.oldLocation === 'string' ) {
2015-02-25 22:51:04 +01:00
entry.oldLocation = fixLocation(entry.oldLocation);
relocationMap[entry.oldLocation] = location;
}
availableLists[location] = entry;
2014-06-24 00:42:43 +02:00
}
2014-07-25 22:12:20 +02:00
// Now get user's selection of lists
vAPI.storage.get(
2014-07-25 22:12:20 +02:00
{ 'remoteBlacklists': availableLists },
onSelectedListsLoaded
2014-06-24 00:42:43 +02:00
);
};
2014-07-25 22:12:20 +02:00
// permanent lists
var location;
var lists = this.permanentLists;
for ( location in lists ) {
if ( lists.hasOwnProperty(location) === false ) {
continue;
}
availableLists[location] = lists[location];
}
// custom lists
var c;
var locations = this.userSettings.externalLists.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;
}
availableLists[location] = {
title: '',
group: 'custom',
external: true
};
}
// get built-in block lists.
this.loadAndPatchStockFilterLists(onBuiltinListsLoaded);
2014-07-25 22:12:20 +02:00
};
/******************************************************************************/
2016-01-03 19:58:25 +01:00
µBlock.autoSelectFilterLists = function(lists) {
var lang = self.navigator.language.slice(0, 2),
list;
for ( var path in lists ) {
if ( lists.hasOwnProperty(path) === false ) {
continue;
}
list = lists[path];
if ( list.off !== true ) {
continue;
}
if ( list.lang === lang ) {
list.off = false;
}
}
};
/******************************************************************************/
2015-02-24 00:31:29 +01:00
µBlock.createShortUniqueId = function(path) {
var md5 = YaMD5.hashStr(path);
return md5.slice(0, 4) + md5.slice(-4);
};
µBlock.createShortUniqueId.idLength = 8;
/******************************************************************************/
2016-01-03 19:58:25 +01:00
// This is used to be re-entrancy resistant.
µBlock.loadingFilterLists = false;
2014-09-08 23:46:58 +02:00
µBlock.loadFilterLists = function(callback) {
2016-01-03 19:58:25 +01:00
// Callers are expected to check this first.
if ( this.loadingFilterLists ) {
return;
}
this.loadingFilterLists = true;
2015-02-24 00:31:29 +01:00
//quickProfiler.start('µBlock.loadFilterLists()');
2014-07-25 22:12:20 +02:00
var µb = this;
2015-02-24 00:31:29 +01:00
var filterlistsCount = 0;
2014-07-25 22:12:20 +02:00
2014-09-08 23:46:58 +02:00
if ( typeof callback !== 'function' ) {
callback = this.noopFunc;
}
2015-03-11 19:52:20 +01:00
// Never fetch from remote servers when we load filter lists: this has to
// be as fast as possible.
µb.assets.remoteFetchBarrier += 1;
2015-02-24 00:31:29 +01:00
var onDone = function() {
2015-03-11 19:52:20 +01:00
// Remove barrier to remote fetching
µb.assets.remoteFetchBarrier -= 1;
µb.staticNetFilteringEngine.freeze();
2014-09-08 23:46:58 +02:00
µb.cosmeticFilteringEngine.freeze();
µb.redirectEngine.freeze();
vAPI.storage.set({ 'remoteBlacklists': µb.remoteBlacklists });
2015-02-24 19:48:03 +01:00
//quickProfiler.stop(0);
2016-09-24 20:36:08 +02:00
vAPI.messaging.broadcast({ what: 'staticFilteringDataChanged' });
2014-09-08 23:46:58 +02:00
callback();
2015-02-24 00:31:29 +01:00
2015-11-29 23:06:58 +01:00
µb.selfieManager.create();
2016-01-03 19:58:25 +01:00
µb.loadingFilterLists = false;
2014-07-25 22:12:20 +02:00
};
2015-02-24 00:31:29 +01:00
var applyCompiledFilters = function(path, compiled) {
var snfe = µb.staticNetFilteringEngine;
var cfe = µb.cosmeticFilteringEngine;
var acceptedCount = snfe.acceptedCount + cfe.acceptedCount;
2016-03-17 18:56:21 +01:00
var discardedCount = snfe.discardedCount + cfe.discardedCount;
2016-02-17 15:28:20 +01:00
µb.applyCompiledFilters(compiled, path === µb.userFiltersPath);
2015-02-24 00:31:29 +01:00
if ( µb.remoteBlacklists.hasOwnProperty(path) ) {
var entry = µb.remoteBlacklists[path];
entry.entryCount = snfe.acceptedCount + cfe.acceptedCount - acceptedCount;
2016-03-17 18:56:21 +01:00
entry.entryUsedCount = entry.entryCount - (snfe.discardedCount + cfe.discardedCount - discardedCount);
2014-06-24 00:42:43 +02:00
}
};
2015-02-24 00:31:29 +01:00
var onCompiledListLoaded = function(details) {
applyCompiledFilters(details.path, details.content);
filterlistsCount -= 1;
if ( filterlistsCount === 0 ) {
onDone();
}
};
var onFilterListsReady = function(lists) {
2014-07-25 22:12:20 +02:00
µb.remoteBlacklists = lists;
2015-02-24 00:31:29 +01:00
µb.redirectEngine.reset();
2014-09-08 23:46:58 +02:00
µb.cosmeticFilteringEngine.reset();
2015-02-24 00:31:29 +01:00
µb.staticNetFilteringEngine.reset();
2015-11-29 23:06:58 +01:00
µb.selfieManager.destroy();
µb.staticFilteringReverseLookup.resetLists();
2014-06-24 00:42:43 +02:00
2015-02-24 00:31:29 +01:00
// We need to build a complete list of assets to pull first: this is
// because it *may* happens that some load operations are synchronous:
// This happens for assets which do not exist, ot assets with no
// content.
var toLoad = [];
for ( var path in lists ) {
if ( lists.hasOwnProperty(path) === false ) {
2014-06-24 00:42:43 +02:00
continue;
}
2015-02-24 00:31:29 +01:00
if ( lists[path].off ) {
continue;
}
toLoad.push(path);
2015-02-06 07:20:04 +01:00
}
2015-02-24 00:31:29 +01:00
filterlistsCount = toLoad.length;
if ( filterlistsCount === 0 ) {
2016-01-03 19:58:25 +01:00
return onDone();
2015-02-24 00:31:29 +01:00
}
var i = toLoad.length;
while ( i-- ) {
µb.getCompiledFilterList(toLoad[i], onCompiledListLoaded);
2014-06-24 00:42:43 +02:00
}
};
2015-02-24 00:31:29 +01:00
this.getAvailableLists(onFilterListsReady);
this.loadRedirectResources();
2014-06-24 00:42:43 +02:00
};
/******************************************************************************/
2015-02-24 00:31:29 +01:00
µBlock.getCompiledFilterListPath = function(path) {
return 'cache://compiled-filter-list:' + this.createShortUniqueId(path);
};
/******************************************************************************/
2014-06-24 00:42:43 +02:00
2015-02-24 00:31:29 +01:00
µBlock.getCompiledFilterList = function(path, callback) {
var compiledPath = this.getCompiledFilterListPath(path);
var µb = this;
2014-06-24 00:42:43 +02:00
2015-02-24 00:31:29 +01:00
var onRawListLoaded = function(details) {
2015-06-08 14:09:08 +02:00
if ( details.content === '' ) {
callback(details);
return;
}
var listMeta = µb.remoteBlacklists[path];
// https://github.com/gorhill/uBlock/issues/313
// Always try to fetch the name if this is an external filter list.
if ( listMeta && (listMeta.title === '' || listMeta.group === 'custom') ) {
2015-06-08 14:09:08 +02:00
var matches = details.content.slice(0, 1024).match(/(?:^|\n)!\s*Title:([^\n]+)/i);
if ( matches !== null ) {
listMeta.title = matches[1].trim();
2015-02-25 00:29:58 +01:00
}
2015-02-24 00:31:29 +01:00
}
2015-06-08 14:09:08 +02:00
//console.debug('µBlock.getCompiledFilterList/onRawListLoaded: compiling "%s"', path);
details.content = µb.compileFilters(details.content);
µb.assets.put(compiledPath, details.content);
2015-02-24 00:31:29 +01:00
callback(details);
};
2014-09-25 21:44:18 +02:00
2015-02-24 00:31:29 +01:00
var onCompiledListLoaded = function(details) {
if ( details.content === '' ) {
//console.debug('µBlock.getCompiledFilterList/onCompiledListLoaded: no compiled version for "%s"', path);
µb.assets.get(path, onRawListLoaded);
return;
}
//console.debug('µBlock.getCompiledFilterList/onCompiledListLoaded: using compiled version for "%s"', path);
details.path = path;
callback(details);
};
2014-09-25 21:44:18 +02:00
2015-02-24 00:31:29 +01:00
this.assets.get(compiledPath, onCompiledListLoaded);
};
2015-02-24 00:31:29 +01:00
/******************************************************************************/
2015-02-24 00:31:29 +01:00
µBlock.purgeCompiledFilterList = function(path) {
this.assets.purge(this.getCompiledFilterListPath(path));
2014-09-25 21:44:18 +02:00
};
/******************************************************************************/
µBlock.purgeFilterList = function(path) {
this.purgeCompiledFilterList(path);
this.assets.purge(path);
};
/******************************************************************************/
2014-09-25 21:44:18 +02:00
2015-02-24 00:31:29 +01:00
µBlock.compileFilters = function(rawText) {
2014-09-25 21:44:18 +02:00
var rawEnd = rawText.length;
2015-02-24 00:31:29 +01:00
var compiledFilters = [];
2014-06-24 00:42:43 +02:00
// Useful references:
// https://adblockplus.org/en/filter-cheatsheet
// https://adblockplus.org/en/filters
var staticNetFilteringEngine = this.staticNetFilteringEngine;
2014-09-08 23:46:58 +02:00
var cosmeticFilteringEngine = this.cosmeticFilteringEngine;
2015-01-23 17:32:49 +01:00
var reIsWhitespaceChar = /\s/;
var reMaybeLocalIp = /^[\d:f]/;
var reIsLocalhostRedirect = /\s+(?:broadcasthost|local|localhost|localhost\.localdomain)(?=\s|$)/;
var reLocalIp = /^(?:0\.0\.0\.0|127\.0\.0\.1|::1|fe80::1%lo0)/;
2015-02-24 00:31:29 +01:00
2014-06-24 00:42:43 +02:00
var lineBeg = 0, lineEnd, currentLineBeg;
2015-01-23 17:32:49 +01:00
var line, lineRaw, c, pos;
2014-06-24 00:42:43 +02:00
while ( lineBeg < rawEnd ) {
lineEnd = rawText.indexOf('\n', lineBeg);
2014-09-25 21:44:18 +02:00
if ( lineEnd === -1 ) {
2014-06-24 00:42:43 +02:00
lineEnd = rawText.indexOf('\r', lineBeg);
2014-09-25 21:44:18 +02:00
if ( lineEnd === -1 ) {
2014-06-24 00:42:43 +02:00
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.
2015-01-23 17:32:49 +01:00
line = lineRaw = rawText.slice(lineBeg, lineEnd).trim();
2014-06-24 00:42:43 +02:00
currentLineBeg = lineBeg;
lineBeg = lineEnd + 1;
2015-01-23 17:32:49 +01:00
if ( line.length === 0 ) {
continue;
}
2014-06-24 00:42:43 +02:00
// Strip comments
c = line.charAt(0);
if ( c === '!' || c === '[' ) {
continue;
}
2014-09-25 21:44:18 +02:00
// Parse or skip cosmetic filters
2015-01-23 17:32:49 +01:00
// All cosmetic filters are caught here
2015-02-24 00:31:29 +01:00
if ( cosmeticFilteringEngine.compile(line, compiledFilters) ) {
2014-09-25 21:44:18 +02:00
continue;
2014-06-24 00:42:43 +02:00
}
2015-01-23 17:32:49 +01:00
// Whatever else is next can be assumed to not be a cosmetic filter
// Most comments start in first column
2014-06-24 00:42:43 +02:00
if ( c === '#' ) {
continue;
}
2015-01-23 17:32:49 +01:00
// Catch comments somewhere on the line
// Remove:
// ... #blah blah blah
// ... # blah blah blah
// Don't remove:
// ...#blah blah blah
// because some ABP filters uses the `#` character (URL fragment)
pos = line.indexOf('#');
if ( pos !== -1 && reIsWhitespaceChar.test(line.charAt(pos - 1)) ) {
line = line.slice(0, pos).trim();
}
2014-06-24 00:42:43 +02:00
// https://github.com/gorhill/httpswitchboard/issues/15
// Ensure localhost et al. don't end up in the ubiquitous blacklist.
2015-01-23 17:32:49 +01:00
// With hosts files, we need to remove local IP redirection
if ( reMaybeLocalIp.test(c) ) {
// Ignore hosts file redirect configuration
// 127.0.0.1 localhost
// 255.255.255.255 broadcasthost
if ( reIsLocalhostRedirect.test(line) ) {
continue;
}
line = line.replace(reLocalIp, '').trim();
}
2015-03-07 19:20:18 +01:00
2015-01-23 17:32:49 +01:00
if ( line.length === 0 ) {
continue;
}
2014-06-24 00:42:43 +02:00
2015-02-24 00:31:29 +01:00
staticNetFilteringEngine.compile(line, compiledFilters);
}
return compiledFilters.join('\n');
};
2014-06-24 00:42:43 +02:00
2015-02-24 00:31:29 +01:00
/******************************************************************************/
2014-06-24 00:42:43 +02:00
2016-02-17 15:28:20 +01:00
// https://github.com/gorhill/uBlock/issues/1395
2016-02-17 16:04:55 +01:00
// Added `firstparty` argument: to avoid discarding cosmetic filters when
2016-02-17 15:28:20 +01:00
// applying 1st-party filters.
µBlock.applyCompiledFilters = function(rawText, firstparty) {
2016-08-13 22:42:58 +02:00
var skipCosmetic = !firstparty && !this.userSettings.parseAllABPHideFilters,
skipGenericCosmetic = this.userSettings.ignoreGenericCosmeticFilters,
staticNetFilteringEngine = this.staticNetFilteringEngine,
cosmeticFilteringEngine = this.cosmeticFilteringEngine,
lineIter = new this.LineIterator(rawText);
while ( lineIter.eot() === false ) {
cosmeticFilteringEngine.fromCompiledContent(lineIter, skipGenericCosmetic, skipCosmetic);
staticNetFilteringEngine.fromCompiledContent(lineIter);
2014-06-24 00:42:43 +02:00
}
};
/******************************************************************************/
2015-02-24 00:31:29 +01:00
// `switches` contains the filter lists for which the switch must be revisited.
2014-06-24 00:42:43 +02:00
2015-03-11 04:46:18 +01:00
µBlock.selectFilterLists = function(switches) {
switches = switches || {};
// Only the lists referenced by the switches are touched.
var filterLists = this.remoteBlacklists;
var entry, state, location;
var i = switches.length;
while ( i-- ) {
entry = switches[i];
state = entry.off === true;
location = entry.location;
if ( filterLists.hasOwnProperty(location) === false ) {
if ( state !== true ) {
filterLists[location] = { off: state };
}
continue;
2015-02-24 00:31:29 +01:00
}
2015-03-11 04:46:18 +01:00
if ( filterLists[location].off === state ) {
continue;
}
filterLists[location].off = state;
}
2015-02-24 00:31:29 +01:00
2015-03-11 04:46:18 +01:00
vAPI.storage.set({ 'remoteBlacklists': filterLists });
};
2015-03-09 17:57:52 +01:00
2015-03-11 04:46:18 +01:00
/******************************************************************************/
2015-03-09 17:57:52 +01:00
2015-03-11 04:46:18 +01:00
// Plain reload of all filters.
2015-02-24 00:31:29 +01:00
2015-03-11 04:46:18 +01:00
µBlock.reloadAllFilters = function() {
var µb = this;
2015-02-24 00:31:29 +01:00
2015-03-11 04:46:18 +01:00
// We are just reloading the filter lists: we do not want assets to update.
2016-01-03 19:58:25 +01:00
// TODO: probably not needed anymore, since filter lists are now always
// loaded without update => see `µb.assets.remoteFetchBarrier`.
2015-03-11 04:46:18 +01:00
this.assets.autoUpdate = false;
var onFiltersReady = function() {
µb.assets.autoUpdate = µb.userSettings.autoUpdate;
2015-02-24 00:31:29 +01:00
};
2015-03-11 04:46:18 +01:00
this.loadFilterLists(onFiltersReady);
2014-06-24 00:42:43 +02:00
};
/******************************************************************************/
2015-11-24 05:34:03 +01:00
µBlock.loadRedirectResources = function(callback) {
var µb = this;
if ( typeof callback !== 'function' ) {
callback = this.noopFunc;
}
2015-11-24 05:34:03 +01:00
var onResourcesLoaded = function(details) {
if ( details.content !== '' ) {
2015-11-24 05:34:03 +01:00
µb.redirectEngine.resourcesFromString(details.content);
}
callback();
};
this.assets.get('assets/ublock/resources.txt', onResourcesLoaded);
};
/******************************************************************************/
µBlock.loadPublicSuffixList = function(callback) {
2015-02-24 00:31:29 +01:00
var µb = this;
var path = µb.pslPath;
var compiledPath = 'cache://compiled-publicsuffixlist';
2014-09-08 23:46:58 +02:00
if ( typeof callback !== 'function' ) {
callback = this.noopFunc;
}
2015-02-24 00:31:29 +01:00
var onRawListLoaded = function(details) {
if ( details.content !== '' ) {
//console.debug('µBlock.loadPublicSuffixList/onRawListLoaded: compiling "%s"', path);
2014-06-24 00:42:43 +02:00
publicSuffixList.parse(details.content, punycode.toASCII);
2015-02-24 00:31:29 +01:00
µb.assets.put(compiledPath, JSON.stringify(publicSuffixList.toSelfie()));
}
callback();
};
var onCompiledListLoaded = function(details) {
if ( details.content === '' ) {
//console.debug('µBlock.loadPublicSuffixList/onCompiledListLoaded: no compiled version for "%s"', path);
µb.assets.get(path, onRawListLoaded);
return;
2014-06-24 00:42:43 +02:00
}
2015-02-24 00:31:29 +01:00
//console.debug('µBlock.loadPublicSuffixList/onCompiledListLoaded: using compiled version for "%s"', path);
publicSuffixList.fromSelfie(JSON.parse(details.content));
2014-09-08 23:46:58 +02:00
callback();
2014-06-24 00:42:43 +02:00
};
2015-02-24 00:31:29 +01:00
this.assets.get(compiledPath, onCompiledListLoaded);
2014-06-24 00:42:43 +02:00
};
/******************************************************************************/
2014-09-08 23:46:58 +02:00
// This is to be sure the selfie is generated in a sane manner: the selfie will
// be generated if the user doesn't change his filter lists selection for
2014-09-08 23:46:58 +02:00
// some set time.
2014-09-25 21:44:18 +02:00
2015-11-29 23:06:58 +01:00
µBlock.selfieManager = (function() {
var µb = µBlock;
var timer = null;
2014-09-08 23:46:58 +02:00
2015-11-29 23:06:58 +01:00
var create = function() {
timer = null;
2014-09-08 23:46:58 +02:00
2015-11-29 23:06:58 +01:00
var selfie = {
magic: µb.systemSettings.selfieMagic,
publicSuffixList: publicSuffixList.toSelfie(),
filterLists: µb.remoteBlacklists,
staticNetFilteringEngine: µb.staticNetFilteringEngine.toSelfie(),
redirectEngine: µb.redirectEngine.toSelfie(),
cosmeticFilteringEngine: µb.cosmeticFilteringEngine.toSelfie()
};
vAPI.cacheStorage.set({ selfie: selfie });
2015-11-29 23:06:58 +01:00
};
var createAsync = function(after) {
if ( typeof after !== 'number' ) {
after = µb.selfieAfter;
}
if ( timer !== null ) {
clearTimeout(timer);
}
timer = vAPI.setTimeout(create, after);
};
var destroy = function() {
if ( timer !== null ) {
clearTimeout(timer);
timer = null;
}
vAPI.cacheStorage.remove('selfie');
2015-11-29 23:06:58 +01:00
};
return {
create: createAsync,
destroy: destroy
};
})();
2014-09-08 23:46:58 +02:00
/******************************************************************************/
2015-07-27 16:10:34 +02:00
// https://github.com/gorhill/uBlock/issues/531
// Overwrite user settings with admin settings if present.
//
// Admin settings match layout of a uBlock backup. Not all data is
// necessarily present, i.e. administrators may removed entries which
// values are left to the user's choice.
2015-10-21 17:53:03 +02:00
µBlock.restoreAdminSettings = function(callback) {
// Support for vAPI.adminStorage is optional (webext).
if ( vAPI.adminStorage instanceof Object === false ) {
callback();
return;
}
2015-10-21 17:53:03 +02:00
var onRead = function(json) {
var µb = µBlock;
var data;
if ( typeof json === 'string' && json !== '' ) {
try {
data = JSON.parse(json);
} catch (ex) {
console.error(ex);
}
2015-07-27 16:10:34 +02:00
}
2015-10-21 17:53:03 +02:00
if ( typeof data !== 'object' || data === null ) {
callback();
return;
}
2015-07-27 16:10:34 +02:00
2015-10-21 17:53:03 +02:00
var bin = {};
var binNotEmpty = false;
2015-07-27 16:10:34 +02:00
2015-10-21 17:53:03 +02:00
if ( typeof data.userSettings === 'object' ) {
for ( var name in µb.userSettings ) {
if ( µb.userSettings.hasOwnProperty(name) === false ) {
continue;
}
if ( data.userSettings.hasOwnProperty(name) === false ) {
continue;
}
bin[name] = data.userSettings[name];
binNotEmpty = true;
2015-07-27 16:10:34 +02:00
}
2015-10-21 17:53:03 +02:00
}
if ( typeof data.filterLists === 'object' ) {
bin.remoteBlacklists = data.filterLists;
2015-07-27 16:10:34 +02:00
binNotEmpty = true;
}
2015-10-21 17:53:03 +02:00
if ( typeof data.netWhitelist === 'string' ) {
bin.netWhitelist = data.netWhitelist;
binNotEmpty = true;
}
2015-07-27 16:10:34 +02:00
2015-10-21 17:53:03 +02:00
if ( typeof data.dynamicFilteringString === 'string' ) {
bin.dynamicFilteringString = data.dynamicFilteringString;
binNotEmpty = true;
}
2015-07-27 16:10:34 +02:00
2015-10-21 17:53:03 +02:00
if ( typeof data.urlFilteringString === 'string' ) {
bin.urlFilteringString = data.urlFilteringString;
binNotEmpty = true;
}
2015-07-27 16:10:34 +02:00
2015-10-21 17:53:03 +02:00
if ( typeof data.hostnameSwitchesString === 'string' ) {
bin.hostnameSwitchesString = data.hostnameSwitchesString;
binNotEmpty = true;
}
2015-07-27 16:10:34 +02:00
2015-10-21 17:53:03 +02:00
if ( binNotEmpty ) {
vAPI.storage.set(bin);
}
2015-07-27 16:10:34 +02:00
2015-10-21 17:53:03 +02:00
if ( typeof data.userFilters === 'string' ) {
2016-02-17 15:28:20 +01:00
µb.assets.put(µb.userFiltersPath, data.userFilters);
2015-10-21 17:53:03 +02:00
}
2015-07-27 16:10:34 +02:00
2015-10-21 17:53:03 +02:00
callback();
};
vAPI.adminStorage.getItem('adminSettings', onRead);
2015-07-27 16:10:34 +02:00
};
/******************************************************************************/
2015-03-11 16:05:13 +01:00
µBlock.updateStartHandler = function(callback) {
var µb = this;
2015-02-13 18:10:10 +01:00
var onListsReady = function(lists) {
2015-03-11 16:05:13 +01:00
var assets = {};
2015-02-13 18:10:10 +01:00
for ( var location in lists ) {
if ( lists.hasOwnProperty(location) === false ) {
continue;
}
if ( lists[location].off ) {
continue;
}
2015-03-11 16:05:13 +01:00
assets[location] = true;
2015-02-13 18:10:10 +01:00
}
2015-03-11 16:05:13 +01:00
assets[µb.pslPath] = true;
assets['assets/ublock/resources.txt'] = true;
2015-03-11 16:05:13 +01:00
callback(assets);
2014-09-08 23:46:58 +02:00
};
2015-03-11 16:05:13 +01:00
2015-02-13 18:10:10 +01:00
this.getAvailableLists(onListsReady);
};
2014-09-08 23:46:58 +02:00
2015-02-13 18:10:10 +01:00
/******************************************************************************/
2015-01-24 18:06:22 +01:00
2015-02-24 00:31:29 +01:00
µBlock.assetUpdatedHandler = function(details) {
var path = details.path || '';
if ( this.remoteBlacklists.hasOwnProperty(path) === false ) {
return;
}
var entry = this.remoteBlacklists[path];
if ( entry.off ) {
return;
}
// Compile the list while we have the raw version in memory
//console.debug('µBlock.getCompiledFilterList/onRawListLoaded: compiling "%s"', path);
this.assets.put(
this.getCompiledFilterListPath(path),
this.compileFilters(details.content)
);
};
/******************************************************************************/
2015-02-13 18:10:10 +01:00
µBlock.updateCompleteHandler = function(details) {
var µb = this;
var updatedCount = details.updatedCount;
2015-03-11 19:52:20 +01:00
// Assets are supposed to have been all updated, prevent fetching from
2015-02-13 18:10:10 +01:00
// remote servers.
2015-03-11 19:52:20 +01:00
µb.assets.remoteFetchBarrier += 1;
2014-09-08 23:46:58 +02:00
var onFiltersReady = function() {
2015-03-11 19:52:20 +01:00
µb.assets.remoteFetchBarrier -= 1;
2014-09-08 23:46:58 +02:00
};
var onPSLReady = function() {
2015-02-13 18:10:10 +01:00
if ( updatedCount !== 0 ) {
2015-02-15 13:16:31 +01:00
//console.debug('storage.js > µBlock.updateCompleteHandler: reloading filter lists');
2015-02-13 18:10:10 +01:00
µb.loadFilterLists(onFiltersReady);
} else {
2014-12-15 20:37:09 +01:00
onFiltersReady();
2014-09-08 23:46:58 +02:00
}
};
2015-02-13 18:10:10 +01:00
if ( details.hasOwnProperty(this.pslPath) ) {
2015-02-15 13:16:31 +01:00
//console.debug('storage.js > µBlock.updateCompleteHandler: reloading PSL');
2015-02-13 18:10:10 +01:00
this.loadPublicSuffixList(onPSLReady);
updatedCount -= 1;
} else {
onPSLReady();
}
2014-06-24 00:42:43 +02:00
};
2015-02-24 00:31:29 +01:00
/******************************************************************************/
µBlock.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.remoteBlacklists.hasOwnProperty(path) ) {
//console.debug('µBlock.assetCacheRemovedHandler: decompiling "%s"', path);
this.purgeCompiledFilterList(path);
continue;
}
if ( path === this.pslPath ) {
//console.debug('µBlock.assetCacheRemovedHandler: decompiling "%s"', path);
this.assets.purge('cache://compiled-publicsuffixlist');
continue;
}
}
2015-11-29 23:06:58 +01:00
this.selfieManager.destroy();
2015-02-24 00:31:29 +01:00
barrier = false;
};
return handler;
})();
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/602
// - Load and patch `filter-list.json`
// - Load and patch user's `remoteBlacklists`
// - Load and patch cached filter lists
// - Load and patch compiled filter lists
//
// Once enough time has passed to safely assume all uBlock Origin
// installations have been converted to the new stock filter lists, this code
// can be removed.
µBlock.patchFilterLists = function(filterLists) {
var modified = false;
var oldListKey, newListKey, listEntry;
for ( var listKey in filterLists ) {
if ( filterLists.hasOwnProperty(listKey) === false ) {
continue;
}
oldListKey = listKey;
if ( this.oldListToNewListMap.hasOwnProperty(oldListKey) === false ) {
oldListKey = 'assets/thirdparties/' + listKey;
if ( this.oldListToNewListMap.hasOwnProperty(oldListKey) === false ) {
continue;
}
}
newListKey = this.oldListToNewListMap[oldListKey];
2015-09-01 00:43:40 +02:00
// https://github.com/gorhill/uBlock/issues/668
// https://github.com/gorhill/uBlock/issues/669
// Beware: an entry for the new list key may already exists. If it is
// the case, leave it as is.
if ( newListKey !== '' && filterLists.hasOwnProperty(newListKey) === false ) {
listEntry = filterLists[listKey];
listEntry.homeURL = undefined;
filterLists[newListKey] = listEntry;
}
delete filterLists[listKey];
modified = true;
}
return modified;
};
µBlock.loadAndPatchStockFilterLists = function(callback) {
var onStockListsLoaded = function(details) {
var µb = µBlock;
var stockLists;
try {
stockLists = JSON.parse(details.content);
} catch (e) {
stockLists = {};
}
// Migrate assets affected by the change to their new name.
2015-09-13 16:26:36 +02:00
var reExternalURL = /^https?:\/\//;
var newListKey;
for ( var oldListKey in stockLists ) {
if ( stockLists.hasOwnProperty(oldListKey) === false ) {
continue;
}
// https://github.com/gorhill/uBlock/issues/708
// Support migrating external stock filter lists as well.
2015-09-13 16:26:36 +02:00
if ( reExternalURL.test(oldListKey) === false ) {
oldListKey = 'assets/thirdparties/' + oldListKey;
}
if ( µb.oldListToNewListMap.hasOwnProperty(oldListKey) === false ) {
continue;
}
newListKey = µb.oldListToNewListMap[oldListKey];
if ( newListKey === '' ) {
continue;
}
// Rename cached asset to preserve content -- so it does not
// need to be fetched from remote server.
µb.assets.rename(oldListKey, newListKey);
µb.assets.purge(µb.getCompiledFilterListPath(oldListKey));
}
µb.patchFilterLists(stockLists);
// Stock lists information cascades into
// - In-memory user's selected filter lists, so we need to patch this.
µb.patchFilterLists(µb.remoteBlacklists);
// Stock lists information cascades into
// - In-storage user's selected filter lists, so we need to patch this.
vAPI.storage.get('remoteBlacklists', function(bin) {
var userLists = bin.remoteBlacklists || {};
if ( µb.patchFilterLists(userLists) ) {
µb.keyvalSetOne('remoteBlacklists', userLists);
}
details.content = JSON.stringify(stockLists);
callback(details);
});
};
this.assets.get('assets/ublock/filter-lists.json', onStockListsLoaded);
};