1use crate::error::Error;
10use crate::error::Error::MalformedFileMarker;
11use crate::load::RuleFrom::{Disk, Mem};
12use crate::parser::marker;
13use crate::parser::parse::StrTrace;
14use std::fs::File;
15use std::io::{BufRead, BufReader};
16use std::path::{Path, PathBuf};
17use std::{fs, io};
18
19pub(crate) type RuleSource = (PathBuf, String);
20
21pub(crate) enum RuleFrom {
22 Disk(PathBuf),
23 Mem(String),
24}
25
26pub fn rules_from_disk(path: &str) -> Result<Vec<RuleSource>, Error> {
27 rules_from(Disk(PathBuf::from(path)))
28}
29
30pub(crate) fn rules_from(src: RuleFrom) -> Result<Vec<RuleSource>, Error> {
31 let r = match src {
32 Disk(path) if path.is_dir() => rules_dir(&path)?,
33 Disk(path) => rules_file(path)?,
34 Mem(txt) => rules_text(txt)?,
35 };
36 Ok(r)
37}
38
39fn rules_file(path: PathBuf) -> Result<Vec<RuleSource>, io::Error> {
40 let reader = File::open(&path).map(BufReader::new)?;
41 let lines = reader
42 .lines()
43 .flatten()
44 .map(|s| (path.clone(), s))
45 .collect();
46 Ok(lines)
47}
48
49pub fn read_sorted_d_files(from: &Path) -> Result<Vec<PathBuf>, io::Error> {
50 let d_files: Result<Vec<PathBuf>, io::Error> =
51 fs::read_dir(from)?.map(|e| e.map(|e| e.path())).collect();
52
53 let mut d_files: Vec<PathBuf> = d_files?
54 .into_iter()
55 .filter(|p| p.is_file() && p.display().to_string().ends_with(".rules"))
56 .collect();
57
58 d_files.sort_by_key(|p| p.display().to_string());
59
60 Ok(d_files)
61}
62
63fn rules_dir(rules_source_path: &Path) -> Result<Vec<RuleSource>, io::Error> {
64 let d_files = read_sorted_d_files(rules_source_path)?;
65
66 let d_files: Result<Vec<(PathBuf, File)>, io::Error> = d_files
67 .into_iter()
68 .map(|p| (p.clone(), File::open(&p)))
69 .map(|(p, r)| r.map(|f| (p, f)))
70 .collect();
71
72 let d_files = d_files?.into_iter().map(|(p, f)| (p, BufReader::new(f)));
73
74 let d_files = d_files.into_iter().map(|(path, rdr)| {
76 (
77 path.clone(),
78 rdr.lines()
79 .collect::<Result<Vec<String>, io::Error>>()
80 .unwrap_or_else(|_| {
81 panic!("failed to read lines from rules file, {}", path.display())
82 }),
83 )
84 });
85
86 let d_files: Vec<RuleSource> = d_files
87 .into_iter()
88 .flat_map(|(src, lines)| {
89 lines
90 .iter()
91 .map(|l| (src.clone(), l.clone()))
92 .collect::<Vec<RuleSource>>()
93 })
94 .collect();
95
96 Ok(d_files)
98}
99
100fn rules_text(rules_text: String) -> Result<Vec<RuleSource>, Error> {
101 let mut origin: Option<PathBuf> = Some(PathBuf::from("00-analyzer.rules"));
102 let mut lines = vec![];
103 for (num, line) in rules_text.split('\n').map(|s| s.trim()).enumerate() {
104 match marker::parse(StrTrace::new(line)) {
105 Ok((_, v)) => origin = Some(v),
106 Err(_) if line.starts_with('[') || line.ends_with(']') => {
107 return Err(MalformedFileMarker(num + 1, line.trim().to_string()));
110 }
111 Err(_) => {
112 let r = origin
113 .as_ref()
114 .map(|p| (p.clone(), line.to_string()))
115 .map(RuleSource::from)
116 .unwrap();
117 lines.push(r);
118 }
119 };
120 }
121
122 Ok(lines)
124}
125
126#[cfg(test)]
127mod test {
128 use crate::error;
129 use crate::load::rules_text;
130 use std::collections::HashMap;
131 use std::path::PathBuf;
132
133 fn to_map(txt: &str) -> Result<HashMap<PathBuf, Vec<String>>, error::Error> {
134 let mut mapped: HashMap<PathBuf, Vec<String>> = HashMap::new();
135 for (p, r) in rules_text(txt.to_string())? {
136 if !mapped.contains_key(&p) {
137 mapped.insert(p.clone(), vec![]);
138 }
139 mapped.get_mut(&p).unwrap().push(r.clone());
140 }
141 Ok(mapped)
142 }
143
144 #[test]
145 fn text_single() -> Result<(), error::Error> {
146 let txt = r#"
147 [foo.rules]
148 allow perm=any all : all"#;
149 let r = to_map(txt)?;
150 assert!(r.contains_key(&PathBuf::from("foo.rules")));
151 Ok(())
152 }
153
154 #[test]
155 fn text_multi_file() -> Result<(), error::Error> {
156 let txt = r#"
157 [foo.rules]
158 allow perm=any all : all
159 [bar.rules]
160 allow perm=any all : all"#;
161 let r = to_map(txt)?;
162 assert!(r.contains_key(&PathBuf::from("foo.rules")));
163 assert!(r.contains_key(&PathBuf::from("bar.rules")));
164 Ok(())
165 }
166
167 #[test]
168 fn text_empty_file() -> Result<(), error::Error> {
169 let txt = r#"
170 [foo.rules]
171 [bar.rules]
172 allow perm=any all : all"#;
173 let r = to_map(txt)?;
174 assert!(!r.contains_key(&PathBuf::from("foo.rules")));
175 assert!(r.contains_key(&PathBuf::from("bar.rules")));
176 Ok(())
177 }
178}