diff --git a/platform/chromium/manifest.json b/platform/chromium/manifest.json index 2e7e33c19..9195aac3b 100644 --- a/platform/chromium/manifest.json +++ b/platform/chromium/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "µBlock", - "version": "0.8.5.7", + "version": "0.8.6.0", "default_locale": "en", "description": "__MSG_extShortDesc__", diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js index 5d9b30bb0..500e8a987 100644 --- a/platform/firefox/vapi-background.js +++ b/platform/firefox/vapi-background.js @@ -45,7 +45,7 @@ vAPI.firefox = true; // TODO: read these data from somewhere... vAPI.app = { name: 'µBlock', - version: '0.8.5.7' + version: '0.8.6.0' }; /******************************************************************************/ diff --git a/src/css/devtool-log.css b/src/css/devtool-log.css index 3011fbd19..36557925b 100644 --- a/src/css/devtool-log.css +++ b/src/css/devtool-log.css @@ -61,27 +61,31 @@ body[dir="rtl"] #content { vertical-align: top; } #content table tr td:nth-of-type(1) { + padding: 3px 0; + text-align: center; + } +#content table tr td:nth-of-type(2) { white-space: normal; width: 25%; word-break: break-all; word-wrap: break-word; } -#content table tr td:nth-of-type(2) { +#content table tr td:nth-of-type(3) { white-space: nowrap; } -#content table tr td:nth-of-type(3) { +#content table tr td:nth-of-type(4) { border-right: none; white-space: normal; width: 60%; word-break: break-all; word-wrap: break-word; } -#content table tr td:nth-of-type(3) b { +#content table tr td:nth-of-type(4) b { font-weight: normal; } -#content table tr.blocked td:nth-of-type(3) b { +#content table tr.blocked td:nth-of-type(4) b { background-color: rgba(192, 0, 0, 0.2); } -#content table tr.allowed td:nth-of-type(3) b { +#content table tr.allowed td:nth-of-type(4) b { background-color: rgba(0, 160, 0, 0.2); } \ No newline at end of file diff --git a/src/js/background.js b/src/js/background.js index 4306d65b9..4cbdf72a5 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -107,7 +107,7 @@ return { firstUpdateAfter: 5 * oneMinute, nextUpdateAfter: 7 * oneHour, - selfieMagic: 'qidcglrwobsm', + selfieMagic: 'knreayqtuguf', selfieAfter: 7 * oneMinute, pageStores: {}, diff --git a/src/js/contentscript-end.js b/src/js/contentscript-end.js index b6bbbf0a6..6b936018d 100644 --- a/src/js/contentscript-end.js +++ b/src/js/contentscript-end.js @@ -33,20 +33,29 @@ // https://github.com/gorhill/uBlock/issues/464 if ( document instanceof HTMLDocument === false ) { + //console.debug('contentscript-end.js > not a HTLMDocument'); return false; } if ( !vAPI ) { + //console.debug('contentscript-end.js > vAPI not found'); return; } if ( vAPI.canExecuteContentScript() !== true ) { + //console.debug('contentscript-end.js > can\'t execute'); + return; +} + +// Pointless to execute without the start script having done its job. +if ( !vAPI.contentscriptStartInjected ) { return; } // https://github.com/gorhill/uBlock/issues/456 // Already injected? if ( vAPI.contentscriptEndInjected ) { + //console.debug('contentscript-end.js > content script already injected'); return; } vAPI.contentscriptEndInjected = true; diff --git a/src/js/contentscript-start.js b/src/js/contentscript-start.js index ab95b3427..83166b21e 100644 --- a/src/js/contentscript-start.js +++ b/src/js/contentscript-start.js @@ -36,22 +36,26 @@ // https://github.com/gorhill/uBlock/issues/464 if ( document instanceof HTMLDocument === false ) { + //console.debug('contentscript-start.js > not a HTLMDocument'); return false; } // Because in case if ( !vAPI ) { + //console.debug('contentscript-start.js > vAPI not found'); return; } // Because Safari if ( vAPI.canExecuteContentScript() !== true ) { + //console.debug('contentscript-start.js > can\'t execute'); return; } // https://github.com/gorhill/uBlock/issues/456 // Already injected? if ( vAPI.contentscriptStartInjected ) { + //console.debug('contentscript-start.js > content script already injected'); return; } vAPI.contentscriptStartInjected = true; @@ -139,7 +143,13 @@ var filteringHandler = function(details) { // The port will never be used again at this point, disconnecting allows // the browser to flush this script from memory. } - // Do not close the port before we are done. + + // If no filters were found, maybe the script was injected before uBlock's + // process was fully initialized. When this happens, pages won't be + // cleaned right after browser launch. + vAPI.contentscriptStartInjected = !details || details.cosmeticHide.length !== 0; + + // Cleanup before leaving localMessager.close(); }; diff --git a/src/js/devtool-log.js b/src/js/devtool-log.js index 5646b8ab1..f2a8e67b2 100644 --- a/src/js/devtool-log.js +++ b/src/js/devtool-log.js @@ -51,6 +51,8 @@ var renderURL = function(url, filter) { } if ( reText === '*' ) { reText = '\\*'; + } else if ( reText.charAt(0) === '/' && reText.slice(-1) === '/' ) { + reText = reText.slice(1, -1); } else { reText = reText .replace(/\./g, '\\.') @@ -87,6 +89,7 @@ var createRow = function() { tr.appendChild(doc.createElement('td')); tr.appendChild(doc.createElement('td')); tr.appendChild(doc.createElement('td')); + tr.appendChild(doc.createElement('td')); return tr; }; @@ -96,18 +99,26 @@ var renderLogEntry = function(entry) { var tr = createRow(); if ( entry.result.charAt(1) === 'b' ) { tr.classList.add('blocked'); + tr.cells[0].textContent = '\u2009\u2212\u2009'; } else if ( entry.result.charAt(1) === 'a' ) { tr.classList.add('allowed'); if ( entry.result.charAt(0) === 'm' ) { tr.classList.add('mirrored'); } + tr.cells[0].textContent = '\u2009+\u2009'; + } else { + tr.cells[0].textContent = '\u2009\u00A0\u2009'; } if ( entry.type === 'main_frame' ) { tr.classList.add('maindoc'); } - tr.cells[0].textContent = entry.result.slice(3); - tr.cells[1].textContent = entry.type; - vAPI.insertHTML(tr.cells[2], renderURL(entry.url, entry.result)); + var filterText = entry.result.slice(3); + if ( entry.result.lastIndexOf('sa', 0) === 0 ) { + filterText = '@@' + filterText; + } + tr.cells[1].textContent = filterText; + tr.cells[2].textContent = entry.type; + vAPI.insertHTML(tr.cells[3], renderURL(entry.url, entry.result)); tbody.insertBefore(tr, tbody.firstChild); }; diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 8013e3c41..c795eeb03 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -79,8 +79,6 @@ const AllowAnyParty = AllowAction | AnyParty; var pageHostname = ''; // short-lived register -var reIgnoreEmpty = /^\s+$/; -var reIgnoreComment = /^\[|^!/; var reHostnameRule = /^[0-9a-z][0-9a-z.-]+[0-9a-z]$/; var reHostnameToken = /^[0-9a-z]+/g; var reGoodToken = /[%0-9a-z]{2,}/g; @@ -813,6 +811,61 @@ FilterManyWildcardsHostname.fromSelfie = function(s) { return new FilterManyWildcardsHostname(args[0], atoi(args[1]), args[2]); }; +/******************************************************************************/ + +// Regex-based filters + +var FilterRegex = function(s) { + this.re = new RegExp(s); +}; + +FilterRegex.prototype.match = function(url) { + return this.re.test(url); +}; + +FilterRegex.prototype.fid = '//'; + +FilterRegex.prototype.toString = function() { + return '/' + this.re.source + '/'; +}; + +FilterRegex.prototype.toSelfie = function() { + return this.re.source; +}; + +FilterRegex.fromSelfie = function(s) { + return new FilterRegex(s); +}; + +/******************************************************************************/ + +var FilterRegexHostname = function(s, hostname) { + this.re = new RegExp(s); + this.hostname = hostname; +}; + +FilterRegexHostname.prototype.match = function(url) { + // test hostname first, it's cheaper than evaluating a regex + return pageHostname.slice(-this.hostname.length) === this.hostname && + this.re.test(url); +}; + +FilterRegexHostname.prototype.fid = '//h'; + +FilterRegexHostname.prototype.toString = function() { + return '/' + this.re.source + '/$domain=' + this.hostname; +}; + +FilterRegexHostname.prototype.toSelfie = function() { + return this.re.source + '\t' + + this.hostname; +}; + +FilterRegexHostname.fromSelfie = function(s) { + var pos = s.indexOf('\t'); + return new FilterRegexHostname(s.slice(0, pos), s.slice(pos + 1)); +}; + /******************************************************************************/ /******************************************************************************/ @@ -920,12 +973,15 @@ FilterBucket.fromSelfie = function() { /******************************************************************************/ -var makeFilter = function(details, tokenBeg) { +var makeFilter = function(details) { var s = details.f; + if ( details.isRegex ) { + return new FilterRegex(s); + } var wcOffset = s.indexOf('*'); if ( wcOffset !== -1 ) { if ( s.indexOf('*', wcOffset + 1) !== -1 ) { - return details.anchor === 0 ? new FilterManyWildcards(s, tokenBeg) : null; + return details.anchor === 0 ? new FilterManyWildcards(s, details.tokenBeg) : null; } var lSegment = s.slice(0, wcOffset); var rSegment = s.slice(wcOffset + 1); @@ -935,10 +991,10 @@ var makeFilter = function(details, tokenBeg) { if ( details.anchor > 0 ) { return new FilterSingleWildcardRightAnchored(lSegment, rSegment); } - if ( tokenBeg === 0 ) { + if ( details.tokenBeg === 0 ) { return new FilterSingleWildcardPrefix0(lSegment, rSegment); } - return new FilterSingleWildcard(lSegment, rSegment, tokenBeg); + return new FilterSingleWildcard(lSegment, rSegment, details.tokenBeg); } if ( details.anchor < 0 ) { return new FilterPlainLeftAnchored(s); @@ -949,23 +1005,26 @@ var makeFilter = function(details, tokenBeg) { if ( details.hostnameAnchored ) { return new FilterPlainHnAnchored(s); } - if ( tokenBeg === 0 ) { + if ( details.tokenBeg === 0 ) { return new FilterPlainPrefix0(s); } - if ( tokenBeg === 1 ) { + if ( details.tokenBeg === 1 ) { return new FilterPlainPrefix1(s); } - return new FilterPlain(s, tokenBeg); + return new FilterPlain(s, details.tokenBeg); }; /******************************************************************************/ -var makeHostnameFilter = function(details, tokenBeg, hostname) { +var makeHostnameFilter = function(details, hostname) { var s = details.f; + if ( details.isRegex ) { + return new FilterRegexHostname(s, hostname); + } var wcOffset = s.indexOf('*'); if ( wcOffset !== -1 ) { if ( s.indexOf('*', wcOffset + 1) !== -1 ) { - return details.anchor === 0 ? new FilterManyWildcardsHostname(s, tokenBeg, hostname) : null; + return details.anchor === 0 ? new FilterManyWildcardsHostname(s, details.tokenBeg, hostname) : null; } var lSegment = s.slice(0, wcOffset); var rSegment = s.slice(wcOffset + 1); @@ -975,10 +1034,10 @@ var makeHostnameFilter = function(details, tokenBeg, hostname) { if ( details.anchor > 0 ) { return new FilterSingleWildcardRightAnchoredHostname(lSegment, rSegment, hostname); } - if ( tokenBeg === 0 ) { + if ( details.tokenBeg === 0 ) { return new FilterSingleWildcardPrefix0Hostname(lSegment, rSegment, hostname); } - return new FilterSingleWildcardHostname(lSegment, rSegment, tokenBeg, hostname); + return new FilterSingleWildcardHostname(lSegment, rSegment, details.tokenBeg, hostname); } if ( details.anchor < 0 ) { return new FilterPlainLeftAnchoredHostname(s, hostname); @@ -986,13 +1045,13 @@ var makeHostnameFilter = function(details, tokenBeg, hostname) { if ( details.anchor > 0 ) { return new FilterPlainRightAnchoredHostname(s, hostname); } - if ( tokenBeg === 0 ) { + if ( details.tokenBeg === 0 ) { return new FilterPlainPrefix0Hostname(s, hostname); } - if ( tokenBeg === 1 ) { + if ( details.tokenBeg === 1 ) { return new FilterPlainPrefix1Hostname(s, hostname); } - return new FilterPlainHostname(s, tokenBeg, hostname); + return new FilterPlainHostname(s, details.tokenBeg, hostname); }; /******************************************************************************/ @@ -1060,7 +1119,10 @@ var trimChar = function(s, c) { /******************************************************************************/ var FilterParser = function() { + this.reHasWildcard = /[\^\*]/; + this.reHasUppercase = /[A-Z]/; this.hostnames = []; + this.notHostnames = []; this.types = []; this.reset(); }; @@ -1092,8 +1154,12 @@ FilterParser.prototype.reset = function() { this.hostnameAnchored = false; this.hostnamePure = false; this.hostnames.length = 0; - this.notHostname = false; + this.notHostnames.length = 0; + this.isRegex = false; this.thirdParty = false; + this.token = ''; + this.tokenBeg = 0; + this.tokenEnd = 0; this.types.length = 0; this.important = 0; this.unsupported = false; @@ -1140,101 +1206,22 @@ FilterParser.prototype.parseOptParty = function(not) { FilterParser.prototype.parseOptHostnames = function(raw) { var hostnames = raw.split('|'); - var hostname, not; + var hostname; for ( var i = 0; i < hostnames.length; i++ ) { hostname = hostnames[i]; - not = hostname.charAt(0) === '~'; - if ( not ) { - hostname = hostname.slice(1); + if ( hostname.charAt(0) === '~' ) { + this.notHostnames.push(hostname.slice(1)); + } else { + this.hostnames.push(hostname); } - // https://github.com/gorhill/uBlock/issues/191 - // Well it doesn't seem to make a whole lot of sense to have both - // non-negated hostnames mixed with negated hostnames. - if ( this.hostnames.length !== 0 && not !== this.notHostname ) { - console.error('FilterContainer.parseOptHostnames(): ambiguous filter syntax: "%s"', this.f); - this.unsupported = true; - return; - } - this.notHostname = not; - this.hostnames.push(hostname); } }; /******************************************************************************/ -FilterParser.prototype.parse = function(s) { - // important! - this.reset(); - - if ( reHostnameRule.test(s) ) { - this.f = s; - this.hostnamePure = this.hostnameAnchored = true; - return this; - } - - // element hiding filter? - if ( s.indexOf('##') >= 0 || s.indexOf('#@') >= 0 ) { - this.elemHiding = true; - return this; - } - - // block or allow filter? - if ( s.slice(0, 2) === '@@' ) { - this.action = AllowAction; - s = s.slice(2); - } - - // options - var pos = s.indexOf('$'); - if ( pos > 0 ) { - this.fopts = s.slice(pos + 1); - s = s.slice(0, pos); - } - - // regex? (not supported) - if ( s.charAt(0) === '/' && s.slice(-1) === '/' ) { - this.unsupported = true; - return this; - } - - // hostname anchoring - if ( s.slice(0, 2) === '||' ) { - this.hostnameAnchored = true; - s = s.slice(2); - } - - // left-anchored - if ( s.charAt(0) === '|' ) { - this.anchor = -1; - s = s.slice(1); - } - - // right-anchored - if ( s.slice(-1) === '|' ) { - this.anchor = 1; - s = s.slice(0, -1); - } - - // normalize placeholders - // TODO: transforming `^` into `*` is not a strict interpretation of - // ABP syntax. - s = s.replace(/\^/g, '*'); - s = s.replace(/\*\*+/g, '*'); - - // remove leading and trailing wildcards - s = trimChar(s, '*'); - - // pure hostname-based? - this.hostnamePure = this.hostnameAnchored && reHostnameRule.test(s); - - this.f = s; - - if ( !this.fopts ) { - return this; - } - - // parse options - var opts = this.fopts.split(','); +FilterParser.prototype.parseOptions = function(s) { + this.fopts = s; + var opts = s.split(','); var opt, not; for ( var i = 0; i < opts.length; i++ ) { opt = opts[i]; @@ -1270,9 +1257,119 @@ FilterParser.prototype.parse = function(s) { this.unsupported = true; break; } +}; + +/******************************************************************************/ + +FilterParser.prototype.parse = function(s) { + // important! + this.reset(); + + // plain hostname? + if ( reHostnameRule.test(s) ) { + this.f = s; + this.hostnamePure = this.hostnameAnchored = true; + return this; + } + + // element hiding filter? + var pos = s.indexOf('#'); + if ( pos !== -1 ) { + var c = s.charAt(pos + 1); + if ( c === '#' || c === '@' ) { + console.error('static-net-filtering.js > unexpected cosmetic filters'); + this.elemHiding = true; + return this; + } + } + + // options + pos = s.indexOf('$'); + if ( pos !== -1 ) { + this.parseOptions(s.slice(pos + 1)); + s = s.slice(0, pos); + } + + // block or allow filter? + if ( s.lastIndexOf('@@', 0) === 0 ) { + this.action = AllowAction; + s = s.slice(2); + } + + // regex? + if ( s.charAt(0) === '/' && s.slice(-1) === '/' ) { + this.isRegex = true; + this.f = s.slice(1, -1); + return this; + } + + // hostname anchoring + if ( s.lastIndexOf('||', 0) === 0 ) { + this.hostnameAnchored = true; + s = s.slice(2); + } + + // left-anchored + if ( s.charAt(0) === '|' ) { + this.anchor = -1; + s = s.slice(1); + } + + // right-anchored + if ( s.slice(-1) === '|' ) { + this.anchor = 1; + s = s.slice(0, -1); + } + + // normalize placeholders + // TODO: transforming `^` into `*` is not a strict interpretation of + // ABP syntax. + if ( this.reHasWildcard.test(s) ) { + s = s.replace(/\^/g, '*').replace(/\*\*+/g, '*'); + s = trimChar(s, '*'); + } + + // plain hostname? + this.hostnamePure = this.hostnameAnchored && reHostnameRule.test(s); + + // This might look weird but we gain memory footprint by not going through + // toLowerCase(), at least on Chromium. Because copy-on-write? + + this.f = this.reHasUppercase.test(s) ? s.toLowerCase() : s; + return this; }; +/******************************************************************************/ + +FilterParser.prototype.makeToken = function() { + if ( this.isRegex ) { + this.token = '*'; + return; + } + + var matches; + + if ( this.hostnameAnchored ) { + matches = findHostnameToken(this.f); + if ( !matches || matches[0].length === 0 ) { + return; + } + this.tokenBeg = matches.index; + this.tokenEnd = reHostnameToken.lastIndex; + this.token = this.f.slice(this.tokenBeg, this.tokenEnd); + return; + } + + matches = findFirstGoodToken(this.f); + if ( !matches || matches[0].length === 0 ) { + return; + } + this.tokenBeg = matches.index; + this.tokenEnd = reGoodToken.lastIndex; + this.token = this.f.slice(this.tokenBeg, this.tokenEnd); +}; + /******************************************************************************/ /******************************************************************************/ @@ -1410,7 +1507,9 @@ FilterContainer.prototype.fromSelfie = function(selfie) { '*|': FilterSingleWildcardRightAnchored, '*|h': FilterSingleWildcardRightAnchoredHostname, '*+': FilterManyWildcards, - '*+h': FilterManyWildcardsHostname + '*+h': FilterManyWildcardsHostname, + '//': FilterRegex, + '//h': FilterRegexHostname }; var catKey, tokenKey; @@ -1463,26 +1562,28 @@ FilterContainer.prototype.add = function(s) { // ORDER OF TESTS IS IMPORTANT! // Ignore empty lines - if ( reIgnoreEmpty.test(s) ) { + s = s.trim(); + if ( s.length === 0 ) { return false; } // Ignore comments - if ( reIgnoreComment.test(s) ) { + var c = s.charAt(0); + if ( c === '[' || c === '!' ) { return false; } var parsed = this.filterParser.parse(s); - // Ignore rules with other conditions for now - if ( parsed.unsupported ) { - this.rejectedCount += 1; - // console.log('µBlock> abp-filter.js/FilterContainer.add(): unsupported filter "%s"', s); + // Ignore element-hiding filters + if ( parsed.elemHiding ) { return false; } - // Ignore element-hiding filters - if ( parsed.elemHiding ) { + // Ignore filters with unsupported options + if ( parsed.unsupported ) { + this.rejectedCount += 1; + // console.log('µBlock> abp-filter.js/FilterContainer.add(): unsupported filter "%s"', s); return false; } @@ -1533,95 +1634,95 @@ FilterContainer.prototype.add = function(s) { /******************************************************************************/ FilterContainer.prototype.addFilter = function(parsed) { - // TODO: avoid duplicates - - var matches = parsed.hostnameAnchored ? - findHostnameToken(parsed.f) : - findFirstGoodToken(parsed.f); - if ( !matches || !matches[0].length ) { + parsed.makeToken(); + if ( parsed.token === '' ) { + console.error('static-net-filtering.js > FilterContainer.addFilter("%s"): can\'t tokenize', parsed.f); return false; } - var tokenBeg = matches.index; - var tokenEnd = parsed.hostnameAnchored ? - reHostnameToken.lastIndex : - reGoodToken.lastIndex; - var filter; - - var i = parsed.hostnames.length; - - // Applies to specific domains - - if ( i !== 0 && !parsed.notHostname ) { - while ( i-- ) { - filter = makeHostnameFilter(parsed, tokenBeg, parsed.hostnames[i]); - if ( !filter ) { - return false; - } - this.addFilterEntry(filter, parsed, AnyParty, tokenBeg, tokenEnd); - } - return true; - } var party = AnyParty; if ( parsed.firstParty !== parsed.thirdParty ) { party = parsed.firstParty ? FirstParty : ThirdParty; } - // Applies to all domains, with exception(s) + var filter; + var i = parsed.hostnames.length; + var j = parsed.notHostnames.length; - // https://github.com/gorhill/uBlock/issues/191 - // Invert the purpose of the filter for negated hostnames - if ( i !== 0 && parsed.notHostname ) { - filter = makeFilter(parsed, tokenBeg); + // Applies to all domains without exceptions + if ( i === 0 && j === 0 ) { + filter = makeFilter(parsed); + if ( !filter ) { + return false; + } + this.addFilterEntry(filter, parsed, party); + return true; + } + + // Applies to specific domains + if ( i !== 0 ) { + while ( i-- ) { + filter = makeHostnameFilter(parsed, parsed.hostnames[i]); + if ( !filter ) { + return false; + } + this.addFilterEntry(filter, parsed, party); + } + } + // No exceptions + if ( j === 0 ) { + return true; + } + + // Case: + // - applies everywhere except to specific domains + // Example: + // - ||adm.fwmrm.net/p/msnbc_live/$object-subrequest,third-party,domain=~msnbc.msn.com|~www.nbcnews.com + if ( i === 0 ) { + filter = makeFilter(parsed); if ( !filter ) { return false; } // https://github.com/gorhill/uBlock/issues/251 // Apply third-party option if it is present - this.addFilterEntry(filter, parsed, party, tokenBeg, tokenEnd); - // Reverse purpose of filter - parsed.action ^= ToggleAction; - while ( i-- ) { - filter = makeHostnameFilter(parsed, tokenBeg, parsed.hostnames[i]); - if ( !filter ) { - return false; - } - // https://github.com/gorhill/uBlock/issues/191#issuecomment-53654024 - // If it is a block filter, we need to reverse the order of - // evaluation. - if ( parsed.action === BlockAction ) { - parsed.important = Important; - } - this.addFilterEntry(filter, parsed, AnyParty, tokenBeg, tokenEnd); + this.addFilterEntry(filter, parsed, party); + } + + // Cases: + // - applies everywhere except to specific domains + // - applies to specific domains except other specific domains + // Example: + // - /^https?\:\/\/(?!(...)\/)/$script,third-party,xmlhttprequest,domain=photobucket.com|~secure.photobucket.com + + // Reverse purpose of filter + parsed.action ^= ToggleAction; + while ( j-- ) { + filter = makeHostnameFilter(parsed, parsed.notHostnames[j]); + if ( !filter ) { + return false; } - return true; + // https://github.com/gorhill/uBlock/issues/191#issuecomment-53654024 + // If it is a block filter, we need to reverse the order of + // evaluation. + if ( parsed.action === BlockAction ) { + parsed.important = Important; + } + this.addFilterEntry(filter, parsed, party); } - - // Applies to all domains without exceptions - - filter = makeFilter(parsed, tokenBeg); - if ( !filter ) { - return false; - } - - this.addFilterEntry(filter, parsed, party, tokenBeg, tokenEnd); - return true; }; /******************************************************************************/ -FilterContainer.prototype.addFilterEntry = function(filter, parsed, party, tokenBeg, tokenEnd) { - var s = parsed.f; - var tokenKey = s.slice(tokenBeg, tokenEnd); +FilterContainer.prototype.addFilterEntry = function(filter, parsed, party) { var bits = parsed.action | parsed.important | party; if ( parsed.types.length === 0 ) { - this.addToCategory(bits | AnyType, tokenKey, filter); + this.addToCategory(bits | AnyType, parsed.token, filter); return; } var n = parsed.types.length; for ( var i = 0; i < n; i++ ) { - this.addToCategory(bits | parsed.types[i], tokenKey, filter); + this.addToCategory(bits | parsed.types[i], parsed.token, filter); } }; @@ -1691,6 +1792,13 @@ FilterContainer.prototype.matchTokens = function(bucket, url) { return f; } } + + // Regex-based filters + f = bucket['*']; + if ( f !== undefined && f.match(url) !== false ) { + return f; + } + return false; }; @@ -1756,7 +1864,7 @@ FilterContainer.prototype.matchStringExactType = function(context, requestURL, r var type = typeNameToTypeValue[requestType]; var categories = this.categories; - var bucket; + var bf = false, bucket; // Tokenize only once this.tokenize(url); @@ -1777,7 +1885,6 @@ FilterContainer.prototype.matchStringExactType = function(context, requestURL, r } // Test against block filters - bf = false; if ( bucket = categories[this.makeCategoryKey(BlockAnyParty | type)] ) { bf = this.matchTokens(bucket, url); } @@ -1853,7 +1960,7 @@ FilterContainer.prototype.matchString = function(context) { pageHostname = context.pageHostname || ''; var categories = this.categories; - var bucket; + var bf, bucket; // Tokenize only once this.tokenize(url); diff --git a/src/js/storage.js b/src/js/storage.js index ac87a64e3..3855f8d5b 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -374,12 +374,15 @@ var cosmeticFilteringEngine = this.cosmeticFilteringEngine; var parseCosmeticFilters = this.userSettings.parseAllABPHideFilters; - var reIsCosmeticFilter = /#@?#/; - var reLocalhost = /(?:^|\s)(?:localhost\.localdomain|localhost|local|broadcasthost|0\.0\.0\.0|127\.0\.0\.1|::1|fe80::1%lo0)(?=\s|$)/g; - var reAsciiSegment = /^[\x21-\x7e]+$/; + var reIsCosmeticFilter = /#[@#]/; + var reIsWhitespaceChar = /\s/; + var reMaybeLocalIp = /^[\d:f]/; + var reIsLocalhostRedirect = /\s+(?:broadcasthost|local|localhost|localhost\.localdomain)(?=\s|$)/; + var reLocalIp = /^(?:0\.0\.0\.0|127\.0\.0\.1|::1|fe80::1%lo0)/; + //var reAsciiSegment = /^[\x21-\x7e]+$/; var matches; var lineBeg = 0, lineEnd, currentLineBeg; - var line, c; + var line, lineRaw, c, pos; while ( lineBeg < rawEnd ) { lineEnd = rawText.indexOf('\n', lineBeg); @@ -393,10 +396,14 @@ // rhill 2014-04-18: The trim is important here, as without it there // could be a lingering `\r` which would cause problems in the // following parsing code. - line = rawText.slice(lineBeg, lineEnd).trim(); + line = lineRaw = rawText.slice(lineBeg, lineEnd).trim(); currentLineBeg = lineBeg; lineBeg = lineEnd + 1; + if ( line.length === 0 ) { + continue; + } + // Strip comments c = line.charAt(0); if ( c === '!' || c === '[' ) { @@ -404,6 +411,7 @@ } // Parse or skip cosmetic filters + // All cosmetic filters are caught here if ( parseCosmeticFilters ) { if ( cosmeticFilteringEngine.add(line) ) { continue; @@ -412,36 +420,59 @@ continue; } + // Whatever else is next can be assumed to not be a cosmetic filter + + // Most comments start in first column if ( c === '#' ) { continue; } + // Catch comments somewhere on the line + // Remove: + // ... #blah blah blah + // ... # blah blah blah + // Don't remove: + // ...#blah blah blah + // because some ABP filters uses the `#` character (URL fragment) + pos = line.indexOf('#'); + if ( pos !== -1 && reIsWhitespaceChar.test(line.charAt(pos - 1)) ) { + line = line.slice(0, pos).trim(); + } + // https://github.com/gorhill/httpswitchboard/issues/15 // Ensure localhost et al. don't end up in the ubiquitous blacklist. - // TODO: do this only if it's not an [Adblock] list - line = line - .replace(/\s+#.*$/, '') - .toLowerCase() - .replace(reLocalhost, '') - .trim(); + // With hosts files, we need to remove local IP redirection + if ( reMaybeLocalIp.test(c) ) { + // Ignore hosts file redirect configuration + // 127.0.0.1 localhost + // 255.255.255.255 broadcasthost + if ( reIsLocalhostRedirect.test(line) ) { + continue; + } + line = line.replace(reLocalIp, '').trim(); + } + + if ( line.length === 0 ) { + continue; + } // The filter is whatever sequence of printable ascii character without // whitespaces - matches = reAsciiSegment.exec(line); - if ( matches === null ) { - //console.debug('µBlock.mergeFilterList(): skipping "%s"', lineRaw); - continue; - } + //matches = reAsciiSegment.exec(line); + //if ( matches === null ) { + // console.debug('storage.js > µBlock.mergeFilterList(): skipping "%s"', lineRaw); + // continue; + //} // Bypass anomalies // For example, when a filter contains whitespace characters, or // whatever else outside the range of printable ascii characters. - if ( matches[0] !== line ) { - // console.error('"%s" !== "%s"', matches[0], line); - continue; - } + //if ( matches[0] !== line ) { + // console.error('"%s" !== "%s"', matches[0], line); + // continue; + //} - staticNetFilteringEngine.add(matches[0]); + staticNetFilteringEngine.add(line); } }; @@ -607,16 +638,17 @@ if ( countdown !== 0 ) { return; } + // https://github.com/gorhill/uBlock/issues/426 // Important: remove barrier to remote fetching, this was useful only // for launch time. µb.assets.allowRemoteFetch = true; - vAPI.onLoadAllCompleted(); - // https://github.com/gorhill/uBlock/issues/184 // Check for updates not too far in the future. µb.updater.restart(µb.firstUpdateAfter); + + vAPI.onLoadAllCompleted(); }; // Filters are in memory.