mirror of
https://github.com/gorhill/uBlock.git
synced 2024-09-23 14:11:28 +02:00
efccaf1416
part of the package. The code here is to ensure a seamless transition from local assets which have been converted to remote assets. The only side effect to expect is that the selfie, if any, will be invalidated.
615 lines
17 KiB
JavaScript
615 lines
17 KiB
JavaScript
/*******************************************************************************
|
|
|
|
uBlock - a 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 punycode, µBlock */
|
|
/* jshint bitwise: false */
|
|
|
|
/******************************************************************************/
|
|
|
|
µBlock.Firewall = (function() {
|
|
|
|
'use strict';
|
|
|
|
/******************************************************************************/
|
|
|
|
var magicId = 'chmdgxwtetgu';
|
|
|
|
/******************************************************************************/
|
|
|
|
var Matrix = function() {
|
|
this.reset();
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
var supportedDynamicTypes = {
|
|
'3p': true,
|
|
'image': true,
|
|
'inline-script': true,
|
|
'1p-script': true,
|
|
'3p-script': true,
|
|
'3p-frame': true
|
|
};
|
|
|
|
var typeBitOffsets = {
|
|
'*': 0,
|
|
'inline-script': 2,
|
|
'1p-script': 4,
|
|
'3p-script': 6,
|
|
'3p-frame': 8,
|
|
'image': 10,
|
|
'3p': 12
|
|
};
|
|
|
|
var actionToNameMap = {
|
|
'1': 'block',
|
|
'2': 'allow',
|
|
'3': 'noop'
|
|
};
|
|
|
|
var nameToActionMap = {
|
|
'block': 1,
|
|
'allow': 2,
|
|
'noop': 3
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
// For performance purpose, as simple tests as possible
|
|
var reHostnameVeryCoarse = /[g-z_-]/;
|
|
var reIPv4VeryCoarse = /\.\d+$/;
|
|
var reBadHostname = /[^0-9a-z_.\[\]:-]/;
|
|
|
|
// http://tools.ietf.org/html/rfc5952
|
|
// 4.3: "MUST be represented in lowercase"
|
|
// Also: http://en.wikipedia.org/wiki/IPv6_address#Literal_IPv6_addresses_in_network_resource_identifiers
|
|
|
|
var isIPAddress = function(hostname) {
|
|
if ( reHostnameVeryCoarse.test(hostname) ) {
|
|
return false;
|
|
}
|
|
if ( reIPv4VeryCoarse.test(hostname) ) {
|
|
return true;
|
|
}
|
|
return hostname.charAt(0) === '[';
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
var toBroaderHostname = function(hostname) {
|
|
if ( hostname === '*' ) {
|
|
return '';
|
|
}
|
|
if ( isIPAddress(hostname) ) {
|
|
return '*';
|
|
}
|
|
var pos = hostname.indexOf('.');
|
|
if ( pos === -1 ) {
|
|
return '*';
|
|
}
|
|
return hostname.slice(pos + 1);
|
|
};
|
|
|
|
Matrix.toBroaderHostname = toBroaderHostname;
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.reset = function() {
|
|
this.r = 0;
|
|
this.type = '';
|
|
this.y = '';
|
|
this.z = '';
|
|
this.rules = {};
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.assign = function(other) {
|
|
var thisRules = this.rules;
|
|
var otherRules = other.rules;
|
|
var k;
|
|
|
|
// Remove rules not in other
|
|
for ( k in thisRules ) {
|
|
if ( thisRules.hasOwnProperty(k) === false ) {
|
|
continue;
|
|
}
|
|
if ( otherRules.hasOwnProperty(k) === false ) {
|
|
delete thisRules[k];
|
|
}
|
|
}
|
|
|
|
// Add/change rules in other
|
|
for ( k in otherRules ) {
|
|
if ( otherRules.hasOwnProperty(k) === false ) {
|
|
continue;
|
|
}
|
|
thisRules[k] = otherRules[k];
|
|
}
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.copyRules = function(other, srcHostname, desHostnames) {
|
|
var thisRules = this.rules;
|
|
var otherRules = other.rules;
|
|
var ruleKey, ruleValue;
|
|
|
|
// Specific types
|
|
ruleValue = otherRules['* *'] || 0;
|
|
if ( ruleValue !== 0 ) {
|
|
thisRules['* *'] = ruleValue;
|
|
} else {
|
|
delete thisRules['* *'];
|
|
}
|
|
ruleKey = srcHostname + ' *';
|
|
ruleValue = otherRules[ruleKey] || 0;
|
|
if ( ruleValue !== 0 ) {
|
|
thisRules[ruleKey] = ruleValue;
|
|
} else {
|
|
delete thisRules[ruleKey];
|
|
}
|
|
|
|
// Specific destinations
|
|
for ( var desHostname in desHostnames ) {
|
|
if ( desHostnames.hasOwnProperty(desHostname) === false ) {
|
|
continue;
|
|
}
|
|
ruleKey = '* ' + desHostname;
|
|
ruleValue = otherRules[ruleKey] || 0;
|
|
if ( ruleValue !== 0 ) {
|
|
thisRules[ruleKey] = ruleValue;
|
|
} else {
|
|
delete thisRules[ruleKey];
|
|
}
|
|
ruleKey = srcHostname + ' ' + desHostname ;
|
|
ruleValue = otherRules[ruleKey] || 0;
|
|
if ( ruleValue !== 0 ) {
|
|
thisRules[ruleKey] = ruleValue;
|
|
} else {
|
|
delete thisRules[ruleKey];
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
// - * * type
|
|
// - from * type
|
|
// - * to *
|
|
// - from to *
|
|
|
|
Matrix.prototype.hasSameRules = function(other, srcHostname, desHostnames) {
|
|
var thisRules = this.rules;
|
|
var otherRules = other.rules;
|
|
var ruleKey;
|
|
|
|
// Specific types
|
|
ruleKey = '* *';
|
|
if ( (thisRules[ruleKey] || 0) !== (otherRules[ruleKey] || 0) ) {
|
|
return false;
|
|
}
|
|
ruleKey = srcHostname + ' *';
|
|
if ( (thisRules[ruleKey] || 0) !== (otherRules[ruleKey] || 0) ) {
|
|
return false;
|
|
}
|
|
|
|
// Specific destinations
|
|
for ( var desHostname in desHostnames ) {
|
|
if ( desHostnames.hasOwnProperty(desHostname) === false ) {
|
|
continue;
|
|
}
|
|
ruleKey = '* ' + desHostname;
|
|
if ( (thisRules[ruleKey] || 0) !== (otherRules[ruleKey] || 0) ) {
|
|
return false;
|
|
}
|
|
ruleKey = srcHostname + ' ' + desHostname ;
|
|
if ( (thisRules[ruleKey] || 0) !== (otherRules[ruleKey] || 0) ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.setCell = function(srcHostname, desHostname, type, state) {
|
|
var bitOffset = typeBitOffsets[type];
|
|
var k = srcHostname + ' ' + desHostname;
|
|
var oldBitmap = this.rules[k];
|
|
if ( oldBitmap === undefined ) {
|
|
oldBitmap = 0;
|
|
}
|
|
var newBitmap = oldBitmap & ~(3 << bitOffset) | (state << bitOffset);
|
|
if ( newBitmap === oldBitmap ) {
|
|
return false;
|
|
}
|
|
if ( newBitmap === 0 ) {
|
|
delete this.rules[k];
|
|
} else {
|
|
this.rules[k] = newBitmap;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.unsetCell = function(srcHostname, desHostname, type) {
|
|
this.evaluateCellZY(srcHostname, desHostname, type);
|
|
if ( this.r === 0 ) {
|
|
return false;
|
|
}
|
|
this.setCell(srcHostname, desHostname, type, 0);
|
|
return true;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.setCellZ = function(srcHostname, desHostname, type, action) {
|
|
this.evaluateCellZY(srcHostname, desHostname, type);
|
|
if ( this.r === action ) {
|
|
return false;
|
|
}
|
|
this.setCell(srcHostname, desHostname, type, 0);
|
|
this.evaluateCellZY(srcHostname, desHostname, type);
|
|
if ( this.r === action ) {
|
|
return true;
|
|
}
|
|
this.setCell(srcHostname, desHostname, type, action);
|
|
return true;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.blockCell = function(srcHostname, desHostname, type) {
|
|
return this.setCellZ(srcHostname, desHostname, type, 1);
|
|
};
|
|
|
|
// https://www.youtube.com/watch?v=Csewb_eIStY
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.allowCell = function(srcHostname, desHostname, type) {
|
|
return this.setCellZ(srcHostname, desHostname, type, 2);
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.evaluateCell = function(srcHostname, desHostname, type) {
|
|
var key = srcHostname + ' ' + desHostname;
|
|
var bitmap = this.rules[key];
|
|
if ( bitmap === undefined ) {
|
|
return 0;
|
|
}
|
|
return bitmap >> typeBitOffsets[type] & 3;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.clearRegisters = function() {
|
|
this.r = 0;
|
|
this.type = this.y = this.z = '';
|
|
return this;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
var is3rdParty = function(srcHostname, desHostname) {
|
|
// If at least one is party-less, the relation can't be labelled
|
|
// "3rd-party"
|
|
if ( desHostname === '*' || srcHostname === '*' || srcHostname === '' ) {
|
|
return false;
|
|
}
|
|
|
|
// No domain can very well occurs, for examples:
|
|
// - localhost
|
|
// - file-scheme
|
|
// etc.
|
|
var srcDomain = domainFromHostname(srcHostname) || srcHostname;
|
|
|
|
if ( desHostname.slice(0 - srcDomain.length) !== srcDomain ) {
|
|
return true;
|
|
}
|
|
// Do not confuse 'example.com' with 'anotherexample.com'
|
|
return desHostname.length !== srcDomain.length &&
|
|
desHostname.charAt(desHostname.length - srcDomain.length - 1) !== '.';
|
|
};
|
|
|
|
var domainFromHostname = µBlock.URI.domainFromHostname;
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) {
|
|
this.type = type;
|
|
var bitOffset = typeBitOffsets[type];
|
|
var s = srcHostname;
|
|
var v;
|
|
for (;;) {
|
|
this.z = s;
|
|
v = this.rules[s + ' ' + desHostname];
|
|
if ( v !== undefined ) {
|
|
v = v >>> bitOffset & 3;
|
|
if ( v !== 0 ) {
|
|
this.r = v;
|
|
return v;
|
|
}
|
|
}
|
|
s = toBroaderHostname(s);
|
|
if ( s === '' ) {
|
|
break;
|
|
}
|
|
}
|
|
// srcHostname is '*' at this point
|
|
this.r = 0;
|
|
return 0;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.evaluateCellZY = function(srcHostname, desHostname, type) {
|
|
// Precedence: from most specific to least specific
|
|
|
|
// Specific-destination, any party, any type
|
|
var d = desHostname;
|
|
while ( d !== '*' ) {
|
|
this.y = d;
|
|
if ( this.evaluateCellZ(srcHostname, d, '*') !== 0 ) { return this; }
|
|
d = toBroaderHostname(d);
|
|
}
|
|
|
|
var thirdParty = is3rdParty(srcHostname, desHostname);
|
|
|
|
// Any destination
|
|
this.y = '*';
|
|
|
|
// Specific party
|
|
if ( thirdParty ) {
|
|
// 3rd-party, specific type
|
|
if ( type === 'script' ) {
|
|
if ( this.evaluateCellZ(srcHostname, '*', '3p-script') !== 0 ) { return this; }
|
|
} else if ( type === 'sub_frame' ) {
|
|
if ( this.evaluateCellZ(srcHostname, '*', '3p-frame') !== 0 ) { return this; }
|
|
}
|
|
// 3rd-party, any type
|
|
if ( this.evaluateCellZ(srcHostname, '*', '3p') !== 0 ) { return this; }
|
|
} else if ( type === 'script' ) {
|
|
// 1st party, specific type
|
|
if ( this.evaluateCellZ(srcHostname, '*', '1p-script') !== 0 ) { return this; }
|
|
}
|
|
|
|
// Any destination, any party, specific type
|
|
if ( supportedDynamicTypes.hasOwnProperty(type) ) {
|
|
if ( this.evaluateCellZ(srcHostname, '*', type) !== 0 ) { return this; }
|
|
}
|
|
|
|
// Any destination, any party, any type
|
|
if ( this.evaluateCellZ(srcHostname, '*', '*') !== 0 ) { return this; }
|
|
|
|
this.type = '';
|
|
return this;
|
|
};
|
|
|
|
// http://youtu.be/gSGk1bQ9rcU?t=25m6s
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.mustAllowCellZY = function(srcHostname, desHostname, type) {
|
|
return this.evaluateCellZY(srcHostname, desHostname, type).r === 2;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.mustBlockOrAllow = function() {
|
|
return this.r === 1 || this.r === 2;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.mustBlock = function() {
|
|
return this.r === 1;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.mustAbort = function() {
|
|
return this.r === 3;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.toFilterString = function() {
|
|
if ( this.r === 0 || this.type === '' ) {
|
|
return '';
|
|
}
|
|
var body = this.z + ' ' + this.y + ' ' + this.type;
|
|
if ( this.r === 1 ) {
|
|
return 'db:' + body + ' block';
|
|
}
|
|
if ( this.r === 2 ) {
|
|
return 'da:' + body + ' allow';
|
|
}
|
|
/* this.r === 3 */
|
|
return 'dn:' + body + ' noop';
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.srcHostnameFromRule = function(rule) {
|
|
return rule.slice(0, rule.indexOf(' '));
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.desHostnameFromRule = function(rule) {
|
|
return rule.slice(rule.indexOf(' ') + 1);
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.toString = function() {
|
|
var out = [];
|
|
var rule, type, val;
|
|
var srcHostname, desHostname;
|
|
for ( rule in this.rules ) {
|
|
if ( this.rules.hasOwnProperty(rule) === false ) {
|
|
continue;
|
|
}
|
|
srcHostname = this.srcHostnameFromRule(rule);
|
|
desHostname = this.desHostnameFromRule(rule);
|
|
for ( type in typeBitOffsets ) {
|
|
if ( typeBitOffsets.hasOwnProperty(type) === false ) {
|
|
continue;
|
|
}
|
|
val = this.evaluateCell(srcHostname, desHostname, type);
|
|
if ( val === 0 ) {
|
|
continue;
|
|
}
|
|
out.push(
|
|
punycode.toUnicode(srcHostname) + ' ' +
|
|
punycode.toUnicode(desHostname) + ' ' +
|
|
type + ' ' +
|
|
actionToNameMap[val]
|
|
);
|
|
}
|
|
}
|
|
return out.join('\n');
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.fromString = function(text, append) {
|
|
var textEnd = text.length;
|
|
var lineBeg = 0, lineEnd;
|
|
var line, pos, fields;
|
|
var srcHostname, desHostname, type, action;
|
|
|
|
if ( append !== true ) {
|
|
this.reset();
|
|
}
|
|
|
|
while ( lineBeg < textEnd ) {
|
|
lineEnd = text.indexOf('\n', lineBeg);
|
|
if ( lineEnd < 0 ) {
|
|
lineEnd = text.indexOf('\r', lineBeg);
|
|
if ( lineEnd < 0 ) {
|
|
lineEnd = textEnd;
|
|
}
|
|
}
|
|
line = text.slice(lineBeg, lineEnd).trim();
|
|
lineBeg = lineEnd + 1;
|
|
|
|
pos = line.indexOf('# ');
|
|
if ( pos !== -1 ) {
|
|
line = line.slice(0, pos).trim();
|
|
}
|
|
if ( line === '' ) {
|
|
continue;
|
|
}
|
|
|
|
// URL net filtering rules
|
|
if ( line.indexOf('://') !== -1 ) {
|
|
continue;
|
|
}
|
|
|
|
// Valid rule syntax:
|
|
|
|
// srcHostname desHostname type state
|
|
// type = a valid request type
|
|
// state = [`block`, `allow`, `noop`]
|
|
|
|
// Lines with invalid syntax silently ignored
|
|
|
|
fields = line.split(/\s+/);
|
|
if ( fields.length !== 4 ) {
|
|
continue;
|
|
}
|
|
|
|
// Ignore special rules:
|
|
// hostname-based switch rules
|
|
if ( fields[0].slice(-1) === ':' ) {
|
|
continue;
|
|
}
|
|
|
|
srcHostname = punycode.toASCII(fields[0]);
|
|
desHostname = punycode.toASCII(fields[1]);
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/1082
|
|
// Discard rules with invalid hostnames
|
|
if ( (srcHostname !== '*' && reBadHostname.test(srcHostname)) ||
|
|
(desHostname !== '*' && reBadHostname.test(desHostname))
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
type = fields[2];
|
|
if ( typeBitOffsets.hasOwnProperty(type) === false ) {
|
|
continue;
|
|
}
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/840
|
|
// Discard invalid rules
|
|
if ( desHostname !== '*' && type !== '*' ) {
|
|
continue;
|
|
}
|
|
|
|
action = nameToActionMap[fields[3]];
|
|
if ( typeof action !== 'number' || action < 0 || action > 3 ) {
|
|
continue;
|
|
}
|
|
|
|
this.setCell(srcHostname, desHostname, type, action);
|
|
}
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.toSelfie = function() {
|
|
return {
|
|
magicId: magicId,
|
|
rules: this.rules
|
|
};
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.fromSelfie = function(selfie) {
|
|
this.rules = selfie.rules;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
return Matrix;
|
|
|
|
/******************************************************************************/
|
|
|
|
// http://youtu.be/5-K8R1hDG9E?t=31m1s
|
|
|
|
})();
|
|
|
|
/******************************************************************************/
|
|
|
|
µBlock.sessionFirewall = new µBlock.Firewall();
|
|
µBlock.permanentFirewall = new µBlock.Firewall();
|
|
|
|
/******************************************************************************/
|