decon_spf/spf/
builder.rs

1use crate::core::{DNS_LOOKUP_LIMIT, SPF1, SPF2};
2use crate::spf::mechanism::{builder::All, Kind, Mechanism, MechanismError};
3use crate::spf::validate::{self, Validate};
4use crate::{Spf, SpfError};
5use ipnetwork::IpNetwork;
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8use std::convert::TryInto;
9use std::fmt::{Display, Formatter};
10use std::marker::PhantomData;
11use std::str::FromStr;
12
13/// The default `State` for the SpfBuilder
14#[derive(Debug, Clone, PartialEq)]
15pub struct Builder;
16
17/// The SpfBuilder `State` when it has been created using `parse()`. In this state
18/// the struct can not be modified.
19#[derive(Debug, Clone, PartialEq)]
20pub struct Parsed;
21
22#[derive(Debug, Clone, PartialEq)]
23pub struct Redirected;
24
25#[derive(Debug, Clone, PartialEq)]
26pub struct ContainsAll;
27
28/// The definition of the SpfBuilder struct which contains all information related a single
29/// SPF record.
30#[derive(Debug, Clone, PartialEq)]
31#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32pub struct SpfBuilder<State = Builder> {
33    // Version is usually v=spf1 but may be spf2.0/...
34    version: String,
35    redirect: Option<Mechanism<String>>,
36    a: Option<Vec<Mechanism<String>>>,
37    mx: Option<Vec<Mechanism<String>>>,
38    include: Option<Vec<Mechanism<String>>>,
39    ip4: Option<Vec<Mechanism<IpNetwork>>>,
40    ip6: Option<Vec<Mechanism<IpNetwork>>>,
41    ptr: Option<Mechanism<String>>,
42    exists: Option<Vec<Mechanism<String>>>,
43    all: Option<Mechanism<All>>,
44    #[cfg_attr(feature = "serde", serde(skip))]
45    state: PhantomData<State>,
46}
47
48pub trait Modifiable {}
49
50impl Modifiable for Builder {}
51impl Modifiable for Redirected {}
52impl Modifiable for ContainsAll {}
53
54impl<State> Default for SpfBuilder<State> {
55    fn default() -> Self {
56        Self {
57            version: "".to_string(),
58            redirect: None,
59            a: None,
60            mx: None,
61            include: None,
62            ip4: None,
63            ip6: None,
64            ptr: None,
65            exists: None,
66            all: None,
67            state: Default::default(),
68        }
69    }
70}
71pub struct SpfBuilderIterator {
72    m_iter: std::vec::IntoIter<Mechanism<String>>,
73}
74
75impl Iterator for SpfBuilderIterator {
76    type Item = Mechanism<String>;
77
78    fn next(&mut self) -> Option<Self::Item> {
79        self.m_iter.next()
80    }
81}
82
83/// Converts a `Spf<String> into a `SpfBuilder`struct.
84impl From<Spf<String>> for SpfBuilder<Builder> {
85    fn from(source: Spf<String>) -> SpfBuilder<Builder> {
86        build_spf(source)
87    }
88}
89#[cfg(test)]
90mod string_to_builder {
91    use super::*;
92
93    #[test]
94    fn from_string_to_builder() {
95        let spf = "v=spf1 a mx -all".parse::<Spf<String>>().unwrap();
96        let builder = SpfBuilder::<Builder>::from(spf);
97        assert_eq!(builder.version, "v=spf1");
98        assert!(builder.mx().is_some());
99        assert!(builder.redirect().is_none());
100    }
101    #[test]
102    fn from_string_to_builder_ip() {
103        let spf = "v=spf1 mx ip4:203.32.160.10 -all"
104            .parse::<Spf<String>>()
105            .unwrap();
106        let builder: SpfBuilder<Builder> = SpfBuilder::from(spf);
107        assert_eq!(builder.version, "v=spf1");
108        assert!(builder.mx.is_some());
109        assert!(builder.ip4.is_some());
110        assert!(builder.a().is_none());
111    }
112}
113
114impl<State> Display for SpfBuilder<State> {
115    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
116        write!(f, "{}", self.build_spf_string())
117    }
118}
119
120/// Creates an `SpfBuilder struct` by parsing a string representation of Spf.
121///
122/// # Examples:
123///
124///```rust
125/// use decon_spf::{Parsed, SpfBuilder};
126/// use decon_spf::SpfError;
127/// // Successful
128/// let input = "v=spf1 a mx -all";
129/// let spf: SpfBuilder<Parsed> = input.parse().unwrap();
130/// assert_eq!(spf.to_string(), input);
131///
132/// // Additional Space between `A` and `MX`
133/// let invalid_input = "v=spf1 a   mx -all";
134/// let err: SpfError = invalid_input.parse::<SpfBuilder<Parsed>>().unwrap_err();
135/// assert_eq!(err, SpfError::WhiteSpaceSyntaxError);
136/// //  err.to_string() -> "Spf contains two or more consecutive whitespace characters.");
137///```
138///
139impl FromStr for SpfBuilder<Parsed> {
140    type Err = SpfError;
141    fn from_str(s: &str) -> Result<Self, Self::Err> {
142        validate::check_start_of_spf(s)?;
143        validate::check_spf_length(s)?;
144        // Consider making this a soft Error similar to Spf<String>
145        validate::check_whitespaces(s)?;
146        let source = String::from(s);
147
148        // Basic Checks are ok.
149        let mut spf = SpfBuilder::new();
150        // Setup Vectors
151        let records = source.split_whitespace();
152
153        for record in records {
154            // Consider ensuring we do this once at least and then skip
155            if record.contains(SPF1) || record.starts_with(SPF2) {
156                spf.version = record.to_string();
157            } else if record.contains(crate::core::REDIRECT) {
158                if spf.redirect.is_some() {
159                    return Err(SpfError::ModifierMayOccurOnlyOnce(Kind::Redirect));
160                }
161                let m: Mechanism<String> = record.parse()?;
162                spf.redirect = Some(m);
163            } else if record.contains(crate::core::INCLUDE) {
164                let m: Mechanism<String> = record.parse()?;
165                spf.append_string_mechanism(m);
166            } else if record.contains(crate::core::IP4) || record.contains(crate::core::IP6) {
167                let m = record.parse::<Mechanism<IpNetwork>>()?;
168                spf.append_ip_mechanism(m);
169            } else if record.ends_with(crate::core::ALL) && (record.len() == 3 || record.len() == 4)
170            {
171                spf.all = Some(Mechanism::all_with_qualifier(
172                    crate::core::return_and_remove_qualifier(record, 'a').0,
173                ));
174                // Handle A, MX, Exists and PTR types.
175            } else if let Ok(a_mechanism) = crate::core::spf_regex::capture_matches(record, Kind::A)
176            {
177                spf.append_string_mechanism(a_mechanism);
178            } else if let Ok(mx_mechanism) =
179                crate::core::spf_regex::capture_matches(record, Kind::MX)
180            {
181                spf.append_string_mechanism(mx_mechanism);
182            } else if let Ok(ptr_mechanism) =
183                crate::core::spf_regex::capture_matches(record, Kind::Ptr)
184            {
185                if spf.ptr.is_some() {
186                    return Err(SpfError::ModifierMayOccurOnlyOnce(Kind::Ptr));
187                }
188                spf.ptr = Some(ptr_mechanism);
189            } else if let Ok(exists_mechanism) =
190                crate::core::spf_regex::capture_matches(record, Kind::Exists)
191            {
192                spf.append_string_mechanism(exists_mechanism);
193            } else {
194                return Err(SpfError::InvalidMechanism(
195                    MechanismError::InvalidMechanismFormat(record.to_string()),
196                ));
197            }
198        }
199        Ok(spf)
200    }
201}
202
203impl<State> SpfBuilder<State> {
204    /// Create a new empty SpfBuilder struct.
205    /// ```rust
206    /// use decon_spf::{SpfBuilder, Builder};
207    /// use decon_spf::mechanism::{Mechanism, MechanismError, Qualifier};
208    ///
209    /// // Strict building style.
210    /// let mut spf: SpfBuilder<Builder> = SpfBuilder::new();
211    /// spf.set_v1()
212    ///     .add_a(Mechanism::a(Qualifier::Pass))
213    ///     .add_mx(Mechanism::mx(Qualifier::Pass).with_rrdata("test.com").unwrap());
214    /// // add_all() changes the struct state from Builder to ContainsAll
215    /// let mut spf = spf.add_all(Mechanism::all());
216    ///
217    /// let mut spf = SpfBuilder::new_builder();
218    /// spf
219    ///     .set_v1()
220    ///     .append_mechanism(Mechanism::a(Qualifier::Pass))
221    ///     .append_mechanism(Mechanism::ip_from_string("ip4:203.32.160.10").unwrap())
222    ///     .append_mechanism(Mechanism::all());
223    /// ```
224    pub fn new() -> Self {
225        SpfBuilder::default()
226    }
227    /// Access the version attribute
228    pub fn version(&self) -> &String {
229        &self.version
230    }
231}
232impl SpfBuilder<Builder> {
233    /// Create an SpfBuilder with `State` of [Builder]
234    pub fn new_builder() -> SpfBuilder<Builder> {
235        SpfBuilder {
236            state: PhantomData::<Builder>,
237            ..Default::default()
238        }
239    }
240}
241impl SpfBuilder<Parsed> {
242    /// Create an SpfBuilder with `State` of [Parsed]
243    pub fn new_parsed() -> SpfBuilder<Parsed> {
244        SpfBuilder {
245            state: PhantomData::<Parsed>,
246            ..Default::default()
247        }
248    }
249}
250
251impl<State> SpfBuilder<State>
252where
253    State: Modifiable,
254{
255    /// Set version to `v=spf1`
256    pub fn set_v1(&mut self) -> &mut Self {
257        self.version = String::from(SPF1);
258        self
259    }
260    /// Add an a mechanism
261    pub fn add_a(&mut self, mechanism: Mechanism<String>) -> &mut Self {
262        self.append_mechanism(mechanism)
263    }
264    /// Add an mx mechanism
265    pub fn add_mx(&mut self, mechanism: Mechanism<String>) -> &mut Self {
266        self.append_mechanism(mechanism)
267    }
268    /// Add an include Mechanism
269    pub fn add_include(&mut self, mechanism: Mechanism<String>) -> &mut Self {
270        self.append_mechanism(mechanism)
271    }
272    /// Add either and Ip4 or Ip6 Mechanism
273    pub fn add_ip(&mut self, mechanism: Mechanism<IpNetwork>) -> &mut Self {
274        self.append_mechanism(mechanism)
275    }
276}
277impl SpfBuilder<Builder> {
278    /// Append a Redirect Mechanism to the Spf Struct. This also changes the struct's `State`
279    pub fn add_redirect(mut self, mechanism: Mechanism<String>) -> SpfBuilder<Redirected> {
280        SpfBuilder {
281            version: self.version.to_owned(),
282            redirect: Some(mechanism),
283            a: self.a.take(),
284            mx: self.mx.take(),
285            include: self.include.take(),
286            ip4: self.ip4.take(),
287            ip6: self.ip6.take(),
288            ptr: self.ptr.take(),
289            exists: self.exists.take(),
290            all: self.all.take(),
291            state: PhantomData::<Redirected>,
292        }
293    }
294    /// Add a Mechanism<All> to the SpfBuilder struct. This also changes the `State` to `ContainsAll`
295    pub fn add_all(mut self, mechanism: Mechanism<All>) -> SpfBuilder<ContainsAll> {
296        SpfBuilder {
297            version: self.version.to_owned(),
298            redirect: self.redirect.take(),
299            a: self.a.take(),
300            mx: self.mx.take(),
301            include: self.include.take(),
302            ip4: self.ip4.take(),
303            ip6: self.ip6.take(),
304            ptr: self.ptr.take(),
305            exists: self.exists.take(),
306            all: Some(mechanism),
307            state: PhantomData::<ContainsAll>,
308        }
309    }
310}
311#[cfg_attr(docsrs, doc(cfg(feature = "spf2")))]
312#[cfg(feature = "spf2")]
313impl<State> SpfBuilder<State>
314where
315    State: Modifiable,
316{
317    /// Set version to `spf2.0/pra`
318    pub fn set_v2_pra(&mut self) -> &mut Self {
319        self.version = String::from(crate::core::SPF2_PRA);
320        self
321    }
322    /// Set version to `spf2.0/mfrom`
323    pub fn set_v2_mfrom(&mut self) -> &mut Self {
324        self.version = String::from(crate::core::SPF2_MFROM);
325        self
326    }
327    /// Set version to `spf2.0/pra,mfrom`
328    pub fn set_v2_pra_mfrom(&mut self) -> &mut Self {
329        self.version = String::from(crate::core::SPF2_PRA_MFROM);
330        self
331    }
332    /// Set version to `spf2.0/mfrom,pra`
333    pub fn set_v2_mfrom_pra(&mut self) -> &mut Self {
334        self.version = String::from(crate::core::SPF2_MFROM_PRA);
335        self
336    }
337    /// Check that version is v2
338    pub fn is_v2(&self) -> bool {
339        self.version.starts_with(crate::core::SPF2_PRA)
340            || self.version.starts_with(crate::core::SPF2_MFROM)
341    }
342}
343impl<State> SpfBuilder<State> {
344    /// Clear the passed Kind which has been passed.
345    /// Sets the passed mechanism to `None`
346    ///
347    /// # Note:
348    /// This method clears all associated Mechanism for the [`Kind`] provided.
349    ///
350    /// # Example:
351    /// ```
352    /// use decon_spf::mechanism::{Qualifier, Kind, Mechanism};
353    /// use decon_spf::{Builder, SpfBuilder};
354    /// let mut spf: SpfBuilder<Builder> = SpfBuilder::new();
355    /// spf.set_v1();
356    /// spf.append_mechanism(Mechanism::all_with_qualifier(Qualifier::Pass));
357    /// spf.append_mechanism(Mechanism::a(Qualifier::Pass));
358    /// spf.append_mechanism(Mechanism::ip(Qualifier::Pass,
359    ///                                                  "203.32.160.0/23".parse().unwrap()));
360    /// // Remove ip4 Mechanism
361    /// spf.clear_mechanism(Kind::IpV4);
362    ///```
363    pub fn clear_mechanism(&mut self, kind: Kind)
364    where
365        State: Modifiable,
366    {
367        match kind {
368            Kind::Redirect => self.redirect = None,
369            Kind::A => self.a = None,
370            Kind::MX => self.mx = None,
371            Kind::Include => self.include = None,
372            Kind::IpV4 => self.ip4 = None,
373            Kind::IpV6 => self.ip6 = None,
374            Kind::Exists => self.exists = None,
375            Kind::Ptr => self.ptr = None,
376            Kind::All => self.all = None,
377        }
378    }
379
380    fn append_mechanism_of_redirect(&mut self, mechanism: Mechanism<String>) -> &mut Self {
381        self.redirect = Some(mechanism);
382        self
383    }
384    fn append_mechanism_of_a(&mut self, mechanism: Mechanism<String>) -> &mut Self {
385        if let Some(m_vec) = &mut self.a {
386            let exists = Self::check_mechanism_in_vec(&mechanism, m_vec);
387            if !exists {
388                m_vec.push(mechanism);
389            }
390        } else {
391            self.a = Some(vec![mechanism]);
392        }
393        self
394    }
395
396    // Before adding a Mechanism to its Vec we check to make sure the same Mechanism does not already
397    // exist. If it exists it is not appended to avoid duplication.
398    fn check_mechanism_in_vec<T>(mechanism: &Mechanism<T>, m_vec: &Vec<Mechanism<T>>) -> bool
399    where
400        T: PartialEq,
401    {
402        let mut exists = false;
403        for m in m_vec.iter() {
404            exists = m == mechanism;
405        }
406        exists
407    }
408
409    fn append_mechanism_of_mx(&mut self, mechanism: Mechanism<String>) -> &mut Self {
410        if let Some(m_vec) = &mut self.mx {
411            let exists = Self::check_mechanism_in_vec(&mechanism, m_vec);
412            if !exists {
413                m_vec.push(mechanism);
414            }
415        } else {
416            self.mx = Some(vec![mechanism]);
417        }
418        self
419    }
420    fn append_mechanism_of_include(&mut self, mechanism: Mechanism<String>) -> &mut Self {
421        if let Some(m_vec) = &mut self.include {
422            let exists = Self::check_mechanism_in_vec(&mechanism, m_vec);
423            if !exists {
424                m_vec.push(mechanism);
425            }
426        } else {
427            self.include = Some(vec![mechanism]);
428        }
429        self
430    }
431    fn append_mechanism_of_ip4(&mut self, mechanism: Mechanism<IpNetwork>) -> &mut Self {
432        if let Some(m_vec) = &mut self.ip4 {
433            let exists = Self::check_mechanism_in_vec(&mechanism, m_vec);
434            if !exists {
435                m_vec.push(mechanism);
436            }
437        } else {
438            self.ip4 = Some(vec![mechanism]);
439        }
440        self
441    }
442    fn append_mechanism_of_ip6(&mut self, mechanism: Mechanism<IpNetwork>) -> &mut Self {
443        if let Some(m_vec) = &mut self.ip6 {
444            let exists = Self::check_mechanism_in_vec(&mechanism, m_vec);
445            if !exists {
446                m_vec.push(mechanism);
447            }
448        } else {
449            self.ip6 = Some(vec![mechanism]);
450        }
451        self
452    }
453    fn append_mechanism_of_exists(&mut self, mechanism: Mechanism<String>) -> &mut Self {
454        if let Some(m_vec) = &mut self.exists {
455            let exists = Self::check_mechanism_in_vec(&mechanism, m_vec);
456            if !exists {
457                m_vec.push(mechanism);
458            }
459        } else {
460            self.exists = Some(vec![mechanism]);
461        }
462        self
463    }
464    fn append_mechanism_of_ptr(&mut self, mechanism: Mechanism<String>) -> &mut Self {
465        self.ptr = Some(mechanism);
466        self
467    }
468    fn append_mechanism_of_all(&mut self, mechanism: Mechanism<All>) -> &mut Self {
469        self.all = Some(mechanism);
470        self
471    }
472    fn append_string_mechanism(&mut self, mechanism: Mechanism<String>) -> &mut Self {
473        match mechanism.kind() {
474            Kind::Redirect => self.append_mechanism_of_redirect(mechanism),
475            Kind::A => self.append_mechanism_of_a(mechanism),
476            Kind::MX => self.append_mechanism_of_mx(mechanism),
477            Kind::Include => self.append_mechanism_of_include(mechanism),
478            Kind::Exists => self.append_mechanism_of_exists(mechanism),
479            Kind::Ptr => self.append_mechanism_of_ptr(mechanism),
480            Kind::All => {
481                self.append_mechanism_of_all(mechanism.try_into().expect("Not a Mechanism<All>"))
482            }
483            _ => {
484                panic!("What the heck? Unmatched case?")
485            }
486        }
487    }
488    fn append_ip_mechanism(&mut self, mechanism: Mechanism<IpNetwork>) -> &mut Self {
489        match mechanism.kind() {
490            Kind::IpV4 => self.append_mechanism_of_ip4(mechanism),
491            Kind::IpV6 => self.append_mechanism_of_ip6(mechanism),
492            _ => {
493                unreachable!()
494            }
495        }
496    }
497    /// This is generic method for adding Mechanism`<T>` to the SpfBuilder struct.
498    /// # Note:
499    /// This approach does not provide protection to prevent `redirect` and `all` from both being present in a single SpfBuilder struct.
500    /// If you wish to prevent this. Please use the [add_redirect()](SpfBuilder::add_redirect()) and [add_all()](SpfBuilder::add_all()) functions.
501    /// ```
502    /// use decon_spf::mechanism::{Qualifier, Mechanism};
503    /// use decon_spf::{Builder, Spf, SpfBuilder};
504    /// let mut spf: SpfBuilder<Builder> = SpfBuilder::new();
505    /// spf.set_v1();
506    /// spf.append_mechanism(Mechanism::redirect(Qualifier::Pass,
507    ///                                 "_spf.example.com").unwrap())
508    ///    .append_mechanism(Mechanism::all_with_qualifier(Qualifier::Pass));
509    ///
510    /// let mut spf: SpfBuilder<Builder> = SpfBuilder::new_builder();
511    /// spf.set_v1().add_a(Mechanism::a(Qualifier::Pass));
512    /// let mut spf = spf.add_all(Mechanism::all()); // spf -> SpfBuilder<ContainsAll>
513    /// // spf.redirect() and spf.add_all() are only defined for SpfBuilder<Builder>
514    /// // As such they do not exist for <ContainsAll> or <Redirect>
515    ///
516    /// ```
517    pub fn append_mechanism<T>(&mut self, mechanism: Mechanism<T>) -> &mut Self
518    where
519        Self: Append<T>,
520    {
521        self.append(mechanism);
522        self
523    }
524
525    fn build_spf_string(&self) -> String {
526        let mut spf = String::new();
527        spf.push_str(self.version());
528        if let Some(a) = self.a() {
529            spf.push_str(crate::core::build_spf_str(a).as_str());
530        };
531        if let Some(mx) = self.mx() {
532            spf.push_str(crate::core::build_spf_str(mx).as_str());
533        };
534        if let Some(includes) = self.includes() {
535            spf.push_str(crate::core::build_spf_str(includes).as_str());
536        }
537        if let Some(ip4) = self.ip4() {
538            spf.push_str(crate::core::build_spf_str_from_ip(ip4).as_str());
539        }
540        if let Some(ip6) = self.ip6() {
541            spf.push_str(crate::core::build_spf_str_from_ip(ip6).as_str());
542        }
543        if let Some(exists) = self.exists() {
544            spf.push_str(crate::core::build_spf_str(exists).as_str());
545        }
546        if let Some(ptr) = self.ptr() {
547            spf.push(' ');
548            spf.push_str(ptr.to_string().as_str());
549        }
550        if self.redirect.is_some() {
551            spf.push(' ');
552            spf.push_str(
553                self.redirect()
554                    .expect("Should not fail")
555                    .to_string()
556                    .as_str(),
557            );
558        }
559        // All can only be used if this is not a redirect.
560        if self.redirect.is_none() && self.all().is_some() {
561            spf.push(' ');
562            spf.push_str(self.all().expect("Should not fail.").to_string().as_str());
563        }
564        spf
565    }
566    /// True if there is a redirect present in the spf record.
567    pub fn is_redirect(&self) -> bool {
568        self.redirect.is_some()
569    }
570    /// Returns a reference to the `Redirect` Mechanism
571    pub fn redirect(&self) -> Option<&Mechanism<String>> {
572        self.redirect.as_ref()
573    }
574    /// Returns a reference to the a `Vec` of `Mechanism<String>` for `Include`
575    pub fn includes(&self) -> Option<&Vec<Mechanism<String>>> {
576        self.include.as_ref()
577    }
578    /// Returns a reference to a `Vec` of `Mechanism<String>` for `A`
579    pub fn a(&self) -> Option<&Vec<Mechanism<String>>> {
580        self.a.as_ref()
581    }
582    /// Returns a reference to a `Vec` of `Mechanism<String>` for `MX`
583    pub fn mx(&self) -> Option<&Vec<Mechanism<String>>> {
584        self.mx.as_ref()
585    }
586    /// Returns a reference to a `Vec` of `Mechanism<IpNetwork>` for `IP4`
587    pub fn ip4(&self) -> Option<&Vec<Mechanism<IpNetwork>>> {
588        self.ip4.as_ref()
589    }
590    /// Returns a reference to a `Vec` of `Mechanism<IpNetwork>` for `IP6`
591    pub fn ip6(&self) -> Option<&Vec<Mechanism<IpNetwork>>> {
592        self.ip6.as_ref()
593    }
594    /// Returns a reference to a `Vec` of `Mechanism<String>` for `Exists`
595    pub fn exists(&self) -> Option<&Vec<Mechanism<String>>> {
596        self.exists.as_ref()
597    }
598    /// Returns a reference to a `Vec` of `Mechanism<String>` for `Ptr`
599    pub fn ptr(&self) -> Option<&Mechanism<String>> {
600        self.ptr.as_ref()
601    }
602    /// Returns a reference to `Mechanism<All>` for `All`
603    pub fn all(&self) -> Option<&Mechanism<All>> {
604        self.all.as_ref()
605    }
606    /// Creates a `Spf<String>` from `SpfBuilder`
607    /// This function also validates the SpfBuilder struct before returning a Spf<String>
608    /// ```
609    /// use decon_spf::{Spf, SpfBuilder, Builder, SpfError};
610    /// use decon_spf::mechanism::{Mechanism, Qualifier};
611    /// let mut builder: SpfBuilder<Builder> = SpfBuilder::new();
612    /// // Note Version was not Set. build() assumes v=spf1 when not present
613    /// builder.add_a(Mechanism::a(Qualifier::Pass));
614    /// let mut builder = builder.add_all(Mechanism::all());
615    /// let spf = match builder.build() {
616    ///     Ok(result) => { result },
617    ///     Err(_) => { panic!() }
618    /// };
619    /// assert_eq!(1,spf.lookup_count());
620    ///
621    /// let mut builder: SpfBuilder<Builder> = SpfBuilder::new();
622    /// builder.append_mechanism(Mechanism::redirect(Qualifier::Pass,"test.com").expect("ok"));
623    /// builder.append_mechanism(Mechanism::all());
624    ///
625    /// let result = match builder.build() {
626    ///     Ok(_) => { panic!()},
627    ///     Err(e) => {
628    ///         assert!(matches!(e, SpfError::RedirectWithAllMechanism));
629    ///         e
630    ///     }
631    /// };
632    /// assert!(result.is_spf_error());
633    ///
634    /// ```
635    pub fn build(mut self) -> Result<Spf<String>, SpfError> {
636        if self.version.is_empty() {
637            self.version = SPF1.to_owned();
638        } else {
639            self.validate_version()?;
640        }
641        self.validate_lookup_count()?;
642        self.validate_ptr()?;
643        self.validate_redirect_all()?;
644        let lookup_count: u8 = self.get_lookup_count() as u8;
645
646        let mut redirect_idx = 0;
647        let mut has_redirect = false;
648        let mut all_idx = 0;
649        let mut mechanisms: Vec<Mechanism<String>> = Vec::with_capacity(DNS_LOOKUP_LIMIT + 1); // +1 for Version information
650
651        if let Some(list) = self.a.as_mut() {
652            mechanisms.append(list);
653        }
654        if let Some(list) = self.mx.as_mut() {
655            mechanisms.append(list);
656        }
657        if let Some(ip4) = self.ip4 {
658            for m in ip4.into_iter() {
659                mechanisms.push(m.into());
660            }
661        }
662        if let Some(ip6) = self.ip6 {
663            for m in ip6.into_iter() {
664                mechanisms.push(m.into());
665            }
666        }
667        if let Some(list) = self.include.as_mut() {
668            mechanisms.append(list);
669        }
670        if let Some(list) = self.exists.as_mut() {
671            mechanisms.append(list);
672        }
673        if let Some(all) = self.all {
674            mechanisms.push(all.into());
675            all_idx = mechanisms.len() - 1;
676        }
677        if let Some(redirect) = self.redirect {
678            mechanisms.push(redirect);
679            has_redirect = true;
680            redirect_idx = mechanisms.len() - 1;
681        }
682        Ok(Spf::<String> {
683            source: "".to_string(),
684            version: self.version,
685            redirect_idx,
686            has_redirect,
687            all_idx,
688            lookup_count,
689            mechanisms,
690        })
691    }
692
693    pub(crate) fn get_lookup_count(&self) -> usize {
694        let mut count: usize = 0;
695        {
696            if let Some(a) = &self.a {
697                count += a.len();
698            }
699            if let Some(mx) = &self.mx {
700                count += mx.len();
701            }
702            if self.redirect.is_some() {
703                count += 1;
704            }
705            if let Some(exists) = &self.exists {
706                count += exists.len();
707            }
708            if self.ptr.is_some() {
709                count += 1;
710            }
711            if let Some(include) = &self.include {
712                count += include.len();
713            }
714        }
715        count
716    }
717}
718
719impl<State> SpfBuilder<State> {
720    /// Allows you to iterate over Mechanisms contained within the SPF record.
721    /// # Note: Version string is not included.
722    pub fn iter(&self) -> SpfBuilderIterator {
723        let mut m: Vec<Mechanism<String>> = vec![];
724
725        if let Some(r) = &self.redirect {
726            m.push(r.clone());
727        }
728
729        if let Some(m_a) = &self.a {
730            m.extend(m_a.iter().cloned())
731        }
732        if let Some(m_mx) = &self.mx {
733            m.extend(m_mx.iter().cloned())
734        }
735        if let Some(include) = &self.include {
736            m.extend(include.iter().cloned())
737        }
738        if let Some(ip4) = &self.ip4 {
739            m.extend(ip4.iter().map(|v| (*v).into()))
740        }
741        if let Some(ip6) = &self.ip6 {
742            m.extend(ip6.iter().map(|v| (*v).into()))
743        }
744        if let Some(exists) = &self.exists {
745            m.extend(exists.iter().cloned())
746        }
747        if let Some(ptr) = &self.ptr {
748            m.push(ptr.clone())
749        }
750        if let Some(all) = &self.all {
751            m.push((*all).clone().into())
752        }
753
754        SpfBuilderIterator {
755            m_iter: m.into_iter(),
756        }
757    }
758}
759
760pub trait Append<T> {
761    fn append(&mut self, mechanism: Mechanism<T>) -> &mut Self;
762}
763impl<State> Append<String> for SpfBuilder<State> {
764    fn append(&mut self, mechanism: Mechanism<String>) -> &mut Self {
765        self.append_string_mechanism(mechanism)
766    }
767}
768
769impl<State> Append<IpNetwork> for SpfBuilder<State> {
770    fn append(&mut self, mechanism: Mechanism<IpNetwork>) -> &mut Self {
771        self.append_ip_mechanism(mechanism)
772    }
773}
774
775impl<State> Append<All> for SpfBuilder<State> {
776    fn append(&mut self, mechanism: Mechanism<All>) -> &mut Self {
777        self.append_mechanism_of_all(mechanism)
778    }
779}
780
781#[test]
782fn spf_builder_iter() {
783    use crate::spf::mechanism::Qualifier;
784    let mut spf_b: SpfBuilder<Builder> = SpfBuilder::new();
785    let mut count = 0;
786    spf_b
787        //.append(Mechanism::redirect(Qualifier::Pass, "example.com").unwrap())
788        .append(Mechanism::a(Qualifier::Pass))
789        .append(Mechanism::ip_from_string("ip4:203.160.10.10").unwrap())
790        .append(Mechanism::ip_from_string("ip6:2001:4860:4000::").unwrap())
791        .append(Mechanism::include(Qualifier::Pass, "test.com").unwrap())
792        .append(Mechanism::all());
793    for _m in spf_b.iter() {
794        count += 1;
795    }
796    assert_eq!(count, 5);
797}
798
799fn build_spf<T>(source: Spf<String>) -> SpfBuilder<T> {
800    let mut new_spf = SpfBuilder::new();
801    new_spf.version = source.version;
802
803    for m in source.mechanisms.into_iter() {
804        match m.kind() {
805            Kind::IpV4 | Kind::IpV6 => {
806                let ip_m = Mechanism::ip_from_string(&*m.to_string())
807                    .expect("Mechanism is not a valid IP address. Should never happen");
808                new_spf.append_mechanism(ip_m);
809            }
810            Kind::All => {
811                new_spf.all = Some(
812                    m.try_into()
813                        .expect("Not All Mechanisms. Should never happen."),
814                );
815            }
816            Kind::Redirect => {
817                new_spf.redirect = Some(m);
818            }
819            _ => {
820                new_spf.append_mechanism(m);
821            }
822        }
823    }
824
825    new_spf
826}