diff --git a/platform/common/vapi-common.js b/platform/common/vapi-common.js index 367def90a..1cf98242f 100644 --- a/platform/common/vapi-common.js +++ b/platform/common/vapi-common.js @@ -163,6 +163,7 @@ vAPI.webextFlavor = { // This is always true. soup.add('ublock').add('webext'); + soup.add('ipaddress'); // Whether this is a dev build. if ( /^\d+\.\d+\.\d+\D/.test(browser.runtime.getManifest().version) ) { @@ -181,8 +182,7 @@ vAPI.webextFlavor = { if ( browser.runtime.getURL('').startsWith('moz-extension://') ) { soup.add('firefox') .add('user_stylesheet') - .add('html_filtering') - .add('ipaddress'); + .add('html_filtering'); const match = /Firefox\/(\d+)/.exec(ua); flavor.major = match && parseInt(match[1], 10) || 115; } else { diff --git a/src/js/background.js b/src/js/background.js index a59671091..a68487f6c 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -305,8 +305,8 @@ const µBlock = { // jshint ignore:line this.realm = ''; this.setMethod(details.method); this.setURL(details.url); + this.setIPAddress(details.ip); this.aliasURL = details.aliasURL || undefined; - this.ipaddress = details.ip || undefined; this.redirectURL = undefined; this.filter = undefined; if ( this.itype !== this.SUB_FRAME ) { diff --git a/src/js/filtering-context.js b/src/js/filtering-context.js index b1d7d7033..0bbd7decf 100644 --- a/src/js/filtering-context.js +++ b/src/js/filtering-context.js @@ -122,6 +122,8 @@ const methodBitToStrMap = new Map([ [ METHOD_PUT, 'put' ], ]); +const reIPv4 = /^\d+\.\d+\.\d+\.\d+$/; + /******************************************************************************/ export const FilteringContext = class { @@ -136,9 +138,9 @@ export const FilteringContext = class { this.stype = undefined; this.url = undefined; this.aliasURL = undefined; - this.ipaddress = undefined; this.hostname = undefined; this.domain = undefined; + this.ipaddress = undefined; this.docId = -1; this.frameId = -1; this.docOrigin = undefined; @@ -176,6 +178,7 @@ export const FilteringContext = class { this.url = other.url; this.hostname = other.hostname; this.domain = other.domain; + this.ipaddress = other.ipaddress; this.docId = other.docId; this.frameId = other.frameId; this.docOrigin = other.docOrigin; @@ -213,7 +216,7 @@ export const FilteringContext = class { setURL(a) { if ( a !== this.url ) { - this.hostname = this.domain = undefined; + this.hostname = this.domain = this.ipaddress = undefined; this.url = a; } return this; @@ -246,6 +249,28 @@ export const FilteringContext = class { return this; } + getIPAddress() { + if ( this.ipaddress !== undefined ) { + return this.ipaddress; + } + const ipaddr = this.getHostname(); + const c0 = ipaddr.charCodeAt(0); + if ( c0 === 0x5B /* [ */ ) { + return (this.ipaddress = ipaddr.slice(1, -1)); + } else if ( c0 >= 0x30 && c0 <= 0x39 ) { + if ( reIPv4.test(ipaddr) ) { + return (this.ipaddress = ipaddr); + } + } + return (this.ipaddress = ''); + } + + // Must always be called *after* setURL() + setIPAddress(ipaddr) { + this.ipaddress = ipaddr || undefined; + return this; + } + getDocOrigin() { if ( this.docOrigin === undefined ) { this.docOrigin = this.tabOrigin; diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index d3520b6e5..3d8e82fd6 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -578,6 +578,7 @@ export const preparserIfTokens = new Set([ 'env_mv3', 'env_safari', 'cap_html_filtering', + 'cap_ipaddress', 'cap_user_stylesheet', 'false', 'ext_abp', diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 052321777..780015209 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -2973,6 +2973,9 @@ registerFilterClass(FilterOnHeaders); /******************************************************************************/ class FilterIPAddress { + static reIPv6IPv4lan = /^::ffff:(7f\w{2}|a\w{2}|a9fe|c0a8):\w+$/; + static reIPv6local = /^f[cd]\w{2}:/; + static match(idata) { const ipaddr = $requestAddress; const details = filterRefs[filterData[idata+1]]; @@ -3011,26 +3014,26 @@ class FilterIPAddress { } return ipaddr.startsWith('192.168.'); } - if ( c0 !== 0x5B /* [ */ ) { return false; } // ipv6 - const c1 = ipaddr.charCodeAt(1); - if ( c1 === 0x3A /* : */ ) { - if ( ipaddr.startsWith('[::') === false ) { return false; } - if ( ipaddr === '[::]' || ipaddr === '[::1]' ) { return true; } - if ( ipaddr.startsWith('[::ffff:') === false ) { return false; } - return /^\[::ffff:(7f\w{2}|a\w{2}|a9fe|c0a8):\w+\]$/.test(ipaddr); + if ( c0 === 0x3A /* : */ ) { + if ( ipaddr.startsWith('::') === false ) { return false; } + if ( ipaddr === '::' || ipaddr === '::1' ) { return true; } + if ( ipaddr.startsWith('::ffff:') === false ) { return false; } + return this.reIPv6IPv4lan.test(ipaddr); } - if ( c1 === 0x36 /* 6 */ ) { - return ipaddr.startsWith('[64:ff9b:'); - } - if ( c1 === 0x66 /* f */ ) { - return /^\[f[cd]\w{2}:/.test(ipaddr); + if ( ipaddr.includes(':') ) { + if ( c0 === 0x36 /* 6 */ ) { + return ipaddr.startsWith('64:ff9b:'); + } + if ( c0 === 0x66 /* f */ ) { + return this.reIPv6local.test(ipaddr); + } } return false; } static isLoopback(ipaddr) { - return ipaddr === '127.0.0.1' || ipaddr === '[::1]'; + return ipaddr === '127.0.0.1' || ipaddr === '::1'; } static compile(details) { @@ -3412,7 +3415,6 @@ class FilterCompiler { this.notTypeBits = 0; this.methodBits = 0; this.notMethodBits = 0; - this.responseHeadersRealm = false; return this; } @@ -3532,13 +3534,11 @@ class FilterCompiler { case sfp.NODE_TYPE_NET_OPTION_NAME_HEADER: { this.optionValues.set('header', parser.getNetOptionValue(id) || ''); this.optionUnitBits |= HEADER_BIT; - this.responseHeadersRealm = true; break; } case sfp.NODE_TYPE_NET_OPTION_NAME_IPADDRESS: this.optionValues.set('ipaddress', parser.getNetOptionValue(id) || ''); this.optionUnitBits |= IPADDRESS_BIT; - this.responseHeadersRealm = true; break; case sfp.NODE_TYPE_NET_OPTION_NAME_METHOD: this.processMethodOption(parser.getNetOptionValue(id)); @@ -4008,7 +4008,7 @@ class FilterCompiler { } // Origin - if ( this.optionValues.has('from') ) { + if ( (this.optionUnitBits & FROM_BIT) !== 0 ) { compileFromDomainOpt( this.optionValues.get('fromList'), units.length !== 0 && patternClass.isSlow === true, @@ -4017,7 +4017,7 @@ class FilterCompiler { } // Destination - if ( this.optionValues.has('to') ) { + if ( (this.optionUnitBits & TO_BIT) !== 0 ) { compileToDomainOpt( this.optionValues.get('toList'), units.length !== 0 && patternClass.isSlow === true, @@ -4026,18 +4026,18 @@ class FilterCompiler { } // Deny-allow - if ( this.optionValues.has('denyallow') ) { + if ( (this.optionUnitBits & DENYALLOW_BIT) !== 0 ) { units.push(FilterDenyAllow.compile(this)); } + // IP address + if ( (this.optionUnitBits & IPADDRESS_BIT) !== 0 ) { + units.push(FilterIPAddress.compile(this)); + } + // Header - if ( this.responseHeadersRealm ) { - if ( this.optionValues.has('ipaddress') ) { - units.push(FilterIPAddress.compile(this)); - } - if ( this.optionValues.has('header') ) { - units.push(FilterOnHeaders.compile(this)); - } + if ( (this.optionUnitBits & HEADER_BIT) !== 0 ) { + units.push(FilterOnHeaders.compile(this)); this.action |= HEADERS_REALM; } @@ -4058,12 +4058,17 @@ class FilterCompiler { modifierBitsFromType.get(this.modifyType); } - this.compileToAtomicFilter( - units.length === 1 - ? units[0] - : FilterCompositeAll.compile(units), - writer - ); + const fdata = units.length === 1 + ? units[0] + : FilterCompositeAll.compile(units); + + this.compileToAtomicFilter(fdata, writer); + + if ( (this.optionUnitBits & IPADDRESS_BIT) !== 0 ) { + if ( (this.action & HEADERS_REALM) !== 0 ) { return; } + this.action |= HEADERS_REALM; + this.compileToAtomicFilter(fdata, writer); + } } compilePattern(units) { @@ -4856,7 +4861,7 @@ StaticNetFilteringEngine.prototype.matchAndFetchModifiers = function( $requestHostname = fctxt.getHostname(); $requestMethodBit = fctxt.method || 0; $requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; - $requestAddress = fctxt.ipaddress || ''; + $requestAddress = fctxt.getIPAddress(); const modifierType = modifierTypeFromName.get(modifierName); const modifierBits = modifierBitsFromType.get(modifierType); @@ -5223,7 +5228,7 @@ StaticNetFilteringEngine.prototype.matchRequest = function(fctxt, modifiers = 0) $requestHostname = fctxt.getHostname(); $requestMethodBit = fctxt.method || 0; $requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; - $requestAddress = fctxt.ipaddress || ''; + $requestAddress = fctxt.getIPAddress(); $isBlockImportant = false; // Evaluate block realm before allow realm, and allow realm before @@ -5259,7 +5264,7 @@ StaticNetFilteringEngine.prototype.matchHeaders = function(fctxt, headers) { $requestHostname = fctxt.getHostname(); $requestMethodBit = fctxt.method || 0; $requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; - $requestAddress = fctxt.ipaddress || ''; + $requestAddress = fctxt.getIPAddress(); $httpHeaders.init(headers); let r = 0;