1use regex::Regex;
6use std::cmp::Ordering;
7use url::Url;
8
9pub struct Request<'a> {
11 pub url: &'a Url,
13 pub resource_type: ResourceType,
15 pub load_type: LoadType,
17}
18
19#[derive(Copy, Clone, Debug, PartialEq)]
21pub enum ResourceType {
22 Document,
24 Image,
26 StyleSheet,
28 Script,
30 Font,
32 Raw,
34 SVGDocument,
36 Media,
38 Popup,
40}
41
42#[derive(Clone, Debug, PartialEq)]
44pub enum ResourceTypeList {
45 All,
47 List(Vec<ResourceType>)
49}
50
51#[derive(Copy, Clone, Debug, PartialEq)]
53pub enum LoadType {
54 FirstParty,
56 ThirdParty,
58}
59
60#[derive(Clone, Debug, PartialEq)]
61pub struct DomainMatcher {
62 pub exact: Box<[String]>,
63 pub subdomain: Box<[String]>,
64}
65
66impl DomainMatcher {
67 fn matches(&self, url: &Url) -> bool {
68 let domain = match url.domain() {
69 Some(domain) => domain,
70 None => return false,
71 };
72 for candidate in &*self.exact {
73 if domain == candidate {
74 return true;
75 }
76 }
77 for suffix in &*self.subdomain {
78 match domain.len().cmp(&suffix.len()) {
79 Ordering::Equal if domain == suffix => return true,
80 Ordering::Greater => {
81 if domain.as_bytes()[domain.len() - suffix.len() - 1] == b'.' {
82 if domain.ends_with(suffix) {
83 return true;
84 }
85 }
86 }
87 _ => {}
88 }
89 }
90 false
91 }
92}
93
94#[derive(Clone, Debug, PartialEq)]
96pub enum DomainConstraint {
97 If(DomainMatcher),
99 Unless(DomainMatcher),
101}
102
103#[derive(Clone, Debug)]
105pub struct Trigger {
106 pub url_filter: Regex,
108 pub resource_type: ResourceTypeList,
110 pub load_type: Option<LoadType>,
112 pub domain_constraint: Option<DomainConstraint>,
115}
116
117impl Trigger {
118 fn matches(&self, request: &Request) -> bool {
119 if let ResourceTypeList::List(ref types) = self.resource_type {
120 if types.iter().find(|t| **t == request.resource_type).is_none() {
121 return false;
122 }
123 }
124
125 if let Some(ref load_type) = self.load_type {
126 if request.load_type != *load_type {
127 return false;
128 }
129 }
130
131 if self.url_filter.is_match(request.url.as_str()) {
132 match self.domain_constraint {
133 Some(DomainConstraint::If(ref matcher)) => {
134 return matcher.matches(&request.url);
135 }
136 Some(DomainConstraint::Unless(ref matcher)) => {
137 return !matcher.matches(&request.url);
138 }
139 None => return true,
140 }
141 }
142
143 false
144 }
145}
146
147impl PartialEq for Trigger {
148 fn eq(&self, other: &Trigger) -> bool {
149 self.url_filter.as_str() == other.url_filter.as_str() &&
150 self.resource_type == other.resource_type &&
151 self.load_type == other.load_type &&
152 self.domain_constraint == other.domain_constraint
153 }
154}
155
156#[derive(Debug, PartialEq)]
158pub enum Reaction {
159 Block,
161 BlockCookies,
163 HideMatchingElements(String)
165}
166
167#[derive(Clone, Debug, PartialEq)]
169pub enum Action {
170 Block,
172 BlockCookies,
174 CssDisplayNone(String),
176 IgnorePreviousRules,
178}
179
180impl Action {
181 fn process(&self, reactions: &mut Vec<Reaction>) {
182 match *self {
183 Action::Block =>
184 reactions.push(Reaction::Block),
185 Action::BlockCookies =>
186 reactions.push(Reaction::BlockCookies),
187 Action::CssDisplayNone(ref selector) =>
188 reactions.push(Reaction::HideMatchingElements(selector.clone())),
189 Action::IgnorePreviousRules =>
190 reactions.clear(),
191 }
192 }
193}
194
195#[derive(Clone, Debug, PartialEq)]
196pub struct Rule {
198 pub trigger: Trigger,
199 pub action: Action,
200}
201
202
203pub fn process_rules_for_request_impl(rules: &[Rule], request: &Request) -> Vec<Reaction> {
207 let mut reactions = vec![];
208 for rule in rules {
209 if rule.trigger.matches(request) {
210 rule.action.process(&mut reactions);
211 }
212 }
213 reactions
214}