Skip to main content

mail_auth/report/dmarc/
parse.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use crate::report::{
8    ActionDisposition, Alignment, AuthResult, DKIMAuthResult, DateRange, Disposition, DkimResult,
9    DmarcResult, Error, Extension, Identifier, PolicyEvaluated, PolicyOverride,
10    PolicyOverrideReason, PolicyPublished, Record, Report, ReportMetadata, Row, SPFAuthResult,
11    SPFDomainScope, SpfResult,
12};
13use flate2::read::GzDecoder;
14use mail_parser::{MessageParser, MimeHeaders, PartType};
15use quick_xml::XmlVersion;
16use quick_xml::events::{BytesStart, Event};
17use quick_xml::reader::Reader;
18use std::borrow::Cow;
19use std::io::{BufRead, Cursor, Read};
20use std::net::IpAddr;
21use std::str::FromStr;
22
23impl Report {
24    pub fn parse_rfc5322(report: &[u8]) -> Result<Self, Error> {
25        let message = MessageParser::new()
26            .parse(report)
27            .ok_or(Error::MailParseError)?;
28        let mut error = Error::NoReportsFound;
29
30        for part in &message.parts {
31            match &part.body {
32                PartType::Text(report)
33                    if part
34                        .content_type()
35                        .and_then(|ct| ct.subtype())
36                        .is_some_and(|t| t.eq_ignore_ascii_case("xml"))
37                        || part
38                            .attachment_name()
39                            .and_then(|n| n.rsplit_once('.'))
40                            .is_some_and(|(_, e)| e.eq_ignore_ascii_case("xml")) =>
41                {
42                    match Report::parse_xml(report.as_bytes()) {
43                        Ok(feedback) => return Ok(feedback),
44                        Err(err) => {
45                            error = err.into();
46                        }
47                    }
48                }
49                PartType::Binary(report) | PartType::InlineBinary(report) => {
50                    enum ReportType {
51                        Xml,
52                        Gzip,
53                        Zip,
54                    }
55
56                    let (_, ext) = part
57                        .attachment_name()
58                        .unwrap_or("file.none")
59                        .rsplit_once('.')
60                        .unwrap_or(("file", "none"));
61                    let subtype = part
62                        .content_type()
63                        .and_then(|ct| ct.subtype())
64                        .unwrap_or("none");
65                    let rt = if subtype.eq_ignore_ascii_case("gzip") {
66                        ReportType::Gzip
67                    } else if subtype.eq_ignore_ascii_case("zip") {
68                        ReportType::Zip
69                    } else if subtype.eq_ignore_ascii_case("xml") {
70                        ReportType::Xml
71                    } else if ext.eq_ignore_ascii_case("gz") {
72                        ReportType::Gzip
73                    } else if ext.eq_ignore_ascii_case("zip") {
74                        ReportType::Zip
75                    } else if ext.eq_ignore_ascii_case("xml") {
76                        ReportType::Xml
77                    } else {
78                        continue;
79                    };
80
81                    match rt {
82                        ReportType::Gzip => {
83                            let report: &[u8] = report.as_ref();
84                            let mut file = GzDecoder::new(report);
85                            let mut buf = Vec::new();
86                            file.read_to_end(&mut buf)
87                                .map_err(|err| Error::UncompressError(err.to_string()))?;
88
89                            match Report::parse_xml(&buf) {
90                                Ok(feedback) => return Ok(feedback),
91                                Err(err) => {
92                                    error = err.into();
93                                }
94                            }
95                        }
96                        ReportType::Zip => {
97                            let mut archive = zip::ZipArchive::new(Cursor::new(report))
98                                .map_err(|err| Error::UncompressError(err.to_string()))?;
99                            for i in 0..archive.len() {
100                                match archive.by_index(i) {
101                                    Ok(mut file) => {
102                                        let mut buf =
103                                            Vec::with_capacity(file.compressed_size() as usize);
104                                        file.read_to_end(&mut buf).map_err(|err| {
105                                            Error::UncompressError(err.to_string())
106                                        })?;
107                                        match Report::parse_xml(&buf) {
108                                            Ok(feedback) => return Ok(feedback),
109                                            Err(err) => {
110                                                error = err.into();
111                                            }
112                                        }
113                                    }
114                                    Err(err) => {
115                                        error = Error::UncompressError(err.to_string());
116                                    }
117                                }
118                            }
119                        }
120                        ReportType::Xml => match Report::parse_xml(report) {
121                            Ok(feedback) => return Ok(feedback),
122                            Err(err) => {
123                                error = err.into();
124                            }
125                        },
126                    }
127                }
128                _ => (),
129            }
130        }
131
132        Err(error)
133    }
134
135    pub fn parse_xml(report: &[u8]) -> Result<Self, String> {
136        let mut version: f32 = 0.0;
137        let mut report_metadata = None;
138        let mut policy_published = None;
139        let mut record = Vec::new();
140        let mut extensions = Vec::new();
141
142        let mut reader = Reader::from_reader(report);
143        reader.config_mut().trim_text(true);
144
145        let mut buf = Vec::with_capacity(128);
146        let mut found_feedback = false;
147
148        while let Some(tag) = reader.next_tag(&mut buf)? {
149            match tag.name().as_ref() {
150                b"feedback" if !found_feedback => {
151                    found_feedback = true;
152                }
153                b"version" if found_feedback => {
154                    version = reader.next_value(&mut buf)?.unwrap_or(0.0);
155                }
156                b"report_metadata" if found_feedback => {
157                    report_metadata = ReportMetadata::parse(&mut reader, &mut buf)?.into();
158                }
159                b"policy_published" if found_feedback => {
160                    policy_published = PolicyPublished::parse(&mut reader, &mut buf)?.into();
161                }
162                b"record" if found_feedback => {
163                    record.push(Record::parse(&mut reader, &mut buf)?);
164                }
165                b"extensions" if found_feedback => {
166                    Extension::parse(&mut reader, &mut buf, &mut extensions)?;
167                }
168                b"" => {}
169                other if !found_feedback => {
170                    return Err(format!(
171                        "Unexpected tag {} at position {}.",
172                        String::from_utf8_lossy(other),
173                        reader.buffer_position()
174                    ));
175                }
176                _ => (),
177            }
178        }
179
180        Ok(Report {
181            version,
182            report_metadata: report_metadata.ok_or("Missing feedback/report_metadata tag.")?,
183            policy_published: policy_published.ok_or("Missing feedback/policy_published tag.")?,
184            record,
185            extensions,
186        })
187    }
188}
189
190impl ReportMetadata {
191    pub(crate) fn parse<R: BufRead>(
192        reader: &mut Reader<R>,
193        buf: &mut Vec<u8>,
194    ) -> Result<Self, String> {
195        let mut rm = ReportMetadata::default();
196
197        while let Some(tag) = reader.next_tag(buf)? {
198            match tag.name().as_ref() {
199                b"org_name" => {
200                    rm.org_name = reader.next_value::<String>(buf)?.unwrap_or_default();
201                }
202                b"email" => {
203                    rm.email = reader.next_value::<String>(buf)?.unwrap_or_default();
204                }
205                b"extra_contact_info" => {
206                    rm.extra_contact_info = reader.next_value::<String>(buf)?;
207                }
208                b"report_id" => {
209                    rm.report_id = reader.next_value::<String>(buf)?.unwrap_or_default();
210                }
211                b"date_range" => {
212                    rm.date_range = DateRange::parse(reader, buf)?;
213                }
214                b"error" => {
215                    if let Some(err) = reader.next_value::<String>(buf)? {
216                        rm.error.push(err);
217                    }
218                }
219                b"" => (),
220                _ => {
221                    reader.skip_tag(buf)?;
222                }
223            }
224        }
225
226        Ok(rm)
227    }
228}
229
230impl DateRange {
231    pub(crate) fn parse<R: BufRead>(
232        reader: &mut Reader<R>,
233        buf: &mut Vec<u8>,
234    ) -> Result<Self, String> {
235        let mut dr = DateRange::default();
236
237        while let Some(tag) = reader.next_tag(buf)? {
238            match tag.name().as_ref() {
239                b"begin" => {
240                    dr.begin = reader.next_value(buf)?.unwrap_or_default();
241                }
242                b"end" => {
243                    dr.end = reader.next_value(buf)?.unwrap_or_default();
244                }
245                b"" => (),
246                _ => {
247                    reader.skip_tag(buf)?;
248                }
249            }
250        }
251
252        Ok(dr)
253    }
254}
255
256impl PolicyPublished {
257    pub(crate) fn parse<R: BufRead>(
258        reader: &mut Reader<R>,
259        buf: &mut Vec<u8>,
260    ) -> Result<Self, String> {
261        let mut p = PolicyPublished::default();
262
263        while let Some(tag) = reader.next_tag(buf)? {
264            match tag.name().as_ref() {
265                b"domain" => {
266                    p.domain = reader.next_value::<String>(buf)?.unwrap_or_default();
267                }
268                b"version_published" => {
269                    p.version_published = reader.next_value(buf)?;
270                }
271                b"adkim" => {
272                    p.adkim = reader.next_value(buf)?.unwrap_or_default();
273                }
274                b"aspf" => {
275                    p.aspf = reader.next_value(buf)?.unwrap_or_default();
276                }
277                b"p" => {
278                    p.p = reader.next_value(buf)?.unwrap_or_default();
279                }
280                b"sp" => {
281                    p.sp = reader.next_value(buf)?.unwrap_or_default();
282                }
283                b"testing" => {
284                    p.testing = reader
285                        .next_value::<String>(buf)?
286                        .is_some_and(|s| s.eq_ignore_ascii_case("y"));
287                }
288                b"fo" => {
289                    p.fo = reader.next_value::<String>(buf)?;
290                }
291                b"" => (),
292                _ => {
293                    reader.skip_tag(buf)?;
294                }
295            }
296        }
297
298        Ok(p)
299    }
300}
301
302impl Extension {
303    pub(crate) fn parse<R: BufRead>(
304        reader: &mut Reader<R>,
305        buf: &mut Vec<u8>,
306        extensions: &mut Vec<Extension>,
307    ) -> Result<(), String> {
308        let decoder = reader.decoder();
309        while let Some(tag) = reader.next_tag(buf)? {
310            match tag.name().as_ref() {
311                b"extension" => {
312                    let mut e = Extension::default();
313                    if let Ok(Some(attr)) = tag.try_get_attribute("name")
314                        && let Ok(attr) =
315                            attr.decoded_and_normalized_value(XmlVersion::Implicit1_0, decoder)
316                    {
317                        e.name = attr.to_string();
318                    }
319                    if let Ok(Some(attr)) = tag.try_get_attribute("definition")
320                        && let Ok(attr) =
321                            attr.decoded_and_normalized_value(XmlVersion::Implicit1_0, decoder)
322                    {
323                        e.definition = attr.to_string();
324                    }
325                    extensions.push(e);
326                    reader.skip_tag(buf)?;
327                }
328                b"" => (),
329                _ => {
330                    reader.skip_tag(buf)?;
331                }
332            }
333        }
334
335        Ok(())
336    }
337}
338
339impl Record {
340    pub(crate) fn parse<R: BufRead>(
341        reader: &mut Reader<R>,
342        buf: &mut Vec<u8>,
343    ) -> Result<Self, String> {
344        let mut r = Record::default();
345
346        while let Some(tag) = reader.next_tag(buf)? {
347            match tag.name().as_ref() {
348                b"row" => {
349                    r.row = Row::parse(reader, buf)?;
350                }
351                b"identifiers" => {
352                    r.identifiers = Identifier::parse(reader, buf)?;
353                }
354                b"auth_results" => {
355                    r.auth_results = AuthResult::parse(reader, buf)?;
356                }
357                b"extensions" => {
358                    Extension::parse(reader, buf, &mut r.extensions)?;
359                }
360                b"" => (),
361                _ => {
362                    reader.skip_tag(buf)?;
363                }
364            }
365        }
366
367        Ok(r)
368    }
369}
370
371impl Row {
372    pub(crate) fn parse<R: BufRead>(
373        reader: &mut Reader<R>,
374        buf: &mut Vec<u8>,
375    ) -> Result<Self, String> {
376        let mut r = Row::default();
377
378        while let Some(tag) = reader.next_tag(buf)? {
379            match tag.name().as_ref() {
380                b"source_ip" => {
381                    if let Some(ip) = reader.next_value::<IpAddr>(buf)? {
382                        r.source_ip = ip.into();
383                    }
384                }
385                b"count" => {
386                    r.count = reader.next_value(buf)?.unwrap_or_default();
387                }
388                b"policy_evaluated" => {
389                    r.policy_evaluated = PolicyEvaluated::parse(reader, buf)?;
390                }
391                b"" => (),
392                _ => {
393                    reader.skip_tag(buf)?;
394                }
395            }
396        }
397
398        Ok(r)
399    }
400}
401
402impl PolicyEvaluated {
403    pub(crate) fn parse<R: BufRead>(
404        reader: &mut Reader<R>,
405        buf: &mut Vec<u8>,
406    ) -> Result<Self, String> {
407        let mut pe = PolicyEvaluated::default();
408
409        while let Some(tag) = reader.next_tag(buf)? {
410            match tag.name().as_ref() {
411                b"disposition" => {
412                    pe.disposition = reader.next_value(buf)?.unwrap_or_default();
413                }
414                b"dkim" => {
415                    pe.dkim = reader.next_value(buf)?.unwrap_or_default();
416                }
417                b"spf" => {
418                    pe.spf = reader.next_value(buf)?.unwrap_or_default();
419                }
420                b"reason" => {
421                    pe.reason.push(PolicyOverrideReason::parse(reader, buf)?);
422                }
423                b"" => (),
424                _ => {
425                    reader.skip_tag(buf)?;
426                }
427            }
428        }
429
430        Ok(pe)
431    }
432}
433
434impl PolicyOverrideReason {
435    pub(crate) fn parse<R: BufRead>(
436        reader: &mut Reader<R>,
437        buf: &mut Vec<u8>,
438    ) -> Result<Self, String> {
439        let mut por = PolicyOverrideReason::default();
440
441        while let Some(tag) = reader.next_tag(buf)? {
442            match tag.name().as_ref() {
443                b"type" => {
444                    por.type_ = reader.next_value(buf)?.unwrap_or_default();
445                }
446                b"comment" => {
447                    por.comment = reader.next_value(buf)?;
448                }
449                b"" => (),
450                _ => {
451                    reader.skip_tag(buf)?;
452                }
453            }
454        }
455
456        Ok(por)
457    }
458}
459
460impl Identifier {
461    pub(crate) fn parse<R: BufRead>(
462        reader: &mut Reader<R>,
463        buf: &mut Vec<u8>,
464    ) -> Result<Self, String> {
465        let mut i = Identifier::default();
466
467        while let Some(tag) = reader.next_tag(buf)? {
468            match tag.name().as_ref() {
469                b"envelope_to" => {
470                    i.envelope_to = reader.next_value(buf)?;
471                }
472                b"envelope_from" => {
473                    i.envelope_from = reader.next_value(buf)?.unwrap_or_default();
474                }
475                b"header_from" => {
476                    i.header_from = reader.next_value(buf)?.unwrap_or_default();
477                }
478                b"" => (),
479                _ => {
480                    reader.skip_tag(buf)?;
481                }
482            }
483        }
484
485        Ok(i)
486    }
487}
488
489impl AuthResult {
490    pub(crate) fn parse<R: BufRead>(
491        reader: &mut Reader<R>,
492        buf: &mut Vec<u8>,
493    ) -> Result<Self, String> {
494        let mut ar = AuthResult::default();
495
496        while let Some(tag) = reader.next_tag(buf)? {
497            match tag.name().as_ref() {
498                b"dkim" => {
499                    ar.dkim.push(DKIMAuthResult::parse(reader, buf)?);
500                }
501                b"spf" => {
502                    ar.spf.push(SPFAuthResult::parse(reader, buf)?);
503                }
504                b"" => (),
505                _ => {
506                    reader.skip_tag(buf)?;
507                }
508            }
509        }
510
511        Ok(ar)
512    }
513}
514
515impl DKIMAuthResult {
516    pub(crate) fn parse<R: BufRead>(
517        reader: &mut Reader<R>,
518        buf: &mut Vec<u8>,
519    ) -> Result<Self, String> {
520        let mut dar = DKIMAuthResult::default();
521
522        while let Some(tag) = reader.next_tag(buf)? {
523            match tag.name().as_ref() {
524                b"domain" => {
525                    dar.domain = reader.next_value(buf)?.unwrap_or_default();
526                }
527                b"selector" => {
528                    dar.selector = reader.next_value(buf)?.unwrap_or_default();
529                }
530                b"result" => {
531                    dar.result = reader.next_value(buf)?.unwrap_or_default();
532                }
533                b"human_result" => {
534                    dar.human_result = reader.next_value(buf)?;
535                }
536                b"" => (),
537                _ => {
538                    reader.skip_tag(buf)?;
539                }
540            }
541        }
542
543        Ok(dar)
544    }
545}
546
547impl SPFAuthResult {
548    pub(crate) fn parse<R: BufRead>(
549        reader: &mut Reader<R>,
550        buf: &mut Vec<u8>,
551    ) -> Result<Self, String> {
552        let mut sar = SPFAuthResult::default();
553
554        while let Some(tag) = reader.next_tag(buf)? {
555            match tag.name().as_ref() {
556                b"domain" => {
557                    sar.domain = reader.next_value(buf)?.unwrap_or_default();
558                }
559                b"scope" => {
560                    sar.scope = reader.next_value(buf)?.unwrap_or_default();
561                }
562                b"result" => {
563                    sar.result = reader.next_value(buf)?.unwrap_or_default();
564                }
565                b"human_result" => {
566                    sar.human_result = reader.next_value(buf)?;
567                }
568                b"" => (),
569                _ => {
570                    reader.skip_tag(buf)?;
571                }
572            }
573        }
574
575        Ok(sar)
576    }
577}
578
579impl FromStr for PolicyOverride {
580    type Err = ();
581
582    fn from_str(s: &str) -> Result<Self, Self::Err> {
583        Ok(match s.as_bytes() {
584            b"forwarded" => PolicyOverride::Forwarded,
585            b"sampled_out" => PolicyOverride::SampledOut,
586            b"trusted_forwarder" => PolicyOverride::TrustedForwarder,
587            b"mailing_list" => PolicyOverride::MailingList,
588            b"local_policy" => PolicyOverride::LocalPolicy,
589            b"other" => PolicyOverride::Other,
590            _ => PolicyOverride::Other,
591        })
592    }
593}
594
595impl FromStr for DmarcResult {
596    type Err = ();
597
598    fn from_str(s: &str) -> Result<Self, Self::Err> {
599        Ok(match s.as_bytes() {
600            b"pass" => DmarcResult::Pass,
601            b"fail" => DmarcResult::Fail,
602            _ => DmarcResult::Unspecified,
603        })
604    }
605}
606
607impl FromStr for DkimResult {
608    type Err = ();
609
610    fn from_str(s: &str) -> Result<Self, Self::Err> {
611        Ok(match s.as_bytes() {
612            b"none" => DkimResult::None,
613            b"pass" => DkimResult::Pass,
614            b"fail" => DkimResult::Fail,
615            b"policy" => DkimResult::Policy,
616            b"neutral" => DkimResult::Neutral,
617            b"temperror" => DkimResult::TempError,
618            b"permerror" => DkimResult::PermError,
619            _ => DkimResult::None,
620        })
621    }
622}
623
624impl FromStr for SpfResult {
625    type Err = ();
626
627    fn from_str(s: &str) -> Result<Self, Self::Err> {
628        Ok(match s.as_bytes() {
629            b"none" => SpfResult::None,
630            b"pass" => SpfResult::Pass,
631            b"fail" => SpfResult::Fail,
632            b"softfail" => SpfResult::SoftFail,
633            b"neutral" => SpfResult::Neutral,
634            b"temperror" => SpfResult::TempError,
635            b"permerror" => SpfResult::PermError,
636            _ => SpfResult::None,
637        })
638    }
639}
640
641impl FromStr for SPFDomainScope {
642    type Err = ();
643
644    fn from_str(s: &str) -> Result<Self, Self::Err> {
645        Ok(match s.as_bytes() {
646            b"helo" => SPFDomainScope::Helo,
647            b"mfrom" => SPFDomainScope::MailFrom,
648            _ => SPFDomainScope::Unspecified,
649        })
650    }
651}
652
653impl FromStr for ActionDisposition {
654    type Err = ();
655
656    fn from_str(s: &str) -> Result<Self, Self::Err> {
657        Ok(match s.as_bytes() {
658            b"none" => ActionDisposition::None,
659            b"pass" => ActionDisposition::Pass,
660            b"quarantine" => ActionDisposition::Quarantine,
661            b"reject" => ActionDisposition::Reject,
662            _ => ActionDisposition::Unspecified,
663        })
664    }
665}
666
667impl FromStr for Disposition {
668    type Err = ();
669
670    fn from_str(s: &str) -> Result<Self, Self::Err> {
671        Ok(match s.as_bytes() {
672            b"none" => Disposition::None,
673            b"quarantine" => Disposition::Quarantine,
674            b"reject" => Disposition::Reject,
675            _ => Disposition::Unspecified,
676        })
677    }
678}
679
680impl FromStr for Alignment {
681    type Err = ();
682
683    fn from_str(s: &str) -> Result<Self, Self::Err> {
684        Ok(match s.as_bytes().first() {
685            Some(b'r') => Alignment::Relaxed,
686            Some(b's') => Alignment::Strict,
687            _ => Alignment::Unspecified,
688        })
689    }
690}
691
692trait ReaderHelper {
693    fn next_tag<'x>(&mut self, buf: &'x mut Vec<u8>) -> Result<Option<BytesStart<'x>>, String>;
694    fn next_value<T: FromStr>(&mut self, buf: &mut Vec<u8>) -> Result<Option<T>, String>;
695    fn skip_tag(&mut self, buf: &mut Vec<u8>) -> Result<(), String>;
696}
697
698impl<R: BufRead> ReaderHelper for Reader<R> {
699    fn next_tag<'x>(&mut self, buf: &'x mut Vec<u8>) -> Result<Option<BytesStart<'x>>, String> {
700        match self.read_event_into(buf) {
701            Ok(Event::Start(e)) => Ok(Some(e)),
702            Ok(Event::End(_)) | Ok(Event::Eof) => Ok(None),
703            Err(e) => Err(format!(
704                "Error at position {}: {:?}",
705                self.buffer_position(),
706                e
707            )),
708            _ => Ok(Some(BytesStart::new(""))),
709        }
710    }
711
712    fn next_value<T: FromStr>(&mut self, buf: &mut Vec<u8>) -> Result<Option<T>, String> {
713        let mut value: Option<String> = None;
714
715        loop {
716            match self.read_event_into(buf) {
717                Ok(Event::Text(e)) => {
718                    let v = e.xml_content(XmlVersion::Implicit1_0).map_err(|e| {
719                        format!(
720                            "Failed to decode text value at position {}: {}",
721                            self.buffer_position(),
722                            e
723                        )
724                    })?;
725                    if let Some(value) = &mut value {
726                        value.push_str(&v);
727                    } else {
728                        value = Some(v.into_owned());
729                    }
730                }
731                Ok(Event::GeneralRef(e)) => {
732                    let v = hashify::tiny_map!(&*e,
733                        b"lt" => "<",
734                        b"gt" => ">",
735                        b"amp" => "&",
736                        b"apos" => "'",
737                        b"quot" => "\"",
738                    )
739                    .map(Cow::Borrowed)
740                    .or_else(|| {
741                        e.resolve_char_ref()
742                            .ok()
743                            .flatten()
744                            .map(|v| Cow::Owned(v.to_string()))
745                    })
746                    .unwrap_or_else(|| e.xml_content(XmlVersion::Implicit1_0).unwrap_or_default());
747
748                    if let Some(value) = &mut value {
749                        value.push_str(&v);
750                    } else {
751                        value = Some(v.into_owned());
752                    }
753                }
754                Ok(Event::End(_)) => {
755                    break;
756                }
757                Ok(Event::Start(e)) => {
758                    return Err(format!(
759                        "Expected value, found unexpected tag {} at position {}.",
760                        String::from_utf8_lossy(e.name().as_ref()),
761                        self.buffer_position()
762                    ));
763                }
764                Ok(Event::Eof) => {
765                    return Err(format!(
766                        "Expected value, found unexpected EOF at position {}.",
767                        self.buffer_position()
768                    ));
769                }
770                _ => (),
771            }
772        }
773
774        Ok(value.and_then(|v| T::from_str(&v).ok()))
775    }
776
777    fn skip_tag(&mut self, buf: &mut Vec<u8>) -> Result<(), String> {
778        let mut tag_count = 0;
779        loop {
780            match self.read_event_into(buf) {
781                Ok(Event::End(_)) => {
782                    if tag_count == 0 {
783                        break;
784                    } else {
785                        tag_count -= 1;
786                    }
787                }
788                Ok(Event::Start(_)) => {
789                    tag_count += 1;
790                }
791                Ok(Event::Eof) => {
792                    return Err(format!(
793                        "Expected value, found unexpected EOF at position {}.",
794                        self.buffer_position()
795                    ));
796                }
797                _ => (),
798            }
799        }
800        Ok(())
801    }
802}
803
804#[cfg(test)]
805mod test {
806    use std::{fs, path::PathBuf};
807
808    use crate::report::Report;
809
810    #[test]
811    fn dmarc_report_parse() {
812        let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
813        test_dir.push("resources");
814        test_dir.push("dmarc-feedback");
815
816        for file_name in fs::read_dir(&test_dir).unwrap() {
817            let mut file_name = file_name.unwrap().path();
818            if !file_name.extension().unwrap().to_str().unwrap().eq("xml") {
819                continue;
820            }
821            println!("Parsing DMARC feedback {}", file_name.to_str().unwrap());
822
823            let feedback = Report::parse_xml(&fs::read(&file_name).unwrap()).unwrap();
824
825            file_name.set_extension("json");
826
827            let expected_feedback =
828                serde_json::from_slice::<Report>(&fs::read(&file_name).unwrap()).unwrap();
829
830            assert_eq!(expected_feedback, feedback);
831
832            /*fs::write(
833                &file_name,
834                serde_json::to_string_pretty(&feedback).unwrap().as_bytes(),
835            )
836            .unwrap();*/
837        }
838    }
839
840    #[test]
841    fn dmarc_report_eml_parse() {
842        let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
843        test_dir.push("resources");
844        test_dir.push("dmarc-feedback");
845
846        for file_name in fs::read_dir(&test_dir).unwrap() {
847            let mut file_name = file_name.unwrap().path();
848            if !file_name.extension().unwrap().to_str().unwrap().eq("eml") {
849                continue;
850            }
851            println!("Parsing DMARC feedback {}", file_name.to_str().unwrap());
852
853            let feedback = Report::parse_rfc5322(&fs::read(&file_name).unwrap()).unwrap();
854
855            file_name.set_extension("json");
856
857            let expected_feedback =
858                serde_json::from_slice::<Report>(&fs::read(&file_name).unwrap()).unwrap();
859
860            assert_eq!(expected_feedback, feedback);
861
862            /*fs::write(
863                &file_name,
864                serde_json::to_string_pretty(&feedback).unwrap().as_bytes(),
865            )
866            .unwrap();*/
867        }
868    }
869}