diff --git a/packages/adblocker/src/filters/network.ts b/packages/adblocker/src/filters/network.ts index 16e487f5b3..035e151940 100644 --- a/packages/adblocker/src/filters/network.ts +++ b/packages/adblocker/src/filters/network.ts @@ -725,14 +725,23 @@ export default class NetworkFilter implements IFilter { // --------------------------------------------------------------------- // // parseOptions // --------------------------------------------------------------------- // + const denyallowEntities: Set = new Set(); for (const rawOption of getFilterOptions(line, optionsIndex + 1, line.length)) { const negation = rawOption[0].charCodeAt(0) === 126; /* '~' */ const option = negation === true ? rawOption[0].slice(1) : rawOption[0]; const value = rawOption[1]; switch (option) { + case 'to': case 'denyallow': { - denyallow = Domains.parse(value.split('|'), debug); + let parts = value.split('|'); + if (option === 'to') { + parts = parts.map((part) => + part.charCodeAt(0) === 126 /* '~' */ ? part.slice(1) : `~${part}`, + ); + } + parts.forEach((part) => denyallowEntities.add(part)); + denyallow = Domains.parse(Array.from(denyallowEntities), debug); break; } case 'domain': @@ -1525,7 +1534,7 @@ export default class NetworkFilter implements IFilter { if (this.denyallow !== undefined) { if (this.denyallow.parts !== undefined) { - options.push(`denyallow=${this.denyallow.parts}`); + options.push(`denyallow=${this.denyallow.parts.replace(/,/g, '|')}`); } else { options.push('denyallow='); } diff --git a/packages/adblocker/test/matching.test.ts b/packages/adblocker/test/matching.test.ts index 5d5c283ff9..73e709bbfc 100644 --- a/packages/adblocker/test/matching.test.ts +++ b/packages/adblocker/test/matching.test.ts @@ -396,6 +396,23 @@ describe('#NetworkFilter.match', () => { url: 'https://sub.y.com/bar', type: 'script', }); + + // to + expect(f`*$3p,from=a.com,to=b.com`).to.matchRequest({ + sourceUrl: 'https://a.com', + url: 'https://b.com/bar', + type: 'script', + }); + expect(f`*$frame,3p,from=a.com|b.com,to=~c.com`).to.not.matchRequest({ + sourceUrl: 'https://a.com', + url: 'https://c.com/bar', + type: 'sub_frame', + }); + expect(f`$frame,csp=non-relevant,to=~safe.com,from=c.com|d.com`).to.not.matchRequest({ + sourceUrl: 'https://c.com', + url: 'https://safe.com/foo', + type: 'sub_frame', + }); }); }); diff --git a/packages/adblocker/test/parsing.test.ts b/packages/adblocker/test/parsing.test.ts index 1b9da5b8b5..5149de76c7 100644 --- a/packages/adblocker/test/parsing.test.ts +++ b/packages/adblocker/test/parsing.test.ts @@ -803,6 +803,29 @@ describe('Network filters', () => { denyallow: undefined, }); }); + + context('to', () => { + it('reverses domains condition', () => { + network('||foo.com$to=foo.com|~bar.com,denyallow=bar.com|~foo.com', { + denyallow: { + hostnames: h(['bar.com']), + entities: undefined, + notHostnames: h(['foo.com']), + notEntities: undefined, + parts: undefined, + }, + }); + network('||foo.com$to=bar.com|baz.com', { + denyallow: { + hostnames: undefined, + entities: undefined, + notHostnames: h(['bar.com', 'baz.com']), + notEntities: undefined, + parts: undefined, + }, + }); + }); + }); }); describe('redirect', () => {