diff --git a/src/js/benchmarks.js b/src/js/benchmarks.js index fc7f987d1..7f9405f4f 100644 --- a/src/js/benchmarks.js +++ b/src/js/benchmarks.js @@ -31,7 +31,7 @@ import staticNetFilteringEngine from './static-net-filtering.js'; import µb from './background.js'; import { FilteringContext } from './filtering-context.js'; import { LineIterator } from './text-utils.js'; -import { sessionFirewall } from './dynamic-net-filtering.js'; +import { sessionFirewall } from './filtering-engines.js'; import { domainFromHostname, diff --git a/src/js/codemirror/ubo-dynamic-filtering.js b/src/js/codemirror/ubo-dynamic-filtering.js index d7f431238..4e670d5f7 100644 --- a/src/js/codemirror/ubo-dynamic-filtering.js +++ b/src/js/codemirror/ubo-dynamic-filtering.js @@ -152,13 +152,9 @@ CodeMirror.defineMode('ubo-dynamic-filtering', ( ) => { match = reToken.exec(string); if ( match === null ) { return; } // hostname or url - const isURLRule = isSwitchRule === false && match[0].includes('://'); + const isURLRule = isSwitchRule === false && match[0].indexOf('://') > 0; if ( isURLRule ) { - if ( isSwitchRule ) { - string = addMatchSlice(match, 'error'); - } else { - string = addMatchSlice(match, sortType === 2 ? 'sortkey' : null); - } + string = addMatchSlice(match, sortType === 2 ? 'sortkey' : null); } else if ( isValidHostname(match[0]) === false ) { string = addMatchSlice(match, 'error'); } else if ( sortType === 1 && isSwitchRule || sortType === 2 ) { diff --git a/src/js/dynamic-net-filtering.js b/src/js/dynamic-net-filtering.js index 5470c98f2..9c950a0e8 100644 --- a/src/js/dynamic-net-filtering.js +++ b/src/js/dynamic-net-filtering.js @@ -19,14 +19,10 @@ Home: https://github.com/gorhill/uBlock */ -/* jshint bitwise: false */ - 'use strict'; /******************************************************************************/ -import '../lib/punycode.js'; - import globals from './globals.js'; import { LineIterator } from './text-utils.js'; @@ -37,18 +33,24 @@ import { /******************************************************************************/ -const punycode = globals.punycode; +const punycode = globals.punycode || undefined; -const supportedDynamicTypes = { +// Object.create(null) is used below to eliminate worries about unexpected +// property names in prototype chain -- and this way we don't have to use +// hasOwnProperty() to avoid this. + +const supportedDynamicTypes = Object.create(null); +Object.assign(supportedDynamicTypes, { '3p': true, 'image': true, 'inline-script': true, '1p-script': true, '3p-script': true, '3p-frame': true -}; +}); -const typeBitOffsets = { +const typeBitOffsets = Object.create(null); +Object.assign(typeBitOffsets, { '*': 0, 'inline-script': 2, '1p-script': 4, @@ -56,27 +58,30 @@ const typeBitOffsets = { '3p-frame': 8, 'image': 10, '3p': 12 -}; +}); -const actionToNameMap = { - '1': 'block', - '2': 'allow', - '3': 'noop' -}; - -const nameToActionMap = { +const nameToActionMap = Object.create(null); +Object.assign(nameToActionMap, { 'block': 1, 'allow': 2, 'noop': 3 -}; +}); -/******************************************************************************/ +const intToActionMap = new Map([ + [ 1, 'block' ], + [ 2, 'allow' ], + [ 3, 'noop' ] +]); // For performance purpose, as simple tests as possible const reBadHostname = /[^0-9a-z_.\[\]:%-]/; const reNotASCII = /[^\x20-\x7F]/; +const decomposedSource = []; +const decomposedDestination = []; -const is3rdParty = function(srcHostname, desHostname) { +/******************************************************************************/ + +function is3rdParty(srcHostname, desHostname) { // If at least one is party-less, the relation can't be labelled // "3rd-party" if ( desHostname === '*' || srcHostname === '*' || srcHostname === '' ) { @@ -95,17 +100,16 @@ const is3rdParty = function(srcHostname, desHostname) { // Do not confuse 'example.com' with 'anotherexample.com' return desHostname.length !== srcDomain.length && desHostname.charAt(desHostname.length - srcDomain.length - 1) !== '.'; -}; +} /******************************************************************************/ -const Matrix = class { +class DynamicHostRuleFiltering { constructor() { this.reset(); } - reset() { this.r = 0; this.type = ''; @@ -113,11 +117,8 @@ const Matrix = class { this.z = ''; this.rules = new Map(); this.changed = false; - this.decomposedSource = []; - this.decomposedDestination = []; } - assign(other) { // Remove rules not in other for ( const k of this.rules.keys() ) { @@ -135,7 +136,6 @@ const Matrix = class { } } - copyRules(from, srcHostname, desHostnames) { // Specific types let thisBits = this.rules.get('* *'); @@ -149,7 +149,7 @@ const Matrix = class { this.changed = true; } - let key = srcHostname + ' *'; + let key = `${srcHostname} *`; thisBits = this.rules.get(key); fromBits = from.rules.get(key); if ( fromBits !== thisBits ) { @@ -163,10 +163,7 @@ const Matrix = class { // Specific destinations for ( const desHostname in desHostnames ) { - if ( desHostnames.hasOwnProperty(desHostname) === false ) { - continue; - } - key = '* ' + desHostname; + key = `* ${desHostname}`; thisBits = this.rules.get(key); fromBits = from.rules.get(key); if ( fromBits !== thisBits ) { @@ -177,7 +174,7 @@ const Matrix = class { } this.changed = true; } - key = srcHostname + ' ' + desHostname ; + key = `${srcHostname} ${desHostname}` ; thisBits = this.rules.get(key); fromBits = from.rules.get(key); if ( fromBits !== thisBits ) { @@ -193,7 +190,6 @@ const Matrix = class { return this.changed; } - // - * * type // - from * type // - * to * @@ -202,20 +198,16 @@ const Matrix = class { hasSameRules(other, srcHostname, desHostnames) { // Specific types let key = '* *'; - if ( this.rules.get(key) !== other.rules.get(key) ) { - return false; - } - key = srcHostname + ' *'; - if ( this.rules.get(key) !== other.rules.get(key) ) { - return false; - } + if ( this.rules.get(key) !== other.rules.get(key) ) { return false; } + key = `${srcHostname} *`; + if ( this.rules.get(key) !== other.rules.get(key) ) { return false; } // Specific destinations for ( const desHostname in desHostnames ) { - key = '* ' + desHostname; + key = `* ${desHostname}`; if ( this.rules.get(key) !== other.rules.get(key) ) { return false; } - key = srcHostname + ' ' + desHostname ; + key = `${srcHostname} ${desHostname}`; if ( this.rules.get(key) !== other.rules.get(key) ) { return false; } @@ -223,15 +215,12 @@ const Matrix = class { return true; } - setCell(srcHostname, desHostname, type, state) { const bitOffset = typeBitOffsets[type]; - const k = srcHostname + ' ' + desHostname; + const k = `${srcHostname} ${desHostname}`; const oldBitmap = this.rules.get(k) || 0; const newBitmap = oldBitmap & ~(3 << bitOffset) | (state << bitOffset); - if ( newBitmap === oldBitmap ) { - return false; - } + if ( newBitmap === oldBitmap ) { return false; } if ( newBitmap === 0 ) { this.rules.delete(k); } else { @@ -241,54 +230,44 @@ const Matrix = class { return true; } - unsetCell(srcHostname, desHostname, type) { this.evaluateCellZY(srcHostname, desHostname, type); - if ( this.r === 0 ) { - return false; - } + if ( this.r === 0 ) { return false; } this.setCell(srcHostname, desHostname, type, 0); this.changed = true; return true; } - evaluateCell(srcHostname, desHostname, type) { - const key = srcHostname + ' ' + desHostname; + const key = `${srcHostname} ${desHostname}`; const bitmap = this.rules.get(key); if ( bitmap === undefined ) { return 0; } return bitmap >> typeBitOffsets[type] & 3; } - clearRegisters() { this.r = 0; this.type = this.y = this.z = ''; return this; } - evaluateCellZ(srcHostname, desHostname, type) { - decomposeHostname(srcHostname, this.decomposedSource); + decomposeHostname(srcHostname, decomposedSource); this.type = type; const bitOffset = typeBitOffsets[type]; - for ( const shn of this.decomposedSource ) { - this.z = shn; - let v = this.rules.get(shn + ' ' + desHostname); - if ( v !== undefined ) { - v = v >>> bitOffset & 3; - if ( v !== 0 ) { - this.r = v; - return v; - } - } + for ( const srchn of decomposedSource ) { + this.z = srchn; + let v = this.rules.get(`${srchn} ${desHostname}`); + if ( v === undefined ) { continue; } + v = v >>> bitOffset & 3; + if ( v === 0 ) { continue; } + return (this.r = v); } // srcHostname is '*' at this point this.r = 0; return 0; } - evaluateCellZY(srcHostname, desHostname, type) { // Pathological cases. if ( desHostname === '' ) { @@ -299,11 +278,11 @@ const Matrix = class { // Precedence: from most specific to least specific // Specific-destination, any party, any type - decomposeHostname(desHostname, this.decomposedDestination); - for ( const dhn of this.decomposedDestination ) { - if ( dhn === '*' ) { break; } - this.y = dhn; - if ( this.evaluateCellZ(srcHostname, dhn, '*') !== 0 ) { + decomposeHostname(desHostname, decomposedDestination); + for ( const deshn of decomposedDestination ) { + if ( deshn === '*' ) { break; } + this.y = deshn; + if ( this.evaluateCellZ(srcHostname, deshn, '*') !== 0 ) { return this.r; } } @@ -338,7 +317,7 @@ const Matrix = class { } // Any destination, any party, specific type - if ( supportedDynamicTypes.hasOwnProperty(type) ) { + if ( supportedDynamicTypes[type] !== undefined ) { if ( this.evaluateCellZ(srcHostname, '*', type) !== 0 ) { return this.r; } @@ -353,87 +332,72 @@ const Matrix = class { return 0; } - mustAllowCellZY(srcHostname, desHostname, type) { return this.evaluateCellZY(srcHostname, desHostname, type) === 2; } - mustBlockOrAllow() { return this.r === 1 || this.r === 2; } - mustBlock() { return this.r === 1; } - mustAbort() { return this.r === 3; } - lookupRuleData(src, des, type) { const r = this.evaluateCellZY(src, des, type); if ( r === 0 ) { return; } return `${this.z} ${this.y} ${this.type} ${r}`; } - toLogData() { if ( this.r === 0 || this.type === '' ) { return; } return { source: 'dynamicHost', result: this.r, - raw: `${this.z} ${this.y} ${this.type} ${this.intToActionMap.get(this.r)}` + raw: `${this.z} ${this.y} ${this.type} ${intToActionMap.get(this.r)}` }; } - srcHostnameFromRule(rule) { return rule.slice(0, rule.indexOf(' ')); } - desHostnameFromRule(rule) { return rule.slice(rule.indexOf(' ') + 1); } - toArray() { - const out = [], - toUnicode = punycode.toUnicode; + const out = []; for ( const key of this.rules.keys() ) { - let srcHostname = this.srcHostnameFromRule(key); - let desHostname = this.desHostnameFromRule(key); + const srchn = this.srcHostnameFromRule(key); + const deshn = this.desHostnameFromRule(key); + const srchnPretty = srchn.includes('xn--') && punycode + ? punycode.toUnicode(srchn) + : srchn; + const deshnPretty = deshn.includes('xn--') && punycode + ? punycode.toUnicode(deshn) + : deshn; for ( const type in typeBitOffsets ) { - if ( typeBitOffsets.hasOwnProperty(type) === false ) { continue; } - const val = this.evaluateCell(srcHostname, desHostname, type); + if ( typeBitOffsets[type] === undefined ) { continue; } + const val = this.evaluateCell(srchn, deshn, type); if ( val === 0 ) { continue; } - if ( srcHostname.indexOf('xn--') !== -1 ) { - srcHostname = toUnicode(srcHostname); - } - if ( desHostname.indexOf('xn--') !== -1 ) { - desHostname = toUnicode(desHostname); - } - out.push( - srcHostname + ' ' + - desHostname + ' ' + - type + ' ' + - actionToNameMap[val] - ); + const action = intToActionMap.get(val); + if ( action === undefined ) { continue; } + out.push(`${srchnPretty} ${deshnPretty} ${type} ${action}`); } } return out; } - toString() { return this.toArray().join('\n'); } - fromString(text, append) { const lineIter = new LineIterator(text); if ( append !== true ) { this.reset(); } @@ -442,7 +406,6 @@ const Matrix = class { } } - validateRuleParts(parts) { if ( parts.length < 4 ) { return; } @@ -450,19 +413,25 @@ const Matrix = class { if ( parts[0].endsWith(':') ) { return; } // Ignore URL-based rules - if ( parts[1].indexOf('/') !== -1 ) { return; } + if ( parts[1].includes('/') ) { return; } - if ( typeBitOffsets.hasOwnProperty(parts[2]) === false ) { return; } + if ( typeBitOffsets[parts[2]] === undefined ) { return; } - if ( nameToActionMap.hasOwnProperty(parts[3]) === false ) { return; } + if ( nameToActionMap[parts[3]] === undefined ) { return; } // https://github.com/chrisaljoudi/uBlock/issues/840 // Discard invalid rules if ( parts[1] !== '*' && parts[2] !== '*' ) { return; } - // Performance: avoid punycoding if hostnames are made only of ASCII chars. - if ( reNotASCII.test(parts[0]) ) { parts[0] = punycode.toASCII(parts[0]); } - if ( reNotASCII.test(parts[1]) ) { parts[1] = punycode.toASCII(parts[1]); } + // Performance: avoid punycoding when only ASCII chars + if ( punycode !== undefined ) { + if ( reNotASCII.test(parts[0]) ) { + parts[0] = punycode.toASCII(parts[0]); + } + if ( reNotASCII.test(parts[1]) ) { + parts[1] = punycode.toASCII(parts[1]); + } + } // https://github.com/chrisaljoudi/uBlock/issues/1082 // Discard rules with invalid hostnames @@ -476,7 +445,6 @@ const Matrix = class { return parts; } - addFromRuleParts(parts) { if ( this.validateRuleParts(parts) !== undefined ) { this.setCell(parts[0], parts[1], parts[2], nameToActionMap[parts[3]]); @@ -485,7 +453,6 @@ const Matrix = class { return false; } - removeFromRuleParts(parts) { if ( this.validateRuleParts(parts) !== undefined ) { this.setCell(parts[0], parts[1], parts[2], 0); @@ -494,7 +461,6 @@ const Matrix = class { return false; } - toSelfie() { return { magicId: this.magicId, @@ -502,28 +468,18 @@ const Matrix = class { }; } - fromSelfie(selfie) { if ( selfie.magicId !== this.magicId ) { return false; } this.rules = new Map(selfie.rules); this.changed = true; return true; } -}; +} -Matrix.prototype.intToActionMap = new Map([ - [ 1, 'block' ], - [ 2, 'allow' ], - [ 3, 'noop' ] -]); - -Matrix.prototype.magicId = 1; +DynamicHostRuleFiltering.prototype.magicId = 1; /******************************************************************************/ -const sessionFirewall = new Matrix(); -const permanentFirewall = new Matrix(); - -export { permanentFirewall, sessionFirewall }; +export default DynamicHostRuleFiltering; /******************************************************************************/ diff --git a/src/js/filtering-engines.js b/src/js/filtering-engines.js new file mode 100644 index 000000000..9b64ed88f --- /dev/null +++ b/src/js/filtering-engines.js @@ -0,0 +1,50 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2014-present 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 +*/ + +'use strict'; + +/******************************************************************************/ + +import DynamicHostRuleFiltering from './dynamic-net-filtering.js'; +import DynamicSwitchRuleFiltering from './hnswitches.js'; +import DynamicURLRuleFiltering from './url-net-filtering.js'; + +/******************************************************************************/ + +const permanentFirewall = new DynamicHostRuleFiltering(); +const sessionFirewall = new DynamicHostRuleFiltering(); + +const permanentURLFiltering = new DynamicURLRuleFiltering(); +const sessionURLFiltering = new DynamicURLRuleFiltering(); + +const permanentSwitches = new DynamicSwitchRuleFiltering(); +const sessionSwitches = new DynamicSwitchRuleFiltering(); + +/******************************************************************************/ + +export { + permanentFirewall, + sessionFirewall, + permanentURLFiltering, + sessionURLFiltering, + permanentSwitches, + sessionSwitches, +}; diff --git a/src/js/hnswitches.js b/src/js/hnswitches.js index 876d4b835..5ff8dd61b 100644 --- a/src/js/hnswitches.js +++ b/src/js/hnswitches.js @@ -25,25 +25,21 @@ /******************************************************************************/ -import '../lib/punycode.js'; - import globals from './globals.js'; -import { LineIterator } from './text-utils.js'; import { decomposeHostname } from './uri-utils.js'; +import { LineIterator } from './text-utils.js'; /******************************************************************************/ -const punycode = globals.punycode; +const punycode = globals.punycode || undefined; +const decomposedSource = []; -/******************************************************************************/ +// Object.create(null) is used below to eliminate worries about unexpected +// property names in prototype chain -- and this way we don't have to use +// hasOwnProperty() to avoid this. -const HnSwitches = function() { - this.reset(); -}; - -/******************************************************************************/ - -const switchBitOffsets = { +const switchBitOffsets = Object.create(null); +Object.assign(switchBitOffsets, { 'no-strict-blocking': 0, 'no-popups': 2, 'no-cosmetic-filtering': 4, @@ -51,23 +47,25 @@ const switchBitOffsets = { 'no-large-media': 8, 'no-csp-reports': 10, 'no-scripting': 12, -}; +}); -const switchStateToNameMap = { +const switchStateToNameMap = Object.create(null); +Object.assign(switchStateToNameMap, { '1': 'true', - '2': 'false' -}; + '2': 'false', +}); -const nameToSwitchStateMap = { +const nameToSwitchStateMap = Object.create(null); +Object.assign(nameToSwitchStateMap, { 'true': 1, 'false': 2, 'on': 1, - 'off': 2 -}; + 'off': 2, +}); /******************************************************************************/ -// For performance purpose, as simple tests as possible +// For performance purpose, as simple test as possible const reNotASCII = /[^\x20-\x7F]/; // http://tools.ietf.org/html/rfc5952 @@ -76,256 +74,216 @@ const reNotASCII = /[^\x20-\x7F]/; /******************************************************************************/ -HnSwitches.prototype.reset = function() { - this.switches = new Map(); - this.n = ''; - this.z = ''; - this.r = 0; - this.changed = true; - this.decomposedSource = []; -}; - -/******************************************************************************/ - -HnSwitches.prototype.assign = function(from) { - // Remove rules not in other - for ( const hn of this.switches.keys() ) { - if ( from.switches.has(hn) === false ) { - this.switches.delete(hn); - this.changed = true; - } +class DynamicSwitchRuleFiltering { + constructor() { + this.reset(); } - // Add/change rules in other - for ( const [hn, bits] of from.switches ) { - if ( this.switches.get(hn) !== bits ) { - this.switches.set(hn, bits); - this.changed = true; - } - } -}; -/******************************************************************************/ - -HnSwitches.prototype.copyRules = function(from, srcHostname) { - const thisBits = this.switches.get(srcHostname); - const fromBits = from.switches.get(srcHostname); - if ( fromBits !== thisBits ) { - if ( fromBits !== undefined ) { - this.switches.set(srcHostname, fromBits); - } else { - this.switches.delete(srcHostname); - } + reset() { + this.switches = new Map(); + this.n = ''; + this.z = ''; + this.r = 0; this.changed = true; } - return this.changed; -}; -/******************************************************************************/ - -HnSwitches.prototype.hasSameRules = function(other, srcHostname) { - return this.switches.get(srcHostname) === other.switches.get(srcHostname); -}; - -/******************************************************************************/ - -// If value is undefined, the switch is removed - -HnSwitches.prototype.toggle = function(switchName, hostname, newVal) { - const bitOffset = switchBitOffsets[switchName]; - if ( bitOffset === undefined ) { return false; } - if ( newVal === this.evaluate(switchName, hostname) ) { return false; } - let bits = this.switches.get(hostname) || 0; - bits &= ~(3 << bitOffset); - bits |= newVal << bitOffset; - if ( bits === 0 ) { - this.switches.delete(hostname); - } else { - this.switches.set(hostname, bits); - } - this.changed = true; - return true; -}; - -/******************************************************************************/ - -HnSwitches.prototype.toggleOneZ = function(switchName, hostname, newState) { - const bitOffset = switchBitOffsets[switchName]; - if ( bitOffset === undefined ) { return false; } - let state = this.evaluateZ(switchName, hostname); - if ( newState === state ) { return false; } - if ( newState === undefined ) { - newState = !state; - } - let bits = this.switches.get(hostname) || 0; - bits &= ~(3 << bitOffset); - if ( bits === 0 ) { - this.switches.delete(hostname); - } else { - this.switches.set(hostname, bits); - } - state = this.evaluateZ(switchName, hostname); - if ( state !== newState ) { - this.switches.set(hostname, bits | ((newState ? 1 : 2) << bitOffset)); - } - this.changed = true; - return true; -}; - -/******************************************************************************/ - -HnSwitches.prototype.toggleBranchZ = function(switchName, targetHostname, newState) { - this.toggleOneZ(switchName, targetHostname, newState); - - // Turn off all descendant switches, they will inherit the state of the - // branch's origin. - const targetLen = targetHostname.length; - for ( const hostname of this.switches.keys() ) { - if ( hostname === targetHostname ) { continue; } - if ( hostname.length <= targetLen ) { continue; } - if ( hostname.endsWith(targetHostname) === false ) { continue; } - if ( hostname.charAt(hostname.length - targetLen - 1) !== '.' ) { - continue; + assign(from) { + // Remove rules not in other + for ( const hn of this.switches.keys() ) { + if ( from.switches.has(hn) === false ) { + this.switches.delete(hn); + this.changed = true; + } + } + // Add/change rules in other + for ( const [hn, bits] of from.switches ) { + if ( this.switches.get(hn) !== bits ) { + this.switches.set(hn, bits); + this.changed = true; + } } - this.toggle(switchName, hostname, 0); } - return this.changed; -}; - -/******************************************************************************/ - -HnSwitches.prototype.toggleZ = function(switchName, hostname, deep, newState) { - if ( deep === true ) { - return this.toggleBranchZ(switchName, hostname, newState); + copyRules(from, srcHostname) { + const thisBits = this.switches.get(srcHostname); + const fromBits = from.switches.get(srcHostname); + if ( fromBits !== thisBits ) { + if ( fromBits !== undefined ) { + this.switches.set(srcHostname, fromBits); + } else { + this.switches.delete(srcHostname); + } + this.changed = true; + } + return this.changed; } - return this.toggleOneZ(switchName, hostname, newState); -}; -/******************************************************************************/ - -// 0 = inherit from broader scope, up to default state -// 1 = non-default state -// 2 = forced default state (to override a broader non-default state) - -HnSwitches.prototype.evaluate = function(switchName, hostname) { - const bits = this.switches.get(hostname); - if ( bits === undefined ) { - return 0; + hasSameRules(other, srcHostname) { + return this.switches.get(srcHostname) === other.switches.get(srcHostname); } - let bitOffset = switchBitOffsets[switchName]; - if ( bitOffset === undefined ) { - return 0; + + toggle(switchName, hostname, newVal) { + const bitOffset = switchBitOffsets[switchName]; + if ( bitOffset === undefined ) { return false; } + if ( newVal === this.evaluate(switchName, hostname) ) { return false; } + let bits = this.switches.get(hostname) || 0; + bits &= ~(3 << bitOffset); + bits |= newVal << bitOffset; + if ( bits === 0 ) { + this.switches.delete(hostname); + } else { + this.switches.set(hostname, bits); + } + this.changed = true; + return true; } - return (bits >>> bitOffset) & 3; -}; -/******************************************************************************/ + toggleOneZ(switchName, hostname, newState) { + const bitOffset = switchBitOffsets[switchName]; + if ( bitOffset === undefined ) { return false; } + let state = this.evaluateZ(switchName, hostname); + if ( newState === state ) { return false; } + if ( newState === undefined ) { + newState = !state; + } + let bits = this.switches.get(hostname) || 0; + bits &= ~(3 << bitOffset); + if ( bits === 0 ) { + this.switches.delete(hostname); + } else { + this.switches.set(hostname, bits); + } + state = this.evaluateZ(switchName, hostname); + if ( state !== newState ) { + this.switches.set(hostname, bits | ((newState ? 1 : 2) << bitOffset)); + } + this.changed = true; + return true; + } -HnSwitches.prototype.evaluateZ = function(switchName, hostname) { - const bitOffset = switchBitOffsets[switchName]; - if ( bitOffset === undefined ) { + toggleBranchZ(switchName, targetHostname, newState) { + this.toggleOneZ(switchName, targetHostname, newState); + + // Turn off all descendant switches, they will inherit the state of the + // branch's origin. + const targetLen = targetHostname.length; + for ( const hostname of this.switches.keys() ) { + if ( hostname === targetHostname ) { continue; } + if ( hostname.length <= targetLen ) { continue; } + if ( hostname.endsWith(targetHostname) === false ) { continue; } + if ( hostname.charAt(hostname.length - targetLen - 1) !== '.' ) { + continue; + } + this.toggle(switchName, hostname, 0); + } + + return this.changed; + } + + toggleZ(switchName, hostname, deep, newState) { + if ( deep === true ) { + return this.toggleBranchZ(switchName, hostname, newState); + } + return this.toggleOneZ(switchName, hostname, newState); + } + + // 0 = inherit from broader scope, up to default state + // 1 = non-default state + // 2 = forced default state (to override a broader non-default state) + + evaluate(switchName, hostname) { + const bits = this.switches.get(hostname); + if ( bits === undefined ) { return 0; } + let bitOffset = switchBitOffsets[switchName]; + if ( bitOffset === undefined ) { return 0; } + return (bits >>> bitOffset) & 3; + } + + evaluateZ(switchName, hostname) { + const bitOffset = switchBitOffsets[switchName]; + if ( bitOffset === undefined ) { + this.r = 0; + return false; + } + this.n = switchName; + for ( const shn of decomposeHostname(hostname, decomposedSource) ) { + let bits = this.switches.get(shn); + if ( bits === undefined ) { continue; } + bits = bits >>> bitOffset & 3; + if ( bits === 0 ) { continue; } + this.z = shn; + this.r = bits; + return bits === 1; + } this.r = 0; return false; } - this.n = switchName; - decomposeHostname(hostname, this.decomposedSource); - for ( const shn of this.decomposedSource ) { - let bits = this.switches.get(shn); - if ( bits !== undefined ) { - bits = bits >>> bitOffset & 3; - if ( bits !== 0 ) { - this.z = shn; - this.r = bits; - return bits === 1; + + toLogData() { + return { + source: 'switch', + result: this.r, + raw: `${this.n}: ${this.z} true` + }; + } + + toArray() { + const out = []; + for ( const hostname of this.switches.keys() ) { + const prettyHn = hostname.includes('xn--') && punycode + ? punycode.toUnicode(hostname) + : hostname; + for ( const switchName in switchBitOffsets ) { + if ( switchBitOffsets[switchName] === undefined ) { continue; } + const val = this.evaluate(switchName, hostname); + if ( val === 0 ) { continue; } + out.push(`${switchName}: ${prettyHn} ${switchStateToNameMap[val]}`); } } + return out; } - this.r = 0; - return false; -}; -/******************************************************************************/ + toString() { + return this.toArray().join('\n'); + } -HnSwitches.prototype.toLogData = function() { - return { - source: 'switch', - result: this.r, - raw: this.n + ': ' + this.z + ' true' - }; -}; - -/******************************************************************************/ - -HnSwitches.prototype.toArray = function() { - const out = []; - const toUnicode = punycode.toUnicode; - for ( let hostname of this.switches.keys() ) { - for ( const switchName in switchBitOffsets ) { - if ( switchBitOffsets.hasOwnProperty(switchName) === false ) { - continue; - } - const val = this.evaluate(switchName, hostname); - if ( val === 0 ) { continue; } - if ( hostname.includes('xn--') ) { - hostname = toUnicode(hostname); - } - out.push(switchName + ': ' + hostname + ' ' + switchStateToNameMap[val]); + fromString(text, append) { + const lineIter = new LineIterator(text); + if ( append !== true ) { this.reset(); } + while ( lineIter.eot() === false ) { + this.addFromRuleParts(lineIter.next().trim().split(/\s+/)); } } - return out; -}; -HnSwitches.prototype.toString = function() { - return this.toArray().join('\n'); -}; - -/******************************************************************************/ - -HnSwitches.prototype.fromString = function(text, append) { - const lineIter = new LineIterator(text); - if ( append !== true ) { this.reset(); } - while ( lineIter.eot() === false ) { - this.addFromRuleParts(lineIter.next().trim().split(/\s+/)); + validateRuleParts(parts) { + if ( parts.length < 3 ) { return; } + if ( parts[0].endsWith(':') === false ) { return; } + if ( nameToSwitchStateMap[parts[2]] === undefined ) { return; } + if ( reNotASCII.test(parts[1]) && punycode !== undefined ) { + parts[1] = punycode.toASCII(parts[1]); + } + return parts; } -}; -/******************************************************************************/ - -HnSwitches.prototype.validateRuleParts = function(parts) { - if ( parts.length < 3 ) { return; } - if ( parts[0].endsWith(':') === false ) { return; } - if ( nameToSwitchStateMap.hasOwnProperty(parts[2]) === false ) { return; } - // Performance: avoid punycoding if hostname is made only of ASCII chars. - if ( reNotASCII.test(parts[1]) ) { parts[1] = punycode.toASCII(parts[1]); } - return parts; -}; - -/******************************************************************************/ - -HnSwitches.prototype.addFromRuleParts = function(parts) { - if ( this.validateRuleParts(parts) !== undefined ) { + addFromRuleParts(parts) { + if ( this.validateRuleParts(parts) === undefined ) { return false; } const switchName = parts[0].slice(0, -1); - if ( switchBitOffsets.hasOwnProperty(switchName) ) { - this.toggle(switchName, parts[1], nameToSwitchStateMap[parts[2]]); - return true; - } - } - return false; -}; - -HnSwitches.prototype.removeFromRuleParts = function(parts) { - if ( this.validateRuleParts(parts) !== undefined ) { - this.toggle(parts[0].slice(0, -1), parts[1], 0); + if ( switchBitOffsets[switchName] === undefined ) { return false; } + this.toggle(switchName, parts[1], nameToSwitchStateMap[parts[2]]); return true; } - return false; -}; + + removeFromRuleParts(parts) { + if ( this.validateRuleParts(parts) !== undefined ) { + this.toggle(parts[0].slice(0, -1), parts[1], 0); + return true; + } + return false; + } +} /******************************************************************************/ -const sessionSwitches = new HnSwitches(); -const permanentSwitches = new HnSwitches(); - -export { permanentSwitches, sessionSwitches }; +export default DynamicSwitchRuleFiltering; /******************************************************************************/ diff --git a/src/js/html-filtering.js b/src/js/html-filtering.js index a563826fb..fd98aa872 100644 --- a/src/js/html-filtering.js +++ b/src/js/html-filtering.js @@ -25,7 +25,7 @@ import logger from './logger.js'; import µb from './background.js'; -import { sessionFirewall } from './dynamic-net-filtering.js'; +import { sessionFirewall } from './filtering-engines.js'; import { StaticExtFilteringHostnameDB, diff --git a/src/js/httpheader-filtering.js b/src/js/httpheader-filtering.js index 3212bb660..b9126d99b 100644 --- a/src/js/httpheader-filtering.js +++ b/src/js/httpheader-filtering.js @@ -26,7 +26,7 @@ import logger from './logger.js'; import µb from './background.js'; import { entityFromDomain } from './uri-utils.js'; -import { sessionFirewall } from './dynamic-net-filtering.js'; +import { sessionFirewall } from './filtering-engines.js'; import { StaticExtFilteringHostnameDB, diff --git a/src/js/messaging.js b/src/js/messaging.js index 65c6cea9d..6ca831879 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -44,12 +44,11 @@ import { webRequest } from './traffic.js'; import { permanentFirewall, sessionFirewall, -} from './dynamic-net-filtering.js'; - -import { permanentSwitches, sessionSwitches, -} from './hnswitches.js'; + permanentURLFiltering, + sessionURLFiltering, +} from './filtering-engines.js'; import { domainFromHostname, @@ -59,11 +58,6 @@ import { isNetworkURI, } from './uri-utils.js'; -import { - permanentURLFiltering, - sessionURLFiltering, -} from './url-net-filtering.js'; - import './benchmarks.js'; /******************************************************************************/ @@ -488,10 +482,11 @@ const onMessage = function(request, sender, callback) { break; case 'revertFirewallRules': + // TODO: use Set() to message around sets of hostnames sessionFirewall.copyRules( permanentFirewall, request.srcHostname, - request.desHostnames + Object.assign(Object.create(null), request.desHostnames) ); sessionSwitches.copyRules( permanentSwitches, @@ -507,11 +502,12 @@ const onMessage = function(request, sender, callback) { break; case 'saveFirewallRules': + // TODO: use Set() to message around sets of hostnames if ( permanentFirewall.copyRules( sessionFirewall, request.srcHostname, - request.desHostnames + Object.assign(Object.create(null), request.desHostnames) ) ) { µb.savePermanentFirewallRules(); diff --git a/src/js/pagestore.js b/src/js/pagestore.js index 48e7e4e1a..c6bc6cd19 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -29,9 +29,12 @@ import staticNetFilteringEngine from './static-net-filtering.js'; import µb from './background.js'; import { orphanizeString } from './text-utils.js'; import { redirectEngine } from './redirect-engine.js'; -import { sessionFirewall } from './dynamic-net-filtering.js'; -import { sessionSwitches } from './hnswitches.js'; -import { sessionURLFiltering } from './url-net-filtering.js'; + +import { + sessionFirewall, + sessionSwitches, + sessionURLFiltering, +} from './filtering-engines.js'; import { domainFromHostname, diff --git a/src/js/scriptlet-filtering.js b/src/js/scriptlet-filtering.js index 8eedb38bc..ebd3067fb 100644 --- a/src/js/scriptlet-filtering.js +++ b/src/js/scriptlet-filtering.js @@ -26,7 +26,7 @@ import logger from './logger.js'; import µb from './background.js'; import { redirectEngine } from './redirect-engine.js'; -import { sessionFirewall } from './dynamic-net-filtering.js'; +import { sessionFirewall } from './filtering-engines.js'; import { StaticExtFilteringHostnameDB, diff --git a/src/js/start.js b/src/js/start.js index 9308f5d7b..c8ad67521 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -39,17 +39,11 @@ import { webRequest } from './traffic.js'; import { permanentFirewall, sessionFirewall, -} from './dynamic-net-filtering.js'; - -import { permanentSwitches, sessionSwitches, -} from './hnswitches.js'; - -import { permanentURLFiltering, sessionURLFiltering, -} from './url-net-filtering.js'; +} from './filtering-engines.js'; /******************************************************************************/ diff --git a/src/js/storage.js b/src/js/storage.js index c1d37512a..27120a8d3 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -36,14 +36,17 @@ import staticFilteringReverseLookup from './reverselookup.js'; import staticNetFilteringEngine from './static-net-filtering.js'; import µb from './background.js'; import { hostnameFromURI } from './uri-utils.js'; -import { permanentFirewall } from './dynamic-net-filtering.js'; -import { permanentSwitches } from './hnswitches.js'; -import { permanentURLFiltering } from './url-net-filtering.js'; import { redirectEngine } from './redirect-engine.js'; import { sparseBase64 } from './base64-custom.js'; import { StaticFilteringParser } from './static-filtering-parser.js'; import { ubolog, ubologSet } from './console.js'; +import { + permanentFirewall, + permanentSwitches, + permanentURLFiltering, +} from './filtering-engines.js'; + import { CompiledListReader, CompiledListWriter, diff --git a/src/js/tab.js b/src/js/tab.js index 45ee4ab02..5127bf59f 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -29,9 +29,12 @@ import scriptletFilteringEngine from './scriptlet-filtering.js'; import staticNetFilteringEngine from './static-net-filtering.js'; import µb from './background.js'; import { PageStore } from './pagestore.js'; -import { sessionFirewall } from './dynamic-net-filtering.js'; -import { sessionSwitches } from './hnswitches.js'; -import { sessionURLFiltering } from './url-net-filtering.js'; + +import { + sessionFirewall, + sessionSwitches, + sessionURLFiltering, +} from './filtering-engines.js'; import { domainFromHostname, diff --git a/src/js/traffic.js b/src/js/traffic.js index b3ecc7ab7..9d372d6c1 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -30,9 +30,12 @@ import scriptletFilteringEngine from './scriptlet-filtering.js'; import staticNetFilteringEngine from './static-net-filtering.js'; import textEncode from './text-encode.js'; import µb from './background.js'; -import { sessionFirewall } from './dynamic-net-filtering.js'; -import { sessionSwitches } from './hnswitches.js'; -import { sessionURLFiltering } from './url-net-filtering.js'; + +import { + sessionFirewall, + sessionSwitches, + sessionURLFiltering, +} from './filtering-engines.js'; import { entityFromDomain, diff --git a/src/js/ublock.js b/src/js/ublock.js index 304fbf6e2..f9db09e2b 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -33,17 +33,11 @@ import { redirectEngine } from './redirect-engine.js'; import { permanentFirewall, sessionFirewall, -} from './dynamic-net-filtering.js'; - -import { permanentSwitches, sessionSwitches, -} from './hnswitches.js'; - -import { permanentURLFiltering, sessionURLFiltering, -} from './url-net-filtering.js'; +} from './filtering-engines.js'; /******************************************************************************/ /******************************************************************************/ diff --git a/src/js/url-net-filtering.js b/src/js/url-net-filtering.js index 2b452c645..08f2cfe10 100644 --- a/src/js/url-net-filtering.js +++ b/src/js/url-net-filtering.js @@ -43,27 +43,38 @@ const actionToNameMap = { 3: 'noop' }; -const nameToActionMap = { +const nameToActionMap = Object.create(null); +Object.assign(nameToActionMap, { 'block': 1, 'allow': 2, 'noop': 3 -}; +}); const knownInvalidTypes = new Set([ 'doc', 'main_frame', ]); -/******************************************************************************/ +const intToActionMap = new Map([ + [ 1, ' block' ], + [ 2, ' allow' ], + [ 3, ' noop' ] +]); -const RuleEntry = function(url, action) { - this.url = url; - this.action = action; -}; +const decomposedSource = []; /******************************************************************************/ -const indexOfURL = function(entries, url) { +class RuleEntry { + constructor(url, action) { + this.url = url; + this.action = action; + } +} + +/******************************************************************************/ + +function indexOfURL(entries, url) { // TODO: binary search -- maybe, depends on common use cases const urlLen = url.length; // URLs must be ordered by increasing length. @@ -73,11 +84,11 @@ const indexOfURL = function(entries, url) { if ( entry.url === url ) { return i; } } return -1; -}; +} /******************************************************************************/ -const indexOfMatch = function(entries, url) { +function indexOfMatch(entries, url) { const urlLen = url.length; let i = entries.length; while ( i-- ) { @@ -93,22 +104,22 @@ const indexOfMatch = function(entries, url) { } while ( i-- ); } return -1; -}; +} /******************************************************************************/ -const indexFromLength = function(entries, len) { +function indexFromLength(entries, len) { // TODO: binary search -- maybe, depends on common use cases // URLs must be ordered by increasing length. for ( let i = 0; i < entries.length; i++ ) { if ( entries[i].url.length > len ) { return i; } } return -1; -}; +} /******************************************************************************/ -const addRuleEntry = function(entries, url, action) { +function addRuleEntry(entries, url, action) { const entry = new RuleEntry(url, action); const i = indexFromLength(entries, url.length); if ( i === -1 ) { @@ -116,263 +127,210 @@ const addRuleEntry = function(entries, url, action) { } else { entries.splice(i, 0, entry); } -}; +} /******************************************************************************/ -const URLNetFiltering = function() { - this.reset(); -}; +class DynamicURLRuleFiltering { + constructor() { + this.reset(); + } -/******************************************************************************/ + reset() { + this.rules = new Map(); + // registers, filled with result of last evaluation + this.context = ''; + this.url = ''; + this.type = ''; + this.r = 0; + this.changed = false; + } -URLNetFiltering.prototype.reset = function() { - this.rules = new Map(); - // registers, filled with result of last evaluation - this.context = ''; - this.url = ''; - this.type = ''; - this.r = 0; - this.changed = false; - this.decomposedSource = []; -}; - -/******************************************************************************/ - -URLNetFiltering.prototype.assign = function(other) { - // Remove rules not in other - for ( const key of this.rules.keys() ) { - if ( other.rules.has(key) === false ) { - this.rules.delete(key); + assign(other) { + // Remove rules not in other + for ( const key of this.rules.keys() ) { + if ( other.rules.has(key) === false ) { + this.rules.delete(key); + } } + // Add/change rules in other + for ( const entry of other.rules ) { + this.rules.set(entry[0], entry[1].slice()); + } + this.changed = true; } - // Add/change rules in other - for ( const entry of other.rules ) { - this.rules.set(entry[0], entry[1].slice()); + + setRule(srcHostname, url, type, action) { + if ( action === 0 ) { + return this.removeRule(srcHostname, url, type); + } + const bucketKey = srcHostname + ' ' + type; + let entries = this.rules.get(bucketKey); + if ( entries === undefined ) { + entries = []; + this.rules.set(bucketKey, entries); + } + const i = indexOfURL(entries, url); + if ( i !== -1 ) { + const entry = entries[i]; + if ( entry.action === action ) { return false; } + entry.action = action; + } else { + addRuleEntry(entries, url, action); + } + this.changed = true; + return true; } - this.changed = true; -}; -/******************************************************************************/ - -URLNetFiltering.prototype.setRule = function(srcHostname, url, type, action) { - if ( action === 0 ) { - return this.removeRule(srcHostname, url, type); + removeRule(srcHostname, url, type) { + const bucketKey = srcHostname + ' ' + type; + const entries = this.rules.get(bucketKey); + if ( entries === undefined ) { return false; } + const i = indexOfURL(entries, url); + if ( i === -1 ) { return false; } + entries.splice(i, 1); + if ( entries.length === 0 ) { + this.rules.delete(bucketKey); + } + this.changed = true; + return true; } - const bucketKey = srcHostname + ' ' + type; - let entries = this.rules.get(bucketKey); - if ( entries === undefined ) { - entries = []; - this.rules.set(bucketKey, entries); - } - const i = indexOfURL(entries, url); - if ( i !== -1 ) { - const entry = entries[i]; - if ( entry.action === action ) { return false; } - entry.action = action; - } else { - addRuleEntry(entries, url, action); - } - this.changed = true; - return true; -}; -/******************************************************************************/ - -URLNetFiltering.prototype.removeRule = function(srcHostname, url, type) { - const bucketKey = srcHostname + ' ' + type; - const entries = this.rules.get(bucketKey); - if ( entries === undefined ) { return false; } - const i = indexOfURL(entries, url); - if ( i === -1 ) { return false; } - entries.splice(i, 1); - if ( entries.length === 0 ) { - this.rules.delete(bucketKey); - } - this.changed = true; - return true; -}; - -/******************************************************************************/ - -URLNetFiltering.prototype.evaluateZ = function(context, target, type) { - this.r = 0; - if ( this.rules.size === 0 ) { + evaluateZ(context, target, type) { + this.r = 0; + if ( this.rules.size === 0 ) { return 0; } + decomposeHostname(context, decomposedSource); + for ( const srchn of decomposedSource ) { + this.context = srchn; + let entries = this.rules.get(`${srchn} ${type}`); + if ( entries !== undefined ) { + const i = indexOfMatch(entries, target); + if ( i !== -1 ) { + const entry = entries[i]; + this.url = entry.url; + this.type = type; + this.r = entry.action; + return this.r; + } + } + entries = this.rules.get(`${srchn} *`); + if ( entries !== undefined ) { + const i = indexOfMatch(entries, target); + if ( i !== -1 ) { + const entry = entries[i]; + this.url = entry.url; + this.type = '*'; + this.r = entry.action; + return this.r; + } + } + } return 0; } - decomposeHostname(context, this.decomposedSource); - for ( let shn of this.decomposedSource ) { - this.context = shn; - let entries = this.rules.get(shn + ' ' + type); - if ( entries !== undefined ) { - let i = indexOfMatch(entries, target); - if ( i !== -1 ) { - let entry = entries[i]; - this.url = entry.url; - this.type = type; - this.r = entry.action; - return this.r; + + mustAllowCellZ(context, target, type) { + return this.evaluateZ(context, target, type).r === 2; + } + + mustBlockOrAllow() { + return this.r === 1 || this.r === 2; + } + + toLogData() { + if ( this.r === 0 ) { return; } + const { context, url, type } = this; + return { + source: 'dynamicUrl', + result: this.r, + rule: [ context, url, type, intToActionMap.get(this.r) ], + raw: `${context} ${url} ${type} ${intToActionMap.get(this.r)}`, + }; + } + + copyRules(other, context, urls, type) { + let i = urls.length; + while ( i-- ) { + const url = urls[i]; + other.evaluateZ(context, url, type); + const otherOwn = other.r !== 0 && + other.context === context && + other.url === url && + other.type === type; + this.evaluateZ(context, url, type); + const thisOwn = this.r !== 0 && + this.context === context && + this.url === url && + this.type === type; + if ( otherOwn && !thisOwn ) { + this.setRule(context, url, type, other.r); + this.changed = true; + } + if ( !otherOwn && thisOwn ) { + this.removeRule(context, url, type); + this.changed = true; } } - entries = this.rules.get(shn + ' *'); - if ( entries !== undefined ) { - let i = indexOfMatch(entries, target); - if ( i !== -1 ) { - let entry = entries[i]; - this.url = entry.url; - this.type = '*'; - this.r = entry.action; - return this.r; + return this.changed; + } + + toArray() { + const out = []; + for ( const [ key, entries ] of this.rules ) { + let pos = key.indexOf(' '); + const hn = key.slice(0, pos); + pos = key.lastIndexOf(' '); + const type = key.slice(pos + 1); + for ( const { url, action } of entries ) { + out.push(`${hn} ${url} ${type} ${actionToNameMap[action]}`); } } + return out; } - return 0; -}; -/******************************************************************************/ + toString() { + return this.toArray().sort().join('\n'); + } -URLNetFiltering.prototype.mustAllowCellZ = function(context, target, type) { - return this.evaluateZ(context, target, type).r === 2; -}; - -/******************************************************************************/ - -URLNetFiltering.prototype.mustBlockOrAllow = function() { - return this.r === 1 || this.r === 2; -}; - -/******************************************************************************/ - -URLNetFiltering.prototype.toLogData = function() { - if ( this.r === 0 ) { return; } - return { - source: 'dynamicUrl', - result: this.r, - rule: [ - this.context, - this.url, - this.type, - this.intToActionMap.get(this.r) - ], - raw: this.context + ' ' + - this.url + ' ' + - this.type + ' ' + - this.intToActionMap.get(this.r) - }; -}; - -URLNetFiltering.prototype.intToActionMap = new Map([ - [ 1, ' block' ], - [ 2, ' allow' ], - [ 3, ' noop' ] -]); - -/******************************************************************************/ - -URLNetFiltering.prototype.copyRules = function(other, context, urls, type) { - let i = urls.length; - while ( i-- ) { - const url = urls[i]; - other.evaluateZ(context, url, type); - const otherOwn = other.r !== 0 && - other.context === context && - other.url === url && - other.type === type; - this.evaluateZ(context, url, type); - const thisOwn = this.r !== 0 && - this.context === context && - this.url === url && - this.type === type; - if ( otherOwn && !thisOwn ) { - this.setRule(context, url, type, other.r); - this.changed = true; - } - if ( !otherOwn && thisOwn ) { - this.removeRule(context, url, type); - this.changed = true; + fromString(text) { + this.reset(); + const lineIter = new LineIterator(text); + while ( lineIter.eot() === false ) { + this.addFromRuleParts(lineIter.next().trim().split(/\s+/)); } } - return this.changed; -}; -/******************************************************************************/ - -// "url-filtering:" hostname url type action - -URLNetFiltering.prototype.toArray = function() { - const out = []; - for ( var item of this.rules ) { - const key = item[0]; - let pos = key.indexOf(' '); - const hn = key.slice(0, pos); - pos = key.lastIndexOf(' '); - const type = key.slice(pos + 1); - const entries = item[1]; - for ( let i = 0; i < entries.length; i++ ) { - const entry = entries[i]; - out.push( - hn + ' ' + - entry.url + ' ' + - type + ' ' + - actionToNameMap[entry.action] - ); + validateRuleParts(parts) { + if ( parts.length !== 4 ) { return; } + if ( parts[1].indexOf('://') <= 0 ) { return; } + if ( + /[^a-z_-]/.test(parts[2]) && parts[2] !== '*' || + knownInvalidTypes.has(parts[2]) + ) { + return; } + if ( nameToActionMap[parts[3]] === undefined ) { return; } + return parts; } - return out; -}; -URLNetFiltering.prototype.toString = function() { - return this.toArray().sort().join('\n'); -}; + addFromRuleParts(parts) { + if ( this.validateRuleParts(parts) !== undefined ) { + this.setRule(parts[0], parts[1], parts[2], nameToActionMap[parts[3]]); + return true; + } + return false; + } + + removeFromRuleParts(parts) { + if ( this.validateRuleParts(parts) !== undefined ) { + this.removeRule(parts[0], parts[1], parts[2]); + return true; + } + return false; + } +} /******************************************************************************/ -URLNetFiltering.prototype.fromString = function(text) { - this.reset(); - const lineIter = new LineIterator(text); - while ( lineIter.eot() === false ) { - this.addFromRuleParts(lineIter.next().trim().split(/\s+/)); - } -}; - -/******************************************************************************/ - -URLNetFiltering.prototype.validateRuleParts = function(parts) { - if ( parts.length !== 4 ) { return; } - if ( parts[1].indexOf('://') <= 0 ) { return; } - if ( - /[^a-z_-]/.test(parts[2]) && parts[2] !== '*' || - knownInvalidTypes.has(parts[2]) - ) { - return; - } - if ( nameToActionMap.hasOwnProperty(parts[3]) === false ) { return; } - return parts; -}; - -/******************************************************************************/ - -URLNetFiltering.prototype.addFromRuleParts = function(parts) { - if ( this.validateRuleParts(parts) !== undefined ) { - this.setRule(parts[0], parts[1], parts[2], nameToActionMap[parts[3]]); - return true; - } - return false; -}; - -URLNetFiltering.prototype.removeFromRuleParts = function(parts) { - if ( this.validateRuleParts(parts) !== undefined ) { - this.removeRule(parts[0], parts[1], parts[2]); - return true; - } - return false; -}; - -/******************************************************************************/ - -const sessionURLFiltering = new URLNetFiltering(); -const permanentURLFiltering = new URLNetFiltering(); - -export { permanentURLFiltering, sessionURLFiltering }; +export default DynamicURLRuleFiltering; /******************************************************************************/ diff --git a/tools/make-nodejs.sh b/tools/make-nodejs.sh index fe66805a9..cc5fe95bc 100755 --- a/tools/make-nodejs.sh +++ b/tools/make-nodejs.sh @@ -11,14 +11,17 @@ rm -rf $DES mkdir -p $DES/js cp src/js/base64-custom.js $DES/js cp src/js/biditrie.js $DES/js +cp src/js/dynamic-net-filtering.js $DES/js cp src/js/filtering-context.js $DES/js cp src/js/globals.js $DES/js +cp src/js/hnswitches.js $DES/js cp src/js/hntrie.js $DES/js cp src/js/static-filtering-parser.js $DES/js cp src/js/static-net-filtering.js $DES/js cp src/js/static-filtering-io.js $DES/js cp src/js/text-utils.js $DES/js cp src/js/uri-utils.js $DES/js +cp src/js/url-net-filtering.js $DES/js mkdir -p $DES/lib cp -R src/lib/punycode.js $DES/lib/