uBlock/src/js/ublock.js

319 lines
No EOL
10 KiB
JavaScript

/*******************************************************************************
µBlock - a Chromium browser extension to block 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/uBlock
*/
/* global vAPI, µBlock */
/******************************************************************************/
(function(){
'use strict';
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/405
// 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) {
return directive.indexOf('/') !== -1 &&
directive.indexOf('*') !== -1;
};
var matchWhitelistDirective = function(url, hostname, directive) {
// Directive is a plain hostname
if ( directive.indexOf('/') === -1 ) {
return hostname.slice(-directive.length) === directive;
}
// Match URL exactly
if ( directive.indexOf('*') === -1 ) {
return url === directive;
}
// 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);
};
/******************************************************************************/
µBlock.getNetFilteringSwitch = function(url) {
var netWhitelist = this.netWhitelist;
var buckets, i;
var pos = url.indexOf('#');
var targetURL = pos !== -1 ? url.slice(0, pos) : url;
var targetHostname = this.URI.hostnameFromURI(targetURL);
var key = targetHostname;
for (;;) {
if ( netWhitelist.hasOwnProperty(key) ) {
buckets = netWhitelist[key];
i = buckets.length;
while ( i-- ) {
if ( matchWhitelistDirective(targetURL, targetHostname, buckets[i]) ) {
// console.log('"%s" matche url "%s"', buckets[i], targetURL);
return false;
}
}
}
pos = key.indexOf('.');
if ( pos === -1 ) {
break;
}
key = key.slice(pos + 1);
}
return true;
};
/******************************************************************************/
µBlock.toggleNetFilteringSwitch = function(url, scope, newState) {
var currentState = this.getNetFilteringSwitch(url);
if ( newState === undefined ) {
newState = !currentState;
}
if ( newState === currentState ) {
return currentState;
}
var netWhitelist = this.netWhitelist;
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;
// Add to directive list
if ( newState === false ) {
if ( netWhitelist.hasOwnProperty(key) === false ) {
netWhitelist[key] = [];
}
netWhitelist[key].push(directive);
this.saveWhitelist();
return true;
}
// Remove from directive list whatever causes current URL to be whitelisted
var buckets, i;
for (;;) {
if ( netWhitelist.hasOwnProperty(key) ) {
buckets = netWhitelist[key];
i = buckets.length;
while ( i-- ) {
directive = buckets[i];
if ( !matchWhitelistDirective(targetURL, targetHostname, directive) ) {
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 ) {
delete netWhitelist[key];
}
}
pos = key.indexOf('.');
if ( pos === -1 ) {
break;
}
key = key.slice(pos + 1);
}
this.saveWhitelist();
return true;
};
/******************************************************************************/
µBlock.stringFromWhitelist = function(whitelist) {
var r = {};
var i, bucket;
for ( var key in whitelist ) {
if ( whitelist.hasOwnProperty(key) === false ) {
continue;
}
bucket = whitelist[key];
i = bucket.length;
while ( i-- ) {
r[bucket[i]] = true;
}
}
return Object.keys(r).sort(function(a,b){return a.localeCompare(b);}).join('\n');
};
/******************************************************************************/
µBlock.whitelistFromString = function(s) {
var whitelist = {
'#': []
};
var reInvalidHostname = /[^a-z0-9.\-\[\]:]/;
var reHostnameExtractor = /([a-z0-9\[][a-z0-9.\-:]*[a-z0-9\]])\/(?:[^\x00-\x20\/]|$)[^\x00-\x20]*$/;
var lines = s.split(/[\n\r]+/);
var line, matches, key, directive;
for ( var i = 0; i < lines.length; i++ ) {
line = lines[i].trim();
// 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;
}
}
// 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:
// https://github.com/gorhill/uBlock/issues/185
if ( whitelist.hasOwnProperty(key) === false ) {
whitelist[key] = [];
}
whitelist[key].push(directive);
}
return whitelist;
};
/******************************************************************************/
// Return all settings if none specified.
µBlock.changeUserSettings = function(name, value) {
if ( name === undefined ) {
return this.userSettings;
}
if ( typeof name !== 'string' || name === '' ) {
return;
}
// Do not allow an unknown user setting to be created
if ( this.userSettings[name] === undefined ) {
return;
}
if ( value === undefined ) {
return this.userSettings[name];
}
// Pre-change
switch ( name ) {
default:
break;
}
// Change
this.userSettings[name] = value;
// Post-change
switch ( name ) {
case 'contextMenuEnabled':
this.contextMenu.toggle(value);
break;
case 'experimentalEnabled':
if ( typeof this.mirrors === 'object' ) {
// https://github.com/gorhill/uBlock/issues/540
// Disabling local mirroring for the time being
this.mirrors.toggle(false /* value */);
}
break;
default:
break;
}
this.saveUserSettings();
};
/******************************************************************************/
µBlock.elementPickerExec = function(tabId, targetElement) {
this.elementPickerTarget = targetElement || '';
vAPI.tabs.injectScript(tabId, { file: 'js/element-picker.js' });
};
/******************************************************************************/
µBlock.toggleFirewallRule = function(details) {
var changed = false;
if ( details.action !== 0 ) {
this.sessionFirewall.setCellZ(details.srcHostname, details.desHostname, details.requestType, details.action);
} else {
this.sessionFirewall.unsetCell(details.srcHostname, details.desHostname, details.requestType);
}
// https://github.com/gorhill/uBlock/issues/731#issuecomment-73937469
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();
}
// https://github.com/gorhill/uBlock/issues/420
this.cosmeticFilteringEngine.removeFromSelectorCache(details.srcHostname, 'net');
};
/******************************************************************************/
µBlock.isBlockResult = function(result) {
return typeof result === 'string' && result.charAt(1) === 'b';
};
/******************************************************************************/
µBlock.isAllowResult = function(result) {
return typeof result !== 'string' || result.charAt(1) !== 'b';
};
/******************************************************************************/
})();