use crate::cosmetic_filter_utils::decode_script_with_permission;
#[cfg(test)]
use crate::filters::cosmetic::CosmeticFilter;
use crate::filters::cosmetic::{CosmeticFilterAction, CosmeticFilterOperator};
use crate::filters::filter_data_context::FilterDataContextRef;
use crate::flatbuffers::containers::flat_map::FlatMapView;
use crate::flatbuffers::containers::flat_multimap::FlatMultiMapView;
use crate::flatbuffers::containers::hash_map::HashMapStringView;
use crate::flatbuffers::containers::hash_set::HashSetView;
use crate::flatbuffers::unsafe_tools::fb_vector_to_slice;
use crate::resources::{PermissionMask, ResourceStorage};
use crate::utils::Hash;
use std::collections::{HashMap, HashSet};
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct UrlSpecificResources {
pub hide_selectors: HashSet<String>,
pub procedural_actions: HashSet<String>,
pub exceptions: HashSet<String>,
pub injected_script: String,
pub generichide: bool,
}
impl UrlSpecificResources {
pub fn empty() -> Self {
Self {
hide_selectors: HashSet::new(),
procedural_actions: HashSet::new(),
exceptions: HashSet::new(),
injected_script: String::new(),
generichide: false,
}
}
}
pub(crate) struct CosmeticFilterCache {
filter_data_context: FilterDataContextRef,
}
#[derive(Deserialize, Serialize, Clone)]
pub struct ProceduralOrActionFilter {
pub selector: Vec<CosmeticFilterOperator>,
#[serde(skip_serializing_if = "Option::is_none")]
pub action: Option<CosmeticFilterAction>,
}
impl ProceduralOrActionFilter {
pub fn as_css(&self) -> Option<(String, String)> {
match (&self.selector[..], &self.action) {
([CosmeticFilterOperator::CssSelector(selector)], None) => {
Some((selector.to_string(), "display: none !important".to_string()))
}
(
[CosmeticFilterOperator::CssSelector(selector)],
Some(CosmeticFilterAction::Style(style)),
) => Some((selector.to_string(), style.to_string())),
_ => None,
}
}
#[cfg(test)]
pub(crate) fn from_css(selector: String, style: String) -> Self {
Self {
selector: vec![CosmeticFilterOperator::CssSelector(selector)],
action: Some(CosmeticFilterAction::Style(style)),
}
}
}
fn hostname_domain_hashes(hostname: &str, domain: &str) -> (Vec<Hash>, Vec<Hash>) {
let request_entities =
crate::filters::cosmetic::get_entity_hashes_from_labels(hostname, domain);
let request_hostnames =
crate::filters::cosmetic::get_hostname_hashes_from_labels(hostname, domain);
(request_entities, request_hostnames)
}
impl CosmeticFilterCache {
pub fn from_context(filter_data_context: FilterDataContextRef) -> Self {
Self {
filter_data_context,
}
}
#[cfg(test)]
pub fn from_rules(rules: Vec<CosmeticFilter>) -> Self {
use crate::engine::Engine;
use crate::FilterSet;
let mut filter_set = FilterSet::new(true);
filter_set.cosmetic_filters = rules;
let engine = Engine::from_filter_set(filter_set, true);
engine.cosmetic_cache()
}
pub fn hidden_class_id_selectors(
&self,
classes: impl IntoIterator<Item = impl AsRef<str>>,
ids: impl IntoIterator<Item = impl AsRef<str>>,
exceptions: &HashSet<String>,
) -> Vec<String> {
let mut selectors = vec![];
let cosmetic_filters = self.filter_data_context.memory.root().cosmetic_filters();
let simple_class_rules = HashSetView::new(cosmetic_filters.simple_class_rules());
let simple_id_rules = HashSetView::new(cosmetic_filters.simple_id_rules());
let complex_class_rules = HashMapStringView::new(
cosmetic_filters.complex_class_rules_index(),
cosmetic_filters.complex_class_rules_values(),
);
let complex_id_rules = HashMapStringView::new(
cosmetic_filters.complex_id_rules_index(),
cosmetic_filters.complex_id_rules_values(),
);
classes.into_iter().for_each(|class| {
let class = class.as_ref();
if simple_class_rules.contains(class) && !exceptions.contains(&format!(".{class}")) {
selectors.push(format!(".{class}"));
}
if let Some(values) = complex_class_rules.get(class) {
for sel in values.data() {
if !exceptions.contains(sel) {
selectors.push(sel.to_string());
}
}
}
});
ids.into_iter().for_each(|id| {
let id = id.as_ref();
if simple_id_rules.contains(id) && !exceptions.contains(&format!("#{id}")) {
selectors.push(format!("#{id}"));
}
if let Some(values) = complex_id_rules.get(id) {
for sel in values.data() {
if !exceptions.contains(sel) {
selectors.push(sel.to_string());
}
}
}
});
selectors
}
pub fn hostname_cosmetic_resources(
&self,
resources: &ResourceStorage,
hostname: &str,
generichide: bool,
) -> UrlSpecificResources {
let domain_str = {
let (start, end) = crate::url_parser::get_host_domain(hostname);
&hostname[start..end]
};
let (request_entities, request_hostnames) = hostname_domain_hashes(hostname, domain_str);
let mut specific_hide_selectors = HashSet::new();
let mut procedural_actions = HashSet::new();
let mut script_injections = HashMap::<&str, PermissionMask>::new();
let mut exceptions = HashSet::new();
let mut except_all_scripts = false;
let hashes: Vec<&Hash> = request_entities
.iter()
.chain(request_hostnames.iter())
.collect();
let cosmetic_filters = self.filter_data_context.memory.root().cosmetic_filters();
let hostname_rules_view = FlatMapView::new(
fb_vector_to_slice(cosmetic_filters.hostname_index()),
cosmetic_filters.hostname_values(),
);
let hostname_hide_view = FlatMultiMapView::new(
fb_vector_to_slice(cosmetic_filters.hostname_hide_index()),
cosmetic_filters.hostname_hide_values(),
);
let hostname_inject_script_view = FlatMultiMapView::new(
fb_vector_to_slice(cosmetic_filters.hostname_inject_script_index()),
cosmetic_filters.hostname_inject_script_values(),
);
for hash in hashes.iter() {
if let Some(hide_iterator) = hostname_hide_view.get(**hash) {
for hide_selector in hide_iterator {
if !exceptions.contains(hide_selector) {
specific_hide_selectors.insert(hide_selector.to_owned());
}
}
}
if let Some(script_iterator) = hostname_inject_script_view.get(**hash) {
for encoded_script in script_iterator {
let (permission, script) = decode_script_with_permission(encoded_script);
script_injections
.entry(script)
.and_modify(|entry| *entry |= permission)
.or_insert(permission);
}
}
if let Some(hostname_rules) = hostname_rules_view.get(**hash) {
if let Some(procedural_actions_rules) = hostname_rules.procedural_action() {
for action in procedural_actions_rules.iter() {
procedural_actions.insert(action.to_owned());
}
}
}
}
for hash in hashes.iter() {
if let Some(hostname_rules) = hostname_rules_view.get(**hash) {
if let Some(unhide_rules) = hostname_rules.unhide() {
for selector in unhide_rules.iter() {
specific_hide_selectors.remove(selector);
exceptions.insert(selector.to_owned());
}
}
if let Some(procedural_exceptions) = hostname_rules.procedural_action_exception() {
for action in procedural_exceptions.iter() {
procedural_actions.remove(action);
}
}
if let Some(uninject_scripts) = hostname_rules.uninject_script() {
for script in uninject_scripts.iter() {
if script.is_empty() {
except_all_scripts = true;
script_injections.clear();
}
if except_all_scripts {
continue;
}
script_injections.remove(script);
}
}
}
}
let hide_selectors = if generichide {
specific_hide_selectors
} else {
let cosmetic_filters = self.filter_data_context.memory.root().cosmetic_filters();
let misc_generic_selectors_vector = cosmetic_filters.misc_generic_selectors();
let mut hide_selectors = HashSet::new();
for selector in misc_generic_selectors_vector.iter() {
if !exceptions.contains(selector) {
hide_selectors.insert(selector.to_string());
}
}
specific_hide_selectors.into_iter().for_each(|sel| {
hide_selectors.insert(sel);
});
hide_selectors
};
let injected_script = resources.get_scriptlet_resources(script_injections);
UrlSpecificResources {
hide_selectors,
procedural_actions,
exceptions,
injected_script,
generichide,
}
}
}
#[cfg(test)]
#[path = "../tests/unit/cosmetic_filter_cache.rs"]
mod unit_tests;