diff --git a/assets/checksums.txt b/assets/checksums.txt
index 3b09c66df..4889a717e 100644
--- a/assets/checksums.txt
+++ b/assets/checksums.txt
@@ -4,6 +4,7 @@ b2dbf435507aa0262b289c67cbef2142 assets/ublock/filters.txt
146704ad1c0393e342afdb416762c183 assets/ublock/badware.txt
c9c5cc56bec563bc1885847f925b9be2 assets/ublock/mirror-candidates.txt
f9455a47b5024cc08ff3675ce79b58a9 assets/ublock/filter-lists.json
+a37de4c1166ed23c273d44611142b5e4 assets/ublock/redirect-rules.txt
94c0a3eab74c42783855f07b22a429cf assets/thirdparties/home.fredfiber.no/langsholt/adblock.txt
a82cb5ba5caf035ce00e97de81db5de7 assets/thirdparties/www.zoso.ro/pages/rolist.txt
72373316d0e7ad22604d307c2d93e7cc assets/thirdparties/adblock.gardar.net/is.abp.txt
diff --git a/assets/ublock/redirect-rules.txt b/assets/ublock/redirect-rules.txt
new file mode 100644
index 000000000..960f5eab9
--- /dev/null
+++ b/assets/ublock/redirect-rules.txt
@@ -0,0 +1,22 @@
+redirects:
+
+ hd-main.js application/javascript
+ var L = (function(){
+ var l = {};
+ var fn = function(){};
+ var props = ["pf","ed","Qe","fd","xh","Uc","ef","zd","Ad","Qc","Ri","Wc","Vc","Xc","Wg","rd","qd","sd","Pe","Id","Hd","Jd","fg","Fd","Ed","Gd","ek","Cd","Bd","Dd","Nj","Sc","Rc","Tc","wg","xd","wd","yd","fh","ld","md","nd","Re","cd","Pc","ke","Yc","Xg","jd","kd","oh","ad","bd","mi","gd","hd","ae","dd","fk","ij","ud","td","vd","ig","od","pd","Yd","$j","Oc","bf"];
+ for ( var i = 0; i < props.length; i++ ) {
+ l[props[i]] = fn;
+ }
+ return l;
+ })();
+
+rules:
+
+ # Prevent difficult to block video ads. Examples:
+ # http://www.chip.de/news/Halbvoll-in-2-Minuten-Huawei-zeigt-neue-Superakkus_85752247.html
+ # Block filters to use:
+ # ||s3.amazonaws.com/homad-global-configs.schneevonmorgen.com/hd-main.js$script,domain=cdnapi.kaltura.com,important
+ # ||hgc.svonm.com/hd-main.js$script,domain=cdnapi.kaltura.com,important
+ cdnapi.kaltura.com s3.amazonaws.com script /\/hd-main.js$/ hd-main.js
+ cdnapi.kaltura.com hgc.svonm.com script /\/hd-main.js$/ hd-main.js
diff --git a/src/background.html b/src/background.html
index 43863d28d..1a6b5c9ae 100644
--- a/src/background.html
+++ b/src/background.html
@@ -15,6 +15,7 @@
+
diff --git a/src/js/redirect-engine.js b/src/js/redirect-engine.js
new file mode 100644
index 000000000..d6d4a6127
--- /dev/null
+++ b/src/js/redirect-engine.js
@@ -0,0 +1,192 @@
+/*******************************************************************************
+
+ uBlock Origin - a browser extension to block requests.
+ Copyright (C) 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
+ 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
+*/
+
+/* global µBlock */
+
+/******************************************************************************/
+
+µBlock.redirectEngine = (function(){
+
+'use strict';
+
+/******************************************************************************/
+
+var toBroaderHostname = function(hostname) {
+ if ( hostname === '*' ) {
+ return '';
+ }
+ var pos = hostname.indexOf('.');
+ if ( pos === -1 ) {
+ return '*';
+ }
+ return hostname.slice(pos + 1);
+};
+
+/******************************************************************************/
+
+var RedirectEngine = function() {
+ this.reset();
+};
+
+/******************************************************************************/
+
+RedirectEngine.prototype.reset = function() {
+ this.redirects = Object.create(null);
+ this.rules = Object.create(null);
+};
+
+/******************************************************************************/
+
+RedirectEngine.prototype.lookup = function(context) {
+ var typeEntry = this.rules[context.requestType];
+ if ( typeEntry === undefined ) {
+ return;
+ }
+ var src, des = context.requestHostname,
+ srcHostname = context.pageHostname,
+ reqURL = context.requestURL,
+ desEntry, entries, i, entry;
+ for (;;) {
+ desEntry = typeEntry[des];
+ if ( desEntry !== undefined ) {
+ src = srcHostname;
+ for (;;) {
+ entries = desEntry[src];
+ if ( entries !== undefined ) {
+ i = entries.length;
+ while ( i-- ) {
+ entry = entries[i];
+ if ( entry.c.test(reqURL) ) {
+ return this.redirects[entry.r];
+ }
+ }
+ }
+ }
+ src = toBroaderHostname(src);
+ if ( src === '' ) {
+ break;
+ }
+ }
+ des = toBroaderHostname(des);
+ if ( des === '' ) {
+ break;
+ }
+ }
+};
+
+/******************************************************************************/
+
+// TODO: combine same key-redirect pairs into a single regex.
+
+RedirectEngine.prototype.fromString = function(text) {
+ var textEnd = text.length;
+ var lineBeg = 0, lineEnd;
+ var mode, modeData, line, fields, encoded, data;
+ var reSource, typeEntry, desEntry, ruleEntries;
+
+ this.reset();
+
+ while ( lineBeg < textEnd ) {
+ lineEnd = text.indexOf('\n', lineBeg);
+ if ( lineEnd < 0 ) {
+ lineEnd = text.indexOf('\r', lineBeg);
+ if ( lineEnd < 0 ) {
+ lineEnd = textEnd;
+ }
+ }
+ line = text.slice(lineBeg, lineEnd).trim();
+ lineBeg = lineEnd + 1;
+
+ if ( line.charAt(0) === '#' ) {
+ continue;
+ }
+
+ if ( line.slice(-1) === ':' ) {
+ mode = line.slice(0, -1);
+ continue;
+ }
+
+ if ( mode === 'redirects' ) {
+ fields = line.split(/\s+/);
+ if ( fields.length !== 2 ) {
+ continue;
+ }
+ mode = 'redirects/redirect';
+ modeData = fields;
+ continue;
+ }
+
+ if ( mode === 'redirects/redirect' ) {
+ if ( line !== '' ) {
+ modeData.push(line);
+ continue;
+ }
+ encoded = modeData[1].indexOf(';') !== -1;
+ data = modeData.slice(2).join(encoded ? '' : '\n');
+ this.redirects[modeData[0]] =
+ 'data:' +
+ modeData[1] +
+ (encoded ? '' : ';base64') +
+ ',' +
+ (encoded ? data : btoa(data));
+ mode = 'redirects';
+ continue;
+ }
+
+ if ( mode === 'rules' ) {
+ fields = line.split(/\s+/);
+ if ( fields.length !== 5 ) {
+ continue;
+ }
+ reSource = fields[3];
+ if ( reSource.charAt(0) !== '/' || reSource.slice(-1) !== '/' ) {
+ reSource = reSource.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ } else {
+ reSource = reSource.slice(1, -1);
+ }
+ typeEntry = this.rules[fields[2]];
+ if ( typeEntry === undefined ) {
+ typeEntry = this.rules[fields[2]] = Object.create(null);
+ }
+ desEntry = typeEntry[fields[1]];
+ if ( desEntry === undefined ) {
+ desEntry = typeEntry[fields[1]] = Object.create(null);
+ }
+ ruleEntries = desEntry[fields[0]];
+ if ( ruleEntries === undefined ) {
+ ruleEntries = desEntry[fields[0]] = [];
+ }
+ ruleEntries.push({
+ c: new RegExp(reSource),
+ r: fields[4]
+ });
+ continue;
+ }
+ }
+};
+
+/******************************************************************************/
+
+return new RedirectEngine();
+
+/******************************************************************************/
+
+})();
diff --git a/src/js/start.js b/src/js/start.js
index 782cb1ff1..517131d8b 100644
--- a/src/js/start.js
+++ b/src/js/start.js
@@ -211,6 +211,7 @@ var onFirstFetchReady = function(fetched) {
fromFetch(µb.restoreBackupSettings, fetched);
onNetWhitelistReady(fetched.netWhitelist);
onVersionReady(fetched.version);
+ µb.loadRedirectRules();
// If we have a selfie, skip loading PSL, filters
if ( onSelfieReady(fetched.selfie) ) {
diff --git a/src/js/storage.js b/src/js/storage.js
index e14986dea..902e06a05 100644
--- a/src/js/storage.js
+++ b/src/js/storage.js
@@ -646,6 +646,26 @@
/******************************************************************************/
+// TODO: toSelfie/fromSelfie.
+
+µBlock.loadRedirectRules = function(callback) {
+ var µb = this;
+
+ if ( typeof callback !== 'function' ) {
+ callback = this.noopFunc;
+ }
+ var onRulesLoaded = function(details) {
+ if ( details.content !== '' ) {
+ µb.redirectEngine.fromString(details.content);
+ }
+ callback();
+ };
+
+ this.assets.get('assets/ublock/redirect-rules.txt', onRulesLoaded);
+};
+
+/******************************************************************************/
+
µBlock.loadPublicSuffixList = function(callback) {
var µb = this;
var path = µb.pslPath;
diff --git a/src/js/traffic.js b/src/js/traffic.js
index 66e654803..348d65cbf 100644
--- a/src/js/traffic.js
+++ b/src/js/traffic.js
@@ -131,9 +131,12 @@ var onBeforeRequest = function(details) {
µb.updateBadgeAsync(tabId);
}
- // https://github.com/chrisaljoudi/uBlock/issues/18
- // Do not use redirection, we need to block outright to be sure the request
- // will not be made. There can be no such guarantee with redirection.
+ // https://github.com/gorhill/uBlock/issues/949
+ // Redirect blocked request?
+ var url = µb.redirectEngine.lookup(requestContext);
+ if ( url !== undefined ) {
+ return { redirectUrl: url };
+ }
return { cancel: true };
};