uBlock/src/js/url-net-filtering.js

365 lines
10 KiB
JavaScript
Raw Normal View History

2015-05-21 20:15:17 +02:00
/*******************************************************************************
uBlock Origin - a browser extension to black/white list requests.
Copyright (C) 2015-2017 Raymond Hill
2015-05-21 20:15:17 +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
*/
'use strict';
2015-05-21 20:15:17 +02:00
/******************************************************************************/
// The purpose of log filtering is to create ad hoc filtering rules, to
// diagnose and assist in the creation of custom filters.
µBlock.URLNetFiltering = (function() {
/*******************************************************************************
buckets: map of [hostname + type]
bucket: array of rule entries, sorted from shorter to longer url
2015-05-21 20:15:17 +02:00
rule entry: { url, action }
*******************************************************************************/
/******************************************************************************/
var actionToNameMap = {
1: 'block',
2: 'allow',
3: 'noop'
};
var nameToActionMap = {
'block': 1,
'allow': 2,
'noop': 3
};
/******************************************************************************/
var RuleEntry = function(url, action) {
this.url = url;
this.action = action;
};
/******************************************************************************/
var indexOfURL = function(entries, url) {
2015-05-21 20:15:17 +02:00
// TODO: binary search -- maybe, depends on common use cases
var urlLen = url.length,
entry;
// URLs must be ordered by increasing length.
for ( var i = 0; i < entries.length; i++ ) {
entry = entries[i];
2015-05-21 20:15:17 +02:00
if ( entry.url.length > urlLen ) {
break;
}
if ( entry.url === url ) {
return i;
}
}
return -1;
};
/******************************************************************************/
var indexOfMatch = function(entries, url) {
var urlLen = url.length,
i = entries.length;
2015-05-21 20:15:17 +02:00
while ( i-- ) {
if ( entries[i].url.length <= urlLen ) {
break;
2015-05-21 20:15:17 +02:00
}
}
if ( i !== -1 ) {
do {
if ( url.startsWith(entries[i].url) ) {
return i;
}
} while ( i-- );
}
2015-05-21 20:15:17 +02:00
return -1;
};
/******************************************************************************/
var indexFromLength = function(entries, len) {
2015-05-21 20:15:17 +02:00
// TODO: binary search -- maybe, depends on common use cases
// URLs must be ordered by increasing length.
for ( var i = 0; i < entries.length; i++ ) {
if ( entries[i].url.length > len ) {
2015-05-21 20:15:17 +02:00
return i;
}
}
return -1;
};
/******************************************************************************/
var addRuleEntry = function(entries, url, action) {
var entry = new RuleEntry(url, action),
i = indexFromLength(entries, url.length);
2015-05-21 20:15:17 +02:00
if ( i === -1 ) {
entries.push(entry);
2015-05-21 20:15:17 +02:00
} else {
entries.splice(i, 0, entry);
2015-05-21 20:15:17 +02:00
}
};
/******************************************************************************/
var URLNetFiltering = function() {
this.reset();
};
/******************************************************************************/
URLNetFiltering.prototype.reset = function() {
this.rules = new Map();
2015-05-21 20:15:17 +02:00
// registers, filled with result of last evaluation
this.context = '';
this.url = '';
this.type = '';
this.r = 0;
};
/******************************************************************************/
URLNetFiltering.prototype.assign = function(other) {
var thisRules = this.rules,
otherRules = other.rules,
iter, item;
2015-05-21 20:15:17 +02:00
// Remove rules not in other
iter = thisRules.entries();
for (;;) {
item = iter.next();
if ( item.done ) { break; }
if ( otherRules.has(item.value) === false ) {
thisRules.delete(item.value);
2015-05-21 20:15:17 +02:00
}
}
// Add/change rules in other
iter = otherRules.entries();
for (;;) {
item = iter.next();
if ( item.done ) { break; }
thisRules.set(item.value[0], item.value[1].slice());
2015-05-21 20:15:17 +02:00
}
};
/******************************************************************************/
URLNetFiltering.prototype.setRule = function(srcHostname, url, type, action) {
if ( action === 0 ) {
return this.removeRule(srcHostname, url, type);
}
var bucketKey = srcHostname + ' ' + type,
entries = this.rules.get(bucketKey);
if ( entries === undefined ) {
entries = [];
this.rules.set(bucketKey, entries);
2015-05-21 20:15:17 +02:00
}
var i = indexOfURL(entries, url),
entry;
2015-05-21 20:15:17 +02:00
if ( i !== -1 ) {
entry = entries[i];
if ( entry.action === action ) { return false; }
2015-05-21 20:15:17 +02:00
entry.action = action;
} else {
addRuleEntry(entries, url, action);
2015-05-21 20:15:17 +02:00
}
return true;
};
/******************************************************************************/
URLNetFiltering.prototype.removeRule = function(srcHostname, url, type) {
var bucketKey = srcHostname + ' ' + type,
entries = this.rules.get(bucketKey);
if ( entries === undefined ) {
2015-05-21 20:15:17 +02:00
return false;
}
var i = indexOfURL(entries, url);
2015-05-21 20:15:17 +02:00
if ( i === -1 ) {
return false;
}
entries.splice(i, 1);
if ( entries.length === 0 ) {
this.rules.delete(bucketKey);
2015-05-21 20:15:17 +02:00
}
return true;
};
/******************************************************************************/
URLNetFiltering.prototype.evaluateZ = function(context, target, type) {
this.r = 0;
if ( this.rules.size === 0 ) {
2015-05-21 20:15:17 +02:00
return this;
}
var entries, pos, i, entry;
2015-05-21 20:15:17 +02:00
for (;;) {
this.context = context;
if ( (entries = this.rules.get(context + ' ' + type)) ) {
i = indexOfMatch(entries, target);
2015-05-21 20:15:17 +02:00
if ( i !== -1 ) {
entry = entries[i];
2015-05-21 20:15:17 +02:00
this.url = entry.url;
this.type = type;
this.r = entry.action;
return this;
}
}
if ( (entries = this.rules.get(context + ' *')) ) {
i = indexOfMatch(entries, target);
2015-05-21 20:15:17 +02:00
if ( i !== -1 ) {
entry = entries[i];
2015-05-21 20:15:17 +02:00
this.url = entry.url;
this.type = '*';
this.r = entry.action;
return this;
}
}
if ( context === '*' ) { break; }
2015-05-21 20:15:17 +02:00
pos = context.indexOf('.');
context = pos !== -1 ? context.slice(pos + 1) : '*';
}
return this;
};
/******************************************************************************/
URLNetFiltering.prototype.mustBlockOrAllow = function() {
return this.r === 1 || this.r === 2;
};
/******************************************************************************/
URLNetFiltering.prototype.toFilterString = function() {
if ( this.r === 0 ) {
return '';
}
var body = this.context + ' ' + this.url + ' ' + this.type;
if ( this.r === 1 ) {
return 'lb:' + body + ' block';
}
if ( this.r === 2 ) {
return 'la:' + body + ' allow';
}
/* this.r === 3 */
return 'ln:' + body + ' noop';
};
/******************************************************************************/
2015-05-22 14:05:55 +02:00
URLNetFiltering.prototype.copyRules = function(other, context, urls, type) {
var changed = false;
var url, otherOwn, thisOwn;
var i = urls.length;
while ( i-- ) {
url = urls[i];
other.evaluateZ(context, url, type);
otherOwn = other.context === context && other.url === url && other.type === type;
this.evaluateZ(context, url, type);
thisOwn = this.context === context && this.url === url && this.type === type;
if ( otherOwn && !thisOwn ) {
this.setRule(context, url, type, other.r);
changed = true;
}
if ( !otherOwn && thisOwn ) {
this.removeRule(context, url, type);
changed = true;
}
}
return changed;
};
/******************************************************************************/
2015-05-26 14:43:46 +02:00
// "url-filtering:" hostname url type action
2015-05-21 20:15:17 +02:00
URLNetFiltering.prototype.toString = function() {
var out = [],
iter = this.rules.entries(),
item, key, pos, hn, type, entries, i, entry;
for (;;) {
item = iter.next();
if ( item.done ) { break; }
key = item.value[0];
pos = key.indexOf(' ');
hn = key.slice(0, pos);
pos = key.lastIndexOf(' ');
type = key.slice(pos + 1);
entries = item.value[1];
for ( i = 0; i < entries.length; i++ ) {
entry = entries[i];
2015-05-21 20:15:17 +02:00
out.push(
hn + ' ' +
entry.url + ' ' +
type + ' ' +
actionToNameMap[entry.action]
);
}
}
return out.sort().join('\n');
};
/******************************************************************************/
URLNetFiltering.prototype.fromString = function(text) {
this.reset();
var lineIter = new µBlock.LineIterator(text),
line, fields;
while ( lineIter.eot() === false ) {
line = lineIter.next().trim();
if ( line === '' ) { continue; }
2015-05-21 20:15:17 +02:00
// Coarse test
if ( line.indexOf('://') === -1 ) {
continue;
}
fields = line.split(/\s+/);
if ( fields.length !== 4 ) {
continue;
}
// Finer test
if ( fields[1].indexOf('://') === -1 ) {
continue;
}
if ( nameToActionMap.hasOwnProperty(fields[3]) === false ) {
continue;
}
this.setRule(fields[0], fields[1], fields[2], nameToActionMap[fields[3]]);
}
};
/******************************************************************************/
return URLNetFiltering;
/******************************************************************************/
})();
/******************************************************************************/
µBlock.sessionURLFiltering = new µBlock.URLNetFiltering();
µBlock.permanentURLFiltering = new µBlock.URLNetFiltering();
/******************************************************************************/