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#[derive(Debug, Clone, PartialEq)]
15pub struct Builder;
16
17#[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#[derive(Debug, Clone, PartialEq)]
31#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32pub struct SpfBuilder<State = Builder> {
33 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
83impl 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
120impl 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 validate::check_whitespaces(s)?;
146 let source = String::from(s);
147
148 let mut spf = SpfBuilder::new();
150 let records = source.split_whitespace();
152
153 for record in records {
154 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 } 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 pub fn new() -> Self {
225 SpfBuilder::default()
226 }
227 pub fn version(&self) -> &String {
229 &self.version
230 }
231}
232impl SpfBuilder<Builder> {
233 pub fn new_builder() -> SpfBuilder<Builder> {
235 SpfBuilder {
236 state: PhantomData::<Builder>,
237 ..Default::default()
238 }
239 }
240}
241impl SpfBuilder<Parsed> {
242 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 pub fn set_v1(&mut self) -> &mut Self {
257 self.version = String::from(SPF1);
258 self
259 }
260 pub fn add_a(&mut self, mechanism: Mechanism<String>) -> &mut Self {
262 self.append_mechanism(mechanism)
263 }
264 pub fn add_mx(&mut self, mechanism: Mechanism<String>) -> &mut Self {
266 self.append_mechanism(mechanism)
267 }
268 pub fn add_include(&mut self, mechanism: Mechanism<String>) -> &mut Self {
270 self.append_mechanism(mechanism)
271 }
272 pub fn add_ip(&mut self, mechanism: Mechanism<IpNetwork>) -> &mut Self {
274 self.append_mechanism(mechanism)
275 }
276}
277impl SpfBuilder<Builder> {
278 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 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 pub fn set_v2_pra(&mut self) -> &mut Self {
319 self.version = String::from(crate::core::SPF2_PRA);
320 self
321 }
322 pub fn set_v2_mfrom(&mut self) -> &mut Self {
324 self.version = String::from(crate::core::SPF2_MFROM);
325 self
326 }
327 pub fn set_v2_pra_mfrom(&mut self) -> &mut Self {
329 self.version = String::from(crate::core::SPF2_PRA_MFROM);
330 self
331 }
332 pub fn set_v2_mfrom_pra(&mut self) -> &mut Self {
334 self.version = String::from(crate::core::SPF2_MFROM_PRA);
335 self
336 }
337 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 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 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 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 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 pub fn is_redirect(&self) -> bool {
568 self.redirect.is_some()
569 }
570 pub fn redirect(&self) -> Option<&Mechanism<String>> {
572 self.redirect.as_ref()
573 }
574 pub fn includes(&self) -> Option<&Vec<Mechanism<String>>> {
576 self.include.as_ref()
577 }
578 pub fn a(&self) -> Option<&Vec<Mechanism<String>>> {
580 self.a.as_ref()
581 }
582 pub fn mx(&self) -> Option<&Vec<Mechanism<String>>> {
584 self.mx.as_ref()
585 }
586 pub fn ip4(&self) -> Option<&Vec<Mechanism<IpNetwork>>> {
588 self.ip4.as_ref()
589 }
590 pub fn ip6(&self) -> Option<&Vec<Mechanism<IpNetwork>>> {
592 self.ip6.as_ref()
593 }
594 pub fn exists(&self) -> Option<&Vec<Mechanism<String>>> {
596 self.exists.as_ref()
597 }
598 pub fn ptr(&self) -> Option<&Mechanism<String>> {
600 self.ptr.as_ref()
601 }
602 pub fn all(&self) -> Option<&Mechanism<All>> {
604 self.all.as_ref()
605 }
606 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); 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 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::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}