keramics_vfs/pathfilter/
filter.rs1use std::sync::Arc;
15
16use keramics_core::ErrorTrace;
17use keramics_formats::{Path, PathCharacterMappings, PathComponent};
18
19use super::enums::ScanTreeType;
20use super::scan_tree::ScanTree;
21use super::signature::PathFilterSignature;
22
23pub struct PathFilter {
25 signatures: Vec<Arc<PathFilterSignature>>,
27
28 case_folding_mappings: Option<PathCharacterMappings>,
30
31 prefix_scan_tree: ScanTree,
33
34 suffix_scan_tree: ScanTree,
36}
37
38impl PathFilter {
39 pub fn new() -> Self {
41 Self {
42 signatures: Vec::new(),
43 case_folding_mappings: None,
44 prefix_scan_tree: ScanTree::new(ScanTreeType::Prefix),
45 suffix_scan_tree: ScanTree::new(ScanTreeType::Suffix),
46 }
47 }
48
49 pub fn add_signature(&mut self, signature: PathFilterSignature) {
51 self.signatures.push(Arc::new(signature));
52 }
53
54 pub fn build(&mut self) -> Result<(), ErrorTrace> {
56 let mut signatures: Vec<Arc<PathFilterSignature>> = Vec::new();
57
58 for signature in self.signatures.iter() {
59 match &self.case_folding_mappings {
60 Some(mappings) => match signature.new_with_case_folding(mappings) {
61 Ok(case_folded_signature) => signatures.push(Arc::new(case_folded_signature)),
62 Err(mut error) => {
63 keramics_core::error_trace_add_frame!(
64 error,
65 "create path filter signature with case folding"
66 );
67 return Err(error);
68 }
69 },
70 None => signatures.push(signature.clone()),
71 }
72 }
73 match self.prefix_scan_tree.build(&signatures) {
74 Ok(_) => {}
75 Err(mut error) => {
76 keramics_core::error_trace_add_frame!(error, "Unable to build prefix scan tree");
77 return Err(error);
78 }
79 }
80 Ok(())
83 }
84
85 pub fn is_match(
87 &self,
88 path: &Path,
89 data_fork_name: Option<&PathComponent>,
90 ) -> Result<bool, ErrorTrace> {
91 let (path, data_fork_name): (Path, Option<PathComponent>) =
92 match &self.case_folding_mappings {
93 Some(mappings) => {
94 let path: Path = match path.new_with_case_folding(mappings) {
95 Ok(case_folded_path) => case_folded_path,
96 Err(mut error) => {
97 keramics_core::error_trace_add_frame!(
98 error,
99 "Unable to apply case folding on path"
100 );
101 return Err(error);
102 }
103 };
104 let data_fork_name: Option<PathComponent> = match data_fork_name {
105 Some(name) => match name.new_with_case_folding(mappings) {
106 Ok(case_folded_name) => Some(case_folded_name),
107 Err(mut error) => {
108 keramics_core::error_trace_add_frame!(
109 error,
110 "Unable to apply case folding on data fork name"
111 );
112 return Err(error);
113 }
114 },
115 None => None,
116 };
117 (path, data_fork_name)
118 }
119 None => (path.clone(), data_fork_name.cloned()),
120 };
121
122 match self.prefix_scan_tree.scan_path(&path) {
123 Some(signature) => Ok(data_fork_name.as_ref() == signature.data_fork_name.as_ref()),
124 None => {
125 Ok(false)
127 }
128 }
129 }
130
131 pub fn set_case_folding(&mut self, case_folding_mappings: PathCharacterMappings) {
133 self.case_folding_mappings = Some(case_folding_mappings);
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 use std::sync::Arc;
142
143 use keramics_types::Ucs2CharacterMappings;
144 use keramics_types::constants::UCS2_CASE_MAPPINGS;
145
146 #[test]
147 fn test_add_signature() -> Result<(), ErrorTrace> {
148 let mut path_filter: PathFilter = PathFilter::new();
149
150 let data_fork_name: PathComponent = PathComponent::from("rsrc");
151 path_filter.add_signature(PathFilterSignature::new(
152 Path::from("/Library/Caches/com.apple.Safari"),
153 Some(data_fork_name.clone()),
154 ));
155
156 assert_eq!(path_filter.signatures.len(), 1);
157 assert_eq!(
158 path_filter.signatures[0].path,
159 Path::from("/Library/Caches/com.apple.Safari")
160 );
161 assert_eq!(
162 path_filter.signatures[0].data_fork_name,
163 Some(data_fork_name)
164 );
165 Ok(())
166 }
167
168 #[test]
169 fn test_build() -> Result<(), ErrorTrace> {
170 let mut path_filter: PathFilter = PathFilter::new();
171
172 path_filter.add_signature(PathFilterSignature::new(
173 Path::from("/Windows/System32/winevt/Logs/Application.evtx"),
174 None,
175 ));
176 path_filter.build()
177 }
178
179 #[test]
180 fn test_is_match() -> Result<(), ErrorTrace> {
181 let mut path_filter: PathFilter = PathFilter::new();
182
183 path_filter.add_signature(PathFilterSignature::new(
184 Path::from("/Windows/System32/winevt/Logs/Application.evtx"),
185 None,
186 ));
187 path_filter.add_signature(PathFilterSignature::new(
188 Path::from("/Windows/SoftwareDistribution/DataStore/DataStore.edb"),
189 None,
190 ));
191 path_filter.build()?;
192
193 let path: Path = Path::from("/Windows/System32/winevt/Logs/Application.evtx");
194 let result: bool = path_filter.is_match(&path, None)?;
195 assert_eq!(result, true);
196
197 let path: Path = Path::from("/Windows/SoftwareDistribution/DataStore/DataStore.edb");
198 let result: bool = path_filter.is_match(&path, None)?;
199 assert_eq!(result, true);
200
201 let path: Path = Path::from("/WINDOWS/SoftwareDistribution/DataStore/DataStore.edb");
202 let result: bool = path_filter.is_match(&path, None)?;
203 assert_eq!(result, false);
204
205 let path: Path = Path::from("/Windows/System32/winevt/Logs/Security.evtx");
206 let result: bool = path_filter.is_match(&path, None)?;
207 assert_eq!(result, false);
208
209 Ok(())
210 }
211
212 #[test]
213 fn test_is_match_with_data_fork_name() -> Result<(), ErrorTrace> {
214 let mut path_filter: PathFilter = PathFilter::new();
215
216 path_filter.add_signature(PathFilterSignature::new(
217 Path::from("/Library/Caches/com.apple.Safari"),
218 Some(PathComponent::from("rsrc")),
219 ));
220 path_filter.build()?;
221
222 let path: Path = Path::from("/Library/Caches/com.apple.Safari");
223
224 let data_fork_name: PathComponent = PathComponent::from("rsrc");
225 let result: bool = path_filter.is_match(&path, Some(&data_fork_name))?;
226 assert_eq!(result, true);
227
228 let data_fork_name: PathComponent = PathComponent::from("data");
229 let result: bool = path_filter.is_match(&path, Some(&data_fork_name))?;
230 assert_eq!(result, false);
231
232 let result: bool = path_filter.is_match(&path, None)?;
233 assert_eq!(result, false);
234
235 Ok(())
236 }
237
238 #[test]
239 fn test_is_match_with_case_folding() -> Result<(), ErrorTrace> {
240 let mut path_filter: PathFilter = PathFilter::new();
241
242 let mappings: PathCharacterMappings = PathCharacterMappings::Ucs2(Arc::new(
243 Ucs2CharacterMappings::from(UCS2_CASE_MAPPINGS.as_slice()),
244 ));
245 path_filter.set_case_folding(mappings);
246
247 path_filter.add_signature(PathFilterSignature::new(
248 Path::from("/Windows/System32/winevt/Logs/Application.evtx"),
249 None,
250 ));
251 path_filter.build()?;
252
253 let path: Path = Path::from("/windows/system32/winevt/logs/application.evtx");
254 let result: bool = path_filter.is_match(&path, None)?;
255 assert_eq!(result, true);
256
257 let path: Path = Path::from("/windows/system32/winevt/logs/security.evtx");
258 let result: bool = path_filter.is_match(&path, None)?;
259 assert_eq!(result, false);
260
261 Ok(())
262 }
263
264 #[test]
265 fn test_is_match_without_signatures() -> Result<(), ErrorTrace> {
266 let mut path_filter: PathFilter = PathFilter::new();
267 path_filter.build()?;
268
269 let path: Path = Path::from("/Library/Caches/com.apple.Safari");
270
271 let data_fork_name: PathComponent = PathComponent::from("rsrc");
272 let result: bool = path_filter.is_match(&path, Some(&data_fork_name))?;
273 assert_eq!(result, false);
274
275 Ok(())
276 }
277}