2015-06-11 18:12:23 +02:00
|
|
|
/*******************************************************************************
|
|
|
|
|
2017-01-18 19:17:47 +01:00
|
|
|
uBlock Origin - a browser extension to block requests.
|
2018-07-21 18:22:53 +02:00
|
|
|
Copyright (C) 2015-present Raymond Hill
|
2015-06-11 18:12:23 +02: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
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* global onmessage, postMessage */
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-01-14 20:57:31 +01:00
|
|
|
const reBlockStart = /^#block-start-(\d+)\n/gm;
|
|
|
|
let listEntries = Object.create(null);
|
2017-12-28 19:49:02 +01:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-01-14 20:57:31 +01:00
|
|
|
const extractBlocks = function(content, begId, endId) {
|
2017-12-28 19:49:02 +01:00
|
|
|
reBlockStart.lastIndex = 0;
|
2019-01-14 20:57:31 +01:00
|
|
|
const out = [];
|
|
|
|
let match = reBlockStart.exec(content);
|
2017-12-28 19:49:02 +01:00
|
|
|
while ( match !== null ) {
|
2019-01-14 20:57:31 +01:00
|
|
|
const beg = match.index + match[0].length;
|
|
|
|
const blockId = parseInt(match[1], 10);
|
2017-12-28 19:49:02 +01:00
|
|
|
if ( blockId >= begId && blockId < endId ) {
|
2019-09-30 17:41:43 +02:00
|
|
|
const end = content.indexOf('#block-end-' + match[1], beg);
|
2017-12-28 19:49:02 +01:00
|
|
|
out.push(content.slice(beg, end));
|
|
|
|
reBlockStart.lastIndex = end;
|
|
|
|
}
|
|
|
|
match = reBlockStart.exec(content);
|
|
|
|
}
|
|
|
|
return out.join('\n');
|
|
|
|
};
|
2015-06-11 18:12:23 +02:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-09-30 14:53:02 +02:00
|
|
|
// https://github.com/MajkiIT/polish-ads-filter/issues/14768#issuecomment-536006312
|
|
|
|
// Avoid reporting badfilter-ed filters.
|
|
|
|
|
2019-01-14 20:57:31 +01:00
|
|
|
const fromNetFilter = function(details) {
|
|
|
|
const lists = [];
|
|
|
|
const compiledFilter = details.compiledFilter;
|
2017-12-28 19:49:02 +01:00
|
|
|
|
2019-01-14 20:57:31 +01:00
|
|
|
for ( const assetKey in listEntries ) {
|
|
|
|
const entry = listEntries[assetKey];
|
2017-05-25 23:46:59 +02:00
|
|
|
if ( entry === undefined ) { continue; }
|
2019-09-30 17:41:43 +02:00
|
|
|
const content = extractBlocks(entry.content, 0, 1);
|
2018-07-21 18:22:53 +02:00
|
|
|
let pos = 0;
|
2016-07-05 01:42:34 +02:00
|
|
|
for (;;) {
|
|
|
|
pos = content.indexOf(compiledFilter, pos);
|
2017-05-12 16:35:11 +02:00
|
|
|
if ( pos === -1 ) { break; }
|
2016-07-05 01:42:34 +02:00
|
|
|
// We need an exact match.
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1392
|
|
|
|
// https://github.com/gorhill/uBlock/issues/835
|
2019-01-14 20:57:31 +01:00
|
|
|
const notFound = pos !== 0 && content.charCodeAt(pos - 1) !== 0x0A;
|
2017-05-25 23:46:59 +02:00
|
|
|
pos += compiledFilter.length;
|
|
|
|
if (
|
|
|
|
notFound ||
|
|
|
|
pos !== content.length && content.charCodeAt(pos) !== 0x0A
|
|
|
|
) {
|
|
|
|
continue;
|
2016-07-05 01:42:34 +02:00
|
|
|
}
|
2017-05-25 23:46:59 +02:00
|
|
|
lists.push({
|
2018-07-21 18:22:53 +02:00
|
|
|
assetKey: assetKey,
|
2017-05-25 23:46:59 +02:00
|
|
|
title: entry.title,
|
|
|
|
supportURL: entry.supportURL
|
|
|
|
});
|
|
|
|
break;
|
2015-10-16 17:42:45 +02:00
|
|
|
}
|
2015-06-11 18:12:23 +02:00
|
|
|
}
|
|
|
|
|
2019-01-14 20:57:31 +01:00
|
|
|
const response = {};
|
2015-06-13 17:21:55 +02:00
|
|
|
response[details.rawFilter] = lists;
|
|
|
|
|
2015-06-11 18:12:23 +02:00
|
|
|
postMessage({
|
|
|
|
id: details.id,
|
2015-06-13 17:21:55 +02:00
|
|
|
response: response
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// Looking up filter lists from a cosmetic filter is a bit more complicated
|
|
|
|
// than with network filters:
|
|
|
|
//
|
|
|
|
// The filter is its raw representation, not its compiled version. This is
|
|
|
|
// because the cosmetic filtering engine can't translate a live cosmetic
|
|
|
|
// filter into its compiled version. Reason is I do not want to burden
|
|
|
|
// cosmetic filtering with the resource overhead of being able to re-compile
|
|
|
|
// live cosmetic filters. I want the cosmetic filtering code to be left
|
|
|
|
// completely unaffected by reverse lookup requirements.
|
|
|
|
//
|
|
|
|
// Mainly, given a CSS selector and a hostname as context, we will derive
|
|
|
|
// various versions of compiled filters and see if there are matches. This way
|
|
|
|
// the whole CPU cost is incurred by the reverse lookup code -- in a worker
|
2015-06-13 19:32:14 +02:00
|
|
|
// thread, and the cosmetic filtering engine incurs no cost at all.
|
2015-06-13 17:21:55 +02:00
|
|
|
//
|
|
|
|
// For this though, the reverse lookup code here needs some knowledge of
|
|
|
|
// the inners of the cosmetic filtering engine.
|
|
|
|
// FilterContainer.fromCompiledContent() is our reference code to create
|
|
|
|
// the various compiled versions.
|
|
|
|
|
2019-01-14 20:57:31 +01:00
|
|
|
const fromCosmeticFilter = function(details) {
|
|
|
|
const match = /^#@?#\^?/.exec(details.rawFilter);
|
|
|
|
const prefix = match[0];
|
|
|
|
const exception = prefix.charAt(1) === '@';
|
|
|
|
const selector = details.rawFilter.slice(prefix.length);
|
2019-05-21 00:29:28 +02:00
|
|
|
const isHtmlFilter = prefix.endsWith('^');
|
2017-10-21 19:43:46 +02:00
|
|
|
|
2017-10-23 15:01:00 +02:00
|
|
|
// The longer the needle, the lower the number of false positives.
|
2019-01-14 20:57:31 +01:00
|
|
|
const needle = selector.match(/\w+/g).reduce(function(a, b) {
|
2018-03-01 19:11:17 +01:00
|
|
|
return a.length > b.length ? a : b;
|
2017-10-23 15:01:00 +02:00
|
|
|
});
|
2017-05-25 23:46:59 +02:00
|
|
|
|
2019-01-14 20:57:31 +01:00
|
|
|
const reHostname = new RegExp(
|
2017-05-25 23:46:59 +02:00
|
|
|
'^' +
|
|
|
|
details.hostname.split('.').reduce(
|
|
|
|
function(acc, item) {
|
|
|
|
return acc === ''
|
|
|
|
? item
|
|
|
|
: '(' + acc + '\\.)?' + item;
|
|
|
|
},
|
|
|
|
''
|
|
|
|
) +
|
|
|
|
'$'
|
|
|
|
);
|
|
|
|
|
2018-09-06 18:51:50 +02:00
|
|
|
let reEntity,
|
2017-05-25 23:46:59 +02:00
|
|
|
domain = details.domain,
|
|
|
|
pos = domain.indexOf('.');
|
2015-06-13 17:21:55 +02:00
|
|
|
if ( pos !== -1 ) {
|
2017-05-25 23:46:59 +02:00
|
|
|
reEntity = new RegExp(
|
|
|
|
'^' +
|
|
|
|
domain.slice(0, pos).split('.').reduce(
|
|
|
|
function(acc, item) {
|
|
|
|
return acc === ''
|
|
|
|
? item
|
|
|
|
: '(' + acc + '\\.)?' + item;
|
|
|
|
},
|
|
|
|
''
|
|
|
|
) +
|
|
|
|
'\\.\\*$'
|
2015-06-13 17:21:55 +02:00
|
|
|
);
|
|
|
|
}
|
2017-05-25 23:46:59 +02:00
|
|
|
|
2019-01-14 20:57:31 +01:00
|
|
|
const hostnameMatches = hn => {
|
2018-09-06 18:51:50 +02:00
|
|
|
return hn === '' ||
|
|
|
|
reHostname.test(hn) ||
|
|
|
|
reEntity !== undefined && reEntity.test(hn);
|
|
|
|
};
|
2015-06-13 17:21:55 +02:00
|
|
|
|
2019-01-14 20:57:31 +01:00
|
|
|
const response = Object.create(null);
|
2018-09-06 18:51:50 +02:00
|
|
|
|
2019-01-14 20:57:31 +01:00
|
|
|
for ( const assetKey in listEntries ) {
|
|
|
|
const entry = listEntries[assetKey];
|
2017-05-25 23:46:59 +02:00
|
|
|
if ( entry === undefined ) { continue; }
|
2018-09-06 18:51:50 +02:00
|
|
|
let content = extractBlocks(entry.content, 1000, 2000),
|
|
|
|
isProcedural,
|
|
|
|
found;
|
|
|
|
let pos = 0;
|
2018-03-01 19:11:17 +01:00
|
|
|
while ( (pos = content.indexOf(needle, pos)) !== -1 ) {
|
2018-09-06 18:51:50 +02:00
|
|
|
let beg = content.lastIndexOf('\n', pos);
|
2017-10-23 15:01:00 +02:00
|
|
|
if ( beg === -1 ) { beg = 0; }
|
2018-09-06 18:51:50 +02:00
|
|
|
let end = content.indexOf('\n', pos);
|
2017-10-23 15:01:00 +02:00
|
|
|
if ( end === -1 ) { end = content.length; }
|
2018-03-01 19:11:17 +01:00
|
|
|
pos = end;
|
2019-09-21 17:30:38 +02:00
|
|
|
const fargs = JSON.parse(content.slice(beg, end));
|
|
|
|
const filterType = fargs[0];
|
2018-07-22 16:47:02 +02:00
|
|
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/2763
|
2019-09-21 17:30:38 +02:00
|
|
|
if ( filterType >= 0 && filterType <= 5 && details.ignoreGeneric ) {
|
2018-07-22 16:47:02 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-05-21 00:29:28 +02:00
|
|
|
// Do not confuse cosmetic filters with HTML ones.
|
2019-09-21 17:30:38 +02:00
|
|
|
if ( (filterType === 64) !== isHtmlFilter ) { continue; }
|
2019-05-21 00:29:28 +02:00
|
|
|
|
2019-09-21 17:30:38 +02:00
|
|
|
switch ( filterType ) {
|
2018-09-06 18:51:50 +02:00
|
|
|
// Lowly generic cosmetic filters
|
|
|
|
case 0: // simple id-based
|
2017-10-23 15:01:00 +02:00
|
|
|
if (
|
2019-05-16 19:44:49 +02:00
|
|
|
exception === false &&
|
2017-10-23 15:01:00 +02:00
|
|
|
fargs[1] === selector.slice(1) &&
|
|
|
|
selector.charAt(0) === '#'
|
|
|
|
) {
|
|
|
|
found = prefix + selector;
|
|
|
|
}
|
|
|
|
break;
|
2018-09-06 18:51:50 +02:00
|
|
|
case 2: // simple class-based
|
2017-10-23 15:01:00 +02:00
|
|
|
if (
|
2019-05-16 19:44:49 +02:00
|
|
|
exception === false &&
|
2017-10-23 15:01:00 +02:00
|
|
|
fargs[1] === selector.slice(1) &&
|
|
|
|
selector.charAt(0) === '.'
|
|
|
|
) {
|
|
|
|
found = prefix + selector;
|
|
|
|
}
|
|
|
|
break;
|
2018-09-06 18:51:50 +02:00
|
|
|
case 1: // complex id-based
|
|
|
|
case 3: // complex class-based
|
2019-05-16 19:44:49 +02:00
|
|
|
if ( exception === false && fargs[2] === selector ) {
|
2017-10-23 15:01:00 +02:00
|
|
|
found = prefix + selector;
|
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
break;
|
2018-09-06 18:51:50 +02:00
|
|
|
// Highly generic cosmetic filters
|
|
|
|
case 4: // simple highly generic
|
|
|
|
case 5: // complex highly generic
|
|
|
|
case 7: // generic exception
|
2017-10-23 15:01:00 +02:00
|
|
|
if ( fargs[1] === selector ) {
|
2017-10-22 18:48:13 +02:00
|
|
|
found = prefix + selector;
|
2017-05-25 23:46:59 +02:00
|
|
|
}
|
|
|
|
break;
|
2018-09-06 18:51:50 +02:00
|
|
|
// Specific cosmetic filtering
|
2017-05-25 23:46:59 +02:00
|
|
|
case 8:
|
2019-05-14 14:52:34 +02:00
|
|
|
// HTML filtering
|
|
|
|
case 64:
|
|
|
|
if ( exception !== ((fargs[2] & 0b01) !== 0) ) { break; }
|
|
|
|
isProcedural = (fargs[2] & 0b10) !== 0;
|
2017-10-23 15:01:00 +02:00
|
|
|
if (
|
2017-12-28 19:49:02 +01:00
|
|
|
isProcedural === false && fargs[3] !== selector ||
|
|
|
|
isProcedural && JSON.parse(fargs[3]).raw !== selector
|
2017-10-23 15:01:00 +02:00
|
|
|
) {
|
|
|
|
break;
|
|
|
|
}
|
2019-09-21 17:30:38 +02:00
|
|
|
if ( hostnameMatches(fargs[1]) === false ) { break; }
|
|
|
|
// https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/
|
|
|
|
// Ignore match if specific cosmetic filters are disabled
|
|
|
|
if (
|
|
|
|
filterType === 8 &&
|
|
|
|
exception === false &&
|
|
|
|
details.ignoreSpecific
|
|
|
|
) {
|
|
|
|
break;
|
2017-05-25 23:46:59 +02:00
|
|
|
}
|
2019-09-21 17:30:38 +02:00
|
|
|
found = fargs[1] + prefix + selector;
|
2017-05-25 23:46:59 +02:00
|
|
|
break;
|
2018-09-06 18:51:50 +02:00
|
|
|
// Scriptlet injection
|
2018-05-31 16:41:03 +02:00
|
|
|
case 32:
|
2019-05-14 14:52:34 +02:00
|
|
|
if ( exception !== ((fargs[2] & 1) !== 0) ) { break; }
|
2018-05-31 16:41:03 +02:00
|
|
|
if ( fargs[3] !== selector ) { break; }
|
2019-05-14 14:52:34 +02:00
|
|
|
if ( hostnameMatches(fargs[1]) ) {
|
|
|
|
found = fargs[1] + prefix + selector;
|
2018-05-31 16:41:03 +02:00
|
|
|
}
|
|
|
|
break;
|
2015-06-13 17:21:55 +02:00
|
|
|
}
|
2017-05-25 23:46:59 +02:00
|
|
|
if ( found !== undefined ) {
|
|
|
|
if ( response[found] === undefined ) {
|
|
|
|
response[found] = [];
|
|
|
|
}
|
|
|
|
response[found].push({
|
2018-07-22 14:14:02 +02:00
|
|
|
assetKey: assetKey,
|
2017-05-25 23:46:59 +02:00
|
|
|
title: entry.title,
|
|
|
|
supportURL: entry.supportURL
|
|
|
|
});
|
|
|
|
break;
|
2015-06-13 17:21:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
postMessage({
|
|
|
|
id: details.id,
|
|
|
|
response: response
|
2015-06-11 18:12:23 +02:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2017-01-18 19:17:47 +01:00
|
|
|
onmessage = function(e) { // jshint ignore:line
|
2019-01-14 20:57:31 +01:00
|
|
|
const msg = e.data;
|
2015-06-11 18:12:23 +02:00
|
|
|
|
|
|
|
switch ( msg.what ) {
|
|
|
|
case 'resetLists':
|
|
|
|
listEntries = Object.create(null);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'setList':
|
2017-01-18 19:17:47 +01:00
|
|
|
listEntries[msg.details.assetKey] = msg.details;
|
2015-06-11 18:12:23 +02:00
|
|
|
break;
|
|
|
|
|
2015-06-13 17:21:55 +02:00
|
|
|
case 'fromNetFilter':
|
|
|
|
fromNetFilter(msg);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'fromCosmeticFilter':
|
|
|
|
fromCosmeticFilter(msg);
|
2015-06-11 18:12:23 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|