1use std::fmt;
6
7use wildmatch::WildMatch;
8
9use super::HostParams;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Host {
14 pub pattern: Vec<HostClause>,
16 pub params: HostParams,
17}
18
19impl Host {
20 pub fn new(pattern: Vec<HostClause>, params: HostParams) -> Self {
21 Self { pattern, params }
22 }
23
24 pub fn intersects(&self, host: &str) -> bool {
26 let mut has_matched = false;
27 for entry in self.pattern.iter() {
28 let matches = entry.intersects(host);
29 if matches && entry.negated {
31 return false;
32 }
33 has_matched |= matches;
34 }
35 has_matched
36 }
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct HostClause {
42 pub pattern: String,
43 pub negated: bool,
44}
45
46impl fmt::Display for HostClause {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 if self.negated {
49 write!(f, "!{}", self.pattern)
50 } else {
51 write!(f, "{}", self.pattern)
52 }
53 }
54}
55
56impl HostClause {
57 pub fn new(pattern: String, negated: bool) -> Self {
59 Self { pattern, negated }
60 }
61
62 pub fn intersects(&self, host: &str) -> bool {
64 WildMatch::new(self.pattern.as_str()).matches(host)
65 }
66}
67
68#[cfg(test)]
69mod tests {
70
71 use pretty_assertions::assert_eq;
72
73 use super::*;
74 use crate::DefaultAlgorithms;
75
76 #[test]
77 fn should_build_host_clause() {
78 let clause = HostClause::new("192.168.1.1".to_string(), false);
79 assert_eq!(clause.pattern.as_str(), "192.168.1.1");
80 assert_eq!(clause.negated, false);
81 }
82
83 #[test]
84 fn should_intersect_host_clause() {
85 let clause = HostClause::new("192.168.*.*".to_string(), false);
86 assert!(clause.intersects("192.168.2.30"));
87 let clause = HostClause::new("192.168.?0.*".to_string(), false);
88 assert!(clause.intersects("192.168.40.28"));
89 }
90
91 #[test]
92 fn should_not_intersect_host_clause() {
93 let clause = HostClause::new("192.168.*.*".to_string(), false);
94 assert_eq!(clause.intersects("172.26.104.4"), false);
95 }
96
97 #[test]
98 fn should_init_host() {
99 let host = Host::new(
100 vec![HostClause::new("192.168.*.*".to_string(), false)],
101 HostParams::new(&DefaultAlgorithms::default()),
102 );
103 assert_eq!(host.pattern.len(), 1);
104 }
105
106 #[test]
107 fn should_intersect_clause() {
108 let host = Host::new(
109 vec![
110 HostClause::new("192.168.*.*".to_string(), false),
111 HostClause::new("172.26.*.*".to_string(), false),
112 HostClause::new("10.8.*.*".to_string(), false),
113 HostClause::new("10.8.0.8".to_string(), true),
114 ],
115 HostParams::new(&DefaultAlgorithms::default()),
116 );
117 assert!(host.intersects("192.168.1.32"));
118 assert!(host.intersects("172.26.104.4"));
119 assert!(host.intersects("10.8.0.10"));
120 }
121
122 #[test]
123 fn should_not_intersect_clause() {
124 let host = Host::new(
125 vec![
126 HostClause::new("192.168.*.*".to_string(), false),
127 HostClause::new("172.26.*.*".to_string(), false),
128 HostClause::new("10.8.*.*".to_string(), false),
129 HostClause::new("10.8.0.8".to_string(), true),
130 ],
131 HostParams::new(&DefaultAlgorithms::default()),
132 );
133 assert_eq!(host.intersects("192.169.1.32"), false);
134 assert_eq!(host.intersects("172.28.104.4"), false);
135 assert_eq!(host.intersects("10.9.0.8"), false);
136 assert_eq!(host.intersects("10.8.0.8"), false);
137 }
138
139 #[test]
140 fn should_display_host_clause() {
141 let clause = HostClause::new("192.168.*.*".to_string(), false);
142 assert_eq!(clause.to_string(), "192.168.*.*");
143
144 let negated_clause = HostClause::new("192.168.1.1".to_string(), true);
145 assert_eq!(negated_clause.to_string(), "!192.168.1.1");
146 }
147
148 #[test]
149 fn should_not_intersect_with_empty_pattern() {
150 let host = Host::new(vec![], HostParams::new(&DefaultAlgorithms::default()));
151 assert_eq!(host.intersects("any-host"), false);
152 }
153
154 #[test]
155 fn should_intersect_with_single_char_wildcard() {
156 let clause = HostClause::new("server?".to_string(), false);
157 assert!(clause.intersects("server1"));
158 assert!(clause.intersects("serverA"));
159 assert!(!clause.intersects("server12"));
160 assert!(!clause.intersects("server"));
161 }
162
163 #[test]
164 fn should_intersect_with_only_negated_clauses_after_positive() {
165 let host = Host::new(
167 vec![
168 HostClause::new("*.example.com".to_string(), false),
169 HostClause::new("secret.example.com".to_string(), true),
170 ],
171 HostParams::new(&DefaultAlgorithms::default()),
172 );
173 assert!(host.intersects("www.example.com"));
174 assert!(!host.intersects("secret.example.com"));
175 assert!(!host.intersects("other.net"));
176 }
177
178 #[test]
179 fn should_handle_wildcard_at_start() {
180 let clause = HostClause::new("*-server".to_string(), false);
181 assert!(clause.intersects("prod-server"));
182 assert!(clause.intersects("dev-server"));
183 assert!(!clause.intersects("server-prod"));
184 }
185
186 #[test]
187 fn should_handle_wildcard_at_end() {
188 let clause = HostClause::new("server-*".to_string(), false);
189 assert!(clause.intersects("server-prod"));
190 assert!(clause.intersects("server-dev"));
191 assert!(!clause.intersects("prod-server"));
192 }
193
194 #[test]
195 fn should_match_exact_pattern() {
196 let clause = HostClause::new("exact-host".to_string(), false);
197 assert!(clause.intersects("exact-host"));
198 assert!(!clause.intersects("exact-host-extra"));
199 assert!(!clause.intersects("prefix-exact-host"));
200 }
201
202 #[test]
203 fn should_match_universal_wildcard() {
204 let clause = HostClause::new("*".to_string(), false);
205 assert!(clause.intersects("any-host"));
206 assert!(clause.intersects("192.168.1.1"));
207 assert!(clause.intersects(""));
208 }
209
210 #[test]
211 fn should_intersect_negated_clause_returns_true_for_matching_negated() {
212 let clause = HostClause::new("192.168.*.*".to_string(), true);
214 assert!(clause.intersects("192.168.1.1")); }
216}