1use crate::mechanism::{Kind, Mechanism};
2use crate::spf::errors::SpfErrors;
3use crate::spf::validate::{self, check_whitespaces, Validate};
4use crate::{Spf, SpfError};
5use ipnetwork::IpNetwork;
6use std::convert::TryFrom;
7use std::fmt::{Display, Formatter};
8use std::str::FromStr;
9
10impl Display for Spf<String> {
11 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
12 if !&self.source.is_empty() {
13 write!(f, "{}", self.source)
14 } else {
15 let mut spf_string = String::new();
16 spf_string.push_str(self.version().as_str());
17 for m in self.iter() {
18 spf_string.push_str(format!(" {}", m).as_str());
19 }
20 write!(f, "{}", spf_string)
21 }
22 }
23}
24
25impl FromStr for Spf<String> {
33 type Err = SpfError;
34 fn from_str(s: &str) -> Result<Self, Self::Err> {
35 validate::check_start_of_spf(s)?;
36 validate::check_spf_length(s)?;
37
38 let mut redirect_idx: usize = 0;
40 let mut all_idx = 0;
42 let mut idx = 0;
43 let mut spf = Spf::default();
44 let mechanisms = s.split_whitespace();
45 for m in mechanisms {
46 if m.contains(crate::core::SPF1) {
47 spf.version = m.to_string();
48 } else if m.contains(crate::core::IP4) || m.contains(crate::core::IP6) {
49 let m_ip = m.parse::<Mechanism<IpNetwork>>()?;
50 spf.mechanisms.push(m_ip.into());
51 } else {
52 let m_str = m.parse::<Mechanism<String>>()?;
53 spf.lookup_count += Self::update_lookup_count(&m_str);
54 match *m_str.kind() {
55 Kind::Redirect => {
56 if !spf.has_redirect {
57 spf.has_redirect = true;
58 redirect_idx = idx;
59 } else {
60 return Err(SpfError::ModifierMayOccurOnlyOnce(Kind::Redirect));
61 }
62 }
63 Kind::All => {
64 all_idx = idx;
65 }
66 _ => {}
67 }
68 spf.mechanisms.push(m_str);
69 idx += 1;
70 }
71 }
72 spf.source = s.to_string();
73 spf.redirect_idx = redirect_idx;
74 spf.all_idx = all_idx;
75 Ok(spf)
76 }
77}
78
79impl TryFrom<&str> for Spf<String> {
80 type Error = SpfError;
81
82 fn try_from(s: &str) -> Result<Self, Self::Error> {
83 Spf::from_str(s)
84 }
85}
86
87impl Spf<String> {
88 #[allow(dead_code)]
91 pub fn new(s: &str) -> Result<Self, SpfError> {
92 s.parse::<Spf<String>>()
93 }
94
95 pub fn is_v1(&self) -> bool {
97 self.version.contains(crate::core::SPF1)
98 }
99 pub fn built(&self) -> bool {
107 self.source.is_empty()
108 }
109 pub fn redirect(&self) -> Option<&Mechanism<String>> {
111 if self.redirect_idx == 0 {
112 match self
113 .mechanisms
114 .first()
115 .expect("There should be a Mechanism<>")
116 .kind()
117 {
118 Kind::Redirect => self.mechanisms.first(),
119 _ => None,
120 }
121 } else {
122 Some(&self.mechanisms[self.redirect_idx])
123 }
124 }
125 pub fn all(&self) -> Option<&Mechanism<String>> {
127 if self.all_idx == 0 {
128 match self
129 .mechanisms
130 .first()
131 .expect("There should be a Mechanism<>")
132 .kind()
133 {
134 Kind::All => self.mechanisms.first(),
135 _ => None,
136 }
137 } else {
138 Some(&self.mechanisms[self.all_idx])
139 }
140 }
141
142 pub fn validate(&self) -> Result<(), SpfErrors> {
169 let mut errors = SpfErrors::new();
170
171 for check in [self.validate_version(), self.validate_length()] {
173 if let Err(e) = check {
174 errors.register_source(self.source.clone());
175 errors.register_error(e);
176 return Err(errors);
177 }
178 }
179
180 let soft_checks = [
182 self.validate_ptr(),
183 self.validate_lookup_count(),
184 self.validate_redirect_all(),
185 check_whitespaces(&self.source),
187 ];
188
189 for check in soft_checks {
190 if let Err(e) = check {
191 errors.register_error(e);
192 }
193 }
194 if errors.errors().is_empty() {
196 Ok(())
197 } else {
198 errors.register_source(self.source.clone());
199 Err(errors)
200 }
201 }
202
203 fn update_lookup_count(m_str: &Mechanism<String>) -> u8 {
205 match *m_str.kind() {
206 Kind::Redirect | Kind::A | Kind::MX | Kind::Include | Kind::Ptr | Kind::Exists => 1,
207 _ => 0,
208 }
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use crate::Spf;
215
216 #[cfg(feature = "ptr")]
217 use crate::SpfError;
218 #[test]
219 fn basic_disallow() {
220 let spf = "v=spf1 -all".parse::<Spf<String>>().unwrap();
221 assert!(!spf.source.is_empty());
222 assert_eq!(spf.redirect(), None);
223 assert_eq!(spf.has_redirect, false);
224 assert_eq!(spf.all_idx, 0);
225 assert_eq!(spf.lookup_count(), 0);
226 assert_eq!(spf.all().unwrap().to_string(), "-all");
227 let validation_result = spf.validate();
228 assert!(validation_result.is_ok());
229 }
230 #[test]
231 #[cfg(not(feature = "ptr"))]
232 fn ptr_allowed_() {
233 let spf = "v=spf1 ptr -all".parse::<Spf<String>>().unwrap();
234 assert!(!spf.source.is_empty());
235 assert_eq!(spf.redirect(), None);
236 assert_eq!(spf.has_redirect, false);
237 assert_eq!(spf.all_idx, 1);
238 let validation_result = spf.validate();
239 assert!(validation_result.is_ok());
240 }
241 #[test]
242 #[cfg(feature = "ptr")]
243 fn ptr_not_allowed_() {
244 let spf = "v=spf1 ptr -all".parse::<Spf<String>>().unwrap();
245 assert!(!spf.source.is_empty());
246 assert_eq!(spf.redirect(), None);
247 assert_eq!(spf.has_redirect, false);
248 assert_eq!(spf.all_idx, 1);
249 let validation_result_vec = spf.validate();
250 assert!(validation_result_vec.is_err());
251 let result = validation_result_vec.unwrap_err();
252 assert_eq!(result.errors()[0], SpfError::DeprecatedPtrDetected);
253 }
254
255 mod hard_errors {
256 use crate::mechanism::Kind;
257 use crate::{Spf, SpfError};
258
259 #[test]
260 fn multiple_redirects() {
261 let spf = "v=spf1 redirect=_spf.example.com redirect=_spf.example.com"
262 .parse::<Spf<String>>()
263 .unwrap_err();
264 assert_eq!(spf, SpfError::ModifierMayOccurOnlyOnce(Kind::Redirect));
265 }
266 }
267 mod soft_errors {
268 use crate::{Spf, SpfError};
269
270 #[test]
271 fn redirect_with_all() {
272 let spf = "v=spf1 redirect=_spf.example.com -all"
273 .parse::<Spf<String>>()
274 .unwrap()
275 .validate();
276
277 assert_eq!(
278 spf.unwrap_err().errors()[0],
279 SpfError::RedirectWithAllMechanism
280 );
281 }
282 #[test]
283 fn all_with_redirect() {
284 let spf = "v=spf1 -all redirect=_spf.example.com"
285 .parse::<Spf<String>>()
286 .unwrap()
287 .validate();
288 assert_eq!(
289 spf.unwrap_err().errors()[0],
290 SpfError::RedirectWithAllMechanism
291 );
292 }
293
294 #[cfg(feature = "strict-dns")]
295 mod strict_dns {
296 use crate::mechanism::MechanismError;
297 use crate::{Spf, SpfError};
298
299 #[test]
300 fn test() {
301 let spf = "v=spf1 redirect=_spf.example.xx -all"
302 .parse::<Spf<String>>()
303 .unwrap_err();
304 assert!(matches!(
305 spf,
306 SpfError::InvalidMechanism(MechanismError::InvalidDomainHost(_))
307 ))
308 }
309 }
310 }
311}