1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4pub use ipnet::IpNet;
5
6pub mod acl;
7pub mod error;
8pub mod utils;
9
10pub use acl::{AclClassification, HttpAcl, HttpAclBuilder, HttpRequestMethod};
11pub use utils::IntoIpRange;
12
13#[cfg(test)]
14mod tests {
15 use std::net::IpAddr;
16 use std::sync::Arc;
17
18 use ipnet::IpNet;
19
20 use super::{AclClassification, HttpAclBuilder};
21
22 #[test]
23 fn acl() {
24 let acl = HttpAclBuilder::new()
25 .add_allowed_host("example.com".to_string())
26 .unwrap()
27 .add_allowed_host("example.org".to_string())
28 .unwrap()
29 .add_denied_host("example.net".to_string())
30 .unwrap()
31 .add_allowed_port_range(8080..=8080)
32 .unwrap()
33 .add_denied_port_range(8443..=8443)
34 .unwrap()
35 .add_allowed_ip_range("1.0.0.0/8".parse::<IpNet>().unwrap())
36 .unwrap()
37 .add_denied_ip_range("9.0.0.0/8".parse::<IpNet>().unwrap())
38 .unwrap()
39 .build();
40
41 assert!(acl.is_host_allowed("example.com").is_allowed());
42 assert!(acl.is_host_allowed("example.org").is_allowed());
43 assert!(!acl.is_host_allowed("example.net").is_allowed());
44 assert!(acl.is_port_allowed(8080).is_allowed());
45 assert!(!acl.is_port_allowed(8443).is_allowed());
46 assert!(acl.is_ip_allowed(&"1.1.1.1".parse().unwrap()).is_allowed());
47 assert!(acl.is_ip_allowed(&"9.9.9.9".parse().unwrap()).is_denied());
48 assert!(
49 acl.is_ip_allowed(&"192.168.1.1".parse().unwrap())
50 .is_denied()
51 );
52 }
53
54 #[test]
55 fn host_acl() {
56 let acl = HttpAclBuilder::new()
57 .add_allowed_host("example.com".to_string())
58 .unwrap()
59 .add_allowed_host("example.org".to_string())
60 .unwrap()
61 .add_denied_host("example.net".to_string())
62 .unwrap()
63 .build();
64
65 assert!(acl.is_host_allowed("example.com").is_allowed());
66 assert!(acl.is_host_allowed("example.org").is_allowed());
67 assert!(!acl.is_host_allowed("example.net").is_allowed());
68 }
69
70 #[test]
71 fn port_acl() {
72 let acl = HttpAclBuilder::new()
73 .clear_allowed_port_ranges()
74 .add_allowed_port_range(8080..=8080)
75 .unwrap()
76 .add_denied_port_range(8443..=8443)
77 .unwrap()
78 .build();
79
80 assert!(acl.is_port_allowed(8080).is_allowed());
81 assert!(!acl.is_port_allowed(8443).is_allowed());
82 }
83
84 #[test]
85 fn ip_acl() {
86 let acl = HttpAclBuilder::new()
87 .clear_allowed_ip_ranges()
88 .add_allowed_ip_range("1.0.0.0/8".parse::<IpNet>().unwrap())
89 .unwrap()
90 .add_denied_ip_range("9.0.0.0/8".parse::<IpNet>().unwrap())
91 .unwrap()
92 .build();
93
94 assert!(acl.is_ip_allowed(&"1.1.1.1".parse().unwrap()).is_allowed());
95 assert!(acl.is_ip_allowed(&"9.9.9.9".parse().unwrap()).is_denied());
96 assert!(
97 acl.is_ip_allowed(&"192.168.1.1".parse().unwrap())
98 .is_denied()
99 );
100 }
101
102 #[test]
103 fn private_ip_acl() {
104 let acl = HttpAclBuilder::new()
105 .private_ip_ranges(true)
106 .ip_acl_default(true)
107 .build();
108
109 assert!(
110 acl.is_ip_allowed(&"192.168.1.1".parse().unwrap())
111 .is_allowed()
112 );
113 }
114
115 #[test]
116 fn default_ip_acl() {
117 let acl = HttpAclBuilder::new().build();
118
119 assert!(
120 acl.is_ip_allowed(&"192.168.1.1".parse().unwrap())
121 .is_denied()
122 );
123 assert!(acl.is_ip_allowed(&"1.1.1.1".parse().unwrap()).is_denied());
124 assert!(!acl.is_port_allowed(8080).is_allowed());
125 }
126
127 #[test]
128 fn url_path_acl() {
129 let acl = HttpAclBuilder::new()
130 .add_allowed_url_path("/allowed".to_string())
131 .unwrap()
132 .add_allowed_url_path("/allowed/:id".to_string())
133 .unwrap()
134 .add_denied_url_path("/denied".to_string())
135 .unwrap()
136 .add_denied_url_path("/denied/{*path}".to_string())
137 .unwrap()
138 .build();
139
140 assert!(acl.is_url_path_allowed("/allowed").is_allowed());
141 assert!(acl.is_url_path_allowed("/allowed/allowed").is_allowed());
142 assert!(acl.is_url_path_allowed("/denied").is_denied());
143 assert!(acl.is_url_path_allowed("/denied/denied").is_denied());
144 assert!(acl.is_url_path_allowed("/denied/denied/denied").is_denied());
145 }
146
147 #[test]
148 fn header_acl() {
149 let acl = HttpAclBuilder::new()
150 .add_allowed_header("X-Allowed".to_string(), Some("true".to_string()))
151 .unwrap()
152 .add_allowed_header("X-Allowed2".to_string(), None)
153 .unwrap()
154 .add_denied_header("X-Denied".to_string(), Some("true".to_string()))
155 .unwrap()
156 .add_denied_header("X-Denied2".to_string(), None)
157 .unwrap()
158 .build();
159
160 assert!(acl.is_header_allowed("X-Allowed", "true").is_allowed());
161 assert!(acl.is_header_allowed("X-Allowed2", "false").is_allowed());
162 assert!(acl.is_header_allowed("X-Denied", "true").is_denied());
163 assert!(acl.is_header_allowed("X-Denied2", "false").is_denied());
164 }
165
166 #[test]
167 fn valid_acl() {
168 let acl =
181 HttpAclBuilder::new().build_full(Some(Arc::new(|scheme, authority, headers, body| {
182 if scheme == "http" {
183 return AclClassification::DeniedUserAcl;
184 }
185
186 if authority.host.is_ip() {
187 return AclClassification::DeniedUserAcl;
188 }
189
190 for (header_name, header_value) in headers {
191 if header_name == "<dangerous-header>" && header_value == "<dangerous-value>" {
192 return AclClassification::DeniedUserAcl;
193 }
194 }
195
196 if let Some(body) = body {
197 if body == b"<dangerous-body>" {
198 return AclClassification::DeniedUserAcl;
199 }
200 }
201
202 AclClassification::AllowedDefault
203 })));
204
205 assert!(
206 acl.is_valid(
207 "https",
208 &"example.com".into(),
209 [("<header>", "<value>")].into_iter(),
210 Some(b"body"),
211 )
212 .is_allowed()
213 );
214 assert!(
215 acl.is_valid(
216 "http",
217 &"example.com".into(),
218 [("<header>", "<value>")].into_iter(),
219 Some(b"body"),
220 )
221 .is_denied()
222 );
223 assert!(
224 acl.is_valid(
225 "https",
226 &"1.1.1.1".parse::<IpAddr>().unwrap().into(),
227 [("<header>", "<value>")].into_iter(),
228 Some(b"body"),
229 )
230 .is_denied()
231 );
232 assert!(
233 acl.is_valid(
234 "https",
235 &"example.com".into(),
236 [("<dangerous-header>", "<dangerous-value>")].into_iter(),
237 Some(b"body"),
238 )
239 .is_denied()
240 );
241 assert!(
242 acl.is_valid(
243 "https",
244 &"example.com".into(),
245 [("<header>", "<value>")].into_iter(),
246 Some(b"<dangerous-body>"),
247 )
248 .is_denied()
249 );
250 }
251}