You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
909 lines
26 KiB
909 lines
26 KiB
/*******************************************************************************
|
|
|
|
µMatrix - a Chromium browser extension to black/white list 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/uMatrix
|
|
*/
|
|
|
|
/* global punycode, µMatrix */
|
|
/* jshint bitwise: false */
|
|
|
|
/******************************************************************************/
|
|
|
|
µMatrix.Matrix = (function() {
|
|
|
|
/******************************************************************************/
|
|
|
|
var µm = µMatrix;
|
|
var magicId = 'tckuvvpyvswo';
|
|
|
|
/******************************************************************************/
|
|
|
|
var Matrix = function() {
|
|
this.reset();
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.Transparent = 0;
|
|
Matrix.Red = 1;
|
|
Matrix.Green = 2;
|
|
Matrix.Gray = 3;
|
|
|
|
Matrix.Indirect = 0x00;
|
|
Matrix.Direct = 0x80;
|
|
|
|
Matrix.RedDirect = Matrix.Red | Matrix.Direct;
|
|
Matrix.RedIndirect = Matrix.Red | Matrix.Indirect;
|
|
Matrix.GreenDirect = Matrix.Green | Matrix.Direct;
|
|
Matrix.GreenIndirect = Matrix.Green | Matrix.Indirect;
|
|
Matrix.GrayDirect = Matrix.Gray | Matrix.Direct;
|
|
Matrix.GrayIndirect = Matrix.Gray | Matrix.Indirect;
|
|
|
|
/******************************************************************************/
|
|
|
|
var typeBitOffsets = {
|
|
'*': 0,
|
|
'doc': 2,
|
|
'cookie': 4,
|
|
'css': 6,
|
|
'image': 8,
|
|
'plugin': 10,
|
|
'script': 12,
|
|
'xhr': 14,
|
|
'frame': 16,
|
|
'other': 18
|
|
};
|
|
|
|
var stateToNameMap = {
|
|
'1': 'block',
|
|
'2': 'allow',
|
|
'3': 'inherit'
|
|
};
|
|
|
|
var nameToStateMap = {
|
|
'block': 1,
|
|
'allow': 2,
|
|
'inherit': 3
|
|
};
|
|
|
|
var switchBitOffsets = {
|
|
'matrix-off': 0,
|
|
'https-strict': 2,
|
|
'ua-spoof': 4,
|
|
'referrer-spoof': 6
|
|
};
|
|
|
|
var switchStateToNameMap = {
|
|
'1': 'true',
|
|
'2': 'false'
|
|
};
|
|
|
|
var nameToSwitchStateMap = {
|
|
'true': 1,
|
|
'false': 2,
|
|
'on': 2, // backward compatibility
|
|
'off': 1 // backward compatibility
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
var columnHeaders = (function() {
|
|
var out = {};
|
|
var i = 0;
|
|
for ( var type in typeBitOffsets ) {
|
|
if ( typeBitOffsets.hasOwnProperty(type) === false ) {
|
|
continue;
|
|
}
|
|
out[type] = i++;
|
|
}
|
|
return out;
|
|
})();
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.getColumnHeaders = function() {
|
|
return columnHeaders;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
var switchNames = (function() {
|
|
var out = {};
|
|
for ( var switchName in switchBitOffsets ) {
|
|
if ( switchBitOffsets.hasOwnProperty(switchName) === false ) {
|
|
continue;
|
|
}
|
|
out[switchName] = true;
|
|
}
|
|
return out;
|
|
})();
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.getSwitchNames = function() {
|
|
return switchNames;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
// For performance purpose, as simple tests as possible
|
|
var reHostnameVeryCoarse = /[g-z_-]/;
|
|
var reIPv4VeryCoarse = /\.\d+$/;
|
|
|
|
// 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;
|
|
|
|
/******************************************************************************/
|
|
|
|
// Find out src-des relationship, using coarse-to-fine grained tests for
|
|
// speed. If desHostname is 1st-party to srcHostname, the domain is returned,
|
|
// otherwise the empty string.
|
|
|
|
var extractFirstPartyDesDomain = function(srcHostname, desHostname) {
|
|
if ( srcHostname === '*' || desHostname === '*' || desHostname === '1st-party' ) {
|
|
return '';
|
|
}
|
|
var desDomain = µm.URI.domainFromHostname(desHostname);
|
|
if ( desDomain === '' ) {
|
|
return '';
|
|
}
|
|
var pos = srcHostname.length - desDomain.length;
|
|
if ( pos < 0 || srcHostname.slice(pos) !== desDomain ) {
|
|
return '';
|
|
}
|
|
if ( pos !== 0 && srcHostname.charAt(pos - 1) !== '.' ) {
|
|
return '';
|
|
}
|
|
return desDomain;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.reset = function() {
|
|
this.switches = {};
|
|
this.rules = {};
|
|
this.rootValue = Matrix.GreenIndirect;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
// Copy another matrix to self. Do this incrementally to minimize impact on
|
|
// a live matrix.
|
|
|
|
Matrix.prototype.assign = function(other) {
|
|
var k;
|
|
// Remove rules not in other
|
|
for ( k in this.rules ) {
|
|
if ( this.rules.hasOwnProperty(k) === false ) {
|
|
continue;
|
|
}
|
|
if ( other.rules.hasOwnProperty(k) === false ) {
|
|
delete this.rules[k];
|
|
}
|
|
}
|
|
// Remove switches not in other
|
|
for ( k in this.switches ) {
|
|
if ( this.switches.hasOwnProperty(k) === false ) {
|
|
continue;
|
|
}
|
|
if ( other.switches.hasOwnProperty(k) === false ) {
|
|
delete this.switches[k];
|
|
}
|
|
}
|
|
// Add/change rules in other
|
|
for ( k in other.rules ) {
|
|
if ( other.rules.hasOwnProperty(k) === false ) {
|
|
continue;
|
|
}
|
|
this.rules[k] = other.rules[k];
|
|
}
|
|
// Add/change switches in other
|
|
for ( k in other.switches ) {
|
|
if ( other.switches.hasOwnProperty(k) === false ) {
|
|
continue;
|
|
}
|
|
this.switches[k] = other.switches[k];
|
|
}
|
|
return this;
|
|
};
|
|
|
|
// https://www.youtube.com/watch?v=e9RS4biqyAc
|
|
|
|
/******************************************************************************/
|
|
|
|
// If value is undefined, the switch is removed
|
|
|
|
Matrix.prototype.setSwitch = function(switchName, srcHostname, newVal) {
|
|
var bitOffset = switchBitOffsets[switchName];
|
|
if ( bitOffset === undefined ) {
|
|
return false;
|
|
}
|
|
if ( newVal === this.evaluateSwitch(switchName, srcHostname) ) {
|
|
return false;
|
|
}
|
|
var bits = this.switches[srcHostname] || 0;
|
|
bits &= ~(3 << bitOffset);
|
|
bits |= newVal << bitOffset;
|
|
if ( bits === 0 ) {
|
|
delete this.switches[srcHostname];
|
|
} else {
|
|
this.switches[srcHostname] = bits;
|
|
}
|
|
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.blacklistCell = function(srcHostname, desHostname, type) {
|
|
var r = this.evaluateCellZ(srcHostname, desHostname, type);
|
|
if ( r === 1 ) {
|
|
return false;
|
|
}
|
|
this.setCell(srcHostname, desHostname, type, 0);
|
|
r = this.evaluateCellZ(srcHostname, desHostname, type);
|
|
if ( r === 1 ) {
|
|
return true;
|
|
}
|
|
this.setCell(srcHostname, desHostname, type, 1);
|
|
return true;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.whitelistCell = function(srcHostname, desHostname, type) {
|
|
var r = this.evaluateCellZ(srcHostname, desHostname, type);
|
|
if ( r === 2 ) {
|
|
return false;
|
|
}
|
|
this.setCell(srcHostname, desHostname, type, 0);
|
|
r = this.evaluateCellZ(srcHostname, desHostname, type);
|
|
if ( r === 2 ) {
|
|
return true;
|
|
}
|
|
this.setCell(srcHostname, desHostname, type, 2);
|
|
return true;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.graylistCell = function(srcHostname, desHostname, type) {
|
|
var r = this.evaluateCellZ(srcHostname, desHostname, type);
|
|
if ( r === 0 || r === 3 ) {
|
|
return false;
|
|
}
|
|
this.setCell(srcHostname, desHostname, type, 0);
|
|
r = this.evaluateCellZ(srcHostname, desHostname, type);
|
|
if ( r === 0 || r === 3 ) {
|
|
return true;
|
|
}
|
|
this.setCell(srcHostname, desHostname, type, 3);
|
|
return true;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
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.evaluateCellZ = function(srcHostname, desHostname, type) {
|
|
var bitOffset = typeBitOffsets[type];
|
|
var s = srcHostname;
|
|
var v;
|
|
for (;;) {
|
|
v = this.rules[s + ' ' + desHostname];
|
|
if ( v !== undefined ) {
|
|
v = v >> bitOffset & 3;
|
|
if ( v !== 0 ) {
|
|
return v;
|
|
}
|
|
}
|
|
// TODO: external rules? (for presets)
|
|
s = toBroaderHostname(s);
|
|
if ( s === '' ) {
|
|
break;
|
|
}
|
|
}
|
|
// srcHostname is '*' at this point
|
|
|
|
// Preset blacklisted hostnames are blacklisted in global scope
|
|
if ( type === '*' && µm.ubiquitousBlacklist.test(desHostname) ) {
|
|
return 1;
|
|
}
|
|
|
|
// https://github.com/gorhill/uMatrix/issues/65
|
|
// Hardcoded global `doc` rule
|
|
if ( type === 'doc' && desHostname === '*' ) {
|
|
return 2;
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.evaluateCellZXY = function(srcHostname, desHostname, type) {
|
|
// Matrix filtering switch
|
|
if ( this.evaluateSwitchZ('matrix-off', srcHostname) ) {
|
|
return Matrix.GreenIndirect;
|
|
}
|
|
|
|
// TODO: There are cells evaluated twice when the type is '*'. Unsure
|
|
// whether it's worth trying to avoid that, as this could introduce
|
|
// overhead which may not be gained back by skipping the redundant tests.
|
|
// And this happens *only* when building the matrix UI, not when
|
|
// evaluating net requests.
|
|
|
|
// Specific-hostname specific-type cell
|
|
var r = this.evaluateCellZ(srcHostname, desHostname, type);
|
|
if ( r === 1 ) { return Matrix.RedDirect; }
|
|
if ( r === 2 ) { return Matrix.GreenDirect; }
|
|
|
|
// Specific-hostname any-type cell
|
|
var rl = this.evaluateCellZ(srcHostname, desHostname, '*');
|
|
if ( rl === 1 ) { return Matrix.RedIndirect; }
|
|
|
|
var d = desHostname;
|
|
var firstPartyDesDomain = extractFirstPartyDesDomain(srcHostname, desHostname);
|
|
|
|
// Ancestor cells, up to 1st-party destination domain
|
|
if ( firstPartyDesDomain !== '' ) {
|
|
for (;;) {
|
|
if ( d === firstPartyDesDomain ) {
|
|
break;
|
|
}
|
|
d = d.slice(d.indexOf('.') + 1);
|
|
|
|
// specific-hostname specific-type cell
|
|
r = this.evaluateCellZ(srcHostname, d, type);
|
|
if ( r === 1 ) { return Matrix.RedIndirect; }
|
|
if ( r === 2 ) { return Matrix.GreenIndirect; }
|
|
// Do not override a narrower rule
|
|
if ( rl !== 2 ) {
|
|
rl = this.evaluateCellZ(srcHostname, d, '*');
|
|
if ( rl === 1 ) { return Matrix.RedIndirect; }
|
|
}
|
|
}
|
|
|
|
// 1st-party specific-type cell: it's a special row, it exists only in
|
|
// global scope.
|
|
r = this.evaluateCellZ(srcHostname, '1st-party', type);
|
|
if ( r === 1 ) { return Matrix.RedIndirect; }
|
|
if ( r === 2 ) { return Matrix.GreenIndirect; }
|
|
// Do not override narrower rule
|
|
if ( rl !== 2 ) {
|
|
rl = this.evaluateCellZ(srcHostname, '1st-party', '*');
|
|
if ( rl === 1 ) { return Matrix.RedIndirect; }
|
|
}
|
|
}
|
|
|
|
// Keep going, up to root
|
|
for (;;) {
|
|
d = toBroaderHostname(d);
|
|
if ( d === '*' ) {
|
|
break;
|
|
}
|
|
|
|
// specific-hostname specific-type cell
|
|
r = this.evaluateCellZ(srcHostname, d, type);
|
|
if ( r === 1 ) { return Matrix.RedIndirect; }
|
|
if ( r === 2 ) { return Matrix.GreenIndirect; }
|
|
// Do not override narrower rule
|
|
if ( rl !== 2 ) {
|
|
rl = this.evaluateCellZ(srcHostname, d, '*');
|
|
if ( rl === 1 ) { return Matrix.RedIndirect; }
|
|
}
|
|
}
|
|
|
|
// Any-hostname specific-type cells
|
|
r = this.evaluateCellZ(srcHostname, '*', type);
|
|
// Line below is strict-blocking
|
|
if ( r === 1 ) { return Matrix.RedIndirect; }
|
|
// Narrower rule wins
|
|
if ( rl === 2 ) { return Matrix.GreenIndirect; }
|
|
if ( r === 2 ) { return Matrix.GreenIndirect; }
|
|
|
|
// Any-hostname any-type cell
|
|
r = this.evaluateCellZ(srcHostname, '*', '*');
|
|
if ( r === 1 ) { return Matrix.RedIndirect; }
|
|
if ( r === 2 ) { return Matrix.GreenIndirect; }
|
|
return this.rootValue;
|
|
};
|
|
|
|
// https://www.youtube.com/watch?v=4C5ZkwrnVfM
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.evaluateRowZXY = function(srcHostname, desHostname) {
|
|
var out = [];
|
|
for ( var type in typeBitOffsets ) {
|
|
if ( typeBitOffsets.hasOwnProperty(type) === false ) {
|
|
continue;
|
|
}
|
|
out.push(this.evaluateCellZXY(srcHostname, desHostname, type));
|
|
}
|
|
return out;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.mustBlock = function(srcHostname, desHostname, type) {
|
|
return (this.evaluateCellZXY(srcHostname, desHostname, type) & 3) === Matrix.Red;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.srcHostnameFromRule = function(rule) {
|
|
return rule.slice(0, rule.indexOf(' '));
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.desHostnameFromRule = function(rule) {
|
|
return rule.slice(rule.indexOf(' ') + 1);
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.setSwitchZ = function(switchName, srcHostname, newState) {
|
|
var bitOffset = switchBitOffsets[switchName];
|
|
if ( bitOffset === undefined ) {
|
|
return false;
|
|
}
|
|
var state = this.evaluateSwitchZ(switchName, srcHostname);
|
|
if ( newState === state ) {
|
|
return false;
|
|
}
|
|
if ( newState === undefined ) {
|
|
newState = !state;
|
|
}
|
|
var bits = this.switches[srcHostname] || 0;
|
|
bits &= ~(3 << bitOffset);
|
|
if ( bits === 0 ) {
|
|
delete this.switches[srcHostname];
|
|
} else {
|
|
this.switches[srcHostname] = bits;
|
|
}
|
|
state = this.evaluateSwitchZ(switchName, srcHostname);
|
|
if ( state === newState ) {
|
|
return true;
|
|
}
|
|
this.switches[srcHostname] = bits | ((newState ? 1 : 2) << bitOffset);
|
|
return true;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
// 0 = inherit from broader scope, up to default state
|
|
// 1 = non-default state
|
|
// 2 = forced default state (to override a broader non-default state)
|
|
|
|
Matrix.prototype.evaluateSwitch = function(switchName, srcHostname) {
|
|
var bits = this.switches[srcHostname] || 0;
|
|
if ( bits === 0 ) {
|
|
return 0;
|
|
}
|
|
var bitOffset = switchBitOffsets[switchName];
|
|
if ( bitOffset === undefined ) {
|
|
return 0;
|
|
}
|
|
return (bits >> bitOffset) & 3;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.evaluateSwitchZ = function(switchName, srcHostname) {
|
|
var bitOffset = switchBitOffsets[switchName];
|
|
if ( bitOffset === undefined ) {
|
|
return false;
|
|
}
|
|
var bits;
|
|
var s = srcHostname;
|
|
for (;;) {
|
|
bits = this.switches[s] || 0;
|
|
if ( bits !== 0 ) {
|
|
bits = bits >> bitOffset & 3;
|
|
if ( bits !== 0 ) {
|
|
return bits === 1;
|
|
}
|
|
}
|
|
s = toBroaderHostname(s);
|
|
if ( s === '' ) {
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
// TODO: In all likelyhood, will have to optmize here, i.e. keeping an
|
|
// up-to-date collection of src hostnames with reference count etc.
|
|
|
|
Matrix.prototype.extractAllSourceHostnames = function() {
|
|
var srcHostnames = {};
|
|
var rules = this.rules;
|
|
for ( var rule in rules ) {
|
|
if ( rules.hasOwnProperty(rule) === false ) {
|
|
continue;
|
|
}
|
|
srcHostnames[rule.slice(0, rule.indexOf(' '))] = true;
|
|
}
|
|
return srcHostnames;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
// TODO: In all likelyhood, will have to optmize here, i.e. keeping an
|
|
// up-to-date collection of src hostnames with reference count etc.
|
|
|
|
Matrix.prototype.extractAllDestinationHostnames = function() {
|
|
var desHostnames = {};
|
|
var rules = this.rules;
|
|
for ( var rule in rules ) {
|
|
if ( rules.hasOwnProperty(rule) === false ) {
|
|
continue;
|
|
}
|
|
desHostnames[this.desHostnameFromRule(rule)] = true;
|
|
}
|
|
return desHostnames;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.toString = function() {
|
|
var out = [];
|
|
var rule, type, switchName, 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 + ' ' +
|
|
stateToNameMap[val]
|
|
);
|
|
}
|
|
}
|
|
for ( srcHostname in this.switches ) {
|
|
if ( this.switches.hasOwnProperty(srcHostname) === false ) {
|
|
continue;
|
|
}
|
|
for ( switchName in switchBitOffsets ) {
|
|
if ( switchBitOffsets.hasOwnProperty(switchName) === false ) {
|
|
continue;
|
|
}
|
|
val = this.evaluateSwitch(switchName, srcHostname);
|
|
if ( val === 0 ) {
|
|
continue;
|
|
}
|
|
out.push(switchName + ': ' + srcHostname + ' ' + switchStateToNameMap[val]);
|
|
}
|
|
}
|
|
return out.join('\n');
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.fromString = function(text, append) {
|
|
var matrix = append ? this : new Matrix();
|
|
var textEnd = text.length;
|
|
var lineBeg = 0, lineEnd;
|
|
var line, pos;
|
|
var fields, fieldVal;
|
|
var switchName;
|
|
var srcHostname = '';
|
|
var desHostname = '';
|
|
var type, state;
|
|
|
|
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;
|
|
}
|
|
|
|
fields = line.split(/\s+/);
|
|
|
|
// Less than 2 fields makes no sense
|
|
if ( fields.length < 2 ) {
|
|
continue;
|
|
}
|
|
|
|
fieldVal = fields[0];
|
|
|
|
// Special directives:
|
|
|
|
// title
|
|
pos = fieldVal.indexOf('title:');
|
|
if ( pos !== -1 ) {
|
|
// TODO
|
|
continue;
|
|
}
|
|
|
|
// Name
|
|
pos = fieldVal.indexOf('name:');
|
|
if ( pos !== -1 ) {
|
|
// TODO
|
|
continue;
|
|
}
|
|
|
|
// Switch on/off
|
|
|
|
// `switch:` srcHostname state
|
|
// state = [`true`, `false`]
|
|
switchName = '';
|
|
if ( fieldVal === 'switch:' || fieldVal === 'matrix:' ) {
|
|
fieldVal = 'matrix-off:';
|
|
}
|
|
pos = fieldVal.indexOf(':');
|
|
if ( pos !== -1 ) {
|
|
switchName = fieldVal.slice(0, pos);
|
|
}
|
|
if ( switchBitOffsets.hasOwnProperty(switchName) ) {
|
|
srcHostname = punycode.toASCII(fields[1]);
|
|
|
|
// No state field: reject
|
|
fieldVal = fields[2];
|
|
if ( fieldVal === null ) {
|
|
continue;
|
|
}
|
|
// Unknown state: reject
|
|
if ( nameToSwitchStateMap.hasOwnProperty(fieldVal) === false ) {
|
|
continue;
|
|
}
|
|
|
|
matrix.setSwitch(switchName, srcHostname, nameToSwitchStateMap[fieldVal]);
|
|
continue;
|
|
}
|
|
|
|
// Unknown directive
|
|
pos = fieldVal.indexOf(':');
|
|
if ( pos !== -1 ) {
|
|
continue;
|
|
}
|
|
|
|
// Valid rule syntax:
|
|
|
|
// srcHostname desHostname [type [state]]
|
|
// type = a valid request type
|
|
// state = [`block`, `allow`, `inherit`]
|
|
|
|
// srcHostname desHostname type
|
|
// type = a valid request type
|
|
// state = `allow`
|
|
|
|
// srcHostname desHostname
|
|
// type = `*`
|
|
// state = `allow`
|
|
|
|
// Lines with invalid syntax silently ignored
|
|
|
|
srcHostname = punycode.toASCII(fields[0]);
|
|
desHostname = punycode.toASCII(fields[1]);
|
|
|
|
fieldVal = fields[2];
|
|
|
|
if ( fieldVal !== undefined ) {
|
|
type = fieldVal;
|
|
// Unknown type: reject
|
|
if ( typeBitOffsets.hasOwnProperty(type) === false ) {
|
|
continue;
|
|
}
|
|
} else {
|
|
type = '*';
|
|
}
|
|
|
|
fieldVal = fields[3];
|
|
|
|
if ( fieldVal !== undefined ) {
|
|
// Unknown state: reject
|
|
if ( nameToStateMap.hasOwnProperty(fieldVal) === false ) {
|
|
continue;
|
|
}
|
|
state = nameToStateMap[fieldVal];
|
|
} else {
|
|
state = 2;
|
|
}
|
|
|
|
matrix.setCell(srcHostname, desHostname, type, state);
|
|
}
|
|
|
|
if ( !append ) {
|
|
this.assign(matrix);
|
|
}
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.toSelfie = function() {
|
|
return {
|
|
magicId: magicId,
|
|
switches: this.switches,
|
|
rules: this.rules
|
|
};
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.fromSelfie = function(selfie) {
|
|
this.switches = selfie.switches;
|
|
this.rules = selfie.rules;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.diff = function(other, srcHostname, desHostnames) {
|
|
var out = [];
|
|
var desHostname, type;
|
|
var switchName, i, thisVal, otherVal;
|
|
for (;;) {
|
|
for ( switchName in switchBitOffsets ) {
|
|
if ( switchBitOffsets.hasOwnProperty(switchName) === false ) {
|
|
continue;
|
|
}
|
|
thisVal = this.evaluateSwitch(switchName, srcHostname);
|
|
otherVal = other.evaluateSwitch(switchName, srcHostname);
|
|
if ( thisVal !== otherVal ) {
|
|
out.push({
|
|
'what': switchName,
|
|
'src': srcHostname
|
|
});
|
|
}
|
|
}
|
|
i = desHostnames.length;
|
|
while ( i-- ) {
|
|
desHostname = desHostnames[i];
|
|
for ( type in typeBitOffsets ) {
|
|
if ( typeBitOffsets.hasOwnProperty(type) === false ) {
|
|
continue;
|
|
}
|
|
thisVal = this.evaluateCell(srcHostname, desHostname, type);
|
|
otherVal = other.evaluateCell(srcHostname, desHostname, type);
|
|
if ( thisVal === otherVal ) {
|
|
continue;
|
|
}
|
|
out.push({
|
|
'what': 'rule',
|
|
'src': srcHostname,
|
|
'des': desHostname,
|
|
'type': type
|
|
});
|
|
}
|
|
}
|
|
srcHostname = toBroaderHostname(srcHostname);
|
|
if ( srcHostname === '' ) {
|
|
break;
|
|
}
|
|
}
|
|
return out;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
Matrix.prototype.applyDiff = function(diff, from) {
|
|
var changed = false;
|
|
var i = diff.length;
|
|
var action, val;
|
|
while ( i-- ) {
|
|
action = diff[i];
|
|
if ( action.what === 'rule' ) {
|
|
val = from.evaluateCell(action.src, action.des, action.type);
|
|
changed = this.setCell(action.src, action.des, action.type, val) || changed;
|
|
continue;
|
|
}
|
|
if ( switchBitOffsets.hasOwnProperty(action.what) ) {
|
|
val = from.evaluateSwitch(action.what, action.src);
|
|
changed = this.setSwitch(action.what, action.src, val) || changed;
|
|
continue;
|
|
}
|
|
}
|
|
return changed;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
return Matrix;
|
|
|
|
/******************************************************************************/
|
|
|
|
// https://www.youtube.com/watch?v=wlNrQGmj6oQ
|
|
|
|
})();
|
|
|
|
/******************************************************************************/
|