diff --git a/release-utils/make-signed-xpi.sh b/release-utils/make-signed-xpi.sh index 8e74035316..3af2f9626f 100755 --- a/release-utils/make-signed-xpi.sh +++ b/release-utils/make-signed-xpi.sh @@ -33,15 +33,6 @@ sed -i -e '/eff.software.projects@gmail.com/,+1d' -e 's/"author": {/"author": "p echo "removing Chrome's update_url" # remove update_url sed -i -e '/"update_url": "https:\/\/clients2.google.com\/service\/update2\/crx"/,+0d' ../checkout/src/manifest.json -# fix the trailing comma -# TODO fragile! at least we validate the JSON below -# https://unix.stackexchange.com/a/26288 -# https://unix.stackexchange.com/a/26290 -sed -i -e '/"storage": {/{ - n - n - s/},/}/ -}' ../checkout/src/manifest.json # lint the checkout folder $WEB_EXT lint -s ../checkout/src diff --git a/src/data/surrogates.js b/src/data/surrogates.js index 98992c1f9c..d504270a11 100644 --- a/src/data/surrogates.js +++ b/src/data/surrogates.js @@ -22,9 +22,6 @@ const MATCH_SUFFIX = 'suffix', /** * `hostnames` maps hostnames to surrogate pattern tokens. - * - * Surrogate pattern tokens are used to look up the actual - * surrogate script code (stored in "surrogates" object below). */ const hostnames = { 'b.scorecardresearch.com': { @@ -80,444 +77,57 @@ const hostnames = { 'widgets.outbrain.com': { match: MATCH_SUFFIX, tokens: [ - '/outbrain.js' - ], + '/outbrain.js', + ] + }, + 'c.amazon-adsystem.com': { + match: MATCH_SUFFIX, + tokens: [ + '/apstag.js', + ] }, }; /** - * "surrogates" maps surrogate pattern tokens to surrogate script code. + * `surrogates` maps pattern tokens to web accessible resource URLs + * containing the actual surrogate script code. */ const surrogates = { - /* eslint-disable no-extra-semi, space-in-parens */ - // Google Analytics (legacy ga.js) // - // https://github.com/gorhill/uBlock/blob/dcc72ba51c30abd4a1216049cc34f6c429ab2090/src/web_accessible_resources/google-analytics_ga.js - // // test cases: // http://checkin.avianca.com/ // https://www.vmware.com/support/pubs/ws_pubs.html (release notes links) // // API reference: // https://developers.google.com/analytics/devguides/collection/gajs/methods/ - '/ga.js': '(' + - function() { - 'use strict'; - const noopfn = function() { - }; - // - const Gaq = function() { - }; - Gaq.prototype.Na = noopfn; - Gaq.prototype.O = noopfn; - Gaq.prototype.Sa = noopfn; - Gaq.prototype.Ta = noopfn; - Gaq.prototype.Va = noopfn; - Gaq.prototype._createAsyncTracker = noopfn; - Gaq.prototype._getAsyncTracker = noopfn; - Gaq.prototype._getPlugin = noopfn; - Gaq.prototype.push = function(a) { - if ( typeof a === 'function' ) { - a(); return; - } - if ( Array.isArray(a) === false ) { - return; - } - // https://twitter.com/catovitch/status/776442930345218048 - // https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiDomainDirectory#_gat.GA_Tracker_._link - if ( a[0] === '_link' && typeof a[1] === 'string' ) { - window.location.assign(a[1]); - } - // https://github.com/gorhill/uBlock/issues/2162 - if ( a[0] === '_set' && a[1] === 'hitCallback' && typeof a[2] === 'function' ) { - a[2](); - } - }; - // - const tracker = (function() { - const out = {}; - const api = [ - '_addIgnoredOrganic _addIgnoredRef _addItem _addOrganic', - '_addTrans _clearIgnoredOrganic _clearIgnoredRef _clearOrganic', - '_cookiePathCopy _deleteCustomVar _getName _setAccount', - '_getAccount _getClientInfo _getDetectFlash _getDetectTitle', - '_getLinkerUrl _getLocalGifPath _getServiceMode _getVersion', - '_getVisitorCustomVar _initData _link _linkByPost', - '_setAllowAnchor _setAllowHash _setAllowLinker _setCampContentKey', - '_setCampMediumKey _setCampNameKey _setCampNOKey _setCampSourceKey', - '_setCampTermKey _setCampaignCookieTimeout _setCampaignTrack _setClientInfo', - '_setCookiePath _setCookiePersistence _setCookieTimeout _setCustomVar', - '_setDetectFlash _setDetectTitle _setDomainName _setLocalGifPath', - '_setLocalRemoteServerMode _setLocalServerMode _setReferrerOverride _setRemoteServerMode', - '_setSampleRate _setSessionTimeout _setSiteSpeedSampleRate _setSessionCookieTimeout', - '_setVar _setVisitorCookieTimeout _trackEvent _trackPageLoadTime', - '_trackPageview _trackSocial _trackTiming _trackTrans', - '_visitCode' - ].join(' ').split(/\s+/); - let i = api.length; - while ( i-- ) { - out[api[i]] = noopfn; - } - out._getLinkerUrl = function(a) { - return a; - }; - return out; - })(); - // - const Gat = function() { - }; - Gat.prototype._anonymizeIP = noopfn; - Gat.prototype._createTracker = noopfn; - Gat.prototype._forceSSL = noopfn; - Gat.prototype._getPlugin = noopfn; - Gat.prototype._getTracker = function() { - return tracker; - }; - Gat.prototype._getTrackerByName = function() { - return tracker; - }; - Gat.prototype._getTrackers = noopfn; - Gat.prototype.aa = noopfn; - Gat.prototype.ab = noopfn; - Gat.prototype.hb = noopfn; - Gat.prototype.la = noopfn; - Gat.prototype.oa = noopfn; - Gat.prototype.pa = noopfn; - Gat.prototype.u = noopfn; - const gat = new Gat(); - window._gat = gat; - // - const gaq = new Gaq(); - (function() { - const aa = window._gaq || []; - if ( Array.isArray(aa) ) { - while ( aa[0] ) { - gaq.push(aa.shift()); - } - } - })(); - window._gaq = gaq.qf = gaq; - } + ')();', + '/ga.js': 'google_ga.js', - // https://github.com/gorhill/uBlock/issues/1265 - // https://github.com/gorhill/uBlock/blob/dcc72ba51c30abd4a1216049cc34f6c429ab2090/src/web_accessible_resources/scorecardresearch_beacon.js - /* eslint-disable no-undef */ - '/beacon.js': '(' + - function() { - 'use strict'; - window.COMSCORE = { - purge: function() { - window._comscore = []; - }, - beacon: function() { - } - }; - } + ')();', - /* eslint-enable no-undef */ + '/beacon.js': 'comscore_beacon.js', // http://www.dplay.se/ett-jobb-for-berg/ (videos) - '/c2/plugins/streamsense_plugin_html5.js': '(' + - function() { - } + ')();', + '/c2/plugins/streamsense_plugin_html5.js': 'noop.js', // https://github.com/EFForg/privacybadger/issues/993 - // https://github.com/gorhill/uBlock/blob/dcc72ba51c30abd4a1216049cc34f6c429ab2090/src/web_accessible_resources/googletagservices_gpt.js - /* eslint-disable no-empty */ - '/gpt.js': '(' + - function() { - 'use strict'; - // https://developers.google.com/doubleclick-gpt/reference - const noopfn = function() { - }.bind(); - const noopthisfn = function() { - return this; - }; - const noopnullfn = function() { - return null; - }; - const nooparrayfn = function() { - return []; - }; - const noopstrfn = function() { - return ''; - }; - // - const companionAdsService = { - addEventListener: noopthisfn, - enableSyncLoading: noopfn, - setRefreshUnfilledSlots: noopfn - }; - const contentService = { - addEventListener: noopthisfn, - setContent: noopfn - }; - const PassbackSlot = function() { - }; - let p = PassbackSlot.prototype; - p.display = noopfn; - p.get = noopnullfn; - p.set = noopthisfn; - p.setClickUrl = noopthisfn; - p.setTagForChildDirectedTreatment = noopthisfn; - p.setTargeting = noopthisfn; - p.updateTargetingFromMap = noopthisfn; - const pubAdsService = { - addEventListener: noopthisfn, - clear: noopfn, - clearCategoryExclusions: noopthisfn, - clearTagForChildDirectedTreatment: noopthisfn, - clearTargeting: noopthisfn, - collapseEmptyDivs: noopfn, - defineOutOfPagePassback: function() { return new PassbackSlot(); }, - definePassback: function() { return new PassbackSlot(); }, - disableInitialLoad: noopfn, - display: noopfn, - enableAsyncRendering: noopfn, - enableSingleRequest: noopfn, - enableSyncRendering: noopfn, - enableVideoAds: noopfn, - get: noopnullfn, - getAttributeKeys: nooparrayfn, - getTargeting: noopfn, - getTargetingKeys: nooparrayfn, - getSlots: nooparrayfn, - refresh: noopfn, - set: noopthisfn, - setCategoryExclusion: noopthisfn, - setCentering: noopfn, - setCookieOptions: noopthisfn, - setForceSafeFrame: noopthisfn, - setLocation: noopthisfn, - setPublisherProvidedId: noopthisfn, - setRequestNonPersonalizedAds: noopthisfn, - setSafeFrameConfig: noopthisfn, - setTagForChildDirectedTreatment: noopthisfn, - setTargeting: noopthisfn, - setVideoContent: noopthisfn, - updateCorrelator: noopfn - }; - const SizeMappingBuilder = function() { - }; - p = SizeMappingBuilder.prototype; - p.addSize = noopthisfn; - p.build = noopnullfn; - const Slot = function() { - }; - p = Slot.prototype; - p.addService = noopthisfn; - p.clearCategoryExclusions = noopthisfn; - p.clearTargeting = noopthisfn; - p.defineSizeMapping = noopthisfn; - p.get = noopnullfn; - p.getAdUnitPath = nooparrayfn; - p.getAttributeKeys = nooparrayfn; - p.getCategoryExclusions = nooparrayfn; - p.getDomId = noopstrfn; - p.getResponseInformation = noopnullfn; - p.getSlotElementId = noopstrfn; - p.getSlotId = noopthisfn; - p.getTargeting = nooparrayfn; - p.getTargetingKeys = nooparrayfn; - p.set = noopthisfn; - p.setCategoryExclusion = noopthisfn; - p.setClickUrl = noopthisfn; - p.setCollapseEmptyDiv = noopthisfn; - p.setTargeting = noopthisfn; - // - const gpt = window.googletag || {}; - const cmd = gpt.cmd || []; - gpt.apiReady = true; - gpt.cmd = []; - gpt.cmd.push = function(a) { - try { - a(); - } catch (ex) { - } - return 1; - }; - gpt.companionAds = function() { return companionAdsService; }; - gpt.content = function() { return contentService; }; - gpt.defineOutOfPageSlot = function() { return new Slot(); }; - gpt.defineSlot = function() { return new Slot(); }; - gpt.destroySlots = noopfn; - gpt.disablePublisherConsole = noopfn; - gpt.display = noopfn; - gpt.enableServices = noopfn; - gpt.getVersion = noopstrfn; - gpt.pubads = function() { return pubAdsService; }; - gpt.pubadsReady = true; - gpt.setAdIframeTitle = noopfn; - gpt.sizeMapping = function() { return new SizeMappingBuilder(); }; - window.googletag = gpt; - while ( cmd.length !== 0 ) { - gpt.cmd.push(cmd.shift()); - } - } + ')();', - /* eslint-enable no-empty */ + '/gpt.js': 'googletagservices_gpt.js', + '/tag/js/gpt.js': 'googletagservices_gpt.js', // https://github.com/EFForg/privacybadger/issues/1014 - /* eslint-disable no-unused-expressions */ - '/app/yqmin': '(' + - function() { - var noopfn = function() { - ; - }; - function YqClass() { - this.observe = noopfn; - this.observeMin = noopfn; - this.scroll_event = noopfn; - this.onready = noopfn; - this.yq_panel_click = noopfn; - this.titleTrim = noopfn; - } - window.Yq || (window.Yq = new YqClass); - } + ')();', - /* eslint-enable no-unused-expressions */ + '/app/yqmin': 'youneeq.js', - // https://github.com/gorhill/uBlock/blob/e86a4cee8787400d8ad445dd4a6e4515405f25d1/src/web_accessible_resources/google-analytics_analytics.js + GTM workaround - /* eslint-disable no-empty */ - '/analytics.js': '(' + - function() { - 'use strict'; - // https://developers.google.com/analytics/devguides/collection/analyticsjs/ - const noopfn = function() { - }; - // - const Tracker = function() { - }; - const p = Tracker.prototype; - p.get = noopfn; - p.set = noopfn; - p.send = noopfn; - // - const w = window; - const gaName = w.GoogleAnalyticsObject || 'ga'; - const gaQueue = w[gaName]; - // https://github.com/uBlockOrigin/uAssets/pull/4115 - const ga = function() { - const len = arguments.length; - if ( len === 0 ) { return; } - const args = Array.from(arguments); - let fn; - let a = args[len-1]; - if ( a instanceof Object && a.hitCallback instanceof Function ) { - fn = a.hitCallback; - } else if ( a instanceof Function ) { - fn = ( ) => { a(ga.create()); }; - } else { - const pos = args.indexOf('hitCallback'); - if ( pos !== -1 && args[pos+1] instanceof Function ) { - fn = args[pos+1]; - } - } - if ( fn instanceof Function === false ) { return; } - try { - fn(); - } catch (ex) { - } - }; - ga.create = function() { - return new Tracker(); - }; - ga.getByName = function() { - return new Tracker(); - }; - ga.getAll = function() { - return []; - }; - ga.remove = noopfn; - // https://github.com/uBlockOrigin/uAssets/issues/2107 - ga.loaded = true; - w[gaName] = ga; - // https://github.com/gorhill/uBlock/issues/3075 - const dl = w.dataLayer; - if ( dl instanceof Object ) { - if ( dl.hide instanceof Object && typeof dl.hide.end === 'function' ) { - dl.hide.end(); - } - /* - if ( typeof dl.push === 'function' ) { - const doCallback = function(item) { - if ( item instanceof Object === false ) { return; } - if ( typeof item.eventCallback !== 'function' ) { return; } - setTimeout(item.eventCallback, 1); - }; - if ( Array.isArray(dl) ) { - dl.push = item => doCallback(item); - const q = dl.slice(); - for ( const item of q ) { - doCallback(item); - } - } - } - */ - } - // empty ga queue - if ( gaQueue instanceof Function && Array.isArray(gaQueue.q) ) { - const q = gaQueue.q.slice(); - gaQueue.q.length = 0; - for ( const entry of q ) { - ga(...entry); - } - } - } + ')();', - /* eslint-enable no-empty */ + '/analytics.js': 'google_analytics.js', - // https://github.com/gorhill/uBlock/blob/dcc72ba51c30abd4a1216049cc34f6c429ab2090/src/web_accessible_resources/outbrain-widget.js + modified to unbreak vice.com - // related uBO issues: - // https://github.com/uBlockOrigin/uAssets/issues/7140 - // https://github.com/uBlockOrigin/uAssets/issues/8078 - '/outbrain.js': '(' + - function() { - 'use strict'; - const noopfn = function() { - }; - const obr = {}; - const methods = [ - 'callClick', 'callLoadMore', 'callRecs', 'callUserZapping', - 'callWhatIs', 'cancelRecommendation', 'cancelRecs', 'closeCard', - 'closeModal', 'closeTbx', 'errorInjectionHandler', 'getCountOfRecs', - 'getStat', 'imageError', 'manualVideoClicked', 'onOdbReturn', - 'onVideoClick', 'pagerLoad', 'recClicked', 'refreshSpecificWidget', - 'refreshWidget', 'reloadWidget', 'renderSpaWidgets', 'researchWidget', - 'returnedError', 'returnedHtmlData', 'returnedIrdData', 'returnedJsonData', - 'scrollLoad', 'showDescription', 'showRecInIframe', 'userZappingMessage', - 'zappingFormAction' - ]; - obr.extern = { - video: { - getVideoRecs: noopfn, - videoClicked: noopfn - } - }; - methods.forEach(function(a) { - obr.extern[a] = noopfn; - }); - window.OBR = window.OBR || obr; - } + ')();', + '/outbrain.js': 'outbrain.js', - // https://github.com/uBlockOrigin/uAssets/blob/0efcadb2ecc2a9f0daa5a1df79841d794b83860f/filters/resources.txt#L38-L41 - 'noopjs': '(' + - function() { - ; - } + ')();', + '/apstag.js': 'amazon_apstag.js', - /* eslint-enable no-extra-semi, space-in-parens */ + 'noopjs': 'noop.js' }; -// aliases -surrogates['/tag/js/gpt.js'] = surrogates['/gpt.js']; - -// reformat surrogate strings to exactly match formatting in uAssets +// expand filenames to extension URLs Object.keys(surrogates).forEach(key => { - surrogates[key] = surrogates[key] - // remove space from anon function if present - .replace(/^\(function \(/, '(function(') - // fix indentation - .split(/[\r\n]/).map(str => str.replace(/^ {4}/, '')).join('\n') - // replace spaces by tabs - .replace(/ {2}/g, '\t'); + let path = '/data/web_accessible_resources/' + surrogates[key]; + surrogates[key] = chrome.runtime.getURL(path); }); const exports = { diff --git a/src/data/web_accessible_resources/.eslintrc.yml b/src/data/web_accessible_resources/.eslintrc.yml new file mode 100644 index 0000000000..f847da3f00 --- /dev/null +++ b/src/data/web_accessible_resources/.eslintrc.yml @@ -0,0 +1,8 @@ +rules: + indent: + - error + - 4 + - outerIIFEBody: 1 + no-empty: off + no-extra-semi: off + space-in-parens: off diff --git a/src/data/web_accessible_resources/README.md b/src/data/web_accessible_resources/README.md new file mode 100644 index 0000000000..2f312f9127 --- /dev/null +++ b/src/data/web_accessible_resources/README.md @@ -0,0 +1,3 @@ +While the scripts here are exposed to websites (as per the web_accessible_resources manifest key), access is controlled by Privacy Badger via per-request single-use secret tokens. + +Search for `warAccessTokens` for the exact implementation. diff --git a/src/data/web_accessible_resources/amazon_apstag.js b/src/data/web_accessible_resources/amazon_apstag.js new file mode 100644 index 0000000000..1b6ee1d78f --- /dev/null +++ b/src/data/web_accessible_resources/amazon_apstag.js @@ -0,0 +1,19 @@ +// https://github.com/gorhill/uBlock/blob/4b95420e5912cb2759da77dbb3d0d64095021c13/src/web_accessible_resources/amazon_apstag.js +(function() { + 'use strict'; + const w = window; + const noopfn = function() { + ; // jshint ignore:line + }.bind(); + const apstag = { + fetchBids: function(a, b) { + if ( b instanceof Function ) { + b([]); + } + }, + init: noopfn, + setDisplayBids: noopfn, + targetingKeys: noopfn, + }; + w.apstag = apstag; +})(); diff --git a/src/data/web_accessible_resources/comscore_beacon.js b/src/data/web_accessible_resources/comscore_beacon.js new file mode 100644 index 0000000000..5b616bc5d6 --- /dev/null +++ b/src/data/web_accessible_resources/comscore_beacon.js @@ -0,0 +1,11 @@ +// https://github.com/gorhill/uBlock/blob/dcc72ba51c30abd4a1216049cc34f6c429ab2090/src/web_accessible_resources/scorecardresearch_beacon.js +(function() { + 'use strict'; + window.COMSCORE = { + purge: function() { + window._comscore = []; + }, + beacon: function() { + } + }; +})(); diff --git a/src/data/web_accessible_resources/google_analytics.js b/src/data/web_accessible_resources/google_analytics.js new file mode 100644 index 0000000000..f9d2508f7c --- /dev/null +++ b/src/data/web_accessible_resources/google_analytics.js @@ -0,0 +1,86 @@ +// https://github.com/gorhill/uBlock/blob/e86a4cee8787400d8ad445dd4a6e4515405f25d1/src/web_accessible_resources/google-analytics_analytics.js + GTM workaround + +(function() { + 'use strict'; + // https://developers.google.com/analytics/devguides/collection/analyticsjs/ + const noopfn = function() { + }; + // + const Tracker = function() { + }; + const p = Tracker.prototype; + p.get = noopfn; + p.set = noopfn; + p.send = noopfn; + // + const w = window; + const gaName = w.GoogleAnalyticsObject || 'ga'; + const gaQueue = w[gaName]; + // https://github.com/uBlockOrigin/uAssets/pull/4115 + const ga = function() { + const len = arguments.length; + if ( len === 0 ) { return; } + const args = Array.from(arguments); + let fn; + let a = args[len-1]; + if ( a instanceof Object && a.hitCallback instanceof Function ) { + fn = a.hitCallback; + } else if ( a instanceof Function ) { + fn = ( ) => { a(ga.create()); }; + } else { + const pos = args.indexOf('hitCallback'); + if ( pos !== -1 && args[pos+1] instanceof Function ) { + fn = args[pos+1]; + } + } + if ( fn instanceof Function === false ) { return; } + try { + fn(); + } catch (ex) { + } + }; + ga.create = function() { + return new Tracker(); + }; + ga.getByName = function() { + return new Tracker(); + }; + ga.getAll = function() { + return []; + }; + ga.remove = noopfn; + // https://github.com/uBlockOrigin/uAssets/issues/2107 + ga.loaded = true; + w[gaName] = ga; + // https://github.com/gorhill/uBlock/issues/3075 + const dl = w.dataLayer; + if ( dl instanceof Object ) { + if ( dl.hide instanceof Object && typeof dl.hide.end === 'function' ) { + dl.hide.end(); + } + /* + if ( typeof dl.push === 'function' ) { + const doCallback = function(item) { + if ( item instanceof Object === false ) { return; } + if ( typeof item.eventCallback !== 'function' ) { return; } + setTimeout(item.eventCallback, 1); + }; + if ( Array.isArray(dl) ) { + dl.push = item => doCallback(item); + const q = dl.slice(); + for ( const item of q ) { + doCallback(item); + } + } + } + */ + } + // empty ga queue + if ( gaQueue instanceof Function && Array.isArray(gaQueue.q) ) { + const q = gaQueue.q.slice(); + gaQueue.q.length = 0; + for ( const entry of q ) { + ga(...entry); + } + } +})(); diff --git a/src/data/web_accessible_resources/google_ga.js b/src/data/web_accessible_resources/google_ga.js new file mode 100644 index 0000000000..7f21a0e487 --- /dev/null +++ b/src/data/web_accessible_resources/google_ga.js @@ -0,0 +1,98 @@ +// https://github.com/gorhill/uBlock/blob/dcc72ba51c30abd4a1216049cc34f6c429ab2090/src/web_accessible_resources/google-analytics_ga.js +(function() { + 'use strict'; + const noopfn = function() { + }; + // + const Gaq = function() { + }; + Gaq.prototype.Na = noopfn; + Gaq.prototype.O = noopfn; + Gaq.prototype.Sa = noopfn; + Gaq.prototype.Ta = noopfn; + Gaq.prototype.Va = noopfn; + Gaq.prototype._createAsyncTracker = noopfn; + Gaq.prototype._getAsyncTracker = noopfn; + Gaq.prototype._getPlugin = noopfn; + Gaq.prototype.push = function(a) { + if ( typeof a === 'function' ) { + a(); return; + } + if ( Array.isArray(a) === false ) { + return; + } + // https://twitter.com/catovitch/status/776442930345218048 + // https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiDomainDirectory#_gat.GA_Tracker_._link + if ( a[0] === '_link' && typeof a[1] === 'string' ) { + window.location.assign(a[1]); + } + // https://github.com/gorhill/uBlock/issues/2162 + if ( a[0] === '_set' && a[1] === 'hitCallback' && typeof a[2] === 'function' ) { + a[2](); + } + }; + // + const tracker = (function() { + const out = {}; + const api = [ + '_addIgnoredOrganic _addIgnoredRef _addItem _addOrganic', + '_addTrans _clearIgnoredOrganic _clearIgnoredRef _clearOrganic', + '_cookiePathCopy _deleteCustomVar _getName _setAccount', + '_getAccount _getClientInfo _getDetectFlash _getDetectTitle', + '_getLinkerUrl _getLocalGifPath _getServiceMode _getVersion', + '_getVisitorCustomVar _initData _link _linkByPost', + '_setAllowAnchor _setAllowHash _setAllowLinker _setCampContentKey', + '_setCampMediumKey _setCampNameKey _setCampNOKey _setCampSourceKey', + '_setCampTermKey _setCampaignCookieTimeout _setCampaignTrack _setClientInfo', + '_setCookiePath _setCookiePersistence _setCookieTimeout _setCustomVar', + '_setDetectFlash _setDetectTitle _setDomainName _setLocalGifPath', + '_setLocalRemoteServerMode _setLocalServerMode _setReferrerOverride _setRemoteServerMode', + '_setSampleRate _setSessionTimeout _setSiteSpeedSampleRate _setSessionCookieTimeout', + '_setVar _setVisitorCookieTimeout _trackEvent _trackPageLoadTime', + '_trackPageview _trackSocial _trackTiming _trackTrans', + '_visitCode' + ].join(' ').split(/\s+/); + let i = api.length; + while ( i-- ) { + out[api[i]] = noopfn; + } + out._getLinkerUrl = function(a) { + return a; + }; + return out; + })(); + // + const Gat = function() { + }; + Gat.prototype._anonymizeIP = noopfn; + Gat.prototype._createTracker = noopfn; + Gat.prototype._forceSSL = noopfn; + Gat.prototype._getPlugin = noopfn; + Gat.prototype._getTracker = function() { + return tracker; + }; + Gat.prototype._getTrackerByName = function() { + return tracker; + }; + Gat.prototype._getTrackers = noopfn; + Gat.prototype.aa = noopfn; + Gat.prototype.ab = noopfn; + Gat.prototype.hb = noopfn; + Gat.prototype.la = noopfn; + Gat.prototype.oa = noopfn; + Gat.prototype.pa = noopfn; + Gat.prototype.u = noopfn; + const gat = new Gat(); + window._gat = gat; + // + const gaq = new Gaq(); + (function() { + const aa = window._gaq || []; + if ( Array.isArray(aa) ) { + while ( aa[0] ) { + gaq.push(aa.shift()); + } + } + })(); + window._gaq = gaq.qf = gaq; +})(); diff --git a/src/data/web_accessible_resources/googletagservices_gpt.js b/src/data/web_accessible_resources/googletagservices_gpt.js new file mode 100644 index 0000000000..cc6e1e02eb --- /dev/null +++ b/src/data/web_accessible_resources/googletagservices_gpt.js @@ -0,0 +1,130 @@ +// https://github.com/gorhill/uBlock/blob/dcc72ba51c30abd4a1216049cc34f6c429ab2090/src/web_accessible_resources/googletagservices_gpt.js +(function() { + 'use strict'; + // https://developers.google.com/doubleclick-gpt/reference + const noopfn = function() { + }.bind(); + const noopthisfn = function() { + return this; + }; + const noopnullfn = function() { + return null; + }; + const nooparrayfn = function() { + return []; + }; + const noopstrfn = function() { + return ''; + }; + // + const companionAdsService = { + addEventListener: noopthisfn, + enableSyncLoading: noopfn, + setRefreshUnfilledSlots: noopfn + }; + const contentService = { + addEventListener: noopthisfn, + setContent: noopfn + }; + const PassbackSlot = function() { + }; + let p = PassbackSlot.prototype; + p.display = noopfn; + p.get = noopnullfn; + p.set = noopthisfn; + p.setClickUrl = noopthisfn; + p.setTagForChildDirectedTreatment = noopthisfn; + p.setTargeting = noopthisfn; + p.updateTargetingFromMap = noopthisfn; + const pubAdsService = { + addEventListener: noopthisfn, + clear: noopfn, + clearCategoryExclusions: noopthisfn, + clearTagForChildDirectedTreatment: noopthisfn, + clearTargeting: noopthisfn, + collapseEmptyDivs: noopfn, + defineOutOfPagePassback: function() { return new PassbackSlot(); }, + definePassback: function() { return new PassbackSlot(); }, + disableInitialLoad: noopfn, + display: noopfn, + enableAsyncRendering: noopfn, + enableSingleRequest: noopfn, + enableSyncRendering: noopfn, + enableVideoAds: noopfn, + get: noopnullfn, + getAttributeKeys: nooparrayfn, + getTargeting: noopfn, + getTargetingKeys: nooparrayfn, + getSlots: nooparrayfn, + refresh: noopfn, + set: noopthisfn, + setCategoryExclusion: noopthisfn, + setCentering: noopfn, + setCookieOptions: noopthisfn, + setForceSafeFrame: noopthisfn, + setLocation: noopthisfn, + setPublisherProvidedId: noopthisfn, + setRequestNonPersonalizedAds: noopthisfn, + setSafeFrameConfig: noopthisfn, + setTagForChildDirectedTreatment: noopthisfn, + setTargeting: noopthisfn, + setVideoContent: noopthisfn, + updateCorrelator: noopfn + }; + const SizeMappingBuilder = function() { + }; + p = SizeMappingBuilder.prototype; + p.addSize = noopthisfn; + p.build = noopnullfn; + const Slot = function() { + }; + p = Slot.prototype; + p.addService = noopthisfn; + p.clearCategoryExclusions = noopthisfn; + p.clearTargeting = noopthisfn; + p.defineSizeMapping = noopthisfn; + p.get = noopnullfn; + p.getAdUnitPath = nooparrayfn; + p.getAttributeKeys = nooparrayfn; + p.getCategoryExclusions = nooparrayfn; + p.getDomId = noopstrfn; + p.getResponseInformation = noopnullfn; + p.getSlotElementId = noopstrfn; + p.getSlotId = noopthisfn; + p.getTargeting = nooparrayfn; + p.getTargetingKeys = nooparrayfn; + p.set = noopthisfn; + p.setCategoryExclusion = noopthisfn; + p.setClickUrl = noopthisfn; + p.setCollapseEmptyDiv = noopthisfn; + p.setTargeting = noopthisfn; + // + const gpt = window.googletag || {}; + const cmd = gpt.cmd || []; + gpt.apiReady = true; + gpt.cmd = []; + gpt.cmd.push = function(a) { + try { + a(); + } catch (ex) { + } + return 1; + }; + gpt.companionAds = function() { return companionAdsService; }; + gpt.content = function() { return contentService; }; + gpt.defineOutOfPageSlot = function() { return new Slot(); }; + gpt.defineSlot = function() { return new Slot(); }; + gpt.destroySlots = noopfn; + gpt.disablePublisherConsole = noopfn; + gpt.display = noopfn; + gpt.enableServices = noopfn; + gpt.getVersion = noopstrfn; + gpt.pubads = function() { return pubAdsService; }; + gpt.pubadsReady = true; + gpt.setAdIframeTitle = noopfn; + gpt.sizeMapping = function() { return new SizeMappingBuilder(); }; + window.googletag = gpt; + while ( cmd.length !== 0 ) { + gpt.cmd.push(cmd.shift()); + } +})(); diff --git a/src/data/web_accessible_resources/outbrain.js b/src/data/web_accessible_resources/outbrain.js new file mode 100644 index 0000000000..ba25b51d1a --- /dev/null +++ b/src/data/web_accessible_resources/outbrain.js @@ -0,0 +1,28 @@ +// https://github.com/gorhill/uBlock/blob/dcc72ba51c30abd4a1216049cc34f6c429ab2090/src/web_accessible_resources/outbrain-widget.js + modified to unbreak vice.com +(function() { + 'use strict'; + const noopfn = function() { + }; + const obr = {}; + const methods = [ + 'callClick', 'callLoadMore', 'callRecs', 'callUserZapping', + 'callWhatIs', 'cancelRecommendation', 'cancelRecs', 'closeCard', + 'closeModal', 'closeTbx', 'errorInjectionHandler', 'getCountOfRecs', + 'getStat', 'imageError', 'manualVideoClicked', 'onOdbReturn', + 'onVideoClick', 'pagerLoad', 'recClicked', 'refreshSpecificWidget', + 'refreshWidget', 'reloadWidget', 'renderSpaWidgets', 'researchWidget', + 'returnedError', 'returnedHtmlData', 'returnedIrdData', 'returnedJsonData', + 'scrollLoad', 'showDescription', 'showRecInIframe', 'userZappingMessage', + 'zappingFormAction' + ]; + obr.extern = { + video: { + getVideoRecs: noopfn, + videoClicked: noopfn + } + }; + methods.forEach(function(a) { + obr.extern[a] = noopfn; + }); + window.OBR = window.OBR || obr; +})(); diff --git a/src/data/web_accessible_resources/youneeq.js b/src/data/web_accessible_resources/youneeq.js new file mode 100644 index 0000000000..059bd44f3e --- /dev/null +++ b/src/data/web_accessible_resources/youneeq.js @@ -0,0 +1,14 @@ +(function () { + var noopfn = function() { + ; + }; + function YqClass() { + this.observe = noopfn; + this.observeMin = noopfn; + this.scroll_event = noopfn; + this.onready = noopfn; + this.yq_panel_click = noopfn; + this.titleTrim = noopfn; + } + window.Yq || (window.Yq = new YqClass); // eslint-disable-line no-unused-expressions +}()); diff --git a/src/js/background.js b/src/js/background.js index 9c21fb78c5..e03151c214 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -219,13 +219,17 @@ Badger.prototype = { }, frames: { : { - url: {String}, + url: {String} host: {String} + warAccessTokens: { + : {String} access token + ... + } }, ... }, origins: { - domain.tld: {String} action taken for this domain + : {String} action taken for this domain ... } }, diff --git a/src/js/constants.js b/src/js/constants.js index 0f0e399ad9..65bb5fdb10 100644 --- a/src/js/constants.js +++ b/src/js/constants.js @@ -15,9 +15,9 @@ * along with Privacy Badger. If not, see . */ -require.scopes.constants = (function() { +require.scopes.constants = (function () { -var exports = { +let exports = { // Tracking status constants NO_TRACKING: "noaction", @@ -52,4 +52,5 @@ exports.BLOCKED_ACTIONS = new Set([ ]); return exports; -})(); + +}()); diff --git a/src/js/surrogates.js b/src/js/surrogates.js index a9865ebcbc..4fb9e11022 100644 --- a/src/js/surrogates.js +++ b/src/js/surrogates.js @@ -33,8 +33,8 @@ const db = require('surrogatedb'); * parameter. This is an optimization: the calling context should already have * this information. * - * @return {(String|Boolean)} The surrogate script as a data URI when there is a - * match, or boolean false when there is no match. + * @return {(String|Boolean)} Extension URL to the surrogate script + * when there is a match; boolean false otherwise. */ function getSurrogateUri(script_url, script_hostname) { // do we have an entry for the script hostname? @@ -49,11 +49,7 @@ function getSurrogateUri(script_url, script_hostname) { // wildcard token: // matches any script URL for the hostname case db.MATCH_ANY: { - if (db.surrogates.hasOwnProperty(conf.token)) { - // return the surrogate code - return 'data:application/javascript;base64,' + btoa(db.surrogates[conf.token]); - } - break; + return db.surrogates[conf.token]; } // one or more suffix tokens: @@ -75,9 +71,9 @@ function getSurrogateUri(script_url, script_hostname) { } } + // there is a match, return the surrogate code if (match) { - // there is a match, return the surrogate code - return 'data:application/javascript;base64,' + btoa(db.surrogates[token]); + return db.surrogates[token]; } } diff --git a/src/js/webrequest.js b/src/js/webrequest.js index ca54268f42..48d3e53738 100644 --- a/src/js/webrequest.js +++ b/src/js/webrequest.js @@ -104,7 +104,10 @@ function onBeforeRequest(details) { if (type == 'script') { let surrogate = getSurrogateUri(url, request_host); if (surrogate) { - return {redirectUrl: surrogate}; + let secret = getWarSecret(tab_id, frame_id, surrogate); + return { + redirectUrl: surrogate + '?key=' + secret + }; } } @@ -132,6 +135,61 @@ function onBeforeRequest(details) { return {cancel: true}; } +/** + * Generates a token for a given tab ID/frame ID/resource URL combination. + * + * @param {Integer} tab_id + * @param {Integer} frame_id + * @param {String} url + * + * @returns {String} + */ +function getWarSecret(tab_id, frame_id, url) { + let secret = (+(("" + Math.random()).slice(2))).toString(16), + frameData = badger.getFrameData(tab_id, frame_id), + tokens = frameData.warAccessTokens; + + if (!tokens) { + tokens = {}; + frameData.warAccessTokens = tokens; + } + + tokens[url] = secret; + + return secret; +} + +/** + * Guards against web_accessible_resources abuse. + * + * Checks whether there is a previously saved token + * for a given tab ID/frame ID/resource URL combination, + * and whether the full request URL contains this token. + * + * @param {Object} details webRequest request details object + * + * @returns {Object|undefined} Can cancel requests + */ +function filterWarRequests(details) { + let url = details.url, + frameData = badger.getFrameData(details.tabId, details.frameId), + tokens = frameData && frameData.warAccessTokens; + + if (!tokens) { + return { cancel: true }; + } + + let qs_start = url.indexOf('?'), + url_no_qs = qs_start && url.slice(0, qs_start), + secret = url_no_qs && tokens[url_no_qs]; + + if (!secret || url != `${url_no_qs}?key=${secret}`) { + return { cancel: true }; + } + + delete tokens[url_no_qs]; +} + /** * Filters outgoing cookies and referer * Injects DNT @@ -1374,6 +1432,12 @@ function startListeners() { chrome.webRequest.onBeforeRequest.addListener(onBeforeRequest, {urls: ["http://*/*", "https://*/*"]}, ["blocking"]); + chrome.webRequest.onBeforeRequest.addListener(filterWarRequests, { + urls: [ + chrome.runtime.getURL('/data/web_accessible_resources/') + '*', + ] + }, ["blocking"]); + let extraInfoSpec = ['requestHeaders', 'blocking']; if (chrome.webRequest.OnBeforeSendHeadersOptions.hasOwnProperty('EXTRA_HEADERS')) { extraInfoSpec.push('extraHeaders'); diff --git a/src/manifest.json b/src/manifest.json index 9b9de3be3b..1afeba6cce 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -24,6 +24,7 @@ "background": { "scripts": [ "js/bootstrap.js", + "js/constants.js", "lib/publicSuffixList.js", "lib/basedomain.js", "data/surrogates.js", @@ -31,7 +32,6 @@ "js/utils.js", "js/surrogates.js", "js/incognito.js", - "js/constants.js", "js/storage.js", "js/heuristicblocking.js", "js/socialwidgetloader.js", @@ -517,5 +517,8 @@ "storage": { "managed_schema": "data/schema.json" }, - "update_url": "https://clients2.google.com/service/update2/crx" + "update_url": "https://clients2.google.com/service/update2/crx", + "web_accessible_resources": [ + "data/web_accessible_resources/*" + ] } diff --git a/src/tests/index.html b/src/tests/index.html index ca08747498..6a3e762973 100644 --- a/src/tests/index.html +++ b/src/tests/index.html @@ -39,12 +39,12 @@ + - diff --git a/src/tests/tests/utils.js b/src/tests/tests/utils.js index 97b447096a..7e9df1886e 100644 --- a/src/tests/tests/utils.js +++ b/src/tests/tests/utils.js @@ -5,6 +5,7 @@ QUnit.module("Utils"); let utils = require('utils'), + surrogatedb = require('surrogatedb'), getSurrogateUri = require('surrogates').getSurrogateUri; QUnit.test("explodeSubdomains", function (assert) { @@ -99,42 +100,61 @@ QUnit.test("disable/enable privacy badger for origin", function (assert) { }); QUnit.test("getSurrogateUri() suffix tokens", function (assert) { - const surrogatedb = require('surrogatedb'); + const TEST_FQDN = 'www.google-analytics.com', + TEST_TOKEN = '/ga.js'; - const BASE64JS = 'data:application/javascript;base64,', - NOOP = function () {}; - - const GA_JS_TESTS = [ + const TESTS = [ + { + url: `http://${TEST_FQDN}${TEST_TOKEN}`, + expected: true, + msg: "ga.js http URL should match" + }, + { + url: `https://${TEST_FQDN}${TEST_TOKEN}`, + expected: true, + msg: "ga.js https URL should match" + }, { - url: 'http://www.google-analytics.com/ga.js', - msg: "Google Analytics ga.js http URL should match" + url: `https://${TEST_FQDN}${TEST_TOKEN}?foo=bar`, + expected: true, + msg: "ga.js URL with querystring should still match" }, { - url: 'https://www.google-analytics.com/ga.js', - msg: "Google Analytics ga.js https URL should match" + url: `https://${TEST_FQDN}/script${TEST_TOKEN}?foo=bar`, + expected: true, + msg: "ga.js URL with some stuff before the match token should still match" }, { - url: 'https://www.google-analytics.com/ga.js?foo=bar', - msg: "Google Analytics ga.js querystring URL should match" + url: `https://${TEST_FQDN}${TEST_TOKEN}/more/path`, + expected: false, + msg: "should not match (token in path but not at end)" + }, + { + url: `https://${TEST_FQDN}/?${TEST_TOKEN}`, + expected: false, + msg: "should not match (token in querystring)" }, ]; - const NYT_SCRIPT_PATH = '/assets/homepage/20160920-111441/js/foundation/lib/framework.js', - NYT_URL = 'https://a1.nyt.com' + NYT_SCRIPT_PATH; - let ga_js_surrogate; - - for (let i = 0; i < GA_JS_TESTS.length; i++) { - ga_js_surrogate = getSurrogateUri( - GA_JS_TESTS[i].url, - 'www.google-analytics.com' - ); - assert.ok(ga_js_surrogate, GA_JS_TESTS[i].msg); + for (let test of TESTS) { + let surrogate = getSurrogateUri( + test.url, window.extractHostFromURL(test.url)); + if (test.expected) { + assert.ok(surrogate, test.msg); + if (surrogate) { + assert.equal( + surrogate, + surrogatedb.surrogates[TEST_TOKEN], + "got the GA surrogate extension URL" + ); + } + } else { + assert.notOk(surrogate, test.msg); + } } - assert.ok( - ga_js_surrogate.startsWith(BASE64JS), - "The returned ga.js surrogate is a base64-encoded JavaScript data URI" - ); + const NYT_SCRIPT_PATH = '/assets/homepage/20160920-111441/js/foundation/lib/framework.js', + NYT_URL = 'https://a1.nyt.com' + NYT_SCRIPT_PATH; // test negative match assert.notOk( @@ -149,26 +169,20 @@ QUnit.test("getSurrogateUri() suffix tokens", function (assert) { NYT_SCRIPT_PATH ] }; - surrogatedb.surrogates[NYT_SCRIPT_PATH] = NOOP; + surrogatedb.surrogates[NYT_SCRIPT_PATH] = surrogatedb.surrogates.noopjs; assert.equal( getSurrogateUri(NYT_URL, window.extractHostFromURL(NYT_URL)), - BASE64JS + btoa(NOOP), + surrogatedb.surrogates.noopjs, "New York Times script URL should now match the noop surrogate" ); }); QUnit.test("getSurrogateUri() wildcard tokens", function (assert) { - const surrogatedb = require('surrogatedb'); - - const BASE64JS = 'data:application/javascript;base64,', - NOOP = function () {}; - // set up test data for wildcard token tests surrogatedb.hostnames['cdn.example.com'] = { match: surrogatedb.MATCH_ANY, - token: 'noop' + token: 'noopjs' }; - surrogatedb.surrogates.noop = NOOP; // https://stackoverflow.com/a/11935263 function get_random_subarray(arr, size) { @@ -194,7 +208,7 @@ QUnit.test("getSurrogateUri() wildcard tokens", function (assert) { assert.equal( getSurrogateUri(url, window.extractHostFromURL(url)), - BASE64JS + btoa(NOOP), + surrogatedb.surrogates.noopjs, "A wildcard token should match all URLs for the hostname: " + url ); }