diff --git a/src/js/background.js b/src/js/background.js index 4cd10eeff..2a5c2366e 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -307,6 +307,7 @@ const µBlock = { // jshint ignore:line this.setMethod(details.method); this.setURL(details.url); 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 3da9d158f..b1d7d7033 100644 --- a/src/js/filtering-context.js +++ b/src/js/filtering-context.js @@ -19,13 +19,9 @@ Home: https://github.com/gorhill/uBlock */ -'use strict'; - -/******************************************************************************/ - import { - hostnameFromURI, domainFromHostname, + hostnameFromURI, originFromURI, } from './uri-utils.js'; @@ -140,6 +136,7 @@ export const FilteringContext = class { this.stype = undefined; this.url = undefined; this.aliasURL = undefined; + this.ipaddress = undefined; this.hostname = undefined; this.domain = undefined; this.docId = -1; @@ -418,42 +415,72 @@ export const FilteringContext = class { static getMethodName(a) { return methodBitToStrMap.get(a) || ''; } + + BEACON = BEACON; + CSP_REPORT = CSP_REPORT; + FONT = FONT; + IMAGE = IMAGE; + IMAGESET = IMAGESET; + MAIN_FRAME = MAIN_FRAME; + MEDIA = MEDIA; + OBJECT = OBJECT; + OBJECT_SUBREQUEST = OBJECT_SUBREQUEST; + PING = PING; + SCRIPT = SCRIPT; + STYLESHEET = STYLESHEET; + SUB_FRAME = SUB_FRAME; + WEBSOCKET = WEBSOCKET; + XMLHTTPREQUEST = XMLHTTPREQUEST; + INLINE_FONT = INLINE_FONT; + INLINE_SCRIPT = INLINE_SCRIPT; + OTHER = OTHER; + FRAME_ANY = FRAME_ANY; + FONT_ANY = FONT_ANY; + INLINE_ANY = INLINE_ANY; + PING_ANY = PING_ANY; + SCRIPT_ANY = SCRIPT_ANY; + METHOD_NONE = METHOD_NONE; + METHOD_CONNECT = METHOD_CONNECT; + METHOD_DELETE = METHOD_DELETE; + METHOD_GET = METHOD_GET; + METHOD_HEAD = METHOD_HEAD; + METHOD_OPTIONS = METHOD_OPTIONS; + METHOD_PATCH = METHOD_PATCH; + METHOD_POST = METHOD_POST; + METHOD_PUT = METHOD_PUT; + + static BEACON = BEACON; + static CSP_REPORT = CSP_REPORT; + static FONT = FONT; + static IMAGE = IMAGE; + static IMAGESET = IMAGESET; + static MAIN_FRAME = MAIN_FRAME; + static MEDIA = MEDIA; + static OBJECT = OBJECT; + static OBJECT_SUBREQUEST = OBJECT_SUBREQUEST; + static PING = PING; + static SCRIPT = SCRIPT; + static STYLESHEET = STYLESHEET; + static SUB_FRAME = SUB_FRAME; + static WEBSOCKET = WEBSOCKET; + static XMLHTTPREQUEST = XMLHTTPREQUEST; + static INLINE_FONT = INLINE_FONT; + static INLINE_SCRIPT = INLINE_SCRIPT; + static OTHER = OTHER; + static FRAME_ANY = FRAME_ANY; + static FONT_ANY = FONT_ANY; + static INLINE_ANY = INLINE_ANY; + static PING_ANY = PING_ANY; + static SCRIPT_ANY = SCRIPT_ANY; + static METHOD_NONE = METHOD_NONE; + static METHOD_CONNECT = METHOD_CONNECT; + static METHOD_DELETE = METHOD_DELETE; + static METHOD_GET = METHOD_GET; + static METHOD_HEAD = METHOD_HEAD; + static METHOD_OPTIONS = METHOD_OPTIONS; + static METHOD_PATCH = METHOD_PATCH; + static METHOD_POST = METHOD_POST; + static METHOD_PUT = METHOD_PUT; }; /******************************************************************************/ - -FilteringContext.prototype.BEACON = FilteringContext.BEACON = BEACON; -FilteringContext.prototype.CSP_REPORT = FilteringContext.CSP_REPORT = CSP_REPORT; -FilteringContext.prototype.FONT = FilteringContext.FONT = FONT; -FilteringContext.prototype.IMAGE = FilteringContext.IMAGE = IMAGE; -FilteringContext.prototype.IMAGESET = FilteringContext.IMAGESET = IMAGESET; -FilteringContext.prototype.MAIN_FRAME = FilteringContext.MAIN_FRAME = MAIN_FRAME; -FilteringContext.prototype.MEDIA = FilteringContext.MEDIA = MEDIA; -FilteringContext.prototype.OBJECT = FilteringContext.OBJECT = OBJECT; -FilteringContext.prototype.OBJECT_SUBREQUEST = FilteringContext.OBJECT_SUBREQUEST = OBJECT_SUBREQUEST; -FilteringContext.prototype.PING = FilteringContext.PING = PING; -FilteringContext.prototype.SCRIPT = FilteringContext.SCRIPT = SCRIPT; -FilteringContext.prototype.STYLESHEET = FilteringContext.STYLESHEET = STYLESHEET; -FilteringContext.prototype.SUB_FRAME = FilteringContext.SUB_FRAME = SUB_FRAME; -FilteringContext.prototype.WEBSOCKET = FilteringContext.WEBSOCKET = WEBSOCKET; -FilteringContext.prototype.XMLHTTPREQUEST = FilteringContext.XMLHTTPREQUEST = XMLHTTPREQUEST; -FilteringContext.prototype.INLINE_FONT = FilteringContext.INLINE_FONT = INLINE_FONT; -FilteringContext.prototype.INLINE_SCRIPT = FilteringContext.INLINE_SCRIPT = INLINE_SCRIPT; -FilteringContext.prototype.OTHER = FilteringContext.OTHER = OTHER; -FilteringContext.prototype.FRAME_ANY = FilteringContext.FRAME_ANY = FRAME_ANY; -FilteringContext.prototype.FONT_ANY = FilteringContext.FONT_ANY = FONT_ANY; -FilteringContext.prototype.INLINE_ANY = FilteringContext.INLINE_ANY = INLINE_ANY; -FilteringContext.prototype.PING_ANY = FilteringContext.PING_ANY = PING_ANY; -FilteringContext.prototype.SCRIPT_ANY = FilteringContext.SCRIPT_ANY = SCRIPT_ANY; - -FilteringContext.prototype.METHOD_NONE = FilteringContext.METHOD_NONE = METHOD_NONE; -FilteringContext.prototype.METHOD_CONNECT = FilteringContext.METHOD_CONNECT = METHOD_CONNECT; -FilteringContext.prototype.METHOD_DELETE = FilteringContext.METHOD_DELETE = METHOD_DELETE; -FilteringContext.prototype.METHOD_GET = FilteringContext.METHOD_GET = METHOD_GET; -FilteringContext.prototype.METHOD_HEAD = FilteringContext.METHOD_HEAD = METHOD_HEAD; -FilteringContext.prototype.METHOD_OPTIONS = FilteringContext.METHOD_OPTIONS = METHOD_OPTIONS; -FilteringContext.prototype.METHOD_PATCH = FilteringContext.METHOD_PATCH = METHOD_PATCH; -FilteringContext.prototype.METHOD_POST = FilteringContext.METHOD_POST = METHOD_POST; -FilteringContext.prototype.METHOD_PUT = FilteringContext.METHOD_PUT = METHOD_PUT; - -/******************************************************************************/ diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index b0a4a15d1..d3520b6e5 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -172,6 +172,7 @@ export const NODE_TYPE_NET_OPTION_NAME_IMAGE = iota++; export const NODE_TYPE_NET_OPTION_NAME_IMPORTANT = iota++; export const NODE_TYPE_NET_OPTION_NAME_INLINEFONT = iota++; export const NODE_TYPE_NET_OPTION_NAME_INLINESCRIPT = iota++; +export const NODE_TYPE_NET_OPTION_NAME_IPADDRESS = iota++; export const NODE_TYPE_NET_OPTION_NAME_MATCHCASE = iota++; export const NODE_TYPE_NET_OPTION_NAME_MEDIA = iota++; export const NODE_TYPE_NET_OPTION_NAME_METHOD = iota++; @@ -249,6 +250,7 @@ export const nodeTypeFromOptionName = new Map([ [ 'important', NODE_TYPE_NET_OPTION_NAME_IMPORTANT ], [ 'inline-font', NODE_TYPE_NET_OPTION_NAME_INLINEFONT ], [ 'inline-script', NODE_TYPE_NET_OPTION_NAME_INLINESCRIPT ], + [ 'ipaddress', NODE_TYPE_NET_OPTION_NAME_IPADDRESS ], [ 'match-case', NODE_TYPE_NET_OPTION_NAME_MATCHCASE ], [ 'media', NODE_TYPE_NET_OPTION_NAME_MEDIA ], [ 'method', NODE_TYPE_NET_OPTION_NAME_METHOD ], @@ -1401,6 +1403,14 @@ export class AstFilterParser { modifierType = type; unredirectableTypeCount += 1; break; + case NODE_TYPE_NET_OPTION_NAME_IPADDRESS: { + const value = this.getNetOptionValue(NODE_TYPE_NET_OPTION_NAME_IPADDRESS); + if ( /^\/.+\/$/.test(value) ) { + try { void new RegExp(value); } + catch(_) { realBad = true; } + } + break; + } case NODE_TYPE_NET_OPTION_NAME_MATCHCASE: realBad = this.isRegexPattern() === false; break; @@ -3104,6 +3114,7 @@ export const netOptionTokenDescriptors = new Map([ [ 'important', { blockOnly: true } ], [ 'inline-font', { canNegate: true } ], [ 'inline-script', { canNegate: true } ], + [ 'ipaddress', { mustAssign: true } ], [ 'match-case', { } ], [ 'media', { canNegate: true } ], [ 'method', { mustAssign: true } ], @@ -4324,6 +4335,7 @@ export const utils = (( ) => { [ 'env_safari', 'safari' ], [ 'cap_html_filtering', 'html_filtering' ], [ 'cap_user_stylesheet', 'user_stylesheet' ], + [ 'cap_ipaddress', 'ipaddress' ], [ 'false', 'false' ], // Hoping ABP-only list maintainers can at least make use of it to // help non-ABP content blockers better deal with filters benefiting @@ -4358,8 +4370,11 @@ export const utils = (( ) => { static evaluateExprToken(token, env = []) { const not = token.charCodeAt(0) === 0x21 /* ! */; if ( not ) { token = token.slice(1); } - const state = preparserTokens.get(token); - if ( state === undefined ) { return; } + let state = preparserTokens.get(token); + if ( state === undefined ) { + if ( token.startsWith('cap_') === false ) { return; } + state = 'false'; + } return state === 'false' && not || env.includes(state) !== not; } diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index eaa2c7131..e8f6de911 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -242,6 +242,7 @@ let $requestTypeValue = 0; let $requestURL = ''; let $requestURLRaw = ''; let $requestHostname = ''; +let $requestAddress = ''; let $docHostname = ''; let $docDomain = ''; let $tokenBeg = 0; @@ -702,6 +703,8 @@ const dnrAddRuleWarning = (rule, msg) => { FilterNotType FilterStrictParty FilterModifier + FilterOnHeaders + FilterIPAddress Collection: FilterCollection @@ -1234,7 +1237,7 @@ class FilterRegex { return [ FilterRegex.fid, details.pattern, - details.patternMatchCase ? 1 : 0 + details.optionValues.has('match-case') ? 1 : 0, ]; } @@ -2075,7 +2078,7 @@ const compileToDomainOpt = (...args) => { class FilterDenyAllow extends FilterToDomainMissSet { static compile(details) { - return super.compile(details.denyallowOpt, 0b01); + return super.compile(details.optionValues.get('denyallow'), 0b01); } static logData(idata, details) { @@ -2937,12 +2940,12 @@ class FilterOnHeaders { } static compile(details) { - return [ FilterOnHeaders.fid, details.headerOpt ]; + return [ FilterOnHeaders.fid, details.optionValues.get('header') ]; } static fromCompiled(args) { return filterDataAlloc( - args[0], // fid + args[0], // fid filterRefAdd({ headerOpt: args[1], $parsed: null, @@ -2963,6 +2966,41 @@ class FilterOnHeaders { registerFilterClass(FilterOnHeaders); +/******************************************************************************/ + +class FilterIPAddress { + static match(idata) { + const details = filterRefs[filterData[idata+1]]; + if ( details.isRegex === false ) { + return $requestAddress === details.pattern; + } + if ( details.$re === undefined ) { + details.$re = new RegExp(details.pattern.slice(1, -1)); + } + return details.$re.test($requestAddress); + } + + static compile(details) { + return [ FilterIPAddress.fid, details.optionValues.get('ipaddress') ]; + } + + static fromCompiled(args) { + const pattern = args[1]; + const details = { + pattern, + isRegex: pattern.startsWith('/') && pattern.endsWith('/'), + }; + return filterDataAlloc(args[0], filterRefAdd(details)); + } + + static logData(idata, details) { + const irefs = filterData[idata+1]; + details.options.push(`ipaddress=${LogData.requote(filterRefs[irefs].pattern)}`); + } +} + +registerFilterClass(FilterIPAddress); + /******************************************************************************/ /******************************************************************************/ @@ -3146,8 +3184,7 @@ class FilterCompiler { return Object.assign(this, other); } this.reToken = /[%0-9A-Za-z]+/g; - this.fromDomainOptList = []; - this.toDomainOptList = []; + this.optionValues = new Map(); this.tokenIdToNormalizedType = new Map([ [ sfp.NODE_TYPE_NET_OPTION_NAME_CNAME, bitFromType('cname') ], [ sfp.NODE_TYPE_NET_OPTION_NAME_CSS, bitFromType('stylesheet') ], @@ -3304,13 +3341,9 @@ class FilterCompiler { this.modifyType = undefined; this.modifyValue = undefined; this.pattern = ''; - this.patternMatchCase = false; this.party = ANYPARTY_REALM; this.optionUnitBits = 0; - this.fromDomainOpt = ''; - this.toDomainOpt = ''; - this.denyallowOpt = ''; - this.headerOpt = undefined; + this.optionValues.clear(); this.isPureHostname = false; this.isGeneric = false; this.isRegex = false; @@ -3322,8 +3355,7 @@ class FilterCompiler { this.notTypeBits = 0; this.methodBits = 0; this.notMethodBits = 0; - this.wildcardPos = -1; - this.caretPos = -1; + this.responseHeadersRealm = false; return this; } @@ -3421,26 +3453,36 @@ class FilterCompiler { case sfp.NODE_TYPE_NET_OPTION_NAME_CSP: if ( this.processCspOption(parser.getNetOptionValue(id)) === false ) { return false; } break; - case sfp.NODE_TYPE_NET_OPTION_NAME_DENYALLOW: - this.denyallowOpt = this.processHostnameList( - parser.getNetFilterDenyallowOptionIterator(), + case sfp.NODE_TYPE_NET_OPTION_NAME_DENYALLOW: { + const value = this.processHostnameList( + parser.getNetFilterDenyallowOptionIterator() ); - if ( this.denyallowOpt === '' ) { return false; } + if ( value === '' ) { return false; } + this.optionValues.set('denyallow', value); this.optionUnitBits |= DENYALLOW_BIT; break; - case sfp.NODE_TYPE_NET_OPTION_NAME_FROM: - this.fromDomainOpt = this.processHostnameList( - parser.getNetFilterFromOptionIterator(), - this.fromDomainOptList - ); - if ( this.fromDomainOpt === '' ) { return false; } + } + case sfp.NODE_TYPE_NET_OPTION_NAME_FROM: { + const iter = parser.getNetFilterFromOptionIterator(); + const list = []; + const value = this.processHostnameList(iter, list); + if ( value === '' ) { return false; } + this.optionValues.set('from', value); + this.optionValues.set('fromList', list); this.optionUnitBits |= FROM_BIT; break; + } case sfp.NODE_TYPE_NET_OPTION_NAME_HEADER: { - this.headerOpt = parser.getNetOptionValue(id) || ''; + 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)); this.optionUnitBits |= METHOD_BIT; @@ -3465,14 +3507,16 @@ class FilterCompiler { this.optionUnitBits |= MODIFY_BIT; break; } - case sfp.NODE_TYPE_NET_OPTION_NAME_TO: - this.toDomainOpt = this.processHostnameList( - parser.getNetFilterToOptionIterator(), - this.toDomainOptList - ); - if ( this.toDomainOpt === '' ) { return false; } + case sfp.NODE_TYPE_NET_OPTION_NAME_TO: { + const iter = parser.getNetFilterToOptionIterator(); + const list = []; + const value = this.processHostnameList(iter, list); + if ( value === '' ) { return false; } + this.optionValues.set('to', value); + this.optionValues.set('toList', list); this.optionUnitBits |= TO_BIT; break; + } default: break; } @@ -3558,6 +3602,7 @@ class FilterCompiler { case sfp.NODE_TYPE_NET_OPTION_NAME_DENYALLOW: case sfp.NODE_TYPE_NET_OPTION_NAME_FROM: case sfp.NODE_TYPE_NET_OPTION_NAME_HEADER: + case sfp.NODE_TYPE_NET_OPTION_NAME_IPADDRESS: case sfp.NODE_TYPE_NET_OPTION_NAME_METHOD: case sfp.NODE_TYPE_NET_OPTION_NAME_PERMISSIONS: case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECT: @@ -3591,7 +3636,7 @@ class FilterCompiler { this.action = BLOCKIMPORTANT_REALM; break; case sfp.NODE_TYPE_NET_OPTION_NAME_MATCHCASE: - this.patternMatchCase = true; + this.optionValues.set('match-case', true); break; case sfp.NODE_TYPE_NET_OPTION_NAME_MP4: { const id = this.action === ALLOW_REALM @@ -3661,11 +3706,6 @@ class FilterCompiler { return this.FILTER_OK; } - if ( this.isGeneric ) { - this.wildcardPos = this.pattern.indexOf('*'); - this.caretPos = this.pattern.indexOf('^'); - } - if ( this.pattern.length > 1024 ) { return this.FILTER_UNSUPPORTED; } @@ -3793,7 +3833,7 @@ class FilterCompiler { isJustOrigin() { if ( this.optionUnitBits !== FROM_BIT ) { return false; } if ( this.isRegex ) { return false; } - if ( /[/~]/.test(this.fromDomainOpt) ) { return false; } + if ( /[/~]/.test(this.optionValues.get('from')) ) { return false; } if ( this.pattern === '*' ) { return true; } if ( this.anchor !== 0b010 ) { return false; } if ( /^(?:http[s*]?:(?:\/\/)?)$/.test(this.pattern) ) { return true; } @@ -3870,7 +3910,7 @@ class FilterCompiler { } else /* 'http:' */ { this.tokenHash = ANY_HTTP_TOKEN_HASH; } - for ( const hn of this.fromDomainOptList ) { + for ( const hn of this.optionValues.get('fromList') ) { this.compileToAtomicFilter(hn, writer); } return; @@ -3911,31 +3951,36 @@ class FilterCompiler { } // Origin - if ( this.fromDomainOpt !== '' ) { + if ( this.optionValues.has('from') ) { compileFromDomainOpt( - this.fromDomainOptList, + this.optionValues.get('fromList'), units.length !== 0 && patternClass.isSlow === true, units ); } // Destination - if ( this.toDomainOpt !== '' ) { + if ( this.optionValues.has('to') ) { compileToDomainOpt( - this.toDomainOptList, + this.optionValues.get('toList'), units.length !== 0 && patternClass.isSlow === true, units ); } // Deny-allow - if ( this.denyallowOpt !== '' ) { + if ( this.optionValues.has('denyallow') ) { units.push(FilterDenyAllow.compile(this)); } // Header - if ( this.headerOpt !== undefined ) { - units.push(FilterOnHeaders.compile(this)); + if ( this.responseHeadersRealm ) { + if ( this.optionValues.has('ipaddress') ) { + units.push(FilterIPAddress.compile(this)); + } + if ( this.optionValues.has('header') ) { + units.push(FilterOnHeaders.compile(this)); + } this.action |= HEADERS_REALM; } @@ -3977,12 +4022,13 @@ class FilterCompiler { units.push(FilterPatternGeneric.compile(this)); return FilterPatternGeneric; } - if ( this.wildcardPos === -1 ) { - if ( this.caretPos === -1 ) { + if ( this.pattern.includes('*') === false ) { + const caretPos = this.pattern.indexOf('^'); + if ( caretPos === -1 ) { units.push(FilterPatternPlain.compile(this)); return FilterPatternPlain; } - if ( this.caretPos === (this.pattern.length - 1) ) { + if ( caretPos === (this.pattern.length - 1) ) { this.pattern = this.pattern.slice(0, -1); units.push(FilterPatternPlain.compile(this)); units.push(FilterTrailingSeparator.compile()); @@ -4027,15 +4073,16 @@ class FilterCompiler { } // These are to quickly test whether a filter is composite -const FROM_BIT = 0b000000001; -const TO_BIT = 0b000000010; -const DENYALLOW_BIT = 0b000000100; -const HEADER_BIT = 0b000001000; -const STRICT_PARTY_BIT = 0b000010000; -const MODIFY_BIT = 0b000100000; -const NOT_TYPE_BIT = 0b001000000; -const IMPORTANT_BIT = 0b010000000; -const METHOD_BIT = 0b100000000; +const FROM_BIT = 0b0000000001; +const TO_BIT = 0b0000000010; +const DENYALLOW_BIT = 0b0000000100; +const HEADER_BIT = 0b0000001000; +const STRICT_PARTY_BIT = 0b0000010000; +const MODIFY_BIT = 0b0000100000; +const NOT_TYPE_BIT = 0b0001000000; +const IMPORTANT_BIT = 0b0010000000; +const METHOD_BIT = 0b0100000000; +const IPADDRESS_BIT = 0b1000000000; FilterCompiler.prototype.FILTER_OK = 0; FilterCompiler.prototype.FILTER_INVALID = 1; @@ -4751,6 +4798,7 @@ StaticNetFilteringEngine.prototype.matchAndFetchModifiers = function( $requestHostname = fctxt.getHostname(); $requestMethodBit = fctxt.method || 0; $requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; + $requestAddress = fctxt.ipaddress || ''; const modifierType = modifierTypeFromName.get(modifierName); const modifierBits = modifierBitsFromType.get(modifierType); @@ -5048,6 +5096,7 @@ StaticNetFilteringEngine.prototype.matchRequestReverse = function(type, url) { $requestURLRaw = url; $requestMethodBit = 0; $requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; + $requestAddress = ''; $isBlockImportant = false; this.$filterUnit = 0; @@ -5116,6 +5165,7 @@ StaticNetFilteringEngine.prototype.matchRequest = function(fctxt, modifiers = 0) $requestHostname = fctxt.getHostname(); $requestMethodBit = fctxt.method || 0; $requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; + $requestAddress = fctxt.ipaddress || ''; $isBlockImportant = false; // Evaluate block realm before allow realm, and allow realm before @@ -5151,6 +5201,7 @@ StaticNetFilteringEngine.prototype.matchHeaders = function(fctxt, headers) { $requestHostname = fctxt.getHostname(); $requestMethodBit = fctxt.method || 0; $requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; + $requestAddress = fctxt.ipaddress || ''; $httpHeaders.init(headers); let r = 0;