//! Holds [`Blocker`], which handles all network-based adblocking queries.
use memchr::{memchr as find_char, memrchr as find_char_reverse}; use once_cell::sync::Lazy; use serde::Serialize; use std::collections::HashSet; use std::ops::DerefMut;
/// Options used when constructing a [`Blocker`]. pubstruct BlockerOptions { pub enable_optimizations: bool,
}
/// Describes how a particular network request should be handled. #[derive(Debug, Serialize, Default)] pubstruct BlockerResult { /// Was a blocking filter matched for this request? pub matched: bool, /// Important is used to signal that a rule with the `important` option /// matched. An `important` match means that exceptions should not apply /// and no further checking is neccesary--the request should be blocked /// (empty body or cancelled). /// /// Brave Browser keeps multiple instances of [`Blocker`], so `important` /// here is used to correct behaviour between them: checking should stop /// instead of moving to the next instance iff an `important` rule matched. pub important: bool, /// Specifies what to load instead of the original request, rather than /// just blocking it outright. This can come from a filter with a `redirect` /// or `redirect-rule` option. If present, the field will contain the body /// of the redirect to be injected. /// /// Note that the presence of a redirect does _not_ imply that the request /// should be blocked. The `redirect-rule` option can produce a redirection /// that's only applied if another blocking filter matches a request. pub redirect: Option<String>, /// `removeparam` may remove URL parameters. If the original request URL was /// modified at all, the new version will be here. This should be used /// as long as the request is not blocked. pub rewritten_url: Option<String>, /// Contains a string representation of any matched exception rule. /// Effectively this means that there was a match, but the request should /// not be blocked. /// /// If debugging was _not_ enabled (see [`crate::FilterSet::new`]), this /// will only contain a constant `"NetworkFilter"` placeholder string. pub exception: Option<String>, /// When `matched` is true, this contains a string representation of the /// matched blocking rule. /// /// If debugging was _not_ enabled (see [`crate::FilterSet::new`]), this /// will only contain a constant `"NetworkFilter"` placeholder string. pub filter: Option<String>,
}
// only check for tags in tagged and exception rule buckets, // pass empty set for the rest static NO_TAGS: Lazy<HashSet<String>> = Lazy::new(HashSet::new);
/// Stores network filters for efficient querying. pubstruct Blocker { // Enabled tags are not serialized - when deserializing, tags of the existing // instance (the one we are recreating lists into) are maintained pub(crate) tags_enabled: HashSet<String>, // Not serialized #[cfg(feature = "single-thread")] pub(crate) regex_manager: std::cell::RefCell<RegexManager>, #[cfg(not(feature = "single-thread"))] pub(crate) regex_manager: std::sync::Mutex<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)] letmut manager = self.regex_manager.borrow_mut(); #[cfg(not(feature = "single-thread"))] letmut manager = self.regex_manager.lock().unwrap();
// Check the filters in the following order: // 1. $important (not subject to exceptions) // 2. redirection ($redirect=resource) // 3. normal filters - if no match by then // 4. exceptions - if any non-important match of forced
// Always check important filters let important_filter = self
.importants()
.check(request, &NO_TAGS, &mut regex_manager);
// only check the rest of the rules if not previously matched let filter = if important_filter.is_none() && !matched_rule { self.tagged_filters_all()
.check(request, &self.tags_enabled, &mut regex_manager)
.or_else(|| self.filters().check(request, &NO_TAGS, &mut regex_manager))
} else {
important_filter
};
let exception = match filter.as_ref() { // if no other rule matches, only check exceptions if forced to
None if matched_rule || force_check_exceptions => { self.exceptions()
.check(request, &self.tags_enabled, &mut regex_manager)
}
None => None, // If matched an important filter, exceptions don't atter
Some(f) if f.is_important() => None,
Some(_) => self
.exceptions()
.check(request, &self.tags_enabled, &mut regex_manager),
};
let redirect_filters = self.redirects()
.check_all(request, &NO_TAGS, regex_manager.deref_mut());
// Extract the highest priority redirect directive. // 1. Exceptions - can bail immediately if found // 2. Find highest priority non-exception redirect let redirect_resource = { letmut exceptions = vec![]; for redirect_filter in redirect_filters.iter() { if redirect_filter.is_exception() { iflet Some(redirect) = redirect_filter.modifier_option.as_ref() {
exceptions.push(redirect);
}
}
} letmut resource_and_priority = None; for redirect_filter in redirect_filters.iter() { if !redirect_filter.is_exception() { iflet Some(redirect) = redirect_filter.modifier_option.as_ref() { if !exceptions.contains(&redirect) { // parse redirect + priority let (resource, priority) = iflet Some(idx) = find_char_reverse(b':', redirect.as_bytes()) { let priority_str = &redirect[idx + 1..]; let resource = &redirect[..idx]; iflet Ok(priority) = priority_str.parse::<i32>() {
(resource, priority)
} else {
(&redirect[..], 0)
}
} else {
(&redirect[..], 0)
}; iflet Some((_, p1)) = resource_and_priority { if priority > p1 {
resource_and_priority = Some((resource, priority));
}
} else {
resource_and_priority = Some((resource, priority));
}
}
}
}
}
resource_and_priority.map(|(r, _)| r)
};
let redirect: Option<String> = redirect_resource.and_then(|resource_name| {
resources.get_redirect_resource(resource_name).or({ // It's acceptable to pass no redirection if no matching resource is loaded. // TODO - it may be useful to return a status flag to indicate that this occurred. #[cfg(test)]
eprintln!("Matched rule with redirect option but did not find corresponding resource to send");
None
})
});
let important = filter.is_some()
&& filter
.as_ref()
.map(|f| f.is_important())
.unwrap_or_else(|| false);
let rewritten_url = if important {
None
} else { Self::apply_removeparam(&self.removeparam(), request, regex_manager.deref_mut())
};
// If something has already matched before but we don't know what, still return a match let matched = exception.is_none() && (filter.is_some() || matched_rule);
BlockerResult {
matched,
important,
redirect,
rewritten_url,
exception: exception.as_ref().map(|f| f.to_string()), // copy the exception
filter: filter.as_ref().map(|f| f.to_string()), // copy the filter
}
}
fn apply_removeparam(
removeparam_filters: &NetworkFilterList,
request: &Request,
regex_manager: &mut RegexManager,
) -> Option<String> { /// Represents an `&`-separated argument from a URL query parameter string enum QParam<'a> { /// Just a key, e.g. `...&key&...`
KeyOnly(&'a str), /// Key-value pair separated by an equal sign, e.g. `...&key=value&...`
KeyValue(&'a str, &'a str),
}
let url = &request.original_url; // Only check for removeparam if there's a query string in the request URL iflet Some(i) = find_char(b'?', url.as_bytes()) { // String indexing safety: indices come from `.len()` or `find_char` on individual ASCII // characters (1 byte each), some plus 1. let params_start = i + 1; let hash_index = iflet Some(j) = find_char(b'#', &url.as_bytes()[params_start..]) {
params_start + j
} else {
url.len()
}; let qparams = &url[params_start..hash_index]; letmut params: Vec<(QParam, bool)> = qparams
.split('&')
.map(|pair| { iflet Some((k, v)) = pair.split_once('=') {
QParam::KeyValue(k, v)
} else {
QParam::KeyOnly(pair)
}
})
.map(|param| (param, true))
.collect();
let filters = removeparam_filters.check_all(request, &NO_TAGS, regex_manager); letmut rewrite = false; for removeparam_filter in filters { iflet Some(removeparam) = &removeparam_filter.modifier_option {
params.iter_mut().for_each(|(param, include)| { iflet QParam::KeyValue(k, v) = param { if !v.is_empty() && k == removeparam {
*include = false;
rewrite = true;
}
}
});
}
} if rewrite { let p = itertools::join(
params
.into_iter()
.filter(|(_, include)| *include)
.map(|(param, _)| param.to_string()), "&",
); let new_param_str = if p.is_empty() {
String::from("")
} else {
format!("?{p}")
};
Some(format!( "{}{}{}",
&url[0..i],
new_param_str,
&url[hash_index..]
))
} else {
None
}
} else {
None
}
}
/// Given a "main_frame" or "subdocument" request, check if some content security policies /// should be injected in the page. pubfn get_csp_directives(&self, request: &Request) -> Option<String> { usecrate::request::RequestType;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.