From 2234933b82d1c17af42ba574b6515937c7515029 Mon Sep 17 00:00:00 2001 From: gorhill Date: Tue, 9 Jun 2015 10:27:08 -0400 Subject: [PATCH] this fixes #59: now accurately reporting static filters in logger --- src/js/document-blocked.js | 2 +- src/js/logger-ui.js | 258 +++++++++++--- src/js/pagestore.js | 31 +- src/js/static-net-filtering.js | 601 +++++++++++++++++++++------------ src/js/tab.js | 29 +- src/js/traffic.js | 47 +-- 6 files changed, 646 insertions(+), 322 deletions(-) diff --git a/src/js/document-blocked.js b/src/js/document-blocked.js index 6c0b70e79..c04644542 100644 --- a/src/js/document-blocked.js +++ b/src/js/document-blocked.js @@ -104,7 +104,7 @@ var proceedPermanent = function() { /******************************************************************************/ uDom('.what').text(details.url); -uDom('#why').text(details.why.slice(3)); +uDom('#why').text(details.why); if ( window.history.length > 1 ) { uDom('#back').on('click', function() { window.history.back(); }); diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index e3209fdf6..0c7c7d5c3 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -103,61 +103,24 @@ var tabIdFromClassName = function(className) { /******************************************************************************/ -var retextFromStaticFilteringResult = function(result) { - var retext = result.slice(3); - var pos = retext.indexOf('$'); - if ( pos > 0 ) { - retext = retext.slice(0, pos); - } - if ( retext === '*' ) { - return '^.*$'; - } - if ( retext.charAt(0) === '/' && retext.slice(-1) === '/' ) { - return retext.slice(1, -1); - } - return retext - .replace(/\./g, '\\.') - .replace(/\?/g, '\\?') - .replace('||', '') - .replace(/\^/g, '.') - .replace(/^\|/g, '^') - .replace(/\|$/g, '$') - .replace(/\*/g, '.*') - ; -}; - -/******************************************************************************/ - -var retextFromURLFilteringResult = function(result) { +var regexFromURLFilteringResult = function(result) { var beg = result.indexOf(' '); var end = result.indexOf(' ', beg + 1); var url = result.slice(beg + 1, end); if ( url === '*' ) { - return '^.*$'; + return new RegExp('^.*$', 'gi'); } - return '^' + url.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return new RegExp('^' + url.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); }; /******************************************************************************/ // Emphasize hostname in URL, as this is what matters in uMatrix's rules. -var nodeFromURL = function(url, filter) { - var filterType = filter.charAt(0); - if ( filterType !== 's' && filterType !== 'l' ) { +var nodeFromURL = function(url, re) { + if ( re instanceof RegExp === false ) { return document.createTextNode(url); } - // make a regex out of the filter - var retext = ''; - if ( filterType === 's' ) { - retext = retextFromStaticFilteringResult(filter); - } else if ( filterType === 'l' ) { - retext = retextFromURLFilteringResult(filter); - } - if ( retext === '' ) { - return document.createTextNode(url); - } - var re = new RegExp(retext, 'gi'); var matches = re.exec(url); if ( matches === null || matches[0].length === 0 ) { return document.createTextNode(url); @@ -173,6 +136,187 @@ var renderedURLTemplate = document.querySelector('#renderedURLTemplate > span'); /******************************************************************************/ +// Pretty much same logic as found in: +// µBlock.staticNetFilteringEngine.filterStringFromCompiled +// µBlock.staticNetFilteringEngine.filterRegexFromCompiled + +var filterDecompiler = (function() { + var typeValToTypeName = { + 1: 'stylesheet', + 2: 'image', + 3: 'object', + 4: 'script', + 5: 'xmlhttprequest', + 6: 'sub_frame', + 7: 'font', + 8: 'other', + 13: 'elemhide', + 14: 'inline-script', + 15: 'popup' + }; + + var toString = function(compiled) { + var opts = []; + var vfields = compiled.split('\v'); + var filter = ''; + var bits = parseInt(vfields[1], 16) | 0; + + if ( bits & 0x01 ) { + filter += '@@'; + } + + var fid = vfields[2] === '.' ? '.' : vfields[3]; + var tfields = fid !== '.' ? vfields[4].split('\t') : []; + var tfield0 = tfields[0]; + + switch ( fid ) { + case '.': + filter += '||' + vfields[3] + '^'; + break; + case 'a': + case 'ah': + case '0a': + case '0ah': + case '1a': + case '1ah': + case '_': + case '_h': + filter += tfield0; + // If the filter resemble a regex, add a trailing `*` as is + // customary to prevent ambiguity in logger. + if ( tfield0.charAt(0) === '/' && tfield0.slice(-1) === '/' ) { + filter += '*'; + } + break; + case '|a': + case '|ah': + filter += '|' + tfield0; + break; + case 'a|': + case 'a|h': + filter += tfield0 + '|'; + break; + case '||a': + case '||ah': + case '||_': + case '||_h': + filter += '||' + tfield0; + break; + case '//': + case '//h': + filter += '/' + tfield0 + '/'; + break; + default: + break; + } + + // Domain option? + switch ( fid ) { + case '0ah': + case '1ah': + case '|ah': + case 'a|h': + case '||ah': + case '||_h': + case '//h': + opts.push('domain=' + tfields[1]); + break; + case 'ah': + case '_h': + opts.push('domain=' + tfields[2]); + break; + default: + break; + } + + // Filter options + if ( bits & 0x02 ) { + opts.push('important'); + } + if ( bits & 0x08 ) { + opts.push('third-party'); + } else if ( bits & 0x04 ) { + opts.push('first-party'); + } + var typeVal = bits >>> 4 & 0x0F; + if ( typeVal ) { + opts.push(typeValToTypeName[typeVal]); + // Because of the way `elemhide` is implemented + if ( typeVal === 13 ) { + filter = '@@' + filter; + } + } + if ( opts.length !== 0 ) { + filter += '$' + opts.join(','); + } + + return filter; + }; + + var reEscape = /[.+?^${}()|[\]\\]/g; + var reWildcards = /\*+/g; + + var toRegex = function(compiled) { + var vfields = compiled.split('\v'); + var fid = vfields[2] === '.' ? '.' : vfields[3]; + var tfields = fid !== '.' ? vfields[4].split('\t') : []; + var reStr; + + switch ( fid ) { + case '.': + reStr = vfields[3] + .replace(reEscape, '\\$&'); + break; + case 'a': + case 'ah': + case '0a': + case '0ah': + case '1a': + case '1ah': + case '_': + case '_h': + case '||a': + case '||ah': + case '||_': + case '||_h': + reStr = tfields[0] + .replace(reEscape, '\\$&') + .replace(reWildcards, '.*'); + break; + case '|a': + case '|ah': + reStr = '^' + tfields[0]. + replace(reEscape, '\\$&') + .replace(reWildcards, '.*'); + break; + case 'a|': + case 'a|h': + reStr = tfields[0] + .replace(reEscape, '\\$&') + .replace(reWildcards, '.*') + '$'; + break; + case '//': + case '//h': + reStr = tfields[0]; + break; + default: + break; + } + + if ( reStr === undefined) { + return null; + } + return new RegExp(reStr, 'gi'); + }; + + return { + toString: toString, + toRegex: toRegex + }; +})(); + +/******************************************************************************/ + var createCellAt = function(tr, index) { var td = tr.cells[index]; var mustAppend = !td; @@ -276,26 +420,31 @@ var renderNetLogEntry = function(tr, entry) { tr.setAttribute('data-hn-frame', entry.d4); } - // Cosmetic filter? var filterCat = filter.slice(0, 3); if ( filterCat.charAt(2) === ':' ) { tr.classList.add(filterCat.slice(0, 2)); } - var filterText = filter.slice(3); - if ( filter.lastIndexOf('sa', 0) === 0 ) { - filterText = '@@' + filterText; + var filteringType = filterCat.charAt(0); + td = tr.cells[2]; + if ( filter !== '' ) { + filter = filter.slice(3); + if ( filteringType === 's' ) { + td.textContent = filterDecompiler.toString(filter); + } else { + td.textContent = filter; + } } - tr.cells[2].textContent = filterText; td = tr.cells[3]; - if ( filter.charAt(1) === 'b' ) { + var filteringOp = filterCat.charAt(1); + if ( filteringOp === 'b' ) { tr.classList.add('blocked'); td.textContent = '--'; - } else if ( filter.charAt(1) === 'a' ) { + } else if ( filteringOp === 'a' ) { tr.classList.add('allowed'); td.textContent = '++'; - } else if ( filter.charAt(1) === 'n' ) { + } else if ( filteringOp === 'n' ) { tr.classList.add('nooped'); td.textContent = '**'; } else { @@ -303,7 +452,14 @@ var renderNetLogEntry = function(tr, entry) { } tr.cells[4].textContent = (prettyRequestTypes[type] || type); - tr.cells[5].appendChild(nodeFromURL(url, filter)); + + var re = null; + if ( filteringType === 's' ) { + re = filterDecompiler.toRegex(filter); + } else if ( filteringType === 'l' ) { + re = regexFromURLFilteringResult(filter); + } + tr.cells[5].appendChild(nodeFromURL(url, re)); }; /******************************************************************************/ diff --git a/src/js/pagestore.js b/src/js/pagestore.js index 5bc93fda2..3d6d4a531 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -114,7 +114,7 @@ NetFilteringResultCache.factory = function() { NetFilteringResultCache.prototype.init = function() { this.urls = {}; this.count = 0; - this.shelfLife = 60 * 1000; + this.shelfLife = 15 * 1000; this.timer = null; this.boundPruneAsyncCallback = this.pruneAsyncCallback.bind(this); }; @@ -309,11 +309,22 @@ PageStore.prototype.init = function(tabId) { // Support `elemhide` filter option. Called at this point so the required // context is all setup at this point. - var context = this.createContextFromPage(); - this.skipCosmeticFiltering = µb.staticNetFilteringEngine - .matchStringExactType(context, tabContext.normalURL, 'cosmetic-filtering') - .charAt(1) === 'b'; - + this.skipCosmeticFiltering = µb.staticNetFilteringEngine.matchStringExactType( + this.createContextFromPage(), + tabContext.normalURL, + 'cosmetic-filtering' + ); + if ( this.skipCosmeticFiltering && µb.logger.isEnabled() ) { + µb.logger.writeOne( + tabId, + 'net', + µb.staticNetFilteringEngine.toResultString(true), + 'elemhide', + tabContext.rawURL, + this.tabHostname, + this.tabHostname + ); + } return this; }; @@ -515,7 +526,9 @@ PageStore.prototype.filterRequest = function(context) { // Static filtering never override dynamic filtering if ( result === '' || result.charAt(1) === 'n' ) { - result = µb.staticNetFilteringEngine.matchString(context) || result; + if ( µb.staticNetFilteringEngine.matchString(context) ) { + result = µb.staticNetFilteringEngine.toResultString(µb.logger.isEnabled()); + } } //console.debug('cache MISS: PageStore.filterRequest("%s")', context.requestURL); @@ -555,7 +568,9 @@ PageStore.prototype.filterRequestNoCache = function(context) { // Static filtering never override dynamic filtering if ( result === '' || result.charAt(1) === 'n' ) { - result = µb.staticNetFilteringEngine.matchString(context) || result; + if ( µb.staticNetFilteringEngine.matchString(context) ) { + result = µb.staticNetFilteringEngine.toResultString(µb.logger.isEnabled()); + } } return result; diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index fa5b8bca3..52f968b84 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -1,7 +1,7 @@ /******************************************************************************* - µBlock - a browser extension to block requests. - Copyright (C) 2014 Raymond Hill + uBlock - a browser extension to block requests. + Copyright (C) 2014-2015 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 @@ -70,6 +70,20 @@ var typeNameToTypeValue = { }; var typeOtherValue = typeNameToTypeValue.other; +var typeValueToTypeName = { + 1: 'stylesheet', + 2: 'image', + 3: 'object', + 4: 'script', + 5: 'xmlhttprequest', + 6: 'sub_frame', + 7: 'font', + 8: 'other', + 13: 'cosmetic-filtering', + 14: 'inline-script', + 15: 'popup' +}; + // All network request types to bitmap // bring origin to 0 (from 4 -- see typeNameToTypeValue) // left-shift 1 by the above-calculated value @@ -175,7 +189,7 @@ var alwaysTruePseudoRegex = { } }; -var strToRegex = function(s, anchor) { +var strToRegex = function(s, anchor, flags) { // https://github.com/chrisaljoudi/uBlock/issues/1038 // Special case: always match. if ( s === '*' ) { @@ -193,7 +207,7 @@ var strToRegex = function(s, anchor) { } //console.debug('µBlock.staticNetFilteringEngine: created RegExp("%s")', reStr); - return new RegExp(reStr); + return new RegExp(reStr, flags); }; /******************************************************************************* @@ -235,15 +249,13 @@ FilterPlain.prototype.match = function(url, tokenBeg) { return url.substr(tokenBeg - this.tokenBeg, this.s.length) === this.s; }; -FilterPlain.fid = FilterPlain.prototype.fid = 'a'; +FilterPlain.fid = +FilterPlain.prototype.fid = +FilterPlain.prototype.rtfid = 'a'; -FilterPlain.prototype.toString = function() { - return this.s; -}; - -FilterPlain.prototype.toSelfie = function() { - return this.s + '\t' + - this.tokenBeg; +FilterPlain.prototype.toSelfie = +FilterPlain.prototype.rtCompile = function() { + return this.s + '\t' + this.tokenBeg; }; FilterPlain.compile = function(details) { @@ -268,22 +280,17 @@ FilterPlainHostname.prototype.match = function(url, tokenBeg) { url.substr(tokenBeg - this.tokenBeg, this.s.length) === this.s; }; -FilterPlainHostname.fid = FilterPlainHostname.prototype.fid = 'ah'; +FilterPlainHostname.fid = +FilterPlainHostname.prototype.fid = +FilterPlainHostname.prototype.rtfid = 'ah'; -FilterPlainHostname.prototype.toString = function() { - return this.s + '$domain=' + this.hostname; -}; - -FilterPlainHostname.prototype.toSelfie = function() { - return this.s + '\t' + - this.tokenBeg + '\t' + - this.hostname; +FilterPlainHostname.prototype.toSelfie = +FilterPlainHostname.prototype.rtCompile = function() { + return this.s + '\t' + this.tokenBeg + '\t' + this.hostname; }; FilterPlainHostname.compile = function(details, hostname) { - return details.f + '\t' + - details.tokenBeg + '\t' + - hostname; + return details.f + '\t' + details.tokenBeg + '\t' + hostname; }; FilterPlainHostname.fromSelfie = function(s) { @@ -301,13 +308,12 @@ FilterPlainPrefix0.prototype.match = function(url, tokenBeg) { return url.substr(tokenBeg, this.s.length) === this.s; }; -FilterPlainPrefix0.fid = FilterPlainPrefix0.prototype.fid = '0a'; +FilterPlainPrefix0.fid = +FilterPlainPrefix0.prototype.fid = +FilterPlainPrefix0.prototype.rtfid = '0a'; -FilterPlainPrefix0.prototype.toString = function() { - return this.s; -}; - -FilterPlainPrefix0.prototype.toSelfie = function() { +FilterPlainPrefix0.prototype.toSelfie = +FilterPlainPrefix0.prototype.rtCompile = function() { return this.s; }; @@ -331,15 +337,13 @@ FilterPlainPrefix0Hostname.prototype.match = function(url, tokenBeg) { url.substr(tokenBeg, this.s.length) === this.s; }; -FilterPlainPrefix0Hostname.fid = FilterPlainPrefix0Hostname.prototype.fid = '0ah'; +FilterPlainPrefix0Hostname.fid = +FilterPlainPrefix0Hostname.prototype.fid = +FilterPlainPrefix0Hostname.prototype.rtfid = '0ah'; -FilterPlainPrefix0Hostname.prototype.toString = function() { - return this.s + '$domain=' + this.hostname; -}; - -FilterPlainPrefix0Hostname.prototype.toSelfie = function() { - return this.s + '\t' + - this.hostname; +FilterPlainPrefix0Hostname.prototype.toSelfie = +FilterPlainPrefix0Hostname.prototype.rtCompile = function() { + return this.s + '\t' + this.hostname; }; FilterPlainPrefix0Hostname.compile = function(details, hostname) { @@ -361,13 +365,12 @@ FilterPlainPrefix1.prototype.match = function(url, tokenBeg) { return url.substr(tokenBeg - 1, this.s.length) === this.s; }; -FilterPlainPrefix1.fid = FilterPlainPrefix1.prototype.fid = '1a'; +FilterPlainPrefix1.fid = +FilterPlainPrefix1.prototype.fid = +FilterPlainPrefix1.prototype.rtfid = '1a'; -FilterPlainPrefix1.prototype.toString = function() { - return this.s; -}; - -FilterPlainPrefix1.prototype.toSelfie = function() { +FilterPlainPrefix1.prototype.toSelfie = +FilterPlainPrefix1.prototype.rtCompile = function() { return this.s; }; @@ -391,15 +394,13 @@ FilterPlainPrefix1Hostname.prototype.match = function(url, tokenBeg) { url.substr(tokenBeg - 1, this.s.length) === this.s; }; -FilterPlainPrefix1Hostname.fid = FilterPlainPrefix1Hostname.prototype.fid = '1ah'; +FilterPlainPrefix1Hostname.fid = +FilterPlainPrefix1Hostname.prototype.fid = +FilterPlainPrefix1Hostname.prototype.rtfid = '1ah'; -FilterPlainPrefix1Hostname.prototype.toString = function() { - return this.s + '$domain=' + this.hostname; -}; - -FilterPlainPrefix1Hostname.prototype.toSelfie = function() { - return this.s + '\t' + - this.hostname; +FilterPlainPrefix1Hostname.prototype.toSelfie = +FilterPlainPrefix1Hostname.prototype.rtCompile = function() { + return this.s + '\t' + this.hostname; }; FilterPlainPrefix1Hostname.compile = function(details, hostname) { @@ -421,13 +422,12 @@ FilterPlainLeftAnchored.prototype.match = function(url) { return url.slice(0, this.s.length) === this.s; }; -FilterPlainLeftAnchored.fid = FilterPlainLeftAnchored.prototype.fid = '|a'; +FilterPlainLeftAnchored.fid = +FilterPlainLeftAnchored.prototype.fid = +FilterPlainLeftAnchored.prototype.rtfid = '|a'; -FilterPlainLeftAnchored.prototype.toString = function() { - return '|' + this.s; -}; - -FilterPlainLeftAnchored.prototype.toSelfie = function() { +FilterPlainLeftAnchored.prototype.toSelfie = +FilterPlainLeftAnchored.prototype.rtCompile = function() { return this.s; }; @@ -451,15 +451,13 @@ FilterPlainLeftAnchoredHostname.prototype.match = function(url) { url.slice(0, this.s.length) === this.s; }; -FilterPlainLeftAnchoredHostname.fid = FilterPlainLeftAnchoredHostname.prototype.fid = '|ah'; +FilterPlainLeftAnchoredHostname.fid = +FilterPlainLeftAnchoredHostname.prototype.fid = +FilterPlainLeftAnchoredHostname.prototype.rtfid = '|ah'; -FilterPlainLeftAnchoredHostname.prototype.toString = function() { - return '|' + this.s + '$domain=' + this.hostname; -}; - -FilterPlainLeftAnchoredHostname.prototype.toSelfie = function() { - return this.s + '\t' + - this.hostname; +FilterPlainLeftAnchoredHostname.prototype.toSelfie = +FilterPlainLeftAnchoredHostname.prototype.rtCompile = function() { + return this.s + '\t' + this.hostname; }; FilterPlainLeftAnchoredHostname.compile = function(details, hostname) { @@ -481,13 +479,12 @@ FilterPlainRightAnchored.prototype.match = function(url) { return url.slice(-this.s.length) === this.s; }; -FilterPlainRightAnchored.fid = FilterPlainRightAnchored.prototype.fid = 'a|'; +FilterPlainRightAnchored.fid = +FilterPlainRightAnchored.prototype.fid = +FilterPlainRightAnchored.prototype.rtfid = 'a|'; -FilterPlainRightAnchored.prototype.toString = function() { - return this.s + '|'; -}; - -FilterPlainRightAnchored.prototype.toSelfie = function() { +FilterPlainRightAnchored.prototype.toSelfie = +FilterPlainRightAnchored.prototype.rtCompile = function() { return this.s; }; @@ -511,15 +508,13 @@ FilterPlainRightAnchoredHostname.prototype.match = function(url) { url.slice(-this.s.length) === this.s; }; -FilterPlainRightAnchoredHostname.fid = FilterPlainRightAnchoredHostname.prototype.fid = 'a|h'; +FilterPlainRightAnchoredHostname.fid = +FilterPlainRightAnchoredHostname.prototype.fid = +FilterPlainRightAnchoredHostname.prototype.rtfid = 'a|h'; -FilterPlainRightAnchoredHostname.prototype.toString = function() { - return this.s + '|$domain=' + this.hostname; -}; - -FilterPlainRightAnchoredHostname.prototype.toSelfie = function() { - return this.s + '\t' + - this.hostname; +FilterPlainRightAnchoredHostname.prototype.toSelfie = +FilterPlainRightAnchoredHostname.prototype.rtCompile = function() { + return this.s + '\t' + this.hostname; }; FilterPlainRightAnchoredHostname.compile = function(details, hostname) { @@ -550,13 +545,12 @@ FilterPlainHnAnchored.prototype.match = function(url, tokenBeg) { reURLPostHostnameAnchors.test(url.slice(pos + 3, tokenBeg)) === false; }; -FilterPlainHnAnchored.fid = FilterPlainHnAnchored.prototype.fid = '||a'; +FilterPlainHnAnchored.fid = +FilterPlainHnAnchored.prototype.fid = +FilterPlainHnAnchored.prototype.rtfid = '||a'; -FilterPlainHnAnchored.prototype.toString = function() { - return '||' + this.s; -}; - -FilterPlainHnAnchored.prototype.toSelfie = function() { +FilterPlainHnAnchored.prototype.toSelfie = +FilterPlainHnAnchored.prototype.rtCompile = function() { return this.s; }; @@ -592,13 +586,12 @@ FilterPlainHnAnchoredHostname.prototype.match = function(url, tokenBeg) { reURLPostHostnameAnchors.test(url.slice(pos + 3, tokenBeg)) === false; }; -FilterPlainHnAnchoredHostname.fid = FilterPlainHnAnchoredHostname.prototype.fid = '||ah'; +FilterPlainHnAnchoredHostname.fid = +FilterPlainHnAnchoredHostname.prototype.fid = +FilterPlainHnAnchoredHostname.prototype.rtfid = '||ah'; -FilterPlainHnAnchoredHostname.prototype.toString = function() { - return '||' + this.s; -}; - -FilterPlainHnAnchoredHostname.prototype.toSelfie = function() { +FilterPlainHnAnchoredHostname.prototype.toSelfie = +FilterPlainHnAnchoredHostname.prototype.rtCompile = function() { return this.s + '\t' + this.hostname; }; @@ -628,19 +621,12 @@ FilterGeneric.prototype.match = function(url) { return this.re.test(url); }; -FilterGeneric.fid = FilterGeneric.prototype.fid = '_'; +FilterGeneric.fid = +FilterGeneric.prototype.fid = +FilterGeneric.prototype.rtfid = '_'; -FilterGeneric.prototype.toString = function() { - if ( this.anchor === 0 ) { - return this.s; - } - if ( this.anchor < 0 ) { - return '|' + this.s; - } - return this.s + '|'; -}; - -FilterGeneric.prototype.toSelfie = function() { +FilterGeneric.prototype.toSelfie = +FilterGeneric.prototype.rtCompile = function() { return this.s + '\t' + this.anchor; }; @@ -671,13 +657,12 @@ FilterGenericHostname.prototype.match = function(url) { return FilterGeneric.prototype.match.call(this, url); }; -FilterGenericHostname.fid = FilterGenericHostname.prototype.fid = '_h'; +FilterGenericHostname.fid = +FilterGenericHostname.prototype.fid = +FilterGenericHostname.prototype.rtfid = '_h'; -FilterGenericHostname.prototype.toString = function() { - return FilterGeneric.prototype.toString.call(this) + '$domain=' + this.hostname; -}; - -FilterGenericHostname.prototype.toSelfie = function() { +FilterGenericHostname.prototype.toSelfie = +FilterGenericHostname.prototype.rtCompile = function() { return FilterGeneric.prototype.toSelfie.call(this) + '\t' + this.hostname; }; @@ -717,13 +702,12 @@ FilterGenericHnAnchored.prototype.match = function(url) { reURLPostHostnameAnchors.test(url.slice(pos + 3, match.index)) === false; }; -FilterGenericHnAnchored.fid = FilterGenericHnAnchored.prototype.fid = '||_'; +FilterGenericHnAnchored.fid = +FilterGenericHnAnchored.prototype.fid = +FilterGenericHnAnchored.prototype.rtfid = '||_'; -FilterGenericHnAnchored.prototype.toString = function() { - return '||' + this.s; -}; - -FilterGenericHnAnchored.prototype.toSelfie = function() { +FilterGenericHnAnchored.prototype.toSelfie = +FilterGenericHnAnchored.prototype.rtCompile = function() { return this.s; }; @@ -751,13 +735,12 @@ FilterGenericHnAnchoredHostname.prototype.match = function(url) { return FilterGenericHnAnchored.prototype.match.call(this, url); }; -FilterGenericHnAnchoredHostname.fid = FilterGenericHnAnchoredHostname.prototype.fid = '||_h'; +FilterGenericHnAnchoredHostname.fid = +FilterGenericHnAnchoredHostname.prototype.fid = +FilterGenericHnAnchoredHostname.prototype.rtfid = '||_h'; -FilterGenericHnAnchoredHostname.prototype.toString = function() { - return '||' + this.s + '$domain=' + this.hostname; -}; - -FilterGenericHnAnchoredHostname.prototype.toSelfie = function() { +FilterGenericHnAnchoredHostname.prototype.toSelfie = +FilterGenericHnAnchoredHostname.prototype.rtCompile = function() { return this.s + '\t' + this.hostname; }; @@ -782,13 +765,12 @@ FilterRegex.prototype.match = function(url) { return this.re.test(url); }; -FilterRegex.fid = FilterRegex.prototype.fid = '//'; +FilterRegex.fid = +FilterRegex.prototype.fid = +FilterRegex.prototype.rtfid = '//'; -FilterRegex.prototype.toString = function() { - return '/' + this.re.source + '/'; -}; - -FilterRegex.prototype.toSelfie = function() { +FilterRegex.prototype.toSelfie = +FilterRegex.prototype.rtCompile = function() { return this.re.source; }; @@ -813,13 +795,12 @@ FilterRegexHostname.prototype.match = function(url) { this.re.test(url); }; -FilterRegexHostname.fid = FilterRegexHostname.prototype.fid = '//h'; +FilterRegexHostname.fid = +FilterRegexHostname.prototype.fid = +FilterRegexHostname.prototype.rtfid = '//h'; -FilterRegexHostname.prototype.toString = function() { - return '/' + this.re.source + '/$domain=' + this.hostname; -}; - -FilterRegexHostname.prototype.toSelfie = function() { +FilterRegexHostname.prototype.toSelfie = +FilterRegexHostname.prototype.rtCompile = function() { return this.re.source + '\t' + this.hostname; }; @@ -1004,13 +985,15 @@ FilterHostnameDict.prototype.match = function() { } hostname = hostname.slice(pos + 1); } - this.h = '||' + hostname + '^'; + this.h = hostname; return this; }; -FilterHostnameDict.fid = FilterHostnameDict.prototype.fid = '{h}'; +FilterHostnameDict.fid = +FilterHostnameDict.prototype.fid = '{h}'; +FilterHostnameDict.rtfid = '.'; -FilterHostnameDict.prototype.toString = function() { +FilterHostnameDict.prototype.rtCompile = function() { return this.h; }; @@ -1075,6 +1058,12 @@ var FilterBucket = function(a, b) { this.filters[1] = b; } } + + Object.defineProperty(this, 'rtfid', { + get: function() { + return this.f.rtfid; + } + }); }; FilterBucket.prototype.add = function(a) { @@ -1106,7 +1095,7 @@ FilterBucket.prototype.match = function(url, tokenBeg) { var filters = this.filters; var n = filters.length; for ( var i = 0; i < n; i++ ) { - if ( filters[i].match(url, tokenBeg) !== false ) { + if ( filters[i].match(url, tokenBeg) ) { this.f = filters[i]; if ( i >= this.vip ) { this.promote(i); @@ -1119,17 +1108,15 @@ FilterBucket.prototype.match = function(url, tokenBeg) { FilterBucket.prototype.fid = '[]'; -FilterBucket.prototype.toString = function() { - if ( this.f !== null ) { - return this.f.toString(); - } - return ''; -}; - FilterBucket.prototype.toSelfie = function() { return this.filters.length.toString(); }; +// Not supposed to be called without a valid filter hit. +FilterBucket.prototype.rtCompile = function() { + return this.f.rtCompile(); +}; + FilterBucket.fromSelfie = function() { return new FilterBucket(); }; @@ -1601,6 +1588,11 @@ FilterContainer.prototype.reset = function() { this.categories = Object.create(null); this.filterParser.reset(); this.filterCounts = {}; + + // Runtime registers + this.keyRegister = undefined; + this.tokenRegister = undefined; + this.fRegister = null; }; /******************************************************************************/ @@ -2019,6 +2011,140 @@ FilterContainer.prototype.fromCompiledContent = function(text, lineBeg) { /******************************************************************************/ +FilterContainer.prototype.filterStringFromCompiled = function(compiled) { + var opts = []; + var vfields = compiled.split('\v'); + var filter = ''; + var bits = parseInt(vfields[1], 16) | 0; + + if ( bits & 0x01 ) { + filter += '@@'; + } + + var rfid = vfields[2] === '.' ? '.' : vfields[3]; + var tfields = rfid !== '.' ? vfields[4].split('\t') : []; + + switch ( rfid ) { + case '.': + filter += '||' + vfields[3] + '^'; + break; + case 'a': + case 'ah': + case '0a': + case '0ah': + case '1a': + case '1ah': + case '_': + case '_h': + filter += tfields[0]; + break; + case '|a': + case '|ah': + filter += '|' + tfields[0]; + break; + case 'a|': + case 'a|h': + filter += tfields[0] + '|'; + break; + case '||a': + case '||ah': + case '||_': + case '||_h': + filter += '||' + tfields[0]; + break; + case '//': + case '//h': + filter += '/' + tfields[0] + '/'; + break; + default: + break; + } + + // Domain option? + switch ( rfid ) { + case '0ah': + case '1ah': + case '|ah': + case 'a|h': + case '||ah': + case '||_h': + case '//h': + opts.push('domain=' + tfields[1]); + break; + case 'ah': + case '_h': + opts.push('domain=' + tfields[2]); + break; + default: + break; + } + + // Filter options + if ( bits & 0x02 ) { + opts.push('important'); + } + if ( bits & 0x08 ) { + opts.push('third-party'); + } else if ( bits & 0x04 ) { + opts.push('first-party'); + } + if ( bits & 0xF0 ) { + opts.push(typeValueToTypeName[bits >>> 4]); + } + if ( opts.length !== 0 ) { + filter += '$' + opts.join(','); + } + + return filter; +}; + +/******************************************************************************/ + +FilterContainer.prototype.filterRegexFromCompiled = function(compiled, flags) { + var vfields = compiled.split('\v'); + var rfid = vfields[2] === '.' ? '.' : vfields[3]; + var tfields = rfid !== '.' ? vfields[4].split('\t') : []; + var re = null; + + switch ( rfid ) { + case '.': + re = strToRegex(vfields[3], 0, flags); + break; + case 'a': + case 'ah': + case '0a': + case '0ah': + case '1a': + case '1ah': + case '_': + case '_h': + case '||a': + case '||ah': + case '||_': + case '||_h': + re = strToRegex(tfields[0], 0, flags); + break; + case '|a': + case '|ah': + re = strToRegex(tfields[0], -1, flags); + break; + case 'a|': + case 'a|h': + re = strToRegex(tfields[0], 1, flags); + break; + case '//': + case '//h': + re = new RegExp(tfields[0]); + break; + default: + break; + } + + return re; +}; + +/******************************************************************************/ + // Since the addition of the `important` evaluation, this means it is now // likely that the url will have to be scanned more than once. So this is // to ensure we do it once only, and reuse results. @@ -2058,8 +2184,10 @@ FilterContainer.prototype.tokenize = function(url) { FilterContainer.prototype.matchTokens = function(bucket, url) { // Hostname-only filters var f = bucket['.']; - if ( f !== undefined && f.match() !== false ) { - return f; + if ( f !== undefined && f.match() ) { + this.tokenRegister = '.'; + this.fRegister = f; + return true; } var tokens = this.tokens; @@ -2072,15 +2200,19 @@ FilterContainer.prototype.matchTokens = function(bucket, url) { break; } f = bucket[token]; - if ( f !== undefined && f.match(url, tokenEntry.beg) !== false ) { - return f; + if ( f !== undefined && f.match(url, tokenEntry.beg) ) { + this.tokenRegister = token; + this.fRegister = f; + return true; } } // Regex-based filters f = bucket['*']; - if ( f !== undefined && f.match(url) !== false ) { - return f; + if ( f !== undefined && f.match(url) ) { + this.tokenRegister = '*'; + this.fRegister = f; + return true; } return false; @@ -2106,60 +2238,66 @@ FilterContainer.prototype.matchStringExactType = function(context, requestURL, r // Be prepared to support unknown types var type = typeNameToTypeValue[requestType] || 0; if ( type === 0 ) { - return ''; + return false; } var categories = this.categories; - var bf = false, bucket; + var bucket; // Tokenize only once this.tokenize(url); + this.fRegister = null; + // https://github.com/chrisaljoudi/uBlock/issues/139 // Test against important block filters if ( bucket = categories[this.makeCategoryKey(BlockAnyParty | Important | type)] ) { - bf = this.matchTokens(bucket, url); - if ( bf !== false ) { - return 'sb:' + bf.toString(); + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = BlockAnyParty | Important | type; + return true; } } if ( bucket = categories[this.makeCategoryKey(BlockAction | Important | type | party)] ) { - bf = this.matchTokens(bucket, url); - if ( bf !== false ) { - return 'sb:' + bf.toString(); + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = BlockAction | Important | type | party; + return true; } } // Test against block filters if ( bucket = categories[this.makeCategoryKey(BlockAnyParty | type)] ) { - bf = this.matchTokens(bucket, url); - } - if ( bf === false ) { - if ( bucket = categories[this.makeCategoryKey(BlockAction | type | party)] ) { - bf = this.matchTokens(bucket, url); + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = BlockAnyParty | type; } } + if ( this.fRegister === null ) { + if ( bucket = categories[this.makeCategoryKey(BlockAction | type | party)] ) { + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = BlockAction | type | party; + } + } + } + // If there is no block filter, no need to test against allow filters - if ( bf === false ) { - return ''; + if ( this.fRegister === null ) { + return false; } // Test against allow filters - var af; if ( bucket = categories[this.makeCategoryKey(AllowAnyParty | type)] ) { - af = this.matchTokens(bucket, url); - if ( af !== false ) { - return 'sa:' + af.toString(); + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = AllowAnyParty | type; + return false; } } if ( bucket = categories[this.makeCategoryKey(AllowAction | type | party)] ) { - af = this.matchTokens(bucket, url); - if ( af !== false ) { - return 'sa:' + af.toString(); + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = AllowAction | type | party; + return false; } } - return 'sb:' + bf.toString(); + return true; }; /******************************************************************************/ @@ -2212,7 +2350,7 @@ FilterContainer.prototype.matchString = function(context) { // Tokenize only once this.tokenize(url); - var bf = false; + this.fRegister = null; // https://github.com/chrisaljoudi/uBlock/issues/139 // Test against important block filters. @@ -2220,86 +2358,113 @@ FilterContainer.prototype.matchString = function(context) { // evaluation. Normally, it is "evaluate block then evaluate allow", with // the `important` property it is "evaluate allow then evaluate block". if ( bucket = filterClasses[this.makeCategoryKey(BlockAnyTypeAnyParty | Important)] ) { - bf = this.matchTokens(bucket, url); - if ( bf !== false ) { - return 'sb:' + bf.toString() + '$important'; + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = BlockAnyTypeAnyParty | Important; + return true; } } if ( bucket = filterClasses[this.makeCategoryKey(BlockAnyType | Important | party)] ) { - bf = this.matchTokens(bucket, url); - if ( bf !== false ) { - return 'sb:' + bf.toString() + '$important'; + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = BlockAnyType | Important | party; + return true; } } if ( bucket = filterClasses[this.makeCategoryKey(BlockAnyParty | Important | type)] ) { - bf = this.matchTokens(bucket, url); - if ( bf !== false ) { - return 'sb:' + bf.toString() + '$important'; + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = BlockAnyParty | Important | type; + return true; } } if ( bucket = filterClasses[this.makeCategoryKey(BlockAction | Important | type | party)] ) { - bf = this.matchTokens(bucket, url); - if ( bf !== false ) { - return 'sb:' + bf.toString() + '$important'; + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = BlockAction | Important | type | party; + return true; } } // Test against block filters - if ( bf === false ) { - if ( bucket = filterClasses[this.makeCategoryKey(BlockAnyTypeAnyParty)] ) { - bf = this.matchTokens(bucket, url); + if ( bucket = filterClasses[this.makeCategoryKey(BlockAnyTypeAnyParty)] ) { + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = BlockAnyTypeAnyParty; } } - if ( bf === false ) { + if ( this.fRegister === null ) { if ( bucket = filterClasses[this.makeCategoryKey(BlockAnyType | party)] ) { - bf = this.matchTokens(bucket, url); + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = BlockAnyType | party; + } } - } - if ( bf === false ) { - if ( bucket = filterClasses[this.makeCategoryKey(BlockAnyParty | type)] ) { - bf = this.matchTokens(bucket, url); - } - } - if ( bf === false ) { - if ( bucket = filterClasses[this.makeCategoryKey(BlockAction | type | party)] ) { - bf = this.matchTokens(bucket, url); + if ( this.fRegister === null ) { + if ( bucket = filterClasses[this.makeCategoryKey(BlockAnyParty | type)] ) { + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = BlockAnyParty | type; + } + } + if ( this.fRegister === null ) { + if ( bucket = filterClasses[this.makeCategoryKey(BlockAction | type | party)] ) { + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = BlockAction | type | party; + } + } + } } } // If there is no block filter, no need to test against allow filters - if ( bf === false ) { - return ''; + if ( this.fRegister === null ) { + return false; } // Test against allow filters - var af; - if ( bucket = filterClasses[this.makeCategoryKey(AllowAnyTypeAnyParty)] ) { - af = this.matchTokens(bucket, url); - if ( af !== false ) { - return 'sa:' + af.toString(); + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = AllowAnyTypeAnyParty; + return false; } } if ( bucket = filterClasses[this.makeCategoryKey(AllowAnyType | party)] ) { - af = this.matchTokens(bucket, url); - if ( af !== false ) { - return 'sa:' + af.toString(); + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = AllowAnyType | party; + return false; } } if ( bucket = filterClasses[this.makeCategoryKey(AllowAnyParty | type)] ) { - af = this.matchTokens(bucket, url); - if ( af !== false ) { - return 'sa:' + af.toString(); + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = AllowAnyParty | type; + return false; } } if ( bucket = filterClasses[this.makeCategoryKey(AllowAction | type | party)] ) { - af = this.matchTokens(bucket, url); - if ( af !== false ) { - return 'sa:' + af.toString(); + if ( this.matchTokens(bucket, url) ) { + this.keyRegister = AllowAction | type | party; + return false; } } - return 'sb:' + bf.toString(); + return true; +}; + +/******************************************************************************/ + +// The `verbose` argment tells whether to return a short or long version of +// the filter string. Typically, if the logger is not enabled, there is no +// point in returning the long version: this saves overhead. + +FilterContainer.prototype.toResultString = function(verbose) { + if ( this.fRegister === null ) { + return ''; + } + var s = this.keyRegister & 0x01 ? 'sa:' : 'sb:'; + if ( !verbose ) { + return s; + } + s += 'n\v' + this.makeCategoryKey(this.keyRegister) + '\v' + this.tokenRegister + '\v'; + if ( this.tokenRegister === '.' ) { + s += this.fRegister.rtCompile(); + } else { + s += this.fRegister.rtfid + '\v' + this.fRegister.rtCompile(); + } + return s; }; /******************************************************************************/ diff --git a/src/js/tab.js b/src/js/tab.js index aa76e49e3..c195bbbb0 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -1,7 +1,7 @@ /******************************************************************************* - µBlock - a browser extension to block requests. - Copyright (C) 2014 Raymond Hill + uBlock - a browser extension to block requests. + Copyright (C) 2014-2015 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 @@ -492,6 +492,7 @@ vAPI.tabs.onPopup = function(details) { }; var result = ''; + var loggerEnabled = µb.logger.isEnabled(); // Check user switch first if ( µb.hnSwitches.evaluateZ('no-popups', openerHostname) ) { @@ -506,7 +507,9 @@ vAPI.tabs.onPopup = function(details) { µb.getNetFilteringSwitch(openerURL) && µb.getNetFilteringSwitch(targetURL) ) { - result = µb.staticNetFilteringEngine.matchStringExactType(context, targetURL, 'popup'); + if ( µb.staticNetFilteringEngine.matchStringExactType(context, targetURL, 'popup') ) { + result = µb.staticNetFilteringEngine.toResultString(loggerEnabled); + } } // https://github.com/chrisaljoudi/uBlock/issues/91 @@ -514,15 +517,17 @@ vAPI.tabs.onPopup = function(details) { if ( pageStore ) { pageStore.logRequest(context, result); } - µb.logger.writeOne( - details.openerTabId, - 'net', - result, - 'popup', - targetURL, - openerHostname, - openerHostname - ); + if ( loggerEnabled ) { + µb.logger.writeOne( + details.openerTabId, + 'net', + result, + 'popup', + targetURL, + openerHostname, + openerHostname + ); + } // Not blocked if ( µb.isAllowResult(result) ) { diff --git a/src/js/traffic.js b/src/js/traffic.js index 340c1014a..0a458f0d8 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -1,7 +1,7 @@ /******************************************************************************* - µBlock - a browser extension to block requests. - Copyright (C) 2014 Raymond Hill + uBlock - a browser extension to block requests. + Copyright (C) 2014-2015 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 @@ -185,11 +185,14 @@ var onBeforeRootFrameRequest = function(details) { // Filtering if ( result === '' ) { - result = µb.staticNetFilteringEngine.matchString(context); - // https://github.com/chrisaljoudi/uBlock/issues/1128 - // Do not block if the match begins after the hostname. - if ( result !== '' ) { - result = toBlockDocResult(requestURL, requestHostname, result); + if ( µb.staticNetFilteringEngine.matchString(context) ) { + // We always need the long-form result here. + result = µb.staticNetFilteringEngine.toResultString(true); + // https://github.com/chrisaljoudi/uBlock/issues/1128 + // Do not block if the match begins after the hostname. + if ( result.charAt(1) === 'b' ) { + result = toBlockDocResult(requestURL, requestHostname, result); + } } } @@ -221,7 +224,7 @@ var onBeforeRootFrameRequest = function(details) { url: requestURL, hn: requestHostname, dn: requestDomain, - why: result + why: µb.staticNetFilteringEngine.filterStringFromCompiled(result.slice(3)) })); vAPI.tabs.replace(tabId, vAPI.getURL('document-blocked.html?details=') + query); @@ -232,32 +235,12 @@ var onBeforeRootFrameRequest = function(details) { /******************************************************************************/ var toBlockDocResult = function(url, hostname, result) { - if ( result.charAt(1) !== 'b' ) { + // Make a regex out of the result + var re = µBlock.staticNetFilteringEngine + .filterRegexFromCompiled(result.slice(3), 'gi'); + if ( re === null ) { return ''; } - - // Make a regex out of the result - var reText = result.slice(3); - var pos = reText.indexOf('$'); - if ( pos > 0 ) { - reText = reText.slice(0, pos); - } - - // We are going to have to take the long way to find out - if ( reText.charAt(0) === '/' && reText.slice(-1) === '/' ) { - reText = reText.slice(1, -1); - } else { - reText = reText - .replace(/\./g, '\\.') - .replace(/\?/g, '\\?') - .replace(/^\|\|/, '') - .replace(/\^/g, '.') - .replace(/^\|/g, '^') - .replace(/\|$/g, '$') - .replace(/\*/g, '.*'); - } - - var re = new RegExp(reText, 'gi'); var matches = re.exec(url); if ( matches === null ) { return '';