Skip to content

Commit ee9ee73

Browse files
committed
AG-50918 Fix parsing of cosmetic rules with $path modifier to correctly extract domain from pattern
Squashed commit of the following: commit 540f0a4 Merge: 3056752 50e4b60 Author: scripthunter7 <d.tota@adguard.com> Date: Fri Feb 13 18:19:43 2026 +0100 Merge branch 'master' into fix/AG-50918 commit 3056752 Author: scripthunter7 <d.tota@adguard.com> Date: Fri Feb 13 16:31:26 2026 +0100 Update changelog commit d95f353 Author: scripthunter7 <d.tota@adguard.com> Date: Thu Feb 12 16:17:29 2026 +0100 AG-50640 Fix parsing of cosmetic rules with $path modifier to correctly extract domain from pattern Handle cosmetic rules with $path modifier by extracting domain from the remaining pattern after the closing bracket when no domain is found in the modifier list. Add test coverage for $path modifier rules and verify they are not disabled by $generichide.
1 parent 50e4b60 commit ee9ee73

File tree

4 files changed

+67
-18
lines changed

4 files changed

+67
-18
lines changed

packages/tsurlfilter/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## Unreleased
9+
10+
### Fixed
11+
12+
- Parsing cosmetic rules with `$path` modifier.
13+
814
## [4.0.0] - TBD
915

1016
### Added

packages/tsurlfilter/src/filterlist/rule-parts.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -508,30 +508,37 @@ function buildCosmeticRuleParts(
508508
i += 1;
509509
}
510510

511-
if (rule[i] === '$') {
512-
// find last unescaped ] from separator
513-
let j = offset - 1;
511+
if (rule[i] !== '$') {
512+
// invalid rule
513+
return null;
514+
}
514515

515-
while (j >= realStart) {
516-
if (rule[j] === CLOSE_SQUARE && rule[j - 1] !== ESCAPE) {
517-
break;
518-
}
519-
j -= 1;
520-
}
516+
// find last unescaped ] from separator
517+
let j = offset - 1;
521518

522-
if (j < realStart) {
523-
return null;
519+
while (j >= realStart) {
520+
if (rule[j] === CLOSE_SQUARE && rule[j - 1] !== ESCAPE) {
521+
break;
524522
}
523+
j -= 1;
524+
}
525525

526-
const domain = extractDomainsFromModifierList(rule, i + 1, j);
526+
if (j < realStart) {
527+
return null;
528+
}
527529

528-
if (domain !== null) {
529-
domainsStart = decodeDomainsStart(domain);
530-
domainsEnd = decodeDomainsEnd(domain);
531-
}
530+
const domain = extractDomainsFromModifierList(rule, i + 1, j);
531+
532+
if (domain !== null) {
533+
domainsStart = decodeDomainsStart(domain);
534+
domainsEnd = decodeDomainsEnd(domain);
532535
} else {
533-
// invalid rule
534-
return null;
536+
// take the rest of the pattern as domains, skipping whitespace after ]
537+
const restStart = findNextNonWhitespace(rule, j + 1, offset);
538+
if (restStart < offset) {
539+
domainsStart = restStart;
540+
domainsEnd = offset;
541+
}
535542
}
536543
} else if (realStart < offset) {
537544
domainsStart = realStart;

packages/tsurlfilter/test/engine/engine.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,3 +1432,26 @@ describe('Async engine creation', () => {
14321432
expect(result.getBasicResult()).not.toBeNull();
14331433
});
14341434
});
1435+
1436+
describe('$path cosmetic modifier', () => {
1437+
it('$generichide should not disable path modifier rules', () => {
1438+
const rulesLocal = [
1439+
'[$path=/subpage1]example.org##.ad-banner',
1440+
'@@||example.org^$generichide',
1441+
];
1442+
const engine = Engine.createSync({
1443+
filters: [
1444+
{
1445+
id: 1,
1446+
content: rulesLocal.join('\n'),
1447+
},
1448+
],
1449+
});
1450+
1451+
const result = engine.getCosmeticResult(
1452+
createRequest('http://example.org/subpage1'),
1453+
CosmeticOption.CosmeticOptionAll,
1454+
);
1455+
expect(result.elementHiding.specific.length).toEqual(1);
1456+
});
1457+
});

packages/tsurlfilter/test/filterlist/rule-parts.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,19 @@ describe('getRuleParts', () => {
5656
expect(rule.slice(parts!.domainsStart, parts!.domainsEnd)).toBe('example.com');
5757
});
5858

59+
test('parses cosmetic rule with modifiers 2', () => {
60+
const rule = '[$path=/AdguardTeam]github.com##body';
61+
const parts = getRuleParts(rule) as CosmeticRuleParts;
62+
63+
expect(parts).not.toBeNull();
64+
expect(parts!.category).toBe(RuleCategory.Cosmetic);
65+
expect(parts!.allowlist).toBeFalsy();
66+
expect(parts!.type).toBe(CosmeticRuleType.ElementHidingRule);
67+
expect(rule.slice(parts!.contentStart, parts!.contentEnd)).toBe('body');
68+
expect(rule.slice(parts!.separatorStart, parts!.separatorEnd)).toBe('##');
69+
expect(rule.slice(parts!.domainsStart, parts!.domainsEnd)).toBe('github.com');
70+
});
71+
5972
test('parses HTML cosmetic rule', () => {
6073
const rule = 'example.com$$.ad';
6174
const parts = getRuleParts(rule) as CosmeticRuleParts;

0 commit comments

Comments
 (0)