2015-11-23 13:52:50 +01:00
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
uBlock Origin - a browser extension to block requests.
|
2018-08-11 16:39:43 +02:00
|
|
|
Copyright (C) 2015-present Raymond Hill
|
2015-11-23 13:52:50 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
|
|
|
|
|
|
Home: https://github.com/gorhill/uBlock
|
|
|
|
*/
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-07-03 15:40:12 +02:00
|
|
|
µBlock.redirectEngine = (( ) => {
|
2015-11-23 13:52:50 +01:00
|
|
|
|
2015-11-26 23:56:30 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-07-06 18:36:28 +02:00
|
|
|
const redirectableResources = new Map([
|
2019-07-03 15:40:12 +02:00
|
|
|
[ '1x1.gif', {
|
|
|
|
alias: '1x1-transparent.gif',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ '2x2.png', {
|
|
|
|
alias: '2x2-transparent.png',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ '3x2.png', {
|
|
|
|
alias: '3x2-transparent.png',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ '32x32.png', {
|
|
|
|
alias: '32x32-transparent.png',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'addthis_widget.js', {
|
|
|
|
alias: 'addthis.com/addthis_widget.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'ampproject_v0.js', {
|
|
|
|
alias: 'ampproject.org/v0.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'chartbeat.js', {
|
|
|
|
alias: 'static.chartbeat.com/chartbeat.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'amazon_ads.js', {
|
|
|
|
alias: 'amazon-adsystem.com/aax2/amzn_ads.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'disqus_embed.js', {
|
|
|
|
alias: 'disqus.com/embed.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'disqus_forums_embed.js', {
|
|
|
|
alias: 'disqus.com/forums/*/embed.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'doubleclick_instream_ad_status.js', {
|
|
|
|
alias: 'doubleclick.net/instream/ad_status.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'google-analytics_analytics.js', {
|
|
|
|
alias: 'google-analytics.com/analytics.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'google-analytics_cx_api.js', {
|
|
|
|
alias: 'google-analytics.com/cx/api.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'google-analytics_ga.js', {
|
|
|
|
alias: 'google-analytics.com/ga.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'google-analytics_inpage_linkid.js', {
|
|
|
|
alias: 'google-analytics.com/inpage_linkid.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'googlesyndication_adsbygoogle.js', {
|
|
|
|
alias: 'googlesyndication.com/adsbygoogle.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'googletagmanager_gtm.js', {
|
|
|
|
alias: 'googletagmanager.com/gtm.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'googletagservices_gpt.js', {
|
|
|
|
alias: 'googletagservices.com/gpt.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'hd-main.js', {
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'ligatus_angular-tag.js', {
|
|
|
|
alias: 'ligatus.com/*/angular-tag.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'monkeybroker.js', {
|
|
|
|
alias: 'd3pkae9owd2lcf.cloudfront.net/mb105.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
2019-07-04 01:26:09 +02:00
|
|
|
[ 'noeval.js', {
|
|
|
|
} ],
|
2019-07-03 15:40:12 +02:00
|
|
|
[ 'noeval-silent.js', {
|
|
|
|
alias: 'silent-noeval.js',
|
|
|
|
} ],
|
2019-07-09 17:04:12 +02:00
|
|
|
[ 'nobab.js', {
|
|
|
|
alias: 'bab-defuser.js',
|
|
|
|
} ],
|
|
|
|
[ 'nofab.js', {
|
|
|
|
alias: 'fuckadblock.js-3.2.0',
|
|
|
|
} ],
|
2019-07-03 15:40:12 +02:00
|
|
|
[ 'noop-0.1s.mp3', {
|
|
|
|
alias: 'noopmp3-0.1s',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'noop-1s.mp4', {
|
|
|
|
alias: 'noopmp4-1s',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'noop.html', {
|
|
|
|
alias: 'noopframe',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'noop.js', {
|
|
|
|
alias: 'noopjs',
|
|
|
|
} ],
|
|
|
|
[ 'noop.txt', {
|
|
|
|
alias: 'nooptext',
|
|
|
|
} ],
|
|
|
|
[ 'outbrain-widget.js', {
|
|
|
|
alias: 'widgets.outbrain.com/outbrain.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
|
|
|
[ 'popads.js', {
|
|
|
|
alias: 'popads.net.js',
|
|
|
|
} ],
|
|
|
|
[ 'popads-dummy.js', {
|
|
|
|
} ],
|
|
|
|
[ 'scorecardresearch_beacon.js', {
|
|
|
|
alias: 'scorecardresearch.com/beacon.js',
|
|
|
|
inject: false
|
|
|
|
} ],
|
2019-07-04 20:08:56 +02:00
|
|
|
[ 'window.open-defuser.js', {
|
|
|
|
} ],
|
2019-07-03 15:40:12 +02:00
|
|
|
]);
|
2018-02-16 15:07:20 +01:00
|
|
|
|
2019-07-08 14:56:36 +02:00
|
|
|
const extToMimeMap = new Map([
|
|
|
|
[ 'gif', 'image/gif' ],
|
|
|
|
[ 'html', 'text/html' ],
|
|
|
|
[ 'js', 'application/javascript' ],
|
|
|
|
[ 'mp3', 'audio/mp3' ],
|
|
|
|
[ 'mp4', 'video/mp4' ],
|
|
|
|
[ 'png', 'image/png' ],
|
|
|
|
[ 'txt', 'text/plain' ],
|
|
|
|
]);
|
|
|
|
|
|
|
|
const validMimes = new Set(extToMimeMap.values());
|
2018-02-15 23:25:38 +01:00
|
|
|
|
2019-07-06 18:36:28 +02:00
|
|
|
const mimeFromName = function(name) {
|
|
|
|
const match = /\.([^.]+)$/.exec(name);
|
|
|
|
if ( match !== null ) {
|
2019-07-08 14:56:36 +02:00
|
|
|
return extToMimeMap.get(match[1]);
|
2019-07-06 18:36:28 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-04-24 11:48:28 +02:00
|
|
|
// https://github.com/gorhill/uBlock/issues/3639
|
|
|
|
// https://github.com/EFForg/https-everywhere/issues/14961
|
|
|
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=111700
|
|
|
|
// Do not redirect to a WAR if the platform suffers from spurious redirect
|
|
|
|
// conflicts, and the request to redirect is not `https:`.
|
|
|
|
// This special handling code can removed once the Chromium issue is fixed.
|
2018-10-31 23:34:54 +01:00
|
|
|
const suffersSpuriousRedirectConflicts = vAPI.webextFlavor.soup.has('chromium');
|
2018-04-24 11:48:28 +02:00
|
|
|
|
2018-02-15 23:25:38 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2018-10-31 23:34:54 +01:00
|
|
|
const RedirectEntry = function() {
|
2015-11-29 17:04:42 +01:00
|
|
|
this.mime = '';
|
|
|
|
this.data = '';
|
2018-02-15 23:25:38 +01:00
|
|
|
this.warURL = undefined;
|
2015-11-26 23:56:30 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2018-02-16 19:37:20 +01:00
|
|
|
// Prevent redirection to web accessible resources when the request is
|
|
|
|
// of type 'xmlhttprequest', because XMLHttpRequest.responseURL would
|
|
|
|
// cause leakage of extension id. See:
|
|
|
|
// - https://stackoverflow.com/a/8056313
|
|
|
|
// - https://bugzilla.mozilla.org/show_bug.cgi?id=998076
|
|
|
|
|
2018-12-13 18:30:54 +01:00
|
|
|
RedirectEntry.prototype.toURL = function(fctxt) {
|
2018-02-18 12:08:48 +01:00
|
|
|
if (
|
|
|
|
this.warURL !== undefined &&
|
2018-12-13 18:30:54 +01:00
|
|
|
fctxt instanceof Object &&
|
|
|
|
fctxt.type !== 'xmlhttprequest' &&
|
2018-04-24 11:48:28 +02:00
|
|
|
(
|
|
|
|
suffersSpuriousRedirectConflicts === false ||
|
2018-12-13 18:30:54 +01:00
|
|
|
fctxt.url.startsWith('https:')
|
2018-04-24 11:48:28 +02:00
|
|
|
)
|
2018-02-18 12:08:48 +01:00
|
|
|
) {
|
2019-04-30 20:36:07 +02:00
|
|
|
return `${this.warURL}${vAPI.warSecret()}`;
|
2018-02-15 23:25:38 +01:00
|
|
|
}
|
2019-07-03 15:40:12 +02:00
|
|
|
if ( this.data === undefined ) { return; }
|
2015-12-23 17:02:36 +01:00
|
|
|
if ( this.data.startsWith('data:') === false ) {
|
|
|
|
if ( this.mime.indexOf(';') === -1 ) {
|
|
|
|
this.data = 'data:' + this.mime + ';base64,' + btoa(this.data);
|
|
|
|
} else {
|
|
|
|
this.data = 'data:' + this.mime + ',' + this.data;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.data;
|
|
|
|
};
|
2015-11-26 23:56:30 +01:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-12-23 17:02:36 +01:00
|
|
|
RedirectEntry.prototype.toContent = function() {
|
|
|
|
if ( this.data.startsWith('data:') ) {
|
2019-07-06 18:36:28 +02:00
|
|
|
const pos = this.data.indexOf(',');
|
|
|
|
const base64 = this.data.endsWith(';base64', pos);
|
2015-12-23 17:02:36 +01:00
|
|
|
this.data = this.data.slice(pos + 1);
|
|
|
|
if ( base64 ) {
|
|
|
|
this.data = atob(this.data);
|
|
|
|
}
|
2015-11-26 23:56:30 +01:00
|
|
|
}
|
2015-12-23 17:02:36 +01:00
|
|
|
return this.data;
|
2015-11-29 17:04:42 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-07-06 18:36:28 +02:00
|
|
|
RedirectEntry.fromContent = function(mime, content) {
|
2019-07-03 15:40:12 +02:00
|
|
|
const r = new RedirectEntry();
|
2015-11-29 17:04:42 +01:00
|
|
|
r.mime = mime;
|
2019-07-06 18:36:28 +02:00
|
|
|
r.data = content;
|
2015-11-29 17:04:42 +01:00
|
|
|
return r;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
RedirectEntry.fromSelfie = function(selfie) {
|
2019-07-06 18:36:28 +02:00
|
|
|
const r = new RedirectEntry();
|
2015-11-29 17:04:42 +01:00
|
|
|
r.mime = selfie.mime;
|
|
|
|
r.data = selfie.data;
|
2018-02-15 23:25:38 +01:00
|
|
|
r.warURL = selfie.warURL;
|
2015-11-29 17:04:42 +01:00
|
|
|
return r;
|
2015-11-26 23:56:30 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
2015-11-23 13:52:50 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2018-10-31 23:34:54 +01:00
|
|
|
const RedirectEngine = function() {
|
2019-07-06 18:36:28 +02:00
|
|
|
this.aliases = new Map();
|
2016-10-10 15:01:05 +02:00
|
|
|
this.resources = new Map();
|
2015-11-23 13:52:50 +01:00
|
|
|
this.reset();
|
2016-01-07 23:30:56 +01:00
|
|
|
this.resourceNameRegister = '';
|
2016-10-10 15:01:05 +02:00
|
|
|
this._desAll = []; // re-use better than re-allocate
|
2015-11-23 13:52:50 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
RedirectEngine.prototype.reset = function() {
|
2016-10-10 15:01:05 +02:00
|
|
|
this.rules = new Map();
|
|
|
|
this.ruleTypes = new Set();
|
|
|
|
this.ruleSources = new Set();
|
|
|
|
this.ruleDestinations = new Set();
|
2017-12-21 23:05:25 +01:00
|
|
|
this.modifyTime = Date.now();
|
2015-11-23 13:52:50 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-11-24 01:18:25 +01:00
|
|
|
RedirectEngine.prototype.freeze = function() {
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2016-10-10 15:01:05 +02:00
|
|
|
RedirectEngine.prototype.toBroaderHostname = function(hostname) {
|
2019-07-06 18:36:28 +02:00
|
|
|
const pos = hostname.indexOf('.');
|
2016-10-10 15:01:05 +02:00
|
|
|
if ( pos !== -1 ) {
|
|
|
|
return hostname.slice(pos + 1);
|
|
|
|
}
|
|
|
|
return hostname !== '*' ? '*' : '';
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2018-12-13 18:30:54 +01:00
|
|
|
RedirectEngine.prototype.lookup = function(fctxt) {
|
|
|
|
const type = fctxt.type;
|
2016-10-10 15:01:05 +02:00
|
|
|
if ( this.ruleTypes.has(type) === false ) { return; }
|
2018-12-13 18:30:54 +01:00
|
|
|
const desAll = this._desAll;
|
|
|
|
const reqURL = fctxt.url;
|
|
|
|
let src = fctxt.getDocHostname();
|
|
|
|
let des = fctxt.getHostname();
|
|
|
|
let n = 0;
|
2016-10-10 15:01:05 +02:00
|
|
|
for (;;) {
|
|
|
|
if ( this.ruleDestinations.has(des) ) {
|
|
|
|
desAll[n] = des; n += 1;
|
|
|
|
}
|
|
|
|
des = this.toBroaderHostname(des);
|
|
|
|
if ( des === '' ) { break; }
|
2015-11-23 13:52:50 +01:00
|
|
|
}
|
2016-10-10 15:01:05 +02:00
|
|
|
if ( n === 0 ) { return; }
|
2015-11-23 13:52:50 +01:00
|
|
|
for (;;) {
|
2016-10-10 15:01:05 +02:00
|
|
|
if ( this.ruleSources.has(src) ) {
|
2018-12-13 18:30:54 +01:00
|
|
|
for ( let i = 0; i < n; i++ ) {
|
|
|
|
const entries = this.rules.get(src + ' ' + desAll[i] + ' ' + type);
|
2016-10-10 15:01:05 +02:00
|
|
|
if ( entries && this.lookupToken(entries, reqURL) ) {
|
|
|
|
return this.resourceNameRegister;
|
2015-11-23 15:49:50 +01:00
|
|
|
}
|
2015-11-23 13:52:50 +01:00
|
|
|
}
|
|
|
|
}
|
2016-10-10 15:01:05 +02:00
|
|
|
src = this.toBroaderHostname(src);
|
|
|
|
if ( src === '' ) { break; }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
RedirectEngine.prototype.lookupToken = function(entries, reqURL) {
|
2018-10-31 23:34:54 +01:00
|
|
|
let j = entries.length;
|
2016-10-10 15:01:05 +02:00
|
|
|
while ( j-- ) {
|
2018-10-31 23:34:54 +01:00
|
|
|
let entry = entries[j];
|
2016-10-10 15:01:05 +02:00
|
|
|
if ( entry.pat instanceof RegExp === false ) {
|
|
|
|
entry.pat = new RegExp(entry.pat, 'i');
|
|
|
|
}
|
|
|
|
if ( entry.pat.test(reqURL) ) {
|
|
|
|
this.resourceNameRegister = entry.tok;
|
|
|
|
return true;
|
2015-11-23 13:52:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2018-12-13 18:30:54 +01:00
|
|
|
RedirectEngine.prototype.toURL = function(fctxt) {
|
|
|
|
let token = this.lookup(fctxt);
|
2018-02-16 19:37:20 +01:00
|
|
|
if ( token === undefined ) { return; }
|
2019-07-06 18:36:28 +02:00
|
|
|
const entry = this.resources.get(this.aliases.get(token) || token);
|
2015-11-26 23:56:30 +01:00
|
|
|
if ( entry !== undefined ) {
|
2018-12-13 18:30:54 +01:00
|
|
|
return entry.toURL(fctxt);
|
2015-11-26 23:56:30 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
RedirectEngine.prototype.matches = function(context) {
|
2019-07-06 18:36:28 +02:00
|
|
|
const token = this.lookup(context);
|
|
|
|
return token !== undefined &&
|
|
|
|
this.resources.has(this.aliases.get(token) || token);
|
2015-11-26 23:56:30 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-11-24 01:18:25 +01:00
|
|
|
RedirectEngine.prototype.addRule = function(src, des, type, pattern, redirect) {
|
2016-10-10 15:01:05 +02:00
|
|
|
this.ruleSources.add(src);
|
|
|
|
this.ruleDestinations.add(des);
|
|
|
|
this.ruleTypes.add(type);
|
2019-07-06 18:36:28 +02:00
|
|
|
const key = `${src} ${des} ${type}`,
|
2016-10-10 15:01:05 +02:00
|
|
|
entries = this.rules.get(key);
|
|
|
|
if ( entries === undefined ) {
|
|
|
|
this.rules.set(key, [ { tok: redirect, pat: pattern } ]);
|
2017-12-21 23:05:25 +01:00
|
|
|
this.modifyTime = Date.now();
|
2016-10-10 15:01:05 +02:00
|
|
|
return;
|
2015-11-24 01:18:25 +01:00
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
let entry;
|
2016-10-10 15:01:05 +02:00
|
|
|
for ( var i = 0, n = entries.length; i < n; i++ ) {
|
|
|
|
entry = entries[i];
|
|
|
|
if ( redirect === entry.tok ) { break; }
|
2016-01-22 00:30:06 +01:00
|
|
|
}
|
2016-10-10 15:01:05 +02:00
|
|
|
if ( i === n ) {
|
|
|
|
entries.push({ tok: redirect, pat: pattern });
|
2016-01-22 00:30:06 +01:00
|
|
|
return;
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
let p = entry.pat;
|
2016-01-22 00:30:06 +01:00
|
|
|
if ( p instanceof RegExp ) {
|
|
|
|
p = p.source;
|
2015-11-24 01:18:25 +01:00
|
|
|
}
|
2016-01-22 00:30:06 +01:00
|
|
|
// Duplicate?
|
2019-07-06 18:36:28 +02:00
|
|
|
let pos = p.indexOf(pattern);
|
2016-01-22 00:30:06 +01:00
|
|
|
if ( pos !== -1 ) {
|
|
|
|
if ( pos === 0 || p.charAt(pos - 1) === '|' ) {
|
|
|
|
pos += pattern.length;
|
2016-10-10 15:01:05 +02:00
|
|
|
if ( pos === p.length || p.charAt(pos) === '|' ) { return; }
|
2016-01-22 00:30:06 +01:00
|
|
|
}
|
|
|
|
}
|
2016-10-10 15:01:05 +02:00
|
|
|
entry.pat = p + '|' + pattern;
|
2015-11-24 01:18:25 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
RedirectEngine.prototype.fromCompiledRule = function(line) {
|
2019-05-14 02:19:10 +02:00
|
|
|
const fields = line.split('\t');
|
|
|
|
if ( fields.length !== 5 ) { return; }
|
2015-11-24 01:18:25 +01:00
|
|
|
this.addRule(fields[0], fields[1], fields[2], fields[3], fields[4]);
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
RedirectEngine.prototype.compileRuleFromStaticFilter = function(line) {
|
2018-12-17 13:46:04 +01:00
|
|
|
const matches = this.reFilterParser.exec(line);
|
|
|
|
if ( matches === null || matches.length !== 4 ) { return; }
|
|
|
|
|
2019-05-14 02:19:10 +02:00
|
|
|
const des = matches[1] || '';
|
|
|
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/572
|
|
|
|
// Extract best possible hostname.
|
|
|
|
let deshn = des;
|
2019-05-14 15:29:45 +02:00
|
|
|
let pos = deshn.lastIndexOf('*');
|
2019-05-14 02:19:10 +02:00
|
|
|
if ( pos !== -1 ) {
|
|
|
|
deshn = deshn.slice(pos + 1);
|
2019-05-14 15:29:45 +02:00
|
|
|
pos = deshn.indexOf('.');
|
|
|
|
if ( pos !== -1 ) {
|
|
|
|
deshn = deshn.slice(pos + 1);
|
|
|
|
} else {
|
|
|
|
deshn = '';
|
2019-05-14 02:19:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const pattern =
|
|
|
|
des
|
2019-05-14 12:52:13 +02:00
|
|
|
.replace(/\*/g, '[\\w.%-]*')
|
2019-05-14 02:19:10 +02:00
|
|
|
.replace(/\./g, '\\.') +
|
|
|
|
matches[2]
|
|
|
|
.replace(/[.+?{}()|[\]\/\\]/g, '\\$&')
|
|
|
|
.replace(/\^/g, '[^\\w.%-]')
|
|
|
|
.replace(/\*/g, '.*?');
|
|
|
|
|
2018-12-17 13:46:04 +01:00
|
|
|
let type,
|
2016-10-26 14:59:15 +02:00
|
|
|
redirect = '',
|
2019-05-14 02:19:10 +02:00
|
|
|
srchns = [];
|
2018-12-17 13:46:04 +01:00
|
|
|
for ( const option of matches[3].split(',') ) {
|
2015-12-15 16:40:40 +01:00
|
|
|
if ( option.startsWith('redirect=') ) {
|
2015-11-24 01:18:25 +01:00
|
|
|
redirect = option.slice(9);
|
|
|
|
continue;
|
|
|
|
}
|
2015-12-15 16:40:40 +01:00
|
|
|
if ( option.startsWith('domain=') ) {
|
2019-05-14 02:19:10 +02:00
|
|
|
srchns = option.slice(7).split('|');
|
2015-11-24 01:18:25 +01:00
|
|
|
continue;
|
|
|
|
}
|
2019-05-14 15:29:45 +02:00
|
|
|
if ( (option === 'first-party' || option === '1p') && deshn !== '' ) {
|
2019-05-14 02:19:10 +02:00
|
|
|
srchns.push(µBlock.URI.domainFromHostname(deshn) || deshn);
|
2015-12-16 03:34:36 +01:00
|
|
|
continue;
|
|
|
|
}
|
2015-11-25 16:05:23 +01:00
|
|
|
// One and only one type must be specified.
|
2018-12-17 13:46:04 +01:00
|
|
|
if ( this.supportedTypes.has(option) ) {
|
|
|
|
if ( type !== undefined ) { return; }
|
|
|
|
type = this.supportedTypes.get(option);
|
2015-11-24 01:18:25 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-24 05:34:03 +01:00
|
|
|
// Need a resource token.
|
2018-12-17 13:46:04 +01:00
|
|
|
if ( redirect === '' ) { return; }
|
2015-11-24 05:34:03 +01:00
|
|
|
|
2015-11-25 16:05:23 +01:00
|
|
|
// Need one single type -- not negated.
|
2018-12-17 13:46:04 +01:00
|
|
|
if ( type === undefined ) { return; }
|
2015-11-24 01:18:25 +01:00
|
|
|
|
2019-05-14 02:19:10 +02:00
|
|
|
if ( deshn === '' ) {
|
|
|
|
deshn = '*';
|
2015-11-24 01:18:25 +01:00
|
|
|
}
|
|
|
|
|
2019-05-14 02:19:10 +02:00
|
|
|
if ( srchns.length === 0 ) {
|
|
|
|
srchns.push('*');
|
2015-11-24 01:18:25 +01:00
|
|
|
}
|
|
|
|
|
2018-12-17 13:46:04 +01:00
|
|
|
const out = [];
|
2019-05-14 02:19:10 +02:00
|
|
|
for ( const srchn of srchns ) {
|
|
|
|
if ( srchn === '' ) { continue; }
|
|
|
|
if ( srchn.startsWith('~') ) { continue; }
|
2019-07-06 18:36:28 +02:00
|
|
|
out.push(`${srchn}\t${deshn}\t${type}\t${pattern}\t${redirect}`);
|
2015-11-24 01:18:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-05-14 02:19:10 +02:00
|
|
|
RedirectEngine.prototype.reFilterParser = /^(?:\|\|([^\/:?#^]+)|\*)([^$]+)\$([^$]+)$/;
|
2015-11-24 01:18:25 +01:00
|
|
|
|
2018-12-17 13:46:04 +01:00
|
|
|
RedirectEngine.prototype.supportedTypes = new Map([
|
|
|
|
[ 'css', 'stylesheet' ],
|
|
|
|
[ 'font', 'font' ],
|
|
|
|
[ 'image', 'image' ],
|
|
|
|
[ 'media', 'media' ],
|
|
|
|
[ 'object', 'object' ],
|
|
|
|
[ 'script', 'script' ],
|
|
|
|
[ 'stylesheet', 'stylesheet' ],
|
|
|
|
[ 'frame', 'sub_frame' ],
|
|
|
|
[ 'subdocument', 'sub_frame' ],
|
|
|
|
[ 'xhr', 'xmlhttprequest' ],
|
|
|
|
[ 'xmlhttprequest', 'xmlhttprequest' ],
|
|
|
|
]);
|
2015-11-24 01:18:25 +01:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-02-14 19:33:55 +01:00
|
|
|
RedirectEngine.prototype.toSelfie = function(path) {
|
2016-10-10 15:01:05 +02:00
|
|
|
// Because rules may contains RegExp instances, we need to manually
|
|
|
|
// convert it to a serializable format. The serialized format must be
|
|
|
|
// suitable to be used as an argument to the Map() constructor.
|
2019-02-14 19:33:55 +01:00
|
|
|
const rules = [];
|
|
|
|
for ( const item of this.rules ) {
|
|
|
|
const rule = [ item[0], [] ];
|
|
|
|
const entries = item[1];
|
|
|
|
let i = entries.length;
|
2016-10-10 15:01:05 +02:00
|
|
|
while ( i-- ) {
|
2019-02-14 19:33:55 +01:00
|
|
|
const entry = entries[i];
|
2016-10-10 15:01:05 +02:00
|
|
|
rule[1].push({
|
|
|
|
tok: entry.tok,
|
|
|
|
pat: entry.pat instanceof RegExp ? entry.pat.source : entry.pat
|
|
|
|
});
|
2015-11-29 23:06:58 +01:00
|
|
|
}
|
2016-10-10 15:01:05 +02:00
|
|
|
rules.push(rule);
|
2015-11-29 23:06:58 +01:00
|
|
|
}
|
2019-02-14 19:33:55 +01:00
|
|
|
return µBlock.assets.put(
|
|
|
|
`${path}/main`,
|
|
|
|
JSON.stringify({
|
|
|
|
rules: rules,
|
|
|
|
ruleTypes: Array.from(this.ruleTypes),
|
|
|
|
ruleSources: Array.from(this.ruleSources),
|
|
|
|
ruleDestinations: Array.from(this.ruleDestinations)
|
|
|
|
})
|
|
|
|
);
|
2015-11-29 17:04:42 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-02-14 19:33:55 +01:00
|
|
|
RedirectEngine.prototype.fromSelfie = function(path) {
|
|
|
|
return µBlock.assets.get(`${path}/main`).then(details => {
|
|
|
|
let selfie;
|
|
|
|
try {
|
|
|
|
selfie = JSON.parse(details.content);
|
|
|
|
} catch (ex) {
|
|
|
|
}
|
|
|
|
if ( selfie instanceof Object === false ) { return false; }
|
|
|
|
this.rules = new Map(selfie.rules);
|
|
|
|
this.ruleTypes = new Set(selfie.ruleTypes);
|
|
|
|
this.ruleSources = new Set(selfie.ruleSources);
|
|
|
|
this.ruleDestinations = new Set(selfie.ruleDestinations);
|
|
|
|
this.modifyTime = Date.now();
|
|
|
|
return true;
|
|
|
|
});
|
2015-11-29 17:04:42 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-12-23 17:02:36 +01:00
|
|
|
RedirectEngine.prototype.resourceURIFromName = function(name, mime) {
|
2019-07-06 18:36:28 +02:00
|
|
|
const entry = this.resources.get(this.aliases.get(name) || name);
|
|
|
|
if (
|
|
|
|
(entry !== undefined) &&
|
|
|
|
(mime === undefined || entry.mime.startsWith(mime))
|
|
|
|
) {
|
2015-12-22 22:32:09 +01:00
|
|
|
return entry.toURL();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-12-23 17:02:36 +01:00
|
|
|
RedirectEngine.prototype.resourceContentFromName = function(name, mime) {
|
2019-07-06 18:36:28 +02:00
|
|
|
const entry = this.resources.get(this.aliases.get(name) || name);
|
|
|
|
if ( entry === undefined ) { return; }
|
2016-10-10 15:01:05 +02:00
|
|
|
if ( mime === undefined || entry.mime.startsWith(mime) ) {
|
2015-12-23 17:02:36 +01:00
|
|
|
return entry.toContent();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-11-24 01:18:25 +01:00
|
|
|
// TODO: combine same key-redirect pairs into a single regex.
|
|
|
|
|
2018-10-31 23:34:54 +01:00
|
|
|
// https://github.com/uBlockOrigin/uAssets/commit/deefe875551197d655f79cb540e62dfc17c95f42
|
|
|
|
// Consider 'none' a reserved keyword, to be used to disable redirection.
|
|
|
|
|
2019-07-06 18:36:28 +02:00
|
|
|
RedirectEngine.prototype.resourcesFromString = function(text) {
|
|
|
|
const lineIter = new µBlock.LineIterator(removeTopCommentBlock(text));
|
2019-07-03 15:40:12 +02:00
|
|
|
const reNonEmptyLine = /\S/;
|
2019-07-06 18:36:28 +02:00
|
|
|
let fields, encoded, details;
|
2015-11-23 13:52:50 +01:00
|
|
|
|
2017-01-18 19:17:47 +01:00
|
|
|
while ( lineIter.eot() === false ) {
|
2018-10-31 23:34:54 +01:00
|
|
|
let line = lineIter.next();
|
2017-01-18 19:17:47 +01:00
|
|
|
if ( line.startsWith('#') ) { continue; }
|
2019-07-06 18:36:28 +02:00
|
|
|
if ( line.startsWith('// ') ) { continue; }
|
2015-11-23 13:52:50 +01:00
|
|
|
|
2015-11-24 19:21:14 +01:00
|
|
|
if ( fields === undefined ) {
|
2019-07-08 14:56:36 +02:00
|
|
|
if ( line === '' ) { continue; }
|
|
|
|
// Modern parser
|
2019-07-06 18:36:28 +02:00
|
|
|
if ( line.startsWith('/// ') ) {
|
|
|
|
const name = line.slice(4).trim();
|
|
|
|
fields = [ name, mimeFromName(name) ];
|
2019-07-08 14:56:36 +02:00
|
|
|
continue;
|
2019-07-06 18:36:28 +02:00
|
|
|
}
|
2019-07-08 14:56:36 +02:00
|
|
|
// Legacy parser
|
|
|
|
const head = line.trim().split(/\s+/);
|
|
|
|
if ( head.length !== 2 ) { continue; }
|
|
|
|
if ( head[0] === 'none' ) { continue; }
|
|
|
|
let pos = head[1].indexOf(';');
|
|
|
|
if ( pos === -1 ) { pos = head[1].length; }
|
|
|
|
if ( validMimes.has(head[1].slice(0, pos)) === false ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
encoded = head[1].indexOf(';') !== -1;
|
|
|
|
fields = head;
|
2019-07-06 18:36:28 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( line.startsWith('/// ') ) {
|
|
|
|
if ( details === undefined ) {
|
|
|
|
details = {};
|
|
|
|
}
|
|
|
|
const [ prop, value ] = line.slice(4).trim().split(/\s+/);
|
|
|
|
if ( value !== undefined ) {
|
|
|
|
details[prop] = value;
|
|
|
|
}
|
2015-11-23 13:52:50 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-11-26 01:38:05 +01:00
|
|
|
if ( reNonEmptyLine.test(line) ) {
|
|
|
|
fields.push(encoded ? line.trim() : line);
|
2015-11-23 13:52:50 +01:00
|
|
|
continue;
|
|
|
|
}
|
2015-11-24 05:34:03 +01:00
|
|
|
|
2019-07-08 14:56:36 +02:00
|
|
|
const name = this.aliases.get(fields[0]) || fields[0];
|
2019-07-06 18:36:28 +02:00
|
|
|
const mime = fields[1];
|
|
|
|
const content = µBlock.orphanizeString(
|
|
|
|
fields.slice(2).join(encoded ? '' : '\n')
|
|
|
|
);
|
|
|
|
|
2015-11-24 19:21:14 +01:00
|
|
|
// No more data, add the resource.
|
2019-07-06 18:36:28 +02:00
|
|
|
this.resources.set(
|
|
|
|
name,
|
|
|
|
RedirectEntry.fromContent(mime, content)
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( details instanceof Object && details.alias ) {
|
|
|
|
this.aliases.set(details.alias, name);
|
2019-07-03 15:40:12 +02:00
|
|
|
}
|
2015-11-24 19:21:14 +01:00
|
|
|
|
|
|
|
fields = undefined;
|
2019-07-06 18:36:28 +02:00
|
|
|
details = undefined;
|
2015-11-24 19:21:14 +01:00
|
|
|
}
|
2015-11-24 05:34:03 +01:00
|
|
|
|
2015-11-24 19:21:14 +01:00
|
|
|
// Process pending resource data.
|
|
|
|
if ( fields !== undefined ) {
|
2019-07-06 18:36:28 +02:00
|
|
|
const name = fields[0];
|
|
|
|
const mime = fields[1];
|
|
|
|
const content = µBlock.orphanizeString(
|
|
|
|
fields.slice(2).join(encoded ? '' : '\n')
|
|
|
|
);
|
2018-10-31 23:34:54 +01:00
|
|
|
this.resources.set(
|
2019-07-06 18:36:28 +02:00
|
|
|
name,
|
|
|
|
RedirectEntry.fromContent(mime, content)
|
2018-10-31 23:34:54 +01:00
|
|
|
);
|
2019-07-06 18:36:28 +02:00
|
|
|
if ( details instanceof Object && details.alias ) {
|
|
|
|
this.aliases.set(details.alias, name);
|
|
|
|
}
|
2015-11-23 13:52:50 +01:00
|
|
|
}
|
2018-02-16 15:07:20 +01:00
|
|
|
|
2018-03-19 15:25:03 +01:00
|
|
|
this.modifyTime = Date.now();
|
2015-11-23 13:52:50 +01:00
|
|
|
};
|
|
|
|
|
2019-07-06 18:36:28 +02:00
|
|
|
const removeTopCommentBlock = function(text) {
|
|
|
|
return text.replace(/^\/\*[\S\s]+?\n\*\/\s*/, '');
|
|
|
|
};
|
|
|
|
|
2018-02-15 23:25:38 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-07-03 15:40:12 +02:00
|
|
|
RedirectEngine.prototype.loadBuiltinResources = function() {
|
|
|
|
this.resources = new Map();
|
2019-07-06 18:36:28 +02:00
|
|
|
this.aliases = new Map();
|
2019-07-03 15:40:12 +02:00
|
|
|
const fetches = [
|
2019-07-06 18:36:28 +02:00
|
|
|
µBlock.assets.fetchText('/assets/resources/scriptlets.js'),
|
2019-07-03 15:40:12 +02:00
|
|
|
];
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
|
|
// TODO: remove once usage of uBO 1.20.4 is widespread.
|
|
|
|
µBlock.assets.remove('ublock-resources');
|
|
|
|
|
|
|
|
for ( const [ name, details ] of redirectableResources ) {
|
2019-07-03 15:40:12 +02:00
|
|
|
if ( details.inject !== false ) {
|
|
|
|
fetches.push(
|
|
|
|
µBlock.assets.fetchText(
|
|
|
|
`/web_accessible_resources/${name}${vAPI.warSecret()}`
|
|
|
|
)
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const entry = RedirectEntry.fromSelfie({
|
|
|
|
mime: mimeFromName(name),
|
|
|
|
warURL: vAPI.getURL(`/web_accessible_resources/${name}`),
|
|
|
|
});
|
|
|
|
this.resources.set(name, entry);
|
|
|
|
if ( details.alias !== undefined ) {
|
2019-07-06 18:36:28 +02:00
|
|
|
this.aliases.set(details.alias, name);
|
2019-07-03 15:40:12 +02:00
|
|
|
}
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
|
2019-07-03 15:40:12 +02:00
|
|
|
return Promise.all(fetches).then(results => {
|
2019-07-06 18:36:28 +02:00
|
|
|
// Built-in redirectable resources
|
2019-07-03 15:40:12 +02:00
|
|
|
for ( let i = 1; i < results.length; i++ ) {
|
|
|
|
const result = results[i];
|
|
|
|
const match = /^\/web_accessible_resources\/([^?]+)/.exec(result.url);
|
|
|
|
if ( match === null ) { continue; }
|
|
|
|
const name = match[1];
|
2019-07-06 18:36:28 +02:00
|
|
|
const content = removeTopCommentBlock(result.content);
|
|
|
|
const details = redirectableResources.get(name);
|
2019-07-03 15:40:12 +02:00
|
|
|
const entry = RedirectEntry.fromSelfie({
|
|
|
|
mime: mimeFromName(name),
|
|
|
|
data: content,
|
2019-07-06 18:36:28 +02:00
|
|
|
warURL: vAPI.getURL(`/web_accessible_resources/${name}`),
|
2019-07-03 15:40:12 +02:00
|
|
|
});
|
|
|
|
this.resources.set(name, entry);
|
|
|
|
if ( details.alias !== undefined ) {
|
2019-07-06 18:36:28 +02:00
|
|
|
this.aliases.set(details.alias, name);
|
2019-07-03 15:40:12 +02:00
|
|
|
}
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
// Additional resources
|
2019-07-03 15:40:12 +02:00
|
|
|
const content = results[0].content;
|
|
|
|
if ( typeof content === 'string' && content.length !== 0 ) {
|
2019-07-06 18:36:28 +02:00
|
|
|
this.resourcesFromString(content);
|
2019-07-03 15:40:12 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-07-09 17:04:12 +02:00
|
|
|
const resourcesSelfieVersion = 5;
|
2018-02-15 23:25:38 +01:00
|
|
|
|
|
|
|
RedirectEngine.prototype.selfieFromResources = function() {
|
2019-02-14 19:33:55 +01:00
|
|
|
µBlock.assets.put(
|
|
|
|
'compiled/redirectEngine/resources',
|
|
|
|
JSON.stringify({
|
|
|
|
version: resourcesSelfieVersion,
|
2019-07-06 18:36:28 +02:00
|
|
|
aliases: Array.from(this.aliases),
|
|
|
|
resources: Array.from(this.resources),
|
2019-02-14 19:33:55 +01:00
|
|
|
})
|
|
|
|
);
|
2018-02-15 23:25:38 +01:00
|
|
|
};
|
|
|
|
|
2019-02-14 19:33:55 +01:00
|
|
|
RedirectEngine.prototype.resourcesFromSelfie = function() {
|
|
|
|
return µBlock.assets.get(
|
|
|
|
'compiled/redirectEngine/resources'
|
|
|
|
).then(details => {
|
|
|
|
let selfie;
|
|
|
|
try {
|
|
|
|
selfie = JSON.parse(details.content);
|
|
|
|
} catch(ex) {
|
2018-02-15 23:25:38 +01:00
|
|
|
}
|
|
|
|
if (
|
|
|
|
selfie instanceof Object === false ||
|
|
|
|
selfie.version !== resourcesSelfieVersion ||
|
|
|
|
Array.isArray(selfie.resources) === false
|
|
|
|
) {
|
2019-02-14 19:33:55 +01:00
|
|
|
return false;
|
2018-02-15 23:25:38 +01:00
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
this.aliases = new Map(selfie.aliases);
|
2018-08-22 14:13:10 +02:00
|
|
|
this.resources = new Map();
|
2019-02-14 19:33:55 +01:00
|
|
|
for ( const [ token, entry ] of selfie.resources ) {
|
|
|
|
this.resources.set(token, RedirectEntry.fromSelfie(entry));
|
2018-02-15 23:25:38 +01:00
|
|
|
}
|
2019-02-14 19:33:55 +01:00
|
|
|
return true;
|
2018-08-22 14:13:10 +02:00
|
|
|
});
|
2018-02-15 23:25:38 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
RedirectEngine.prototype.invalidateResourcesSelfie = function() {
|
2019-02-14 19:33:55 +01:00
|
|
|
µBlock.assets.remove('compiled/redirectEngine/resources');
|
|
|
|
|
|
|
|
// TODO: obsolete, remove eventually
|
2018-08-11 16:39:43 +02:00
|
|
|
µBlock.cacheStorage.remove('resourcesSelfie');
|
2018-02-15 23:25:38 +01:00
|
|
|
};
|
|
|
|
|
2015-11-26 23:56:30 +01:00
|
|
|
/******************************************************************************/
|
2015-11-23 13:52:50 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
return new RedirectEngine();
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
})();
|