From 3e6c8ea727a979d461cf1149084bf1dc99636b19 Mon Sep 17 00:00:00 2001 From: HoJeong Go Date: Fri, 25 Oct 2024 11:00:58 +0200 Subject: [PATCH] fix: exception of script injection not respecting aliases (#4376) Co-authored-by: chrmod --- packages/adblocker/src/engine/engine.ts | 11 ++++-- packages/adblocker/src/filters/cosmetic.ts | 34 ++++++++++++++++++- packages/adblocker/src/resources.ts | 20 +++++++---- packages/adblocker/test/engine/engine.test.ts | 28 ++++++++++++++- 4 files changed, 82 insertions(+), 11 deletions(-) diff --git a/packages/adblocker/src/engine/engine.ts b/packages/adblocker/src/engine/engine.ts index 5b1ab18415..67c2d802bd 100644 --- a/packages/adblocker/src/engine/engine.ts +++ b/packages/adblocker/src/engine/engine.ts @@ -20,7 +20,7 @@ import { fullLists, } from '../fetch.js'; import { HTMLSelector } from '../html-filtering.js'; -import CosmeticFilter from '../filters/cosmetic.js'; +import CosmeticFilter, { normalizeSelector } from '../filters/cosmetic.js'; import NetworkFilter from '../filters/network.js'; import { block } from '../filters/dsl.js'; import { FilterType, IListDiff, IPartialRawDiff, parseFilters } from '../lists.js'; @@ -1091,7 +1091,10 @@ export default class FilterEngine extends EventEmitter { ) { injectionsDisabled = true; } - unhideExceptions.set(unhide.getSelector(), unhide); + unhideExceptions.set( + normalizeSelector(unhide, this.resources.getScriptletCanonicalName.bind(this.resources)), + unhide, + ); } const injections: CosmeticFilter[] = []; @@ -1102,7 +1105,9 @@ export default class FilterEngine extends EventEmitter { // Apply unhide rules + dispatch for (const filter of filters) { // Make sure `rule` is not un-hidden by a #@# filter - const exception = unhideExceptions.get(filter.getSelector()); + const exception = unhideExceptions.get( + normalizeSelector(filter, this.resources.getScriptletCanonicalName.bind(this.resources)), + ); if (exception !== undefined) { continue; diff --git a/packages/adblocker/src/filters/cosmetic.ts b/packages/adblocker/src/filters/cosmetic.ts index 54ecf8fccf..a2ed13650a 100644 --- a/packages/adblocker/src/filters/cosmetic.ts +++ b/packages/adblocker/src/filters/cosmetic.ts @@ -175,6 +175,29 @@ function computeFilterId( return hash >>> 0; } +export function normalizeSelector( + filter: CosmeticFilter, + getScriptletCanonicalName: (name: string) => string | undefined, +): string { + const selector = filter.getSelector(); + + if (filter.isScriptInject() === false) { + return selector; + } + + const parsed = filter.parseScript(); + if (parsed === undefined) { + return selector; + } + + const canonicalName = getScriptletCanonicalName(parsed.name); + if (canonicalName === undefined) { + return selector; + } + + return selector.replace(parsed.name, canonicalName); +} + /*************************************************************************** * Cosmetic filters parsing * ************************************************************************ */ @@ -401,6 +424,7 @@ export default class CosmeticFilter implements IFilter { public readonly rawLine: string | undefined; private id: number | undefined; + private scriptletDetails: { name: string; args: string[] } | undefined; constructor({ mask, @@ -422,6 +446,7 @@ export default class CosmeticFilter implements IFilter { this.id = undefined; this.rawLine = rawLine; + this.scriptletDetails = undefined; } public isCosmeticFilter(): this is CosmeticFilter { @@ -680,6 +705,10 @@ export default class CosmeticFilter implements IFilter { } public parseScript(): { name: string; args: string[] } | undefined { + if (this.scriptletDetails !== undefined) { + return this.scriptletDetails; + } + const selector = this.getSelector(); if (selector.length === 0) { return undefined; @@ -772,7 +801,10 @@ export default class CosmeticFilter implements IFilter { .replace(REGEXP_UNICODE_BACKSLASH, '\\') .replace(REGEXP_ESCAPED_COMMA, ','), ); - return { name: parts[0], args }; + + this.scriptletDetails = { name: parts[0], args }; + + return this.scriptletDetails; } public getScript(getScriptlet: (_: string) => string | undefined): string | undefined { diff --git a/packages/adblocker/src/resources.ts b/packages/adblocker/src/resources.ts index 121e0b016d..2a6b15b1c3 100644 --- a/packages/adblocker/src/resources.ts +++ b/packages/adblocker/src/resources.ts @@ -311,12 +311,7 @@ export default class Resources { } public getScriptlet(name: string): string | undefined { - // Scriptlets with names ending with `.fn` is always treated as dependencies - if (name.endsWith('.fn')) { - return undefined; - } - - const scriptlet = this.scriptletsByName.get(name.endsWith('.js') ? name : `${name}.js`); + const scriptlet = this.getRawScriptlet(name); if (scriptlet === undefined) { return undefined; @@ -338,6 +333,19 @@ export default class Resources { return script; } + public getScriptletCanonicalName(name: string): string | undefined { + return this.getRawScriptlet(name)?.name; + } + + private getRawScriptlet(name: string): Scriptlet | undefined { + // Scriptlets with names ending with `.fn` are always treated as dependencies + if (name.endsWith('.fn')) { + return undefined; + } + + return this.scriptletsByName.get(name.endsWith('.js') ? name : `${name}.js`); + } + private getScriptletDependencies(scriptlet: Scriptlet): string[] { const dependencies: Map = new Map(); const queue: string[] = [...scriptlet.dependencies]; diff --git a/packages/adblocker/test/engine/engine.test.ts b/packages/adblocker/test/engine/engine.test.ts index 5d1bf1af93..4cf8fe6e67 100644 --- a/packages/adblocker/test/engine/engine.test.ts +++ b/packages/adblocker/test/engine/engine.test.ts @@ -1035,6 +1035,32 @@ foo.com###selector matches: [], }, + // = unhide +js() exception with aliasing + { + filters: ['foo.com##+js(scriptlet)', 'foo.com#@#+js(scriptlet0)'], + hostname: 'foo.com', + hrefs: [], + injections: [], + matches: [], + }, + { + filters: ['foo.com##+js(scriptlet, arg0, arg1)', 'foo.com#@#+js(scriptlet0, arg0, arg1)'], + hostname: 'foo.com', + hrefs: [], + injections: [], + matches: [], + }, + { + filters: [ + 'foo.com##+js(scriptlet , malformed)', + 'foo.com#@#+js(scriptlet0 , malformed)', + ], + hostname: 'foo.com', + hrefs: [], + injections: [], + matches: [], + }, + // = unhide +js() disable { filters: [ @@ -1456,7 +1482,7 @@ foo.com###selector scriptlets: [ { name: 'scriptlet.js', - aliases: [], + aliases: ['scriptlet0.js'], body: 'function scriptlet() {}', dependencies: [], executionWorld: 'MAIN',