From 1fe5a16c323b5e0b0808df4fa3a3a851c3fe34fe Mon Sep 17 00:00:00 2001 From: gorhill Date: Tue, 22 Dec 2015 16:32:09 -0500 Subject: [PATCH] this fixes #1100: ability to inject directly a resource from redirection library --- assets/checksums.txt | 2 +- assets/ublock/redirect-resources.txt | 7 ++ src/js/background.js | 4 +- src/js/contentscript-start.js | 32 ++++++ src/js/cosmetic-filtering.js | 144 +++++++++++++++++++++------ src/js/redirect-engine.js | 9 ++ 6 files changed, 163 insertions(+), 35 deletions(-) diff --git a/assets/checksums.txt b/assets/checksums.txt index 56bd07571..72cda380c 100644 --- a/assets/checksums.txt +++ b/assets/checksums.txt @@ -1,5 +1,5 @@ 4d0e777a82576a2ec771742abb91925b assets/ublock/unbreak.txt -f2624cc77fae3f1be5a6b5d5b8027bdc assets/ublock/redirect-resources.txt +62111a29f0a5cb361ba8dbae92054adb assets/ublock/redirect-resources.txt 6c077d6d5b39e8a5a407966ad62c9c32 assets/ublock/privacy.txt 9bcc718383fec8b2ce0f9c379f45da9a assets/ublock/filters.txt 146704ad1c0393e342afdb416762c183 assets/ublock/badware.txt diff --git a/assets/ublock/redirect-resources.txt b/assets/ublock/redirect-resources.txt index 1d9b2bb7b..93a5b3cf0 100644 --- a/assets/ublock/redirect-resources.txt +++ b/assets/ublock/redirect-resources.txt @@ -228,3 +228,10 @@ yavli-defuser.js application/javascript } catch (ex) { } })(); + + +# Addefend defuser +uabinject-defuser.js application/javascript +(function() { + window.uabpdl = window.uabInject = window.uabDetect = true; +})(); diff --git a/src/js/background.js b/src/js/background.js index e04ffdba4..553bf9b2a 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -90,8 +90,8 @@ return { // read-only systemSettings: { - compiledMagic: 'cxubjrcfrnrq', - selfieMagic: 'mnigwksyvgkv' + compiledMagic: 'xtsldiywhvgc', + selfieMagic: 'xtsldiywhvgc' }, restoreBackupSettings: { diff --git a/src/js/contentscript-start.js b/src/js/contentscript-start.js index 90a46a098..4e9ef27c8 100644 --- a/src/js/contentscript-start.js +++ b/src/js/contentscript-start.js @@ -116,6 +116,8 @@ var cosmeticFilters = function(details) { vAPI.hideCosmeticFilters = hideCosmeticFilters; }; +/******************************************************************************/ + var netFilters = function(details) { var parent = document.head || document.documentElement; if ( !parent ) { @@ -131,6 +133,29 @@ var netFilters = function(details) { //console.debug('document.querySelectorAll("%s") = %o', text, document.querySelectorAll(text)); }; +/******************************************************************************/ + +// Create script tags and assign data URIs looked up from our library of +// redirection resources: Sometimes it is useful to use these resources as +// standalone scriptlets. +// Library of redirection resources: +// https://github.com/gorhill/uBlock/blob/master/assets/ublock/redirect-resources.txt + +var injectScripts = function(dataURIs) { + var parent = document.head || document.documentElement; + if ( !parent ) { + return; + } + var i = dataURIs.length, scriptTag; + while ( i-- ) { + scriptTag = document.createElement('script'); + scriptTag.src = dataURIs[i]; + parent.appendChild(scriptTag); + } +}; + +/******************************************************************************/ + var filteringHandler = function(details) { var styleTagCount = vAPI.styles.length; @@ -144,6 +169,9 @@ var filteringHandler = function(details) { if ( details.netHide.length !== 0 ) { netFilters(details); } + if ( details.scripts !== 0 ) { + injectScripts(details.scripts); + } // The port will never be used again at this point, disconnecting allows // the browser to flush this script from memory. } @@ -164,6 +192,8 @@ var filteringHandler = function(details) { localMessager.close(); }; +/******************************************************************************/ + var hideElements = function(selectors) { if ( document.body === null ) { return; @@ -213,6 +243,8 @@ var hideElements = function(selectors) { } }; +/******************************************************************************/ + var url = window.location.href; localMessager.send( { diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index 58f829bda..d38b61aed 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -244,6 +244,7 @@ var FilterParser = function() { this.hostnames = []; this.invalid = false; this.cosmetic = true; + this.reScriptTagFilter = /^script:(contains|inject)\((.+?)\)$/; }; /******************************************************************************/ @@ -353,12 +354,10 @@ FilterParser.prototype.parse = function(raw) { // Examples: // focus.de##script:contains(/uabInject/) // focus.de##script:contains(uabInject) + // focus.de##script:inject(uabinject-defuser.js) - // Inline script tag filter? - if ( - this.suffix.startsWith('script:contains(') === false || - this.suffix.endsWith(')') === false - ) { + var matches = this.reScriptTagFilter.exec(this.suffix); + if ( matches === null ) { return this; } @@ -369,27 +368,32 @@ FilterParser.prototype.parse = function(raw) { return this; } - var suffix = this.suffix.slice(16, -1); - this.suffix = 'script//:'; + var token = matches[2]; - // Plain string-based? - if ( suffix.startsWith('/') === false || suffix.endsWith('/') === false ) { - this.suffix += suffix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - return this; - } - - // Regex-based - this.suffix += suffix.slice(1, -1); - - // Valid regex? - if ( isBadRegex(this.suffix) ) { - console.error( - "uBlock Origin> discarding bad regular expression-based cosmetic filter '%s': '%s'", - raw, - isBadRegex.message - ); + switch ( matches[1] ) { + case 'contains': + this.suffix = 'script?'; + // Plain string- or regex-based? + if ( token.startsWith('/') === false || token.endsWith('/') === false ) { + this.suffix += token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } else { + this.suffix += token.slice(1, -1); + if ( isBadRegex(this.suffix) ) { + console.error( + "uBlock Origin> discarding bad regular expression-based cosmetic filter '%s': '%s'", + raw, + isBadRegex.message + ); + this.invalid = true; + } + } + break; + case 'inject': + this.suffix = 'script+' + token; + break; + default: this.invalid = true; - return this; + break; } return this; @@ -661,6 +665,8 @@ FilterContainer.prototype.reset = function() { this.entityFilters = {}; this.scriptTagFilters = {}; this.scriptTagFilterCount = 0; + this.scriptTags = {}; + this.scriptTagCount = 0; }; /******************************************************************************/ @@ -686,8 +692,11 @@ FilterContainer.prototype.isValidSelector = (function() { return true; } catch (e) { } - if ( s.startsWith('script//:') ) { - return true; + // We reach this point very rarely. + if ( s.startsWith('script') ) { + if ( s.startsWith('?', 6) || s.startsWith('+', 6) ) { + return true; + } } console.error('uBlock> invalid cosmetic filter:', s); return false; @@ -914,8 +923,8 @@ FilterContainer.prototype.fromCompiledContent = function(text, lineBeg, skip) { // h ir twitter.com .promoted-tweet if ( fields[0] === 'h' ) { // Special filter: script tags. Not a real CSS selector. - if ( fields[3].startsWith('script//:') ) { - this.createScriptTagFilter(fields[2], fields[3].slice(9)); + if ( fields[3].startsWith('script') ) { + this.createScriptFilter(fields[2], fields[3].slice(6)); continue; } filter = new FilterHostname(fields[3], fields[2]); @@ -950,8 +959,8 @@ FilterContainer.prototype.fromCompiledContent = function(text, lineBeg, skip) { // entity selector if ( fields[0] === 'e' ) { // Special filter: script tags. Not a real CSS selector. - if ( fields[2].startsWith('script//:') ) { - this.createScriptTagFilter(fields[1], fields[2].slice(9)); + if ( fields[2].startsWith('script?') ) { + this.createScriptFilter(fields[1], fields[2].slice(6)); continue; } bucket = this.entityFilters[fields[1]]; @@ -1019,6 +1028,17 @@ FilterContainer.prototype.skipCompiledContent = function(text, lineBeg) { /******************************************************************************/ +FilterContainer.prototype.createScriptFilter = function(hostname, s) { + if ( s.charAt(0) === '?' ) { + return this.createScriptTagFilter(hostname, s.slice(1)); + } + if ( s.charAt(0) === '+' ) { + return this.createScriptTagInjector(hostname, s.slice(1)); + } +}; + +/******************************************************************************/ + FilterContainer.prototype.createScriptTagFilter = function(hostname, s) { if ( this.scriptTagFilters.hasOwnProperty(hostname) ) { this.scriptTagFilters[hostname] += '|' + s; @@ -1062,6 +1082,61 @@ FilterContainer.prototype.retrieveScriptTagRegex = function(domain, hostname) { /******************************************************************************/ +FilterContainer.prototype.createScriptTagInjector = function(hostname, s) { + if ( this.scriptTags.hasOwnProperty(hostname) ) { + this.scriptTags[hostname].push(s); + } else { + this.scriptTags[hostname] = [s]; + } + this.scriptTagCount += 1; +}; + + +/******************************************************************************/ + +FilterContainer.prototype.retrieveScriptTags = function(domain, hostname) { + if ( this.scriptTagCount === 0 ) { + return; + } + var reng = µb.redirectEngine; + if ( !reng ) { + return; + } + var out = [], + hn = hostname, pos, rnames, i, dataURI; + for (;;) { + rnames = this.scriptTags[hn]; + i = rnames && rnames.length || 0; + while ( i-- ) { + if ( (dataURI = reng.resourceFromName(rnames[i], 'application/javascript')) ) { + out.push(dataURI); + } + } + if ( hn === domain ) { + break; + } + pos = hn.indexOf('.'); + if ( pos === -1 ) { + break; + } + hn = hn.slice(pos + 1); + } + pos = domain.indexOf('.'); + if ( pos !== -1 ) { + hn = domain.slice(0, pos); + rnames = this.scriptTags[hn]; + i = rnames && rnames.length || 0; + while ( i-- ) { + if ( (dataURI = reng.resourceFromName(rnames[i], 'application/javascript')) ) { + out.push(dataURI); + } + } + } + return out; +}; + +/******************************************************************************/ + FilterContainer.prototype.freeze = function() { this.duplicateBuster = {}; @@ -1117,7 +1192,9 @@ FilterContainer.prototype.toSelfie = function() { highHighGenericHideCount: this.highHighGenericHideCount, genericDonthide: this.genericDonthide, scriptTagFilters: this.scriptTagFilters, - scriptTagFilterCount: this.scriptTagFilterCount + scriptTagFilterCount: this.scriptTagFilterCount, + scriptTags: this.scriptTags, + scriptTagCount: this.scriptTagCount }; }; @@ -1180,6 +1257,8 @@ FilterContainer.prototype.fromSelfie = function(selfie) { this.genericDonthide = selfie.genericDonthide; this.scriptTagFilters = selfie.scriptTagFilters; this.scriptTagFilterCount = selfie.scriptTagFilterCount; + this.scriptTags = selfie.scriptTags; + this.scriptTagCount = selfie.scriptTagCount; this.frozen = true; }; @@ -1361,7 +1440,8 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request) { cosmeticHide: [], cosmeticDonthide: [], netHide: [], - netCollapse: µb.userSettings.collapseBlocked + netCollapse: µb.userSettings.collapseBlocked, + scripts: this.retrieveScriptTags(domain, hostname) }; var hash, bucket; diff --git a/src/js/redirect-engine.js b/src/js/redirect-engine.js index 601439ed0..cc094fb60 100644 --- a/src/js/redirect-engine.js +++ b/src/js/redirect-engine.js @@ -359,6 +359,15 @@ RedirectEngine.prototype.fromSelfie = function(selfie) { /******************************************************************************/ +RedirectEngine.prototype.resourceFromName = function(name, mime) { + var entry = this.resources[name]; + if ( entry && (mime === undefined || entry.mime.startsWith(mime)) ) { + return entry.toURL(); + } +}; + +/******************************************************************************/ + // TODO: combine same key-redirect pairs into a single regex. RedirectEngine.prototype.resourcesFromString = function(text) {