sn0int/autonoscope/
url.rs1use crate::errors::*;
2use crate::autonoscope::{Autonoscope, ToRule, AutoRule, RulePrecision};
3use crate::models::*;
4use std::convert::TryFrom;
5
6#[derive(Debug, PartialEq)]
7pub struct UrlRule {
8 url: String,
9 origin: url::Origin,
10 segments: Vec<String>,
11}
12
13impl ToString for UrlRule {
14 fn to_string(&self) -> String {
15 self.url.clone()
16 }
17}
18
19impl TryFrom<Autonoscope> for UrlRule {
20 type Error = Error;
21
22 fn try_from(x: Autonoscope) -> Result<UrlRule> {
23 UrlRule::try_from(x.value.as_str())
24 }
25}
26
27impl TryFrom<&str> for UrlRule {
28 type Error = Error;
29
30 fn try_from(x: &str) -> Result<UrlRule> {
31 let url = x.parse::<url::Url>()?;
32
33 let origin = url.origin();
34
35 let segments = url.path_segments()
36 .ok_or_else(|| format_err!("url can't have a base"))?
37 .filter(|x| !x.is_empty())
38 .map(String::from)
39 .collect();
40
41 Ok(UrlRule {
42 url: x.to_string(),
43 origin,
44 segments,
45 })
46 }
47}
48
49impl AutoRule<Url> for UrlRule {
51 fn matches(&self, url: &Url) -> Result<bool> {
52 self.matches(url.value.as_str())
53 }
54}
55
56impl AutoRule<NewUrl> for UrlRule {
57 fn matches(&self, url: &NewUrl) -> Result<bool> {
58 self.matches(url.value.as_str())
59 }
60}
61
62impl AutoRule<str> for UrlRule {
63 fn matches(&self, url: &str) -> Result<bool> {
64 let url = url.parse::<url::Url>()?;
65
66 if url.origin() != self.origin {
67 return Ok(false);
68 }
69
70 let segments = url.path_segments()
71 .ok_or_else(|| format_err!("url can't have a base"))?
72 .filter(|x| !x.is_empty())
73 .collect::<Vec<_>>();
74
75 if self.segments.len() > segments.len() {
76 return Ok(false);
77 }
78
79 for (rule, path) in self.segments.iter().zip(segments.iter()) {
80 if rule != path {
81 return Ok(false);
82 }
83 }
84
85 Ok(true)
86 }
87}
88
89impl RulePrecision for UrlRule {
90 fn precision(&self) -> usize {
91 self.segments.len()
92 }
93}
94
95impl ToRule for UrlRule {
96 fn to_rule(&self) -> (&'static str, String) {
97 ("url", self.to_string())
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use std::convert::TryFrom;
105
106 #[test]
107 fn test_url_rule_explicit_origin() {
108 let rule = UrlRule::try_from("https://example.com").unwrap();
109 assert!(rule.matches("https://example.com:443/").unwrap());
110 assert_eq!(rule.precision(), 0);
111 }
112
113 #[test]
114 fn test_url_rule_scheme_mismatch() {
115 let rule = UrlRule::try_from("https://example.com").unwrap();
116 assert!(!rule.matches("http://example.com:443/").unwrap());
117 assert_eq!(rule.precision(), 0);
118 }
119
120 #[test]
121 fn test_url_rule_port_mismatch() {
122 let rule = UrlRule::try_from("https://example.com").unwrap();
123 assert!(!rule.matches("https://example.com:80/").unwrap());
124 assert_eq!(rule.precision(), 0);
125 }
126
127 #[test]
128 fn test_url_rule_subdomain_mismatch1() {
129 let rule = UrlRule::try_from("https://example.com").unwrap();
130 assert!(!rule.matches("https://www.example.com/").unwrap());
131 assert_eq!(rule.precision(), 0);
132 }
133
134 #[test]
135 fn test_url_rule_subdomain_mismatch2() {
136 let rule = UrlRule::try_from("https://www.example.com").unwrap();
137 assert!(!rule.matches("https://example.com/").unwrap());
138 assert_eq!(rule.precision(), 0);
139 }
140
141 #[test]
142 fn test_url_rule_ftp() {
143 let rule = UrlRule::try_from("ftp://www.example.com").unwrap();
144 assert!(!rule.matches("https://example.com/").unwrap());
145 assert_eq!(rule.precision(), 0);
146 }
147
148 #[test]
149 fn test_url_rule_outside_of_path() {
150 let rule = UrlRule::try_from("https://www.example.com/asset").unwrap();
151 assert!(!rule.matches("https://example.com/").unwrap());
152 assert_eq!(rule.precision(), 1);
153 }
154
155 #[test]
156 fn test_url_rule_path_match_implicit_slash() {
157 let rule = UrlRule::try_from("https://www.example.com/asset").unwrap();
158 assert!(rule.matches("https://www.example.com/asset/").unwrap());
159 assert_eq!(rule.precision(), 1);
160 }
161
162 #[test]
163 fn test_url_rule_path_match_explicit_slash() {
164 let rule = UrlRule::try_from("https://www.example.com/asset/").unwrap();
165 assert!(rule.matches("https://www.example.com/asset").unwrap());
166 assert_eq!(rule.precision(), 1);
167 }
168
169 #[test]
170 fn test_url_rule_in_folder_implicit_slash() {
171 let rule = UrlRule::try_from("https://www.example.com/asset").unwrap();
172 assert!(rule.matches("https://www.example.com/asset/style.css").unwrap());
173 assert_eq!(rule.precision(), 1);
174 }
175
176 #[test]
177 fn test_url_rule_in_folder_explicit_slash() {
178 let rule = UrlRule::try_from("https://www.example.com/asset/").unwrap();
179 assert!(rule.matches("https://www.example.com/asset/style.css").unwrap());
180 assert_eq!(rule.precision(), 1);
181 }
182}