This is a quick followup on my progress in deconstructing SPF using Rust.
Today I was able to implement both A
and MX
mechanisms.
In my previous articles on rust and Regex
I came up with a possible regular expression that I thought would work. I have since made some adjustments.
My final regular expressions became:
r"^(?P<qualifier>[+?~-])?(?P<mechanism>a(?:[:/]{0,1}.+)?)"
r"^(?P<qualifier>[+?~-])?(?P<mechanism>mx(?:[:/]{0,1}.+)?)"
This is far simpler and cleaner for my needs. Though I might find a use for my previous versions in the future.
First let’s take a look at the helper function which returns and Option
of type SpfMechanism<String>
fn capture_matches(
pattern: Regex,
string: &str,
kind: kinds::MechanismKind,
) -> Option<SpfMechanism<String>> {
let caps = pattern.captures(string);
let mut q: char = '+';
let m: String;
match caps {
None => return None,
Some(caps) => {
// There was a match
// qualifier may be a `None` so check before unwrapping.
if caps.name("qualifier").is_some() {
q = caps
.name("qualifier")
.unwrap()
.as_str()
.chars()
.nth(0)
.unwrap();
};
m = caps.name("mechanism").unwrap().as_str().to_string();
let mechanism = SpfMechanism::new(kind, q, (*m).to_string());
Some(mechanism)
}
}
}
#[test]
fn test_match_on_a_only() {
let string = "a";
let pattern = Regex::new(r"^(?P<qualifier>[+?~-])?(?P<mechanism>a(?:[:/]{0,1}.+)?)").unwrap();
let option_test: Option<SpfMechanism<String>>;
option_test = capture_matches(pattern, &string, kinds::MechanismKind::A);
let test = option_test.unwrap();
assert_eq!(test.is_pass(), true);
assert_eq!(test.as_string(), "a");
assert_eq!(test.as_mechanism(), "a");
}
//snip//
#[test]
fn test_match_on_mx_only() {
let string = "mx";
let pattern = Regex::new(r"^(?P<qualifier>[+?~-])?(?P<mechanism>mx(?:[:/]{0,1}.+)?)").unwrap();
let option_test: Option<SpfMechanism<String>>;
option_test = capture_matches(pattern, &string, kinds::MechanismKind::MX);
let test = option_test.unwrap();
assert_eq!(test.is_pass(), true);
assert_eq!(test.as_string(), "mx");
assert_eq!(test.as_mechanism(), "mx");
}
capture_matches()
has been written with reuse in mind. So it will work with both A
and MX
mechanisms.
What does it do?
We pass it a regular expression
string, the string to be checked, and an SPF MechanismKind.
If there is no match, a None
is returned as such.
If there is a match, we check that the capture group qualifer
is Some()
and if so we get the value stored. It should be a single char
If there is a match as determined previously, mechanism will always be valid.
From this we construct a Mechanism
using SpfMechanism::new()
with the kind
passed in earlier.
Now let’s see how it’s actually put into play in parse()
pub fn parse(&mut self) {
// initialises required variables.
let records = self.source.split_whitespace();
let mut vec_of_includes: Vec<SpfMechanism<String>> = Vec::new();
let mut vec_of_ip4: Vec<SpfMechanism<IpNetwork>> = Vec::new();
let mut vec_of_ip6: Vec<SpfMechanism<IpNetwork>> = Vec::new();
let mut vec_of_a: Vec<SpfMechanism<String>> = Vec::new();
let mut vec_of_mx: Vec<SpfMechanism<String>> = Vec::new();
for record in records {
// Make this lazy.
let a_pattern =
Regex::new(r"^(?P<qualifier>[+?~-])?(?P<mechanism>a(?:[:/]{0,1}.+)?)").unwrap();
let mx_pattern =
Regex::new(r"^(?P<qualifier>[+?~-])?(?P<mechanism>mx(?:[:/]{0,1}.+)?)").unwrap();
if record.contains("redirect=") {
//snip
} else if record.contains("include:") {
//snip
}
} else if record.contains("ip4:") {
//snip
}
} else if record.contains("ip6:") {
//snip
}
} else if record.ends_with("all") {
//snip
} else if let Some(a_mechanism) =
capture_matches(a_pattern, record, kinds::MechanismKind::A) {
vec_of_a.push(a_mechanism);
} else if let Some(mx_mechanism) =
capture_matches(mx_pattern, record, kinds::MechanismKind::MX) {
vec_of_mx.push(mx_mechanism);
}
}
if !vec_of_includes.is_empty() {
self.include = Some(vec_of_includes);
};
if !vec_of_ip4.is_empty() {
self.ip4 = Some(vec_of_ip4);
};
if !vec_of_ip6.is_empty() {
self.ip6 = Some(vec_of_ip6);
};
if !vec_of_a.is_empty() {
self.a = Some(vec_of_a);
}
if !vec_of_mx.is_empty() {
self.mx = Some(vec_of_mx);
}
}
It is used here:
} else if let Some(a_mechanism) =
capture_matches(a_pattern, record, kinds::MechanismKind::A) {
vec_of_a.push(a_mechanism);
} else if let Some(mx_mechanism) =
capture_matches(mx_pattern, record, kinds::MechanismKind::MX) {
vec_of_mx.push(mx_mechanism);
}
capture_matches() returns None
if there is no match, so we can use this to determine if we should push the returned value in to the Vec<..>
Checking things on GitHub
If you want to check things in more gory detail. You can take a look at these diffs
A-Mechanism <-> MX-Mechanism click here
BASIC-TESTING <-> A-Mechanism click here