use crate::spf::mechanism::{Mechanism, Qualifier};
use ipnetwork::IpNetwork;
use lazy_static::lazy_static;
use regex::Regex;
pub(crate) mod spf_regex;
pub(crate) const MAX_SPF_STRING_LENGTH: usize = 512;
pub(crate) const DNS_LOOKUP_LIMIT: usize = 10;
pub(crate) const SPF1: &str = "v=spf1";
#[allow(dead_code)]
pub(crate) const SPF2: &str = "spf2.0";
pub(crate) const SPF2_PRA: &str = "spf2.0/pra";
pub(crate) const SPF2_MFROM: &str = "spf2.0/mfrom";
pub(crate) const SPF2_PRA_MFROM: &str = "spf2.0/pra,mfrom";
pub(crate) const SPF2_MFROM_PRA: &str = "spf2.0/mfrom,pra";
pub(crate) const REDIRECT: &str = "redirect=";
pub(crate) const INCLUDE: &str = "include:";
pub(crate) const ALL: &str = "all";
pub(crate) const IP4: &str = "ip4:";
pub(crate) const IP6: &str = "ip6:";
pub(crate) fn spf_check_whitespace(s: &str) -> bool {
lazy_static! {
static ref RE: Regex = Regex::new(r"\s{2,}").unwrap();
static ref ENDING_SPACE: Regex = Regex::new(r"\s$").unwrap();
}
RE.is_match(s) || ENDING_SPACE.is_match(s)
}
pub(crate) fn char_to_qualifier(c: char) -> Qualifier {
match c {
'+' => Qualifier::Pass,
'-' => Qualifier::Fail,
'~' => Qualifier::SoftFail,
'?' => Qualifier::Neutral,
_ => Qualifier::Pass,
}
}
#[doc(hidden)]
pub(crate) fn return_and_remove_qualifier(record: &str, c: char) -> (Qualifier, &str) {
if c != record.chars().next().unwrap() {
(
char_to_qualifier(record.chars().next().unwrap()),
remove_qualifier(record),
)
} else {
(Qualifier::Pass, record)
}
}
#[cfg(test)]
mod return_and_remove_qualifier_tests {
use super::*;
#[test]
fn return_and_remove_qualifier_no_qualifier() {
let source = "no prefix";
let (c, new_str) = return_and_remove_qualifier(source, 'n');
assert_eq!(Qualifier::Pass, c);
assert_eq!(source, new_str);
}
#[test]
fn return_and_remove_qualifier_pass() {
let source = "+prefix";
let (c, new_str) = return_and_remove_qualifier(source, 'n');
assert_eq!(Qualifier::Pass, c);
assert_eq!("prefix", new_str);
}
#[test]
fn return_and_remove_qualifier_fail() {
let source = "-prefix";
let (c, new_str) = return_and_remove_qualifier(source, 'n');
assert_eq!(Qualifier::Fail, c);
assert_eq!("prefix", new_str);
}
#[test]
fn return_and_remove_qualifier_softfail() {
let source = "~prefix";
let (c, new_str) = return_and_remove_qualifier(source, 'n');
assert_eq!(Qualifier::SoftFail, c);
assert_eq!("prefix", new_str);
}
#[test]
fn return_and_remove_qualifier_neutral() {
let source = "?prefix";
let (c, new_str) = return_and_remove_qualifier(source, 'n');
assert_eq!(Qualifier::Neutral, c);
assert_eq!("prefix", new_str);
}
}
#[doc(hidden)]
pub(crate) fn remove_qualifier(record: &str) -> &str {
let mut chars = record.chars();
chars.next();
chars.as_str()
}
#[test]
fn test_remove_qualifier() {
let test_str = "abc";
let result = remove_qualifier(test_str);
assert_eq!(result, "bc");
}
#[allow(dead_code)]
pub(crate) fn build_spf_str(str: &[Mechanism<String>]) -> String {
let mut partial_spf = String::new();
for i in str.iter() {
partial_spf.push_str(&format!(" {}", i.to_string().as_str()));
}
partial_spf
}
#[allow(dead_code)]
pub(crate) fn build_spf_str_from_ip(str: &[Mechanism<IpNetwork>]) -> String {
let mut partial_spf = String::new();
for i in str.iter() {
partial_spf.push_str(&format!(" {}", i.to_string().as_str()));
}
partial_spf
}
#[cfg(feature = "strict-dns")]
pub(crate) mod dns {
use addr::parse_dns_name;
pub(crate) fn get_domain_before_slash(s: &str) -> &str {
if !s.starts_with('/') && s.contains('/') {
s.split('/').next().unwrap()
} else {
s
}
}
pub(crate) fn is_dns_suffix_valid(name: &str) -> bool {
if name == "a" || name == "mx" || name == "ptr" || name == "all" || name.starts_with('/') {
true
} else {
match parse_dns_name(name) {
Err(_) => false,
Ok(dns) => dns.has_known_suffix(),
}
}
}
#[cfg(feature = "strict-dns")]
pub(crate) mod warn {
use crate::core::dns::is_dns_suffix_valid;
#[allow(dead_code)]
pub(crate) fn check_for_dns_warning(warning_vec: &mut Vec<String>, name: &str) {
if !is_dns_suffix_valid(name) {
warning_vec.push(name.to_string());
}
}
#[cfg(test)]
mod test {
use crate::core::dns::{get_domain_before_slash, is_dns_suffix_valid};
#[test]
fn start_with_slash() {
let input = "/24";
assert_eq!(get_domain_before_slash(input), "/24");
}
#[test]
fn domain_contains_slash() {
let input = "test.com/24";
assert_eq!(get_domain_before_slash(input), "test.com");
}
#[test]
fn domain_without_slash() {
let input = "test.com";
assert_eq!(get_domain_before_slash(input), "test.com");
}
#[test]
fn invalid_tld() {
assert_eq!(is_dns_suffix_valid("t.xx"), false);
}
#[test]
fn valid_domain() {
assert_eq!(is_dns_suffix_valid("test.com"), true);
}
#[test]
fn valid_a() {
assert_eq!(is_dns_suffix_valid("a"), true);
}
#[test]
fn valid_mx() {
assert_eq!(is_dns_suffix_valid("mx"), true);
}
#[test]
fn valid_ptr() {
assert_eq!(is_dns_suffix_valid("ptr"), true);
}
}
}
pub(crate) mod strict {
mod test {}
}
}