decon_spf/spf/
errors.rs

1use crate::spf::mechanism::Kind;
2use crate::spf::mechanism::MechanismError;
3use ipnetwork::IpNetworkError;
4
5/// A list of expected and possible errors for SPF records.
6#[derive(Debug, Clone, PartialEq)]
7pub enum SpfError {
8    /// Source is invalid, SPF struct was not created using `from_str()`
9    InvalidSource,
10    /// Version is invalid
11    InvalidVersion,
12    /// Source string length exceeds 512 Characters
13    SourceLengthExceeded,
14    /// Exceeds RFC lookup limit.
15    LookupLimitExceeded,
16    /// Source Spf String has not been parsed.
17    HasNotBeenParsed,
18    /// Only one white space is permitted between mechanisms or extra whitespace at the
19    /// end of the spf string.
20    WhiteSpaceSyntaxError,
21    /// Invalid SPF
22    InvalidSPF,
23    /// According to RFC7208, **ALL** REDIRECT **MUST** be ignored when found with an\
24    /// 'ALL' Mechanism, irrespective of relative location.\
25    /// [See Section 5.1](https://datatracker.ietf.org/doc/html/rfc7208#section-5.1)
26    RedirectWithAllMechanism,
27    /// REDIRECT **SHOULD** be the final item given in an Spf record when present.
28    RedirectNotFinalMechanism,
29    /// Modifiers may only occur once in any Spf Record
30    ModifierMayOccurOnlyOnce(Kind),
31    /// Network Address is not valid
32    InvalidIPAddr(IpNetworkError),
33    /// SpfError for an invalid Mechanism
34    InvalidMechanism(MechanismError),
35    /// Deprecated `ptr` detected in Spf record.\
36    /// According to RFCs `ptr` is obsolete and should not be used.
37    /// [See Section: 5.5](https://datatracker.ietf.org/doc/html/rfc7208#section-5.5)
38    DeprecatedPtrDetected,
39}
40
41impl std::fmt::Display for SpfError {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        match self {
44            SpfError::InvalidSource => write!(f, "Source string not valid."),
45            SpfError::InvalidVersion => write!(f, "Version string not valid."),
46            SpfError::SourceLengthExceeded => write!(f, "Spf record exceeds 512 characters."),
47            SpfError::LookupLimitExceeded => write!(f, "Too many DNS lookups."),
48            SpfError::HasNotBeenParsed => write!(f, "Source string has not been parsed."),
49            SpfError::WhiteSpaceSyntaxError => {
50                write!(
51                    f,
52                    "Spf contains two or more consecutive whitespace characters."
53                )
54            }
55            SpfError::InvalidSPF => write!(f, "Spf record is invalid."),
56            SpfError::RedirectWithAllMechanism => {
57                write!(f, "Spf record contains both a 'REDIRECT' modifier and 'ALL' mechanism.\nAccording to RFC7208 any redirect MUST be ignored in this case.\n[See Section 5.1](https://datatracker.ietf.org/doc/html/rfc7208#section-5.1)")
58            }
59            SpfError::RedirectNotFinalMechanism => write!(f, "Redirect not last mechanism."),
60            SpfError::ModifierMayOccurOnlyOnce(kind) => write!(f, "Mechanism: {} occurred more than once.", kind),
61            // Is this even needed?
62            SpfError::InvalidIPAddr(err) => write!(f, "{}", err),
63            SpfError::InvalidMechanism(err) => write!(f, "{}", err),
64            SpfError::DeprecatedPtrDetected => write!(
65                f,
66                "Deprecated Ptr mechanism detected.\nThe use of this mechanism is highly discouraged"
67            ),
68        }
69    }
70}
71
72impl From<IpNetworkError> for SpfError {
73    fn from(err: IpNetworkError) -> Self {
74        SpfError::InvalidIPAddr(err)
75    }
76}
77
78impl From<MechanismError> for SpfError {
79    // TODO: This needs to be re-done with match statement to convert all MechanismErrors correctly.
80    fn from(err: MechanismError) -> Self {
81        SpfError::InvalidMechanism(err)
82    }
83}
84impl std::error::Error for SpfError {}
85
86impl SpfError {
87    /// Returns `true` if the SpfError is any of those listed [`SpfError`](SpfError).
88    pub fn is_spf_error(&self) -> bool {
89        matches!(self, Self::InvalidSource)
90            || matches!(self, Self::InvalidVersion)
91            || matches!(self, Self::SourceLengthExceeded)
92            || matches!(self, Self::LookupLimitExceeded)
93            || matches!(self, Self::HasNotBeenParsed)
94            || matches!(self, Self::InvalidSPF)
95            || matches!(self, Self::RedirectWithAllMechanism)
96            || matches!(self, Self::InvalidIPAddr(_))
97    }
98    /// Returns `true` if the SpfError indicates and Invalid Source error.
99    pub fn is_invalid_source(&self) -> bool {
100        matches!(self, Self::InvalidSource)
101    }
102    /// Returns `true` if the SpfError indicates an Invalid Source error.
103    pub fn source_is_invalid(&self) -> bool {
104        matches!(self, Self::InvalidSource)
105    }
106    /// Returns `true` if the SpfError indicates an invalid version type.
107    pub fn version_is_invalid(&self) -> bool {
108        matches!(self, Self::InvalidVersion)
109    }
110    /// Returns `true` if the SpfError indicates source length exceeds 255 characters.
111    pub fn is_source_length_exceeded(&self) -> bool {
112        matches!(self, Self::SourceLengthExceeded)
113    }
114    /// Returns `true` if the SpfError indicates source length exceeds 255 characters.
115    pub fn source_length_exceeded(&self) -> bool {
116        matches!(self, Self::SourceLengthExceeded)
117    }
118    /// Returns `true` if the SpfError indicates SPF contains more than 10 DNS lookups.
119    pub fn is_lookup_limit_exceeded(&self) -> bool {
120        matches!(self, Self::LookupLimitExceeded)
121    }
122    /// Returns `true` if the SpfError indicates SPF contains more than 10 DNS lookups.
123    pub fn lookup_limit_exceeded(&self) -> bool {
124        matches!(self, Self::LookupLimitExceeded)
125    }
126    /// Returns `true` if the SpfError indicates source of Spf has not been parsed.
127    pub fn is_has_not_been_parsed(&self) -> bool {
128        matches!(self, Self::HasNotBeenParsed)
129    }
130    /// Returns `true` if the SpfError indicates source of Spf has not been parsed.
131    pub fn has_not_been_parsed(&self) -> bool {
132        matches!(self, Self::HasNotBeenParsed)
133    }
134    /// Returns `true` if the SpfError indicates this is an invalid Spf Record.
135    pub fn is_invalid_spf(&self) -> bool {
136        matches!(self, Self::InvalidSPF)
137    }
138    /// Returns `true` if the SpfError indicates the presents of `All` Mechanism
139    pub fn is_redirect_with_all_mechanism(&self) -> bool {
140        matches!(self, Self::RedirectWithAllMechanism)
141    }
142    /// Returns `true` if the SpfError indicates an Invalid IP Address
143    pub fn is_invalid_ip_addr(&self) -> bool {
144        matches!(
145            self,
146            Self::InvalidMechanism(MechanismError::InvalidIPNetwork(_))
147        )
148    }
149}
150/// Contains a vector of parsing or validation errors which are represented using
151/// various [SpfError] codes.
152#[derive(Debug, Default, Clone)]
153pub struct SpfErrors {
154    errors: Vec<SpfError>,
155    source: String,
156}
157
158#[allow(dead_code)]
159impl SpfErrors {
160    pub(crate) fn new() -> Self {
161        Self {
162            errors: Vec::new(),
163            source: String::new(),
164        }
165    }
166    pub(crate) fn register_error(&mut self, error: SpfError) {
167        self.errors.push(error);
168    }
169    pub(crate) fn register_source(&mut self, source: String) {
170        self.source = source;
171    }
172    /// Gives access to the Spf string that was being parsed.
173    pub fn source(&self) -> &String {
174        &self.source
175    }
176    /// Gives access to the **Soft** errors contained within the Spf string.
177    pub fn errors(&self) -> &Vec<SpfError> {
178        self.errors.as_ref()
179    }
180}
181
182#[test]
183fn create_spf_errors() {
184    let errors = SpfErrors::new();
185    assert_eq!(errors.errors.len(), 0);
186}
187#[test]
188fn is_any_spf_error() {
189    let err = SpfError::InvalidSource;
190    assert_eq!(err.is_spf_error(), true);
191}
192#[test]
193fn is_invalid_source() {
194    let err = SpfError::InvalidSource;
195    assert_eq!(err.is_invalid_source(), true);
196}
197#[test]
198fn is_source_length_exceeded() {
199    let err = SpfError::SourceLengthExceeded;
200    assert_eq!(err.is_source_length_exceeded(), true);
201}
202#[test]
203fn is_lookup_limit_exceeded() {
204    let err = SpfError::LookupLimitExceeded;
205    assert_eq!(err.is_lookup_limit_exceeded(), true)
206}
207#[test]
208fn is_has_not_been_parsed() {
209    let err = SpfError::HasNotBeenParsed;
210    assert_eq!(err.is_has_not_been_parsed(), true)
211}
212#[test]
213fn is_invalid_spf() {
214    let err = SpfError::InvalidSPF;
215    assert_eq!(err.is_invalid_spf(), true)
216}
217#[test]
218fn is_redirect_with_all_mechanism() {
219    let err = SpfError::RedirectWithAllMechanism;
220    assert_eq!(err.is_redirect_with_all_mechanism(), true)
221}