content_blocker/
repr.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use regex::Regex;
6use std::cmp::Ordering;
7use url::Url;
8
9/// A request that could be filtered.
10pub struct Request<'a> {
11    /// The requested URL.
12    pub url: &'a Url,
13    /// The resource type for which this request was initiated.
14    pub resource_type: ResourceType,
15    /// The relationship of this request to the originating document.
16    pub load_type: LoadType,
17}
18
19/// The type of resource being requested.
20#[derive(Copy, Clone, Debug, PartialEq)]
21pub enum ResourceType {
22    /// A top-level document.
23    Document,
24    /// An image subresource.
25    Image,
26    /// A CSS stylesheet subresource.
27    StyleSheet,
28    /// A JavaScript subresource.
29    Script,
30    /// A web font.
31    Font,
32    /// An uncategorized request (eg. XMLHttpRequest).
33    Raw,
34    /// An SVG document.
35    SVGDocument,
36    /// A media resource.
37    Media,
38    /// A popup resource.
39    Popup,
40}
41
42/// A potential list of resource types being requested.
43#[derive(Clone, Debug, PartialEq)]
44pub enum ResourceTypeList {
45    /// All possible types.
46    All,
47    /// An explicit list of resource types.
48    List(Vec<ResourceType>)
49}
50
51/// The type of load that is being initiated.
52#[derive(Copy, Clone, Debug, PartialEq)]
53pub enum LoadType {
54    /// Same-origin with respect to the originating page.
55    FirstParty,
56    /// Cross-origin with respect to the originating page.
57    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/// Conditions which restrict the set of matches for a particular trigger.
95#[derive(Clone, Debug, PartialEq)]
96pub enum DomainConstraint {
97    /// Only trigger if the domain matches one of the included strings.
98    If(DomainMatcher),
99    /// Trigger unless the domain matches one of the included strings.
100    Unless(DomainMatcher),
101}
102
103/// A set of filters that determine if a given rule's action is performed.
104#[derive(Clone, Debug)]
105pub struct Trigger {
106    /// A simple regex that is matched against the characters in the destination resource's URL.
107    pub url_filter: Regex,
108    /// The classes of resources for which this trigger matches.
109    pub resource_type: ResourceTypeList,
110    /// The category of loads for which this trigger matches.
111    pub load_type: Option<LoadType>,
112    /// Domains which modify the behaviour of this trigger, either specifically including or
113    /// excluding from the matches based on string comparison.
114    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/// The action to take for the provided request.
157#[derive(Debug, PartialEq)]
158pub enum Reaction {
159    /// Block the request from starting.
160    Block,
161    /// Strip the HTTP cookies from the request.
162    BlockCookies,
163    /// Hide the elements matching the given CSS selector in the originating document.
164    HideMatchingElements(String)
165}
166
167/// An action to take when a rule is triggered.
168#[derive(Clone, Debug, PartialEq)]
169pub enum Action {
170    /// Prevent the network request from starting.
171    Block,
172    /// Remove any HTTP cookies from the network request before starting it.
173    BlockCookies,
174    /// Hide elements of the requesting page based on the given CSS selector.
175    CssDisplayNone(String),
176    /// Any previously triggered rules do not have their actions performed.
177    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)]
196/// A single rule, consisting of a condition to trigger this rule, and an action to take.
197pub struct Rule {
198    pub trigger: Trigger,
199    pub action: Action,
200}
201
202
203/// Attempt to match the given request against the provided rules. Returns a list
204/// of actions to take in response; an empty list means that the request should
205/// continue unmodified.
206pub 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}