2014-06-24 00:42:43 +02:00
|
|
|
/*******************************************************************************
|
|
|
|
|
2015-03-07 19:20:18 +01:00
|
|
|
µBlock - a browser extension to block requests.
|
2014-06-24 00:42:43 +02:00
|
|
|
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/uBlock
|
|
|
|
*/
|
|
|
|
|
2014-12-12 23:59:47 +01:00
|
|
|
/* global vAPI, µBlock */
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
|
2014-10-19 13:11:27 +02:00
|
|
|
'use strict';
|
2014-06-24 00:42:43 +02:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/405
|
2014-12-14 14:57:00 +01:00
|
|
|
// Be more flexible with whitelist syntax
|
|
|
|
|
|
|
|
// Any special regexp char will be escaped
|
|
|
|
var whitelistDirectiveEscape = /[-\/\\^$+?.()|[\]{}]/g;
|
|
|
|
|
|
|
|
// All `*` will be expanded into `.*`
|
|
|
|
var whitelistDirectiveEscapeAsterisk = /\*/g;
|
|
|
|
|
|
|
|
// Probably manually entered whitelist directive
|
|
|
|
var isHandcraftedWhitelistDirective = function(directive) {
|
2014-12-14 23:21:59 +01:00
|
|
|
return directive.indexOf('/') !== -1 &&
|
|
|
|
directive.indexOf('*') !== -1;
|
|
|
|
};
|
|
|
|
|
|
|
|
var matchWhitelistDirective = function(url, hostname, directive) {
|
|
|
|
// Directive is a plain hostname
|
2014-12-14 14:57:00 +01:00
|
|
|
if ( directive.indexOf('/') === -1 ) {
|
2014-12-14 23:21:59 +01:00
|
|
|
return hostname.slice(-directive.length) === directive;
|
|
|
|
}
|
|
|
|
// Match URL exactly
|
|
|
|
if ( directive.indexOf('*') === -1 ) {
|
|
|
|
return url === directive;
|
2014-08-02 17:40:27 +02:00
|
|
|
}
|
2014-12-14 23:21:59 +01:00
|
|
|
// Regex escape code inspired from:
|
|
|
|
// "Is there a RegExp.escape function in Javascript?"
|
|
|
|
// http://stackoverflow.com/a/3561711
|
|
|
|
var reStr = directive.replace(whitelistDirectiveEscape, '\\$&')
|
|
|
|
.replace(whitelistDirectiveEscapeAsterisk, '.*');
|
|
|
|
var re = new RegExp(reStr);
|
|
|
|
return re.test(url);
|
2014-12-14 14:57:00 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
2014-08-02 17:40:27 +02:00
|
|
|
|
2014-12-14 14:57:00 +01:00
|
|
|
µBlock.getNetFilteringSwitch = function(url) {
|
2014-12-14 23:21:59 +01:00
|
|
|
var netWhitelist = this.netWhitelist;
|
2015-03-23 20:19:17 +01:00
|
|
|
var buckets, i, pos;
|
|
|
|
var targetHostname = this.URI.hostnameFromURI(url);
|
2014-12-14 23:21:59 +01:00
|
|
|
var key = targetHostname;
|
2014-12-14 14:57:00 +01:00
|
|
|
for (;;) {
|
2014-12-14 23:21:59 +01:00
|
|
|
if ( netWhitelist.hasOwnProperty(key) ) {
|
|
|
|
buckets = netWhitelist[key];
|
2014-12-14 14:57:00 +01:00
|
|
|
i = buckets.length;
|
|
|
|
while ( i-- ) {
|
2015-03-23 20:19:17 +01:00
|
|
|
if ( matchWhitelistDirective(url, targetHostname, buckets[i]) ) {
|
|
|
|
// console.log('"%s" matche url "%s"', buckets[i], url);
|
2014-12-14 14:57:00 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-12-14 23:21:59 +01:00
|
|
|
pos = key.indexOf('.');
|
2014-12-14 14:57:00 +01:00
|
|
|
if ( pos === -1 ) {
|
|
|
|
break;
|
2014-06-24 00:42:43 +02:00
|
|
|
}
|
2014-12-14 23:21:59 +01:00
|
|
|
key = key.slice(pos + 1);
|
2014-06-24 00:42:43 +02:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-08-02 17:40:27 +02:00
|
|
|
µBlock.toggleNetFilteringSwitch = function(url, scope, newState) {
|
2014-12-14 14:57:00 +01:00
|
|
|
var currentState = this.getNetFilteringSwitch(url);
|
2014-06-24 00:42:43 +02:00
|
|
|
if ( newState === undefined ) {
|
|
|
|
newState = !currentState;
|
|
|
|
}
|
|
|
|
if ( newState === currentState ) {
|
|
|
|
return currentState;
|
|
|
|
}
|
2014-08-02 17:40:27 +02:00
|
|
|
|
|
|
|
var netWhitelist = this.netWhitelist;
|
2014-12-14 23:21:59 +01:00
|
|
|
var pos = url.indexOf('#');
|
|
|
|
var targetURL = pos !== -1 ? url.slice(0, pos) : url;
|
|
|
|
var targetHostname = this.URI.hostnameFromURI(targetURL);
|
|
|
|
var key = targetHostname;
|
|
|
|
var directive = scope === 'page' ? targetURL : targetHostname;
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2014-12-14 23:21:59 +01:00
|
|
|
// Add to directive list
|
2014-12-14 14:57:00 +01:00
|
|
|
if ( newState === false ) {
|
2014-12-14 23:21:59 +01:00
|
|
|
if ( netWhitelist.hasOwnProperty(key) === false ) {
|
2014-12-22 20:48:17 +01:00
|
|
|
netWhitelist[key] = [];
|
2014-12-14 14:57:00 +01:00
|
|
|
}
|
2014-12-22 20:48:17 +01:00
|
|
|
netWhitelist[key].push(directive);
|
2014-08-02 17:40:27 +02:00
|
|
|
this.saveWhitelist();
|
2014-06-24 00:42:43 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-12-14 23:21:59 +01:00
|
|
|
// Remove from directive list whatever causes current URL to be whitelisted
|
2014-12-22 20:48:17 +01:00
|
|
|
var buckets, i;
|
2014-12-14 14:57:00 +01:00
|
|
|
for (;;) {
|
2014-12-14 23:21:59 +01:00
|
|
|
if ( netWhitelist.hasOwnProperty(key) ) {
|
|
|
|
buckets = netWhitelist[key];
|
2014-12-14 14:57:00 +01:00
|
|
|
i = buckets.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
directive = buckets[i];
|
2014-12-14 23:21:59 +01:00
|
|
|
if ( !matchWhitelistDirective(targetURL, targetHostname, directive) ) {
|
2014-12-14 14:57:00 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
buckets.splice(i, 1);
|
|
|
|
// If it is a directive which can't be created easily through
|
|
|
|
// the user interface, keep it around as a commented out
|
|
|
|
// directive
|
|
|
|
if ( isHandcraftedWhitelistDirective(directive) ) {
|
|
|
|
netWhitelist['#'].push('# ' + directive);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( buckets.length === 0 ) {
|
2014-12-14 23:21:59 +01:00
|
|
|
delete netWhitelist[key];
|
2014-12-14 14:57:00 +01:00
|
|
|
}
|
2014-06-24 00:42:43 +02:00
|
|
|
}
|
2014-12-14 23:21:59 +01:00
|
|
|
pos = key.indexOf('.');
|
2014-12-14 14:57:00 +01:00
|
|
|
if ( pos === -1 ) {
|
|
|
|
break;
|
|
|
|
}
|
2014-12-14 23:21:59 +01:00
|
|
|
key = key.slice(pos + 1);
|
2014-08-02 17:40:27 +02:00
|
|
|
}
|
|
|
|
this.saveWhitelist();
|
|
|
|
return true;
|
2014-06-24 00:42:43 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
2014-12-08 18:37:35 +01:00
|
|
|
|
2014-12-14 14:57:00 +01:00
|
|
|
µBlock.stringFromWhitelist = function(whitelist) {
|
2014-08-02 17:40:27 +02:00
|
|
|
var r = {};
|
|
|
|
var i, bucket;
|
2014-12-14 14:57:00 +01:00
|
|
|
for ( var key in whitelist ) {
|
|
|
|
if ( whitelist.hasOwnProperty(key) === false ) {
|
2014-08-02 17:40:27 +02:00
|
|
|
continue;
|
|
|
|
}
|
2014-12-14 14:57:00 +01:00
|
|
|
bucket = whitelist[key];
|
|
|
|
i = bucket.length;
|
|
|
|
while ( i-- ) {
|
2014-08-02 17:40:27 +02:00
|
|
|
r[bucket[i]] = true;
|
2014-06-24 01:23:36 +02:00
|
|
|
}
|
|
|
|
}
|
2014-08-02 17:40:27 +02:00
|
|
|
return Object.keys(r).sort(function(a,b){return a.localeCompare(b);}).join('\n');
|
2014-06-24 01:23:36 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-08-02 17:40:27 +02:00
|
|
|
µBlock.whitelistFromString = function(s) {
|
2014-12-14 14:57:00 +01:00
|
|
|
var whitelist = {
|
|
|
|
'#': []
|
|
|
|
};
|
|
|
|
var reInvalidHostname = /[^a-z0-9.\-\[\]:]/;
|
|
|
|
var reHostnameExtractor = /([a-z0-9\[][a-z0-9.\-:]*[a-z0-9\]])\/(?:[^\x00-\x20\/]|$)[^\x00-\x20]*$/;
|
2014-08-02 17:40:27 +02:00
|
|
|
var lines = s.split(/[\n\r]+/);
|
2014-12-14 14:57:00 +01:00
|
|
|
var line, matches, key, directive;
|
2014-08-02 17:40:27 +02:00
|
|
|
for ( var i = 0; i < lines.length; i++ ) {
|
|
|
|
line = lines[i].trim();
|
2014-12-14 14:57:00 +01:00
|
|
|
// Don't throw out commented out lines: user might want to fix them
|
|
|
|
if ( line.charAt(0) === '#' ) {
|
|
|
|
key = '#';
|
|
|
|
directive = line;
|
|
|
|
}
|
|
|
|
// Plain hostname
|
|
|
|
else if ( line.indexOf('/') === -1 ) {
|
|
|
|
if ( reInvalidHostname.test(line) ) {
|
|
|
|
key = '#';
|
|
|
|
directive = '# ' + line;
|
|
|
|
} else {
|
|
|
|
key = directive = line;
|
|
|
|
}
|
2014-08-02 17:40:27 +02:00
|
|
|
}
|
2014-12-14 14:57:00 +01:00
|
|
|
// URL, possibly wildcarded: there MUST be at least one hostname
|
|
|
|
// label (or else it would be just impossible to make an efficient
|
|
|
|
// dict.
|
|
|
|
else {
|
|
|
|
matches = reHostnameExtractor.exec(line);
|
|
|
|
if ( !matches || matches.length !== 2 ) {
|
|
|
|
key = '#';
|
|
|
|
directive = '# ' + line;
|
|
|
|
} else {
|
|
|
|
key = matches[1];
|
|
|
|
directive = line;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Be sure this stays fixed:
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/185
|
2014-12-14 14:57:00 +01:00
|
|
|
|
|
|
|
if ( whitelist.hasOwnProperty(key) === false ) {
|
|
|
|
whitelist[key] = [];
|
2014-08-02 17:40:27 +02:00
|
|
|
}
|
2014-12-14 14:57:00 +01:00
|
|
|
whitelist[key].push(directive);
|
2014-08-02 17:40:27 +02:00
|
|
|
}
|
2014-12-14 14:57:00 +01:00
|
|
|
return whitelist;
|
2014-06-24 00:42:43 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-06-25 00:29:55 +02:00
|
|
|
// Return all settings if none specified.
|
|
|
|
|
2014-06-24 00:42:43 +02:00
|
|
|
µBlock.changeUserSettings = function(name, value) {
|
2014-06-25 00:29:55 +02:00
|
|
|
if ( name === undefined ) {
|
2014-09-28 18:05:46 +02:00
|
|
|
return this.userSettings;
|
2014-06-25 00:29:55 +02:00
|
|
|
}
|
|
|
|
|
2014-06-24 00:42:43 +02:00
|
|
|
if ( typeof name !== 'string' || name === '' ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do not allow an unknown user setting to be created
|
2014-09-28 18:05:46 +02:00
|
|
|
if ( this.userSettings[name] === undefined ) {
|
2014-06-24 00:42:43 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( value === undefined ) {
|
2014-09-28 18:05:46 +02:00
|
|
|
return this.userSettings[name];
|
2014-06-24 00:42:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Pre-change
|
|
|
|
switch ( name ) {
|
2014-10-17 21:44:19 +02:00
|
|
|
default:
|
2014-06-26 15:46:38 +02:00
|
|
|
break;
|
2014-06-24 00:42:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Change
|
2014-09-28 18:05:46 +02:00
|
|
|
this.userSettings[name] = value;
|
2014-06-24 00:42:43 +02:00
|
|
|
|
|
|
|
// Post-change
|
|
|
|
switch ( name ) {
|
2015-02-25 02:10:10 +01:00
|
|
|
case 'collapseBlocked':
|
|
|
|
if ( value === false ) {
|
|
|
|
this.cosmeticFilteringEngine.removeFromSelectorCache('*', 'net');
|
|
|
|
}
|
|
|
|
break;
|
2014-09-28 18:05:46 +02:00
|
|
|
case 'contextMenuEnabled':
|
2014-09-30 21:55:18 +02:00
|
|
|
this.contextMenu.toggle(value);
|
|
|
|
break;
|
|
|
|
case 'experimentalEnabled':
|
|
|
|
if ( typeof this.mirrors === 'object' ) {
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/540
|
2015-01-19 01:17:36 +01:00
|
|
|
// Disabling local mirroring for the time being
|
|
|
|
this.mirrors.toggle(false /* value */);
|
2014-09-30 21:55:18 +02:00
|
|
|
}
|
2014-09-28 18:05:46 +02:00
|
|
|
break;
|
2014-06-26 15:46:38 +02:00
|
|
|
default:
|
|
|
|
break;
|
2014-06-24 00:42:43 +02:00
|
|
|
}
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
this.saveUserSettings();
|
2014-06-24 00:42:43 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
µBlock.elementPickerExec = function(tabId, targetElement) {
|
2015-03-20 16:39:20 +01:00
|
|
|
this.epickerTarget = targetElement || '';
|
2014-10-20 13:26:02 +02:00
|
|
|
vAPI.tabs.injectScript(tabId, { file: 'js/element-picker.js' });
|
2014-09-28 18:05:46 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
2014-10-06 20:02:44 +02:00
|
|
|
|
2015-02-11 06:26:45 +01:00
|
|
|
µBlock.toggleFirewallRule = function(details) {
|
2014-12-31 16:47:19 +01:00
|
|
|
if ( details.action !== 0 ) {
|
2015-02-11 06:26:45 +01:00
|
|
|
this.sessionFirewall.setCellZ(details.srcHostname, details.desHostname, details.requestType, details.action);
|
2014-10-06 20:02:44 +02:00
|
|
|
} else {
|
2015-02-11 06:26:45 +01:00
|
|
|
this.sessionFirewall.unsetCell(details.srcHostname, details.desHostname, details.requestType);
|
2014-10-06 20:02:44 +02:00
|
|
|
}
|
2014-12-17 16:32:50 +01:00
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/731#issuecomment-73937469
|
2015-02-11 21:48:39 +01:00
|
|
|
if ( details.persist ) {
|
|
|
|
if ( details.action !== 0 ) {
|
|
|
|
this.permanentFirewall.setCellZ(details.srcHostname, details.desHostname, details.requestType, details.action);
|
|
|
|
} else {
|
|
|
|
this.permanentFirewall.unsetCell(details.srcHostname, details.desHostname, details.requestType, details.action);
|
|
|
|
}
|
|
|
|
this.savePermanentFirewallRules();
|
|
|
|
}
|
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/420
|
2015-01-06 14:01:15 +01:00
|
|
|
this.cosmeticFilteringEngine.removeFromSelectorCache(details.srcHostname, 'net');
|
2014-12-28 16:07:43 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-01-01 16:55:00 +01:00
|
|
|
µBlock.isBlockResult = function(result) {
|
|
|
|
return typeof result === 'string' && result.charAt(1) === 'b';
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
µBlock.isAllowResult = function(result) {
|
|
|
|
return typeof result !== 'string' || result.charAt(1) !== 'b';
|
|
|
|
};
|
2015-01-06 14:01:15 +01:00
|
|
|
|
2015-01-01 16:55:00 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-03-27 18:00:55 +01:00
|
|
|
µBlock.toggleHostnameSwitch = function(details) {
|
2015-04-07 19:27:46 +02:00
|
|
|
if ( this.hnSwitches.toggleZ(details.name, details.hostname, !!details.deep, details.state) ) {
|
2015-03-27 18:00:55 +01:00
|
|
|
this.saveHostnameSwitches();
|
|
|
|
}
|
2015-04-05 18:03:14 +02:00
|
|
|
|
|
|
|
// Take action if needed
|
2015-04-09 17:19:31 +02:00
|
|
|
if ( details.name === 'no-cosmetic-filtering' ) {
|
2015-04-05 18:03:14 +02:00
|
|
|
vAPI.tabs.injectScript(details.tabId, {
|
|
|
|
file: 'js/cosmetic-' + (details.state ? 'off' : 'on') + '.js',
|
|
|
|
allFrames: true
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Whatever else
|
|
|
|
// ...
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
µBlock.getHiddenElementCount = function(tabId, callback) {
|
|
|
|
callback = callback || this.noopFunc;
|
2015-04-09 00:46:08 +02:00
|
|
|
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
|
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
}
|
2015-04-05 18:03:14 +02:00
|
|
|
vAPI.tabs.injectScript(tabId, { file: 'js/cosmetic-count.js' }, callback);
|
2015-03-27 18:00:55 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-03-07 19:20:18 +01:00
|
|
|
})();
|