1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#![deny(missing_debug_implementations)]
pub mod aggregate_report;
pub mod error_handling;
use error_handling::ParsingError;

use libflate::gzip;
use serde_xml_rs::from_reader;
use std::fs::{read_dir, File};
use std::io::BufReader;
use std::io::Read;
use std::path::Path;

pub fn parse<T: std::convert::AsRef<std::ffi::OsStr>>(path: T) -> Result<aggregate_report::feedback, ParsingError> {
    let path = std::path::Path::new(&path);
    if path.is_dir() {
        panic!("path is a directory")
    }

    let extension = path.extension();

    if let Some(extension) = extension {
        match extension.to_str() {
            Some("xml") => {
                let mut reader = get_file_reader(path)?;
                return parse_reader(&mut reader);
            }
            Some("gz") | Some("gzip") => {
                let reader = get_file_reader(path)?;
                let mut decoder = gzip::Decoder::new(reader)?;
                return parse_reader(&mut decoder);
            }
            Some("zip") => {
                let file = File::open(path)?;
                let mut archive = zip::ZipArchive::new(file)?;
                let mut file = archive.by_index(0)?;
                return parse_reader(&mut file);
            }
            _ => {
                let extension = extension.to_str().unwrap_or("").into();
                return Err(ParsingError::UnknownFile { extension });
            }
        }
    }

    let file = File::open(path)?;
    let mut file = BufReader::new(file);

    parse_reader(&mut file)
}

fn get_file_reader(path: &Path) -> Result<BufReader<File>, ParsingError> {
    let file = File::open(path)?;
    Ok(BufReader::new(file))
}

pub fn parse_reader(reader: &mut dyn Read) -> Result<aggregate_report::feedback, ParsingError> {
    match from_reader(reader) {
        Ok(result) => Ok(result),
        Err(error) => Err(error.into()),
    }
}

/// This will panic if any of the files are not readable
/// and will only print an error on the stderr if parsing 
/// of any specific file fails. Use parse for better error handling
pub fn parse_dir(path: &Path) -> Vec<aggregate_report::feedback> {
    let mut results = Vec::new();

    if path.is_dir() {
        for entry in read_dir(path).unwrap() {
            let entry = entry.unwrap();
            let path = entry.path();

            let result = parse(&path);
            match result {
                Ok(result) => results.push(result),
                Err(error) => eprintln!("could not parse: {:?} because {:?}", path, error),
            }
        }
    }
    results
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_parse_single() {
        parse(Path::new("dmarc.xml")).unwrap();
    }

    #[test]
    fn test_parse_dir() {
        parse_dir(Path::new("./"));
    }
}