https_everywhere_lib_core/
rewriter.rs1use url::Url;
2use std::error::Error;
3use regex::Regex;
4
5use crate::{RuleSet, RuleSets, Storage};
6#[cfg(all(test,feature="add_rulesets"))]
7use crate::rulesets::tests as rulesets_tests;
8
9#[derive(Debug)]
12#[derive(PartialEq)]
13pub enum RewriteAction {
14 CancelRequest,
15 NoOp,
16 RewriteUrl(String),
17}
18
19pub struct Rewriter<'a> {
22 rulesets: &'a RuleSets,
23 storage: &'a dyn Storage,
24}
25
26impl<'a> Rewriter<'a> {
27 pub fn new(rulesets: &'a RuleSets, storage: &'a dyn Storage) -> Rewriter<'a> {
34 Rewriter {
35 rulesets,
36 storage,
37 }
38 }
39
40 pub fn rewrite_url(&self, url: &String) -> Result<RewriteAction, Box<dyn Error>> {
47 if let Some(false) = self.storage.get_bool(String::from("global_enabled")){
48 return Ok(RewriteAction::NoOp);
49 }
50
51 let mut url = Url::parse(url)?;
52 if let Some(hostname) = url.host_str() {
53 let mut hostname = hostname.trim_end_matches('.');
54 if hostname.len() == 0 {
55 hostname = ".";
56 }
57 let hostname = hostname.to_string();
58
59 let mut should_cancel = false;
60 let http_nowhere_on = self.storage.get_bool(String::from("http_nowhere_on"));
61 if let Some(true) = http_nowhere_on {
62 if url.scheme() == "http" || url.scheme() == "ftp" {
63 let num_localhost = Regex::new(r"^127(\.[0-9]{1,3}){3}$").unwrap();
64 if !hostname.ends_with(".onion") &&
65 hostname != "localhost".to_string() &&
66 !num_localhost.is_match(&hostname) &&
67 hostname != "0.0.0.0".to_string() &&
68 hostname != "[::1]".to_string() {
69 should_cancel = true;
70 }
71 }
72 }
73 let mut using_credentials_in_url = false;
74 let tmp_url = url.clone();
75 if url.username() != "" || url.password() != None {
76 using_credentials_in_url = true;
77 url.set_username("").unwrap();
78 url.set_password(None).unwrap();
79 }
80
81 let mut new_url: Option<Url> = None;
82
83 let mut apply_if_active = |ruleset: &RuleSet| {
84 if ruleset.active && new_url.is_none() {
85 new_url = match ruleset.apply(url.as_str()) {
86 None => None,
87 Some(url_str) => Some(Url::parse(&url_str).unwrap())
88 };
89 }
90 };
91
92
93 for ruleset in self.rulesets.potentially_applicable(&hostname) {
94 if let Some(scope) = (*ruleset.scope).clone() {
95 let scope_regex = Regex::new(&scope).unwrap();
96 if scope_regex.is_match(url.as_str()) {
97 apply_if_active(&ruleset);
98 }
99 } else {
100 apply_if_active(&ruleset);
101 }
102 }
103
104 if using_credentials_in_url {
105 match &mut new_url {
106 None => {
107 url.set_username(tmp_url.username()).unwrap();
108 url.set_password(tmp_url.password()).unwrap();
109 },
110 Some(url) => {
111 url.set_username(tmp_url.username()).unwrap();
112 url.set_password(tmp_url.password()).unwrap();
113 }
114 }
115 }
116
117 if let Some(true) = http_nowhere_on {
118 if should_cancel {
119 if new_url.is_none() {
120 return Ok(RewriteAction::CancelRequest);
121 }
122 }
123
124 if let Some(url) = &new_url {
126 if url.as_str().starts_with("http:") ||
127 url.as_str().starts_with("ftp:") {
128 return Ok(RewriteAction::CancelRequest);
129 }
130 }
131 }
132
133 if let Some(url) = new_url {
134 info!("rewrite_url returning redirect url: {}", url.as_str());
135 Ok(RewriteAction::RewriteUrl(url.as_str().to_string()))
136 } else {
137 Ok(RewriteAction::NoOp)
138 }
139 } else {
140 Ok(RewriteAction::NoOp)
141 }
142 }
143}
144
145#[cfg(all(test,feature="add_rulesets"))]
146mod tests {
147 use super::*;
148 use multi_default_trait_impl::{default_trait_impl, trait_impl};
149
150 #[default_trait_impl]
151 impl Storage for DefaultStorage {
152 fn get_int(&self, _key: String) -> Option<usize> { Some(5) }
153 fn set_int(&self, _key: String, _value: usize) {}
154 fn get_string(&self, _key: String) -> Option<String> { Some(String::from("test")) }
155 fn set_string(&self, _key: String, _value: String) {}
156 fn get_bool(&self, key: String) -> Option<bool> {
157 if key == String::from("http_nowhere_on") {
158 Some(false)
159 } else {
160 Some(true)
161 }
162 }
163 fn set_bool(&self, _key: String, _value: bool) {}
164 }
165
166 struct TestStorage;
167 #[trait_impl]
168 impl DefaultStorage for TestStorage {
169 }
170
171 struct HttpNowhereOnStorage;
172 #[trait_impl]
173 impl DefaultStorage for HttpNowhereOnStorage {
174 fn get_bool(&self, _key: String) -> Option<bool> { Some(true) }
175 }
176
177 #[test]
178 fn rewrite_url() {
179 let mut rs = RuleSets::new();
180 rulesets_tests::add_mock_rulesets(&mut rs);
181
182 let rw = Rewriter::new(&rs, &TestStorage);
183
184 assert_eq!(
185 rw.rewrite_url(&String::from("http://freerangekitten.com/")).unwrap(),
186 RewriteAction::RewriteUrl(String::from("https://freerangekitten.com/")));
187
188 assert_eq!(
189 rw.rewrite_url(&String::from("http://fake-example.com/")).unwrap(),
190 RewriteAction::NoOp);
191 }
192
193 #[test]
194 fn rewrite_url_http_nowhere_on() {
195 let mut rs = RuleSets::new();
196 rulesets_tests::add_mock_rulesets(&mut rs);
197
198 let rw = Rewriter::new(&rs, &HttpNowhereOnStorage);
199
200 assert_eq!(
201 rw.rewrite_url(&String::from("http://freerangekitten.com/")).unwrap(),
202 RewriteAction::RewriteUrl(String::from("https://freerangekitten.com/")));
203
204 assert_eq!(
205 rw.rewrite_url(&String::from("http://fake-example.com/")).unwrap(),
206 RewriteAction::CancelRequest);
207
208 assert_eq!(
209 rw.rewrite_url(&String::from("http://fake-example.onion/")).unwrap(),
210 RewriteAction::NoOp);
211
212 assert_eq!(
213 rw.rewrite_url(&String::from("http://fake-example.onion..../")).unwrap(),
214 RewriteAction::NoOp);
215 }
216
217 #[test]
218 fn rewrite_exclusions() {
219 let mut rs = RuleSets::new();
220 rulesets_tests::add_mock_rulesets(&mut rs);
221
222 let rw = Rewriter::new(&rs, &TestStorage);
223
224 assert_eq!(
225 rw.rewrite_url(&String::from("http://chart.googleapis.com/")).unwrap(),
226 RewriteAction::NoOp);
227
228 assert_eq!(
229 rw.rewrite_url(&String::from("http://chart.googleapis.com/123")).unwrap(),
230 RewriteAction::RewriteUrl(String::from("https://chart.googleapis.com/123")));
231 }
232
233 #[test]
234 fn rewrite_with_credentials() {
235 let mut rs = RuleSets::new();
236 rulesets_tests::add_mock_rulesets(&mut rs);
237
238 let rw = Rewriter::new(&rs, &TestStorage);
239
240 assert_eq!(
241 rw.rewrite_url(&String::from("http://eff:techprojects@chart.googleapis.com/123")).unwrap(),
242 RewriteAction::RewriteUrl(String::from("https://eff:techprojects@chart.googleapis.com/123")));
243 }
244}