html_filter/filter/
element.rs

1//! Keeps track of rules applied on attributes or tags. They can
2//! either be blacklisted or whitelisted by the user. This module handles the
3//! logic for the combination of these rules.
4
5use std::collections::HashMap;
6
7use crate::types::tag::Attribute;
8
9/// Stores the status of an element, i.e., whether it ought to be kept or
10/// removed.
11///
12/// This contains only the explicit rules given by the user at the definition of
13/// [`super::Filter`].
14///
15/// It contains a `whitelist` and a `blacklist` to keep track of the filtering
16/// parameters.
17#[derive(Debug)]
18pub struct BlackWhiteList {
19    /// Default behaviour
20    ///
21    /// Only is used when checking for emptiness
22    default: bool,
23    /// Contains the elements and their status
24    ///
25    /// The hashmap maps a name to a target, and a bool. The boolean is `true`
26    /// if the item is whitelisted, and `false` if the item is blacklisted.
27    items: HashMap<String, bool>,
28    /// Indicates if a whitelisted element was pushed into the [`HashMap`].
29    whitelist_empty: bool,
30}
31
32impl BlackWhiteList {
33    /// Check the status of an element
34    pub fn check(&self, name: &str) -> ElementState {
35        self.items.get(name).map_or_else(
36            || {
37                if self.is_empty() && self.default {
38                    ElementState::NotSpecified
39                } else {
40                    ElementState::BlackListed
41                }
42            },
43            |keep| match keep {
44                true => ElementState::WhiteListed,
45                false => ElementState::BlackListed,
46            },
47        )
48    }
49
50    /// Checks if no elements were specified
51    pub const fn is_empty(&self) -> bool {
52        self.whitelist_empty
53    }
54
55    /// Checks if a name was explicitly blacklisted
56    pub fn is_explicitly_blacklisted(&self, name: &str) -> bool {
57        self.items.get(name).map_or_else(|| false, |keep| !*keep)
58    }
59
60    /// Pushes an element as whitelisted or blacklisted
61    pub fn push(&mut self, name: String, keep: bool) -> Result<(), ()> {
62        if keep {
63            self.whitelist_empty = false;
64        }
65        let old = self.items.insert(name, keep);
66        if old.is_some_and(|inner| inner != keep) {
67            Err(())
68        } else {
69            Ok(())
70        }
71    }
72
73    /// Sets the default rule
74    ///
75    /// If no rule is specified for the given tag, default is applied.
76    pub const fn set_default(&mut self, default: bool) {
77        self.default = default;
78    }
79}
80
81impl Default for BlackWhiteList {
82    fn default() -> Self {
83        Self { items: HashMap::new(), whitelist_empty: true, default: true }
84    }
85}
86
87/// Status of an element
88///
89/// An element can be whitelisted or blacklisted by the user. This state
90/// contains both information.
91#[derive(Debug)]
92pub enum ElementState {
93    /// Element ought to be removed
94    BlackListed,
95    /// No rules applied for this element
96    NotSpecified,
97    /// Element ought to be kept
98    WhiteListed,
99}
100
101impl ElementState {
102    /// Computes the output status for multiple checks
103    ///
104    /// This is used to perform multiple successive tests.
105    pub const fn and(&self, other: &Self) -> Self {
106        match (self, other) {
107            (Self::BlackListed, _) | (_, Self::BlackListed) => Self::BlackListed,
108            (Self::NotSpecified, Self::NotSpecified) => Self::NotSpecified,
109            // in this arm, at least one is WhiteListed, because the other case is above.
110            (Self::WhiteListed | Self::NotSpecified, Self::WhiteListed | Self::NotSpecified) =>
111                Self::WhiteListed,
112        }
113    }
114
115    /// Checks if an element was explicitly authorised, i.e., is whitelisted
116    pub const fn is_allowed_or(&self, default: bool) -> bool {
117        match self {
118            Self::BlackListed => false,
119            Self::NotSpecified => default,
120            Self::WhiteListed => true,
121        }
122    }
123}
124
125/// Rules for associating names to values
126//TODO: could add a default to create a method: exact_attributes
127#[derive(Default, Debug)]
128pub struct ValueAssociateHash {
129    /// Names and attributes explicitly not wanted
130    blacklist: Vec<(String, Option<String>)>,
131    /// Names and attributes explicitly wanted
132    whitelist: Vec<(String, Option<String>)>,
133}
134
135impl ValueAssociateHash {
136    /// Checks if the attributes form a correct combination of rules
137    pub fn check(&self, attrs: &[Attribute]) -> ElementState {
138        let attrs_map: HashMap<_, _> = attrs
139            .iter()
140            .map(|attr| (attr.as_name().to_string(), attr.as_value()))
141            .collect();
142        for (wanted_name, wanted_value) in &self.whitelist {
143            match attrs_map.get(wanted_name) {
144                None => return ElementState::BlackListed,
145                Some(found_value) if *found_value != wanted_value.as_ref() =>
146                    return ElementState::BlackListed,
147                Some(_) => (),
148            }
149        }
150        for (wanted_name, wanted_value) in &self.blacklist {
151            match attrs_map.get(wanted_name) {
152                Some(found_value) if *found_value == wanted_value.as_ref() =>
153                    return ElementState::BlackListed,
154                Some(_) | None => (),
155            }
156        }
157        if self.is_empty() {
158            ElementState::NotSpecified
159        } else {
160            ElementState::WhiteListed
161        }
162    }
163
164    /// Checks if the [`ValueAssociateHash`] wasn't given any rules.
165    pub const fn is_empty(&self) -> bool {
166        self.whitelist.is_empty() && self.blacklist.is_empty()
167    }
168
169    /// Checks if one of the attributes was explicitly blacklisted
170    pub fn is_explicitly_blacklisted(&self, attrs: &[Attribute]) -> bool {
171        let blacklist = self
172            .blacklist
173            .iter()
174            .map(|(name, value)| (name, value))
175            .collect::<HashMap<_, _>>();
176        for attr in attrs {
177            if let Some(value) = blacklist.get(&attr.as_name().to_string()) {
178                if attr.as_value() == value.as_ref() {
179                    return true;
180                }
181            }
182        }
183        false
184    }
185
186    /// Adds a rule for the attribute `name`
187    pub fn push(&mut self, name: String, value: Option<String>, keep: bool) {
188        let () = if keep {
189            self.whitelist.push((name, value));
190        } else {
191            self.blacklist.push((name, value));
192        };
193    }
194}