SPF: Adding Support for A and MX Mechanisms in Rust
Tags: rust, spf, learning
Published: 2021-04-27 07:42:27


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`
{% post https://dev.to/basman/looking-at-regex-in-rust-168n %}
{% post https://dev.to/basman/looking-at-regex-in-rust-addendum-9j8 %}
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>`

```rust
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()`

```rust
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:
```rust
} 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](https://github.com/Bas-Man/learning-rust-trust-dns-resolver/compare/A-Mechanism...MX-Mechanism)

**BASIC-TESTING** <-> **A-Mechanism**
[click here](https://github.com/Bas-Man/learning-rust-trust-dns-resolver/compare/BASIC-TESTING...A-Mechanism)