[mv3] Ensure no generic cosmetic filters end up in specific realm

Related issue:
https://github.com/uBlockOrigin/uBOL-home/issues/254

Cosmetic filters with only negated hostnames would end up being
classified as specific, while in reality the filter is generic, with
specific exceptions.

This commit fixes the improper classification. Enforcing specific
exception filters in the generic realm is still an issue to
address.
This commit is contained in:
Raymond Hill 2024-12-08 08:37:29 -05:00
parent ec5a1b3ab6
commit 56ba93700c
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
2 changed files with 55 additions and 55 deletions

View file

@ -83,11 +83,6 @@ const uidint32 = (s) => {
return parseInt(h,16) & 0x7FFFFFFF;
};
const hnSort = (a, b) =>
a.split('.').reverse().join('.').localeCompare(
b.split('.').reverse().join('.')
);
/******************************************************************************/
const stdOutput = [];
@ -681,14 +676,10 @@ function groupHostnamesBySelectors(arrayin) {
const out = Array.from(contentMap).map(a => [
a[0], {
a: a[1].a,
y: a[1].y ? Array.from(a[1].y).sort(hnSort) : '*',
y: a[1].y ? Array.from(a[1].y) : undefined,
n: a[1].n ? Array.from(a[1].n) : undefined,
}
]).sort((a, b) => {
const ha = Array.isArray(a[1].y) ? a[1].y[0] : '*';
const hb = Array.isArray(b[1].y) ? b[1].y[0] : '*';
return hnSort(ha, hb);
});
]);
return out;
}

View file

@ -90,6 +90,39 @@ const keyFromSelector = selector => {
/******************************************************************************/
function addGenericCosmeticFilter(context, selector, isException) {
if ( selector === undefined ) { return; }
if ( selector.length <= 1 ) { return; }
if ( isException ) {
if ( context.genericCosmeticExceptions === undefined ) {
context.genericCosmeticExceptions = new Set();
}
context.genericCosmeticExceptions.add(selector);
return;
}
if ( selector.charCodeAt(0) === 0x7B /* '{' */ ) { return; }
const key = keyFromSelector(selector);
if ( key === undefined ) {
if ( context.genericHighCosmeticFilters === undefined ) {
context.genericHighCosmeticFilters = new Set();
}
context.genericHighCosmeticFilters.add(selector);
return;
}
const type = key.charCodeAt(0);
const hash = hashFromStr(type, key.slice(1));
if ( context.genericCosmeticFilters === undefined ) {
context.genericCosmeticFilters = new Map();
}
let bucket = context.genericCosmeticFilters.get(hash);
if ( bucket === undefined ) {
context.genericCosmeticFilters.set(hash, bucket = []);
}
bucket.push(selector);
}
/******************************************************************************/
function addExtendedToDNR(context, parser) {
if ( parser.isExtendedFilter() === false ) { return false; }
@ -195,35 +228,8 @@ function addExtendedToDNR(context, parser) {
// Generic cosmetic filtering
if ( parser.hasOptions() === false ) {
const { compiled } = parser.result;
if ( compiled === undefined ) { return; }
if ( compiled.length <= 1 ) { return; }
if ( parser.isException() ) {
if ( context.genericCosmeticExceptions === undefined ) {
context.genericCosmeticExceptions = new Set();
}
context.genericCosmeticExceptions.add(compiled);
return;
}
if ( compiled.charCodeAt(0) === 0x7B /* '{' */ ) { return; }
const key = keyFromSelector(compiled);
if ( key === undefined ) {
if ( context.genericHighCosmeticFilters === undefined ) {
context.genericHighCosmeticFilters = new Set();
}
context.genericHighCosmeticFilters.add(compiled);
return;
}
const type = key.charCodeAt(0);
const hash = hashFromStr(type, key.slice(1));
if ( context.genericCosmeticFilters === undefined ) {
context.genericCosmeticFilters = new Map();
}
let bucket = context.genericCosmeticFilters.get(hash);
if ( bucket === undefined ) {
context.genericCosmeticFilters.set(hash, bucket = []);
}
bucket.push(compiled);
const { compiled, exception } = parser.result;
addGenericCosmeticFilter(context, compiled, exception);
return;
}
@ -234,26 +240,22 @@ function addExtendedToDNR(context, parser) {
if ( context.specificCosmeticFilters === undefined ) {
context.specificCosmeticFilters = new Map();
}
const { compiled, exception, raw } = parser.result;
if ( compiled === undefined ) {
context.specificCosmeticFilters.set(`Invalid filter: ...##${raw}`, {
rejected: true
});
return;
}
let details = context.specificCosmeticFilters.get(compiled);
for ( const { hn, not, bad } of parser.getExtFilterDomainIterator() ) {
if ( bad ) { continue; }
if ( not && exception ) { continue; }
if ( isRegex(hn) ) { continue; }
let { compiled, exception, raw } = parser.result;
if ( exception ) { continue; }
let rejected;
if ( compiled === undefined ) {
rejected = `Invalid filter: ${hn}##${raw}`;
}
if ( rejected ) {
compiled = rejected;
}
let details = context.specificCosmeticFilters.get(compiled);
if ( details === undefined ) {
details = {};
if ( rejected ) { details.rejected = true; }
context.specificCosmeticFilters.set(compiled, details);
context.specificCosmeticFilters.set(compiled, details = {});
}
if ( rejected ) { continue; }
if ( not ) {
if ( exception ) {
if ( details.excludeMatches === undefined ) {
details.excludeMatches = [];
}
@ -270,6 +272,13 @@ function addExtendedToDNR(context, parser) {
}
details.matches.push(hn);
}
if ( details === undefined ) { return; }
if ( exception ) { return; }
if ( compiled.startsWith('{') ) { return; }
if ( details.matches === undefined || details.matches.includes('*') ) {
addGenericCosmeticFilter(context, compiled, false);
details.matches = undefined;
}
}
/******************************************************************************/