Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions src/filters/cosmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ impl CosmeticFilter {
};

let mut translate_abp_syntax = false;
let mut adguard_css_injection = false;

// Consume filter options embedded in the `##` marker:
let mut between_sharps = &line[after_sharp_index..second_sharp_index];
Expand All @@ -445,10 +446,15 @@ impl CosmeticFilter {
// `#%#` / `#@%#`
return Err(CosmeticFilterError::UnsupportedSyntax);
}
if between_sharps.starts_with('$') {
// AdGuard `:style` syntax - not supported for now
// `#$?#` for CSS rules, `#@$?#` — for exceptions
return Err(CosmeticFilterError::UnsupportedSyntax);
if let Some(rest) = between_sharps.strip_prefix('$') {
// AdGuard CSS injection: `#$#selector { ... }` (and the extended
// `#$?#` / exception `#@$#` forms). Only the style/remove form is
// handled here — it always carries a `{ ... }` body, which is
// parsed by `parse_after_sharp_nonscript` below. ABP snippet
// injection also uses `#$#` but has no braces; those produce no
// action and are rejected after the selector parse.
adguard_css_injection = true;
between_sharps = rest;
}
if between_sharps.starts_with('?') {
// ABP/ADG extended CSS syntax:
Expand Down Expand Up @@ -507,6 +513,12 @@ impl CosmeticFilter {
(validated_selector, action)
};

if adguard_css_injection && action.is_none() {
// `#$#` without a `{ ... }` block is ABP snippet injection, which
// is not supported.
return Err(CosmeticFilterError::UnsupportedSyntax);
}

if (not_entities.is_some() || not_hostnames.is_some())
&& mask.contains(CosmeticFilterMask::UNHIDE)
{
Expand Down
56 changes: 56 additions & 0 deletions tests/unit/filters/cosmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,62 @@ mod parse_tests {
assert!(parse_cf("example.com##.ad { background: url(https://evil.com) }").is_err());
}

#[test]
fn adguard_css_injection() {
check_parse_result(
"businesshemden.com#$#.mnd-cookie-modal { display: none !important; }",
CosmeticFilterBreakdown {
hostnames: sort_hash_domains(vec!["businesshemden.com"]),
selector: SelectorType::PlainCss(".mnd-cookie-modal".to_string()),
action: Some(CosmeticFilterAction::Style(
"display: none !important;".into(),
)),
..Default::default()
},
);
check_parse_result(
"makeuseof.com#$#body div.fc-consent-root { display: none !important; }",
CosmeticFilterBreakdown {
hostnames: sort_hash_domains(vec!["makeuseof.com"]),
selector: SelectorType::PlainCss("body div.fc-consent-root".to_string()),
action: Some(CosmeticFilterAction::Style(
"display: none !important;".into(),
)),
..Default::default()
},
);
// `#$#` also accepts the `remove:` directive.
check_parse_result(
"example.com#$#.ad { remove: true; }",
CosmeticFilterBreakdown {
hostnames: sort_hash_domains(vec!["example.com"]),
selector: SelectorType::PlainCss(".ad".to_string()),
action: Some(CosmeticFilterAction::Remove),
..Default::default()
},
);
// ABP snippet injection also uses `#$#` but has no `{ ... }` body — it must
// remain unsupported rather than being misparsed as a plain selector.
assert!(parse_cf("example.com#$#abort-on-property-read alert").is_err());
// Generic (hostname-less) injection is rejected like any other action.
assert!(parse_cf("#$#.ad { display: none !important; }").is_err());
}

#[test]
#[cfg(feature = "css-validation")]
fn adguard_css_injection_extended() {
// `#$?#` is the extended (procedural) AdGuard CSS-injection form.
let rule =
parse_cf("example.com#$?#div:has(>div>span.ad) { display: none !important; }").unwrap();
assert_eq!(
rule.action,
Some(CosmeticFilterAction::Style(
"display: none !important;".into()
))
);
assert_eq!(rule.plain_css_selector(), Some("div:has(>div>span.ad)"));
}

#[test]
#[cfg(feature = "css-validation")]
fn abp_style_injection_extended() {
Expand Down
16 changes: 4 additions & 12 deletions tests/unit/lists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,25 +427,17 @@ mod tests {
));
}
{
// AdGuard `#$#` CSS injection is now parsed as a style action.
let input = "nczas.com#$#.adsbygoogle { position: absolute!important; left: -3000px!important; }";
let result = parse_filter(input, true, Default::default());
assert!(matches!(
result,
Err(FilterParseError::Cosmetic(
CosmeticFilterError::UnsupportedSyntax
))
));
assert!(matches!(result, Ok(ParsedFilter::Cosmetic(..))));
}
{
// AdGuard `#@$#` is the exception form of CSS injection.
let input =
"kurnik.pl#@$#.adsbygoogle { height: 1px !important; width: 1px !important; }";
let result = parse_filter(input, true, Default::default());
assert!(matches!(
result,
Err(FilterParseError::Cosmetic(
CosmeticFilterError::UnsupportedSyntax
))
));
assert!(matches!(result, Ok(ParsedFilter::Cosmetic(..))));
}
}
}
Loading