Skip to content
Merged
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
28 changes: 19 additions & 9 deletions src/blocker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ pub struct Blocker {
pub(crate) filter_data_context: FilterDataContextRef,
}

#[cfg(feature = "single-thread")]
pub(crate) type RegexManagerRef<'a> = std::cell::RefMut<'a, RegexManager>;
#[cfg(not(feature = "single-thread"))]
pub(crate) type RegexManagerRef<'a> = std::sync::MutexGuard<'a, RegexManager>;

impl Blocker {
/// Decide if a network request (usually from WebRequest API) should be
/// blocked, redirected or allowed.
Expand Down Expand Up @@ -130,31 +135,36 @@ impl Blocker {
self.get_list(NetworkFilterListId::TaggedFiltersAll)
}

#[cfg(feature = "single-thread")]
fn borrow_regex_manager(&self) -> std::cell::RefMut<'_, RegexManager> {
/// Borrow mutable reference to the regex manager for the ['Blocker`].
/// Only one caller can borrow the regex manager at a time.
pub(crate) fn borrow_regex_manager(&self) -> RegexManagerRef<'_> {
#[cfg(feature = "single-thread")]
#[allow(unused_mut)]
let mut manager = self.regex_manager.borrow_mut();
#[cfg(not(feature = "single-thread"))]
let mut manager = self.regex_manager.lock().unwrap();

#[cfg(not(target_arch = "wasm32"))]
manager.update_time();

manager
}

#[cfg(not(feature = "single-thread"))]
fn borrow_regex_manager(&self) -> std::sync::MutexGuard<'_, RegexManager> {
let mut manager = self.regex_manager.lock().unwrap();
manager.update_time();
manager
}

pub fn check_generic_hide(&self, hostname_request: &Request) -> bool {
let mut regex_manager = self.borrow_regex_manager();
self.generic_hide()
.check(hostname_request, &HashSet::new(), &mut regex_manager)
.is_some()
}

#[cfg(test)]
pub(crate) fn check_exceptions(&self, request: &Request) -> bool {
let mut regex_manager = self.borrow_regex_manager();
self.exceptions()
.check(request, &HashSet::new(), &mut regex_manager)
.is_some()
}

pub fn check_parameterised(
&self,
request: &Request,
Expand Down
10 changes: 10 additions & 0 deletions src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ impl Engine {
self.blocker.check(request, &self.resources)
}

#[cfg(test)]
pub(crate) fn check_network_request_exceptions(&self, request: &Request) -> bool {
self.blocker.check_exceptions(request)
}

pub fn check_network_request_subset(
&self,
request: &Request,
Expand Down Expand Up @@ -266,6 +271,11 @@ impl Engine {
self.blocker.set_regex_discard_policy(new_discard_policy);
}

#[cfg(test)]
pub fn borrow_regex_manager(&self) -> crate::blocker::RegexManagerRef<'_> {
self.blocker.borrow_regex_manager()
}

#[cfg(feature = "debug-info")]
pub fn discard_regex(&mut self, regex_id: u64) {
self.blocker.discard_regex(regex_id);
Expand Down
36 changes: 12 additions & 24 deletions src/filters/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,18 @@ impl NetworkFilter {
FilterTokens::Other(t)
}
}

#[cfg(test)]
pub(crate) fn matches_test(&self, request: &request::Request) -> bool {
let filter_set = crate::FilterSet::new_with_rules(vec![self.clone()], vec![], true);
let engine = crate::Engine::from_filter_set(filter_set, true);

if self.is_exception() {
engine.check_network_request_exceptions(request)
} else {
engine.check_network_request(request).matched
}
}
}

impl NetworkFilterMaskHelper for NetworkFilter {
Expand All @@ -993,30 +1005,6 @@ pub trait NetworkMatchable {
fn matches_test(&self, request: &request::Request) -> bool;
}

impl NetworkMatchable for NetworkFilter {
fn matches(&self, request: &request::Request, regex_manager: &mut RegexManager) -> bool {
use crate::filters::network_matchers::{
check_excluded_domains, check_included_domains, check_options, check_pattern,
};
check_options(self.mask, request)
&& check_included_domains(self.opt_domains.as_deref(), request)
&& check_excluded_domains(self.opt_not_domains.as_deref(), request)
&& check_pattern(
self.mask,
self.filter.iter(),
self.hostname.as_deref(),
(self as *const NetworkFilter) as u64,
request,
regex_manager,
)
}

#[cfg(test)]
fn matches_test(&self, request: &request::Request) -> bool {
self.matches(request, &mut RegexManager::default())
}
}

// ---------------------------------------------------------------------------
// Filter parsing
// ---------------------------------------------------------------------------
Expand Down
35 changes: 0 additions & 35 deletions src/filters/network_matchers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,22 +414,6 @@ pub fn check_options(mask: NetworkFilterMask, request: &request::Request) -> boo
true
}

#[inline]
pub fn check_included_domains(opt_domains: Option<&[Hash]>, request: &request::Request) -> bool {
// Source URL must be among these domains to match
if let Some(included_domains) = opt_domains.as_ref() {
if let Some(source_hashes) = request.source_hostname_hashes.as_ref() {
if source_hashes
.iter()
.all(|h| !utils::bin_lookup(included_domains, *h))
{
return false;
}
}
}
true
}

#[inline]
pub fn check_included_domains_mapped(
opt_domains: Option<&[u32]>,
Expand All @@ -451,25 +435,6 @@ pub fn check_included_domains_mapped(
true
}

#[inline]
pub fn check_excluded_domains(
opt_not_domains: Option<&[Hash]>,
request: &request::Request,
) -> bool {
if let Some(excluded_domains) = opt_not_domains.as_ref() {
if let Some(source_hashes) = request.source_hostname_hashes.as_ref() {
if source_hashes
.iter()
.any(|h| utils::bin_lookup(excluded_domains, *h))
{
return false;
}
}
}

true
}

#[inline]
pub fn check_excluded_domains_mapped(
opt_not_domains: Option<&[u32]>,
Expand Down
2 changes: 1 addition & 1 deletion src/regex_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use std::time::Duration;

#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
use mock_instant::global::Instant;
use mock_instant::thread_local::Instant;
#[cfg(not(test))]
#[cfg(not(target_arch = "wasm32"))]
use std::time::Instant;
Expand Down
35 changes: 18 additions & 17 deletions tests/legacy_harness.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
mod legacy_test_filters {
use adblock::filters::network::NetworkFilter;
use adblock::filters::network::NetworkFilterMask;
use adblock::filters::network::NetworkMatchable;
use adblock::regex_manager::RegexManager;
use adblock::request::Request;
use adblock::Engine;

fn test_filter<'a>(
raw_filter: &str,
Expand Down Expand Up @@ -35,12 +34,15 @@ mod legacy_test_filters {
filter.filter
);

let engine = Engine::from_rules_debug([raw_filter], Default::default());

for to_block in blocked {
assert!(
filter.matches(
&Request::new(to_block, "https://example.com", "other").unwrap(),
&mut RegexManager::default()
),
engine
.check_network_request(
&Request::new(to_block, "https://example.com", "other").unwrap(),
)
.matched,
"Expected filter {} to match {}",
raw_filter,
&to_block
Expand All @@ -49,10 +51,11 @@ mod legacy_test_filters {

for to_pass in not_blocked {
assert!(
!filter.matches(
&Request::new(to_pass, "https://example.com", "other").unwrap(),
&mut RegexManager::default()
),
!engine
.check_network_request(
&Request::new(to_pass, "https://example.com", "other").unwrap(),
)
.matched,
"Expected filter {} to pass {}",
raw_filter,
&to_pass
Expand Down Expand Up @@ -302,14 +305,12 @@ mod legacy_test_filters {
);

// explicit, separate testcase construction of the "script" option as it is not the deafult
let filter = NetworkFilter::parse(
"||googlesyndication.com/safeframe/$third-party,script",
true,
Default::default(),
)
.unwrap();
let request = Request::new("http://tpc.googlesyndication.com/safeframe/1-0-2/html/container.html#xpc=sf-gdn-exp-2&p=http%3A//slashdot.org;", "https://this-is-always-third-party.com", "script").unwrap();
assert!(filter.matches(&request, &mut RegexManager::default()));
let engine = Engine::from_rules_debug(
["||googlesyndication.com/safeframe/$third-party,script"],
Default::default(),
);
assert!(engine.check_network_request(&request).matched);
}
}

Expand Down
39 changes: 29 additions & 10 deletions tests/matching.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use adblock::filters::network::{NetworkFilter, NetworkFilterMaskHelper, NetworkMatchable};
use adblock::regex_manager::RegexManager;
use adblock::filters::network::{NetworkFilter, NetworkFilterMask, NetworkFilterMaskHelper};
use adblock::request::Request;
use adblock::resources::{MimeType, Resource, ResourceType};
use adblock::Engine;
Expand Down Expand Up @@ -70,18 +69,38 @@ fn check_filter_matching() {
"Could not parse filter {filter}"
);
let network_filter = network_filter_res.unwrap();
let mut filters = vec![network_filter.clone()];
if network_filter.is_exception() {
let mut original_filter = network_filter.clone();
original_filter
.mask
.set(NetworkFilterMask::IS_EXCEPTION, false);
filters.push(original_filter);
}
let filter_set = adblock::FilterSet::new_with_rules(filters, vec![], true);
let engine = adblock::Engine::from_filter_set(filter_set, true);

let request_res = Request::new(&req.url, &req.sourceUrl, &req.r#type);
// The dataset has cases where URL is set to just "http://" or "https://", which we do not support
if let Ok(request) = request_res {
assert!(
network_filter.matches(&request, &mut RegexManager::default()),
"Expected {} to match {} at {}, typed {}",
filter,
req.url,
req.sourceUrl,
req.r#type
);
let result = engine.check_network_request(&request);
if !network_filter.is_exception() {
assert!(
result.matched,
"Expected {} to match {} at {}, typed {}",
filter, req.url, req.sourceUrl, req.r#type
);
} else {
assert!(
!result.matched && result.exception.is_some(),
"Expected {} exception to match {} at {}, typed {}",
filter,
req.url,
req.sourceUrl,
req.r#type
);
}

requests_checked += 1;
}
}
Expand Down
63 changes: 52 additions & 11 deletions tests/unit/filters/network_matchers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,9 +350,38 @@ mod match_tests {
}

fn check_options(filter: &NetworkFilter, request: &request::Request) -> bool {
let mut mapping = HashMap::new();
let opt_domains = filter.opt_domains.clone().map(|domains| {
domains
.iter()
.map(|domain| {
mapping.insert(*domain, *domain as u32);
*domain as u32
})
.collect::<Vec<u32>>()
});

let opt_not_domains = filter.opt_not_domains.clone().map(|domains| {
domains
.iter()
.map(|domain| {
mapping.insert(*domain, *domain as u32);
*domain as u32
})
.collect::<Vec<u32>>()
});

super::super::check_options(filter.mask, request)
&& super::super::check_included_domains(filter.opt_domains.as_deref(), request)
&& super::super::check_excluded_domains(filter.opt_not_domains.as_deref(), request)
&& super::super::check_included_domains_mapped(
opt_domains.as_deref(),
request,
&mapping,
)
&& super::super::check_excluded_domains_mapped(
opt_not_domains.as_deref(),
request,
&mapping,
)
}

#[test]
Expand Down Expand Up @@ -659,22 +688,34 @@ mod match_tests {
}

#[test]
#[ignore] // Not going to handle lookaround regexes
#[cfg(feature = "debug-info")]
fn check_lookaround_regex_handled() {
{
use crate::Engine;
let filter = r#"/^https?:\/\/([0-9a-z\-]+\.)?(9anime|animeland|animenova|animeplus|animetoon|animewow|gamestorrent|goodanime|gogoanime|igg-games|kimcartoon|memecenter|readcomiconline|toonget|toonova|watchcartoononline)\.[a-z]{2,4}\/(?!([Ee]xternal|[Ii]mages|[Ss]cripts|[Uu]ploads|ac|ajax|assets|combined|content|cov|cover|(img\/bg)|(img\/icon)|inc|jwplayer|player|playlist-cat-rss|static|thumbs|wp-content|wp-includes)\/)(.*)/$image,other,script,~third-party,xmlhttprequest,domain=~animeland.hu"#;
let network_filter = NetworkFilter::parse(filter, true, Default::default()).unwrap();
let url = "https://data.foo.com/9VjjrjU9Or2aqkb8PDiqTBnULPgeI48WmYEHkYer";
let source = "http://123movies.com";
let engine = Engine::from_rules(vec![filter], Default::default());
let url = "https://9anime.to/watch/episode-1";
let source = "https://9anime.to";
let request = request::Request::new(url, source, "script").unwrap();
let mut regex_manager = RegexManager::default();
assert!(regex_manager.get_compiled_regex_count() == 0);
assert_eq!(
engine
.get_debug_info()
.regex_debug_info
.compiled_regex_count,
0
);
// Regex can't be compiled, so no match.
assert!(
network_filter.matches(&request, &mut regex_manager),
"Expected match for {filter} on {url}"
!engine.check_network_request(&request).matched,
"Expected no match for {filter} on {url}"
);
assert_eq!(
engine
.get_debug_info()
.regex_debug_info
.compiled_regex_count,
1
);
assert!(regex_manager.get_compiled_regex_count() == 1);
}
}

Expand Down
Loading
Loading