From 5301b355a8cd4d6dc8bd305fef630dc554c4d06a Mon Sep 17 00:00:00 2001 From: HoJeong Go Date: Tue, 30 Jul 2024 04:54:08 +0900 Subject: [PATCH 1/7] feat: respect aliases in scriptlet exceptions fixes https://github.com/ghostery/adblocker/issues/4058 --- packages/adblocker/src/engine/engine.ts | 9 +- packages/adblocker/src/filters/cosmetic.ts | 28 +++++- packages/adblocker/src/resources.ts | 111 ++++++++++++++++----- 3 files changed, 116 insertions(+), 32 deletions(-) diff --git a/packages/adblocker/src/engine/engine.ts b/packages/adblocker/src/engine/engine.ts index 95bd977609..dcc1e74385 100644 --- a/packages/adblocker/src/engine/engine.ts +++ b/packages/adblocker/src/engine/engine.ts @@ -883,7 +883,10 @@ export default class FilterEngine extends EventEmitter { ) { injectionsDisabled = true; } - unhideExceptions.set(unhide.getSelector(), unhide); + unhideExceptions.set( + unhide.getNormalizedScriptInjectionSelector(this.resources.js) ?? unhide.getSelector(), + unhide, + ); } const injections: CosmeticFilter[] = []; @@ -894,7 +897,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( + filter.getNormalizedScriptInjectionSelector(this.resources.js) ?? filter.getSelector(), + ); if (exception !== undefined) { continue; diff --git a/packages/adblocker/src/filters/cosmetic.ts b/packages/adblocker/src/filters/cosmetic.ts index cf11eaef62..94c9af330f 100644 --- a/packages/adblocker/src/filters/cosmetic.ts +++ b/packages/adblocker/src/filters/cosmetic.ts @@ -39,6 +39,7 @@ import { } from '../utils.js'; import IFilter from './interface.js'; import { HTMLSelector, extractHTMLSelectorFromRule } from '../html-filtering.js'; +import { Resource } from '../resources.js'; const EMPTY_TOKENS: [Uint32Array] = [EMPTY_UINT32_ARRAY]; export const DEFAULT_HIDDING_STYLE: string = 'display: none !important;'; @@ -774,7 +775,7 @@ export default class CosmeticFilter implements IFilter { return { name: parts[0], args }; } - public getScript(js: Map): string | undefined { + public getScript(js: Map): string | undefined { const parsed = this.parseScript(); if (parsed === undefined) { return undefined; @@ -782,8 +783,9 @@ export default class CosmeticFilter implements IFilter { const { name, args } = parsed; - let script = js.get(name); - if (script !== undefined) { + const resource = js.get(name); + if (resource !== undefined) { + let script = resource.body; for (let i = 0; i < args.length; i += 1) { // escape some characters so they wont get evaluated with escape characters during script injection const arg = args[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); @@ -796,6 +798,26 @@ export default class CosmeticFilter implements IFilter { return undefined; } + public getNormalizedScriptInjectionSelector(js: Map): string | undefined { + if (this.isScriptInject() === false) { + return undefined; + } + + const selector = this.getSelector(); + + const firstCommaIndex = selector.indexOf(','); + if (firstCommaIndex === -1) { + return undefined; + } + + const originResourceName = js.get(selector.slice(1, firstCommaIndex))?.aliasOf; + if (originResourceName === undefined) { + return undefined; + } + + return '(' + originResourceName + selector.slice(firstCommaIndex); + } + public hasHostnameConstraint(): boolean { return this.domains !== undefined; } diff --git a/packages/adblocker/src/resources.ts b/packages/adblocker/src/resources.ts index 641da98975..23670313d0 100644 --- a/packages/adblocker/src/resources.ts +++ b/packages/adblocker/src/resources.ts @@ -20,12 +20,12 @@ function btoaPolyfill(buffer: string): string { return buffer; } -interface Resource { +export interface Resource { contentType: string; body: string; + aliasOf?: string; } -// TODO - support # alias // TODO - support empty resource body /** @@ -41,17 +41,42 @@ export default class Resources { const resources: Map = new Map(); const numberOfResources = buffer.getUint16(); for (let i = 0; i < numberOfResources; i += 1) { - resources.set(buffer.getASCII(), { - contentType: buffer.getASCII(), - body: buffer.getUTF8(), - }); + const name = buffer.getASCII(); + const isAlias = buffer.getBool(); + if (isAlias === true) { + resources.set(name, { + contentType: buffer.getASCII(), + body: buffer.getUTF8(), + }); + } else { + resources.set(name, { + contentType: '', + body: '', + aliasOf: buffer.getASCII(), + }); + } + } + + // Fill aliases after deserializing everything + for (const resource of resources.values()) { + if (resource.aliasOf === undefined) { + continue; + } + + const origin = resources.get(resource.aliasOf); + if (origin === undefined) { + continue; + } + + resource.body = origin.body; + resource.contentType = origin.contentType; } // Deserialize `js` - const js: Map = new Map(); - resources.forEach(({ contentType, body }, name) => { - if (contentType === 'application/javascript') { - js.set(name, body); + const js: Map = new Map(); + resources.forEach((resource, name) => { + if (resource.contentType === 'application/javascript') { + js.set(name, resource); } }); @@ -63,7 +88,7 @@ export default class Resources { } public static parse(data: string, { checksum }: { checksum: string }): Resources { - const typeToResource: Map> = new Map(); + const typeToResource: Map> = new Map(); const trimComments = (str: string) => str.replace(/^\s*#.*$/gm, ''); const chunks = data.split('\n\n'); @@ -72,8 +97,11 @@ export default class Resources { if (resource.length !== 0) { const firstNewLine = resource.indexOf('\n'); const split = resource.slice(0, firstNewLine).split(/\s+/); - const name = split[0]; - const type = split[1]; + const [name, type] = split; + const aliases = (split[2] || '') + .split(',') + .map((alias) => alias.trim()) + .filter((alias) => alias.length !== 0); const body = resource.slice(firstNewLine + 1); if (name === undefined || type === undefined || body === undefined) { @@ -85,15 +113,27 @@ export default class Resources { resources = new Map(); typeToResource.set(type, resources); } - resources.set(name, body); + resources.set(name, { + body, + }); + for (const alias of aliases) { + resources.set(alias, { + body, + aliasOf: name, + }); + } } } // The resource containing javascirpts to be injected - const js: Map = typeToResource.get('application/javascript') || new Map(); + const js: Map = typeToResource.get('application/javascript') || new Map(); for (const [key, value] of js.entries()) { if (key.endsWith('.js')) { - js.set(key.slice(0, -3), value); + js.set(key.slice(0, -3), { + contentType: value.contentType, + body: value.body, + aliasOf: key, + }); } } @@ -101,11 +141,19 @@ export default class Resources { // used for request redirection. const resourcesByName: Map = new Map(); typeToResource.forEach((resources, contentType) => { - resources.forEach((resource: string, name: string) => { - resourcesByName.set(name, { - contentType, - body: resource, - }); + resources.forEach((resource, name) => { + if (resource.aliasOf === undefined) { + resourcesByName.set(name, { + contentType, + body: resource.body, + }); + } else { + resourcesByName.set(name, { + contentType, + body: resource.body, + aliasOf: resource.aliasOf, + }); + } }); }); @@ -117,7 +165,7 @@ export default class Resources { } public readonly checksum: string; - public readonly js: Map; + public readonly js: Map; public readonly resources: Map; constructor({ checksum = '', js = new Map(), resources = new Map() }: Partial = {}) { @@ -142,8 +190,13 @@ export default class Resources { public getSerializedSize(): number { let estimatedSize = sizeOfASCII(this.checksum) + 2 * sizeOfByte(); // resources.size - this.resources.forEach(({ contentType, body }, name) => { - estimatedSize += sizeOfASCII(name) + sizeOfASCII(contentType) + sizeOfUTF8(body); + this.resources.forEach(({ contentType, body, aliasOf }, name) => { + estimatedSize += sizeOfASCII(name); + if (aliasOf === undefined) { + estimatedSize += sizeOfASCII(contentType) + sizeOfUTF8(body); + } else { + estimatedSize += sizeOfASCII(aliasOf); + } }); return estimatedSize; @@ -155,10 +208,14 @@ export default class Resources { // Serialize `resources` buffer.pushUint16(this.resources.size); - this.resources.forEach(({ contentType, body }, name) => { + this.resources.forEach(({ contentType, body, aliasOf }, name) => { buffer.pushASCII(name); - buffer.pushASCII(contentType); - buffer.pushUTF8(body); + if (aliasOf === undefined) { + buffer.pushASCII(contentType); + buffer.pushUTF8(body); + } else { + buffer.pushASCII(aliasOf); + } }); } } From 831daf0c464d1b0d8556a869c15ceb1fd9a45c2b Mon Sep 17 00:00:00 2001 From: HoJeong Go Date: Tue, 30 Jul 2024 04:57:51 +0900 Subject: [PATCH 2/7] fix: invalid serialization and size estimation --- packages/adblocker/src/resources.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/adblocker/src/resources.ts b/packages/adblocker/src/resources.ts index 23670313d0..f86f3be10d 100644 --- a/packages/adblocker/src/resources.ts +++ b/packages/adblocker/src/resources.ts @@ -8,7 +8,7 @@ import { getResourceForMime } from '@remusao/small'; -import { StaticDataView, sizeOfUTF8, sizeOfASCII, sizeOfByte } from './data-view.js'; +import { StaticDataView, sizeOfUTF8, sizeOfASCII, sizeOfByte, sizeOfBool } from './data-view.js'; // Polyfill for `btoa` function btoaPolyfill(buffer: string): string { @@ -191,7 +191,7 @@ export default class Resources { let estimatedSize = sizeOfASCII(this.checksum) + 2 * sizeOfByte(); // resources.size this.resources.forEach(({ contentType, body, aliasOf }, name) => { - estimatedSize += sizeOfASCII(name); + estimatedSize += sizeOfASCII(name) + sizeOfBool(); if (aliasOf === undefined) { estimatedSize += sizeOfASCII(contentType) + sizeOfUTF8(body); } else { @@ -210,6 +210,7 @@ export default class Resources { buffer.pushUint16(this.resources.size); this.resources.forEach(({ contentType, body, aliasOf }, name) => { buffer.pushASCII(name); + buffer.pushBool(aliasOf === undefined); if (aliasOf === undefined) { buffer.pushASCII(contentType); buffer.pushUTF8(body); From 0afc481e296c31e37cb458746fc1d91e5a40f506 Mon Sep 17 00:00:00 2001 From: HoJeong Go Date: Tue, 30 Jul 2024 09:46:06 +0900 Subject: [PATCH 3/7] fix: tests --- packages/adblocker/src/engine/engine.ts | 6 ++++++ packages/adblocker/src/filters/cosmetic.ts | 4 ++-- packages/adblocker/test/engine/engine.test.ts | 20 +++++++++++++++---- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/packages/adblocker/src/engine/engine.ts b/packages/adblocker/src/engine/engine.ts index 5f029d86fd..8bd4062d94 100644 --- a/packages/adblocker/src/engine/engine.ts +++ b/packages/adblocker/src/engine/engine.ts @@ -883,6 +883,12 @@ export default class FilterEngine extends EventEmitter { ) { injectionsDisabled = true; } + console.log( + 'normalised', + unhide.getNormalizedScriptInjectionSelector(this.resources.js), + 'normal', + unhide.getSelector(), + ); unhideExceptions.set( unhide.getNormalizedScriptInjectionSelector(this.resources.js) ?? unhide.getSelector(), unhide, diff --git a/packages/adblocker/src/filters/cosmetic.ts b/packages/adblocker/src/filters/cosmetic.ts index 94c9af330f..9809917bb4 100644 --- a/packages/adblocker/src/filters/cosmetic.ts +++ b/packages/adblocker/src/filters/cosmetic.ts @@ -810,12 +810,12 @@ export default class CosmeticFilter implements IFilter { return undefined; } - const originResourceName = js.get(selector.slice(1, firstCommaIndex))?.aliasOf; + const originResourceName = js.get(selector.slice(0, firstCommaIndex))?.aliasOf; if (originResourceName === undefined) { return undefined; } - return '(' + originResourceName + selector.slice(firstCommaIndex); + return originResourceName + selector.slice(firstCommaIndex); } public hasHostnameConstraint(): boolean { diff --git a/packages/adblocker/test/engine/engine.test.ts b/packages/adblocker/test/engine/engine.test.ts index 59f576a205..d02d547886 100644 --- a/packages/adblocker/test/engine/engine.test.ts +++ b/packages/adblocker/test/engine/engine.test.ts @@ -713,7 +713,10 @@ foo.com###selector it('disabling specific hides does not impact scriptlets', () => { const engine = Engine.parse(['@@||foo.com^$specifichide', 'foo.com##+js(foo)'].join('\n')); - engine.resources.js.set('foo', ''); + engine.resources.js.set('foo', { + contentType: 'application/javascript', + body: '', + }); expect( engine.getCosmeticsFilters({ domain: 'foo.com', @@ -1300,9 +1303,18 @@ foo.com###selector it(JSON.stringify({ filters, hostname, matches, injections }), () => { // Initialize engine with all rules from test case const engine = createEngine(filters.join('\n')); - engine.resources.js.set('scriptlet', 'scriptlet'); - engine.resources.js.set('scriptlet1', 'scriptlet1'); - engine.resources.js.set('scriptlet2', 'scriptlet2'); + engine.resources.js.set('scriptlet', { + contentType: 'application/javascript', + body: 'scriptlet', + }); + engine.resources.js.set('scriptlet1', { + contentType: 'application/javascript', + body: 'scriptlet1', + }); + engine.resources.js.set('scriptlet2', { + contentType: 'application/javascript', + body: 'scriptlet2', + }); // #getCosmeticsFilters const { styles, scripts } = engine.getCosmeticsFilters({ From 97a11c60d6bf76ba568ca1b9f313c7858fab90bd Mon Sep 17 00:00:00 2001 From: HoJeong Go Date: Tue, 30 Jul 2024 09:54:56 +0900 Subject: [PATCH 4/7] test: scriptlet with alias --- packages/adblocker/src/engine/engine.ts | 6 ------ packages/adblocker/src/filters/cosmetic.ts | 4 ++-- packages/adblocker/test/engine/engine.test.ts | 20 +++++++++++++++++++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/adblocker/src/engine/engine.ts b/packages/adblocker/src/engine/engine.ts index 8bd4062d94..5f029d86fd 100644 --- a/packages/adblocker/src/engine/engine.ts +++ b/packages/adblocker/src/engine/engine.ts @@ -883,12 +883,6 @@ export default class FilterEngine extends EventEmitter { ) { injectionsDisabled = true; } - console.log( - 'normalised', - unhide.getNormalizedScriptInjectionSelector(this.resources.js), - 'normal', - unhide.getSelector(), - ); unhideExceptions.set( unhide.getNormalizedScriptInjectionSelector(this.resources.js) ?? unhide.getSelector(), unhide, diff --git a/packages/adblocker/src/filters/cosmetic.ts b/packages/adblocker/src/filters/cosmetic.ts index 9809917bb4..6f1fceb0f1 100644 --- a/packages/adblocker/src/filters/cosmetic.ts +++ b/packages/adblocker/src/filters/cosmetic.ts @@ -805,9 +805,9 @@ export default class CosmeticFilter implements IFilter { const selector = this.getSelector(); - const firstCommaIndex = selector.indexOf(','); + let firstCommaIndex = selector.indexOf(','); if (firstCommaIndex === -1) { - return undefined; + firstCommaIndex = selector.length; } const originResourceName = js.get(selector.slice(0, firstCommaIndex))?.aliasOf; diff --git a/packages/adblocker/test/engine/engine.test.ts b/packages/adblocker/test/engine/engine.test.ts index d02d547886..cbdfa188e2 100644 --- a/packages/adblocker/test/engine/engine.test.ts +++ b/packages/adblocker/test/engine/engine.test.ts @@ -885,6 +885,20 @@ foo.com###selector injections: [], matches: [], }, + { + filters: ['foo.com##+js(alias)', 'foo.com#@#+js(scriptlet)'], + hostname: 'foo.com', + hrefs: [], + injections: [], + matches: [], + }, + { + filters: ['foo.com##+js(scriptlet)', 'foo.com#@#+js(alias)'], + hostname: 'foo.com', + hrefs: [], + injections: [], + matches: [], + }, // = unhide +js() disable { @@ -1316,6 +1330,12 @@ foo.com###selector body: 'scriptlet2', }); + engine.resources.js.set('alias', { + contentType: 'application/javascript', + body: 'scriptlet', + aliasOf: 'scriptlet', + }); + // #getCosmeticsFilters const { styles, scripts } = engine.getCosmeticsFilters({ domain: getDomain(hostname) || '', From 913aeeda09b8127b84a477652309a2700bdd66af Mon Sep 17 00:00:00 2001 From: HoJeong Go Date: Tue, 30 Jul 2024 11:42:50 +0900 Subject: [PATCH 5/7] fix: resources parsing and javascript aliasing --- packages/adblocker/src/resources.ts | 71 +++++++++-------------- packages/adblocker/test/parsing.test.ts | 60 ++++++++++++++++--- packages/adblocker/test/resources.test.ts | 36 +++++++++++- 3 files changed, 111 insertions(+), 56 deletions(-) diff --git a/packages/adblocker/src/resources.ts b/packages/adblocker/src/resources.ts index f86f3be10d..5d0bc16c42 100644 --- a/packages/adblocker/src/resources.ts +++ b/packages/adblocker/src/resources.ts @@ -23,7 +23,7 @@ function btoaPolyfill(buffer: string): string { export interface Resource { contentType: string; body: string; - aliasOf?: string; + aliasOf?: string | undefined; } // TODO - support empty resource body @@ -44,16 +44,16 @@ export default class Resources { const name = buffer.getASCII(); const isAlias = buffer.getBool(); if (isAlias === true) { - resources.set(name, { - contentType: buffer.getASCII(), - body: buffer.getUTF8(), - }); - } else { resources.set(name, { contentType: '', body: '', aliasOf: buffer.getASCII(), }); + } else { + resources.set(name, { + contentType: buffer.getASCII(), + body: buffer.getUTF8(), + }); } } @@ -88,7 +88,7 @@ export default class Resources { } public static parse(data: string, { checksum }: { checksum: string }): Resources { - const typeToResource: Map> = new Map(); + const resources: Map = new Map(); const trimComments = (str: string) => str.replace(/^\s*#.*$/gm, ''); const chunks = data.split('\n\n'); @@ -108,16 +108,20 @@ export default class Resources { continue; } - let resources = typeToResource.get(type); - if (resources === undefined) { - resources = new Map(); - typeToResource.set(type, resources); - } resources.set(name, { + contentType: type, body, }); for (const alias of aliases) { resources.set(alias, { + contentType: type, + body, + aliasOf: name, + }); + } + if (type === 'application/javascript' && name.endsWith('.js')) { + resources.set(name.slice(0, -3), { + contentType: type, body, aliasOf: name, }); @@ -126,41 +130,17 @@ export default class Resources { } // The resource containing javascirpts to be injected - const js: Map = typeToResource.get('application/javascript') || new Map(); - for (const [key, value] of js.entries()) { - if (key.endsWith('.js')) { - js.set(key.slice(0, -3), { - contentType: value.contentType, - body: value.body, - aliasOf: key, - }); + const js: Map = new Map(); + for (const [name, resource] of resources.entries()) { + if (resource.contentType === 'application/javascript') { + js.set(name, resource); } } - // Create a mapping from resource name to { contentType, data } - // used for request redirection. - const resourcesByName: Map = new Map(); - typeToResource.forEach((resources, contentType) => { - resources.forEach((resource, name) => { - if (resource.aliasOf === undefined) { - resourcesByName.set(name, { - contentType, - body: resource.body, - }); - } else { - resourcesByName.set(name, { - contentType, - body: resource.body, - aliasOf: resource.aliasOf, - }); - } - }); - }); - return new Resources({ checksum, js, - resources: resourcesByName, + resources, }); } @@ -210,12 +190,13 @@ export default class Resources { buffer.pushUint16(this.resources.size); this.resources.forEach(({ contentType, body, aliasOf }, name) => { buffer.pushASCII(name); - buffer.pushBool(aliasOf === undefined); - if (aliasOf === undefined) { + const isAlias = aliasOf !== undefined; + buffer.pushBool(aliasOf !== undefined); + if (isAlias) { + buffer.pushASCII(aliasOf); + } else { buffer.pushASCII(contentType); buffer.pushUTF8(body); - } else { - buffer.pushASCII(aliasOf); } }); } diff --git a/packages/adblocker/test/parsing.test.ts b/packages/adblocker/test/parsing.test.ts index 541146e2fe..838043d01a 100644 --- a/packages/adblocker/test/parsing.test.ts +++ b/packages/adblocker/test/parsing.test.ts @@ -1881,14 +1881,36 @@ describe('Cosmetic filters', () => { }); it('returns a script if one exists', () => { - expect(simpleScriptlet?.getScript(new Map([['script.js', 'test']]))).to.equal('test'); + expect( + simpleScriptlet?.getScript( + new Map([ + [ + 'script.js', + { + contentType: 'application/javascript', + body: 'test', + }, + ], + ]), + ), + ).to.equal('test'); }); context('with arguments', () => { it('inject values', () => { - expect(simpleScriptlet?.getScript(new Map([['script.js', '{{1}},{{2}},{{3}}']]))).to.equal( - 'arg1,arg2,arg3', - ); + expect( + simpleScriptlet?.getScript( + new Map([ + [ + 'script.js', + { + contentType: 'application/javascript', + body: '{{1}},{{2}},{{3}}', + }, + ], + ]), + ), + ).to.equal('arg1,arg2,arg3'); }); it('escapes special characters', () => { @@ -1909,9 +1931,19 @@ describe('Cosmetic filters', () => { '\\', ]) { const scriptlet = CosmeticFilter.parse(`foo.com##+js(script.js, ${character})`); - expect(scriptlet?.getScript(new Map([['script.js', '{{1}}']]))).to.equal( - `\\${character}`, - ); + expect( + scriptlet?.getScript( + new Map([ + [ + 'script.js', + { + contentType: 'application/javascript', + body: '{{1}}', + }, + ], + ]), + ), + ).to.equal(`\\${character}`); } }); @@ -1921,7 +1953,19 @@ describe('Cosmetic filters', () => { [String.raw`foo\*`, String.raw`foo\\\*`], ]) { const scriptlet = CosmeticFilter.parse(`foo.com##+js(script.js, ${example[0]})`); - expect(scriptlet?.getScript(new Map([['script.js', '{{1}}']]))).to.equal(example[1]); + expect( + scriptlet?.getScript( + new Map([ + [ + 'script.js', + { + contentType: 'application/javascript', + body: '{{1}}', + }, + ], + ]), + ), + ).to.equal(example[1]); } }); }); diff --git a/packages/adblocker/test/resources.test.ts b/packages/adblocker/test/resources.test.ts index f10c880ab8..11ff0c90a5 100644 --- a/packages/adblocker/test/resources.test.ts +++ b/packages/adblocker/test/resources.test.ts @@ -37,7 +37,17 @@ describe('#Resources', () => { checksum: 'checksum', }); expect(resources.checksum).to.equal('checksum'); - expect(resources.js).to.eql(new Map([['foo', 'content']])); + expect(resources.js).to.eql( + new Map([ + [ + 'foo', + { + contentType: 'application/javascript', + body: 'content', + }, + ], + ]), + ); expect(resources.resources).to.eql( new Map([['foo', { contentType: 'application/javascript', body: 'content' }]]), ); @@ -51,7 +61,17 @@ describe('#Resources', () => { { checksum: 'checksum' }, ); expect(resources.checksum).to.equal('checksum'); - expect(resources.js).to.eql(new Map([['foo', 'content1']])); + expect(resources.js).to.eql( + new Map([ + [ + 'foo', + { + contentType: 'application/javascript', + body: 'content1', + }, + ], + ]), + ); expect(resources.resources).to.eql( new Map([ ['foo', { contentType: 'application/javascript', body: 'content1' }], @@ -83,7 +103,17 @@ content2 { checksum: 'checksum' }, ); expect(resources.checksum).to.equal('checksum'); - expect(resources.js).to.eql(new Map([['foo', 'content1']])); + expect(resources.js).to.eql( + new Map([ + [ + 'foo', + { + contentType: 'application/javascript', + body: 'content1', + }, + ], + ]), + ); expect(resources.resources).to.eql( new Map([ ['foo', { contentType: 'application/javascript', body: 'content1' }], From 0760b0c5c66a897e4b8c5bfd3c539cde6d0a0236 Mon Sep 17 00:00:00 2001 From: HoJeong Go Date: Thu, 1 Aug 2024 13:31:21 +0900 Subject: [PATCH 6/7] test: #getNormalizedSelector --- packages/adblocker/src/engine/engine.ts | 4 +-- packages/adblocker/src/filters/cosmetic.ts | 2 +- packages/adblocker/test/engine/engine.test.ts | 11 ++++--- packages/adblocker/test/parsing.test.ts | 33 +++++++++++++++++++ 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/packages/adblocker/src/engine/engine.ts b/packages/adblocker/src/engine/engine.ts index 5f029d86fd..29d4de557b 100644 --- a/packages/adblocker/src/engine/engine.ts +++ b/packages/adblocker/src/engine/engine.ts @@ -884,7 +884,7 @@ export default class FilterEngine extends EventEmitter { injectionsDisabled = true; } unhideExceptions.set( - unhide.getNormalizedScriptInjectionSelector(this.resources.js) ?? unhide.getSelector(), + unhide.getNormalizedSelector(this.resources.js) ?? unhide.getSelector(), unhide, ); } @@ -898,7 +898,7 @@ export default class FilterEngine extends EventEmitter { for (const filter of filters) { // Make sure `rule` is not un-hidden by a #@# filter const exception = unhideExceptions.get( - filter.getNormalizedScriptInjectionSelector(this.resources.js) ?? filter.getSelector(), + filter.getNormalizedSelector(this.resources.js) ?? filter.getSelector(), ); if (exception !== undefined) { diff --git a/packages/adblocker/src/filters/cosmetic.ts b/packages/adblocker/src/filters/cosmetic.ts index 6f1fceb0f1..e8bcf80262 100644 --- a/packages/adblocker/src/filters/cosmetic.ts +++ b/packages/adblocker/src/filters/cosmetic.ts @@ -798,7 +798,7 @@ export default class CosmeticFilter implements IFilter { return undefined; } - public getNormalizedScriptInjectionSelector(js: Map): string | undefined { + public getNormalizedSelector(js: Map): string | undefined { if (this.isScriptInject() === false) { return undefined; } diff --git a/packages/adblocker/test/engine/engine.test.ts b/packages/adblocker/test/engine/engine.test.ts index cbdfa188e2..cee80fe212 100644 --- a/packages/adblocker/test/engine/engine.test.ts +++ b/packages/adblocker/test/engine/engine.test.ts @@ -387,13 +387,14 @@ $csp=baz,domain=bar.com url: 'https://foo.com', }); - const createEngineWithResource = (filters: string[], resource: string) => { + const createEngineWithResource = (filters: string[], resourceBody: string) => { const engine = createEngine(filters.join('\n')); - engine.resources.js.set(resource, resource); - engine.resources.resources.set(resource, { - body: resource, + const resource = { + body: resourceBody, contentType: 'application/javascript', - }); + }; + engine.resources.js.set(resourceBody, resource); + engine.resources.resources.set(resourceBody, resource); return engine; }; diff --git a/packages/adblocker/test/parsing.test.ts b/packages/adblocker/test/parsing.test.ts index 838043d01a..a5c73bb6d0 100644 --- a/packages/adblocker/test/parsing.test.ts +++ b/packages/adblocker/test/parsing.test.ts @@ -15,6 +15,7 @@ import { parseFilters } from '../src/lists.js'; import { hashStrings, tokenize } from '../src/utils.js'; import { HTMLSelector } from '../src/html-filtering.js'; import { NORMALIZED_TYPE_TOKEN, hashHostnameBackward } from '../src/request.js'; +import { Resource } from '../src/resources.js'; function h(hostnames: string[]): Uint32Array { return new Uint32Array(hostnames.map(hashHostnameBackward)).sort(); @@ -1834,6 +1835,38 @@ describe('Cosmetic filters', () => { // Compound test('script:has-text(===):has-text(/[wW]{14000}/)', ['script', ['===', '/[wW]{14000}/']]); + + context('with normalization', () => { + const js = new Map([ + [ + 'scriptlet', + { + body: 'scriptlet', + contentType: 'application/javascript', + }, + ], + [ + 'alias', + { + body: 'scriptlet', + contentType: 'application/javascript', + aliasOf: 'scriptlet', + }, + ], + ]); + + expect(CosmeticFilter.parse('##+js(scriptlet)')!.getNormalizedSelector(js)).to.be( + 'scriptlet', + ); + expect(CosmeticFilter.parse('##+js(alias)')!.getNormalizedSelector(js)).to.be('scriptlet'); + + expect(CosmeticFilter.parse('##+js(scriptlet, arg1)')!.getNormalizedSelector(js)).to.be( + 'scriptlet, arg1', + ); + expect(CosmeticFilter.parse('##+js(alias, arg1)')!.getNormalizedSelector(js)).to.be( + 'scriptlet, arg1', + ); + }); }); }); From a6b0c24bf6732dab75b508d5813473d673fb3083 Mon Sep 17 00:00:00 2001 From: HoJeong Go Date: Thu, 1 Aug 2024 13:38:51 +0900 Subject: [PATCH 7/7] fix(test): #getNormalizedSelector --- packages/adblocker/test/parsing.test.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/adblocker/test/parsing.test.ts b/packages/adblocker/test/parsing.test.ts index a5c73bb6d0..668655be50 100644 --- a/packages/adblocker/test/parsing.test.ts +++ b/packages/adblocker/test/parsing.test.ts @@ -1836,7 +1836,7 @@ describe('Cosmetic filters', () => { // Compound test('script:has-text(===):has-text(/[wW]{14000}/)', ['script', ['===', '/[wW]{14000}/']]); - context('with normalization', () => { + it('with normalization', () => { const js = new Map([ [ 'scriptlet', @@ -1855,17 +1855,12 @@ describe('Cosmetic filters', () => { ], ]); - expect(CosmeticFilter.parse('##+js(scriptlet)')!.getNormalizedSelector(js)).to.be( + expect(CosmeticFilter.parse('foo.com##+js(alias)')!.getNormalizedSelector(js)).to.be.eql( 'scriptlet', ); - expect(CosmeticFilter.parse('##+js(alias)')!.getNormalizedSelector(js)).to.be('scriptlet'); - - expect(CosmeticFilter.parse('##+js(scriptlet, arg1)')!.getNormalizedSelector(js)).to.be( - 'scriptlet, arg1', - ); - expect(CosmeticFilter.parse('##+js(alias, arg1)')!.getNormalizedSelector(js)).to.be( - 'scriptlet, arg1', - ); + expect( + CosmeticFilter.parse('foo.com##+js(alias, arg1)')!.getNormalizedSelector(js), + ).to.be.eql('scriptlet, arg1'); }); }); });