cfb/internal/
entry.rs

1use crate::internal::{consts, DirEntry, MiniAllocator, ObjType, Timestamp};
2use std::fmt;
3use std::path::{Path, PathBuf};
4use std::sync::{Arc, RwLock};
5use std::time::SystemTime;
6use uuid::Uuid;
7
8//===========================================================================//
9
10/// Metadata about a single object (storage or stream) in a compound file.
11#[derive(Clone)]
12pub struct Entry {
13    name: String,
14    path: PathBuf,
15    obj_type: ObjType,
16    clsid: Uuid,
17    state_bits: u32,
18    creation_time: Timestamp,
19    modified_time: Timestamp,
20    stream_len: u64,
21}
22
23impl Entry {
24    pub(crate) fn new(dir_entry: &DirEntry, path: PathBuf) -> Entry {
25        Entry {
26            name: dir_entry.name.clone(),
27            path,
28            obj_type: dir_entry.obj_type,
29            clsid: dir_entry.clsid,
30            state_bits: dir_entry.state_bits,
31            creation_time: dir_entry.creation_time,
32            modified_time: dir_entry.modified_time,
33            stream_len: dir_entry.stream_len,
34        }
35    }
36
37    /// Returns the name of the object that this entry represents.
38    pub fn name(&self) -> &str {
39        &self.name
40    }
41
42    /// Returns the full path to the object that this entry represents.
43    pub fn path(&self) -> &Path {
44        &self.path
45    }
46
47    /// Returns whether this entry is for a stream object (i.e. a "file" within
48    /// the compound file).
49    pub fn is_stream(&self) -> bool {
50        self.obj_type == ObjType::Stream
51    }
52
53    /// Returns whether this entry is for a storage object (i.e. a "directory"
54    /// within the compound file), either the root or a nested storage.
55    pub fn is_storage(&self) -> bool {
56        self.obj_type == ObjType::Storage || self.obj_type == ObjType::Root
57    }
58
59    /// Returns whether this entry is specifically for the root storage object
60    /// of the compound file.
61    pub fn is_root(&self) -> bool {
62        self.obj_type == ObjType::Root
63    }
64
65    /// Returns the size, in bytes, of the stream that this metadata is for.
66    pub fn len(&self) -> u64 {
67        self.stream_len
68    }
69
70    /// Returns true if the stream is empty.
71    pub fn is_empty(&self) -> bool {
72        self.stream_len == 0
73    }
74
75    /// Returns the CLSID (that is, the object class GUID) for this object.
76    /// This will always be all zeros for stream objects.
77    pub fn clsid(&self) -> &Uuid {
78        &self.clsid
79    }
80
81    /// Returns the user-defined bitflags set for this object.
82    pub fn state_bits(&self) -> u32 {
83        self.state_bits
84    }
85
86    /// Returns the time when the object that this entry represents was
87    /// created.
88    pub fn created(&self) -> SystemTime {
89        self.creation_time.to_system_time()
90    }
91
92    /// Returns the time when the object that this entry represents was last
93    /// modified.
94    pub fn modified(&self) -> SystemTime {
95        self.modified_time.to_system_time()
96    }
97}
98
99impl fmt::Debug for Entry {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
101        write!(
102            f,
103            "{path} ({len} bytes)",
104            path = self.path().display(),
105            len = self.len()
106        )
107    }
108}
109
110//===========================================================================//
111
112#[derive(Clone, Copy, Eq, PartialEq)]
113pub enum EntriesOrder {
114    Nonrecursive,
115    Preorder,
116}
117
118//===========================================================================//
119
120/// An iterator over the entries in a storage object.
121pub struct Entries<'a, F: 'a> {
122    order: EntriesOrder,
123    // TODO: Consider storing a Weak<RefCell<MiniAllocator<F>>> here instead of
124    // a reference to the Rc.  That would allow e.g. opening streams during
125    // iteration.  But we'd need to think about how the iterator should behave
126    // if the CFB tree structure is modified during iteration.
127    minialloc: &'a Arc<RwLock<MiniAllocator<F>>>,
128    stack: Vec<(PathBuf, u32, bool)>,
129}
130
131impl<'a, F> Entries<'a, F> {
132    pub(crate) fn new(
133        order: EntriesOrder,
134        minialloc: &'a Arc<RwLock<MiniAllocator<F>>>,
135        parent_path: PathBuf,
136        start: u32,
137    ) -> Entries<'a, F> {
138        let mut entries = Entries { order, minialloc, stack: Vec::new() };
139        match order {
140            EntriesOrder::Nonrecursive => {
141                entries.stack_left_spine(&parent_path, start);
142            }
143            EntriesOrder::Preorder => {
144                entries.stack.push((parent_path, start, false));
145            }
146        }
147        entries
148    }
149
150    fn stack_left_spine(&mut self, parent_path: &Path, mut current_id: u32) {
151        let minialloc = self.minialloc.read().unwrap();
152        while current_id != consts::NO_STREAM {
153            self.stack.push((parent_path.to_path_buf(), current_id, true));
154            current_id = minialloc.dir_entry(current_id).left_sibling;
155        }
156    }
157}
158
159impl<'a, F> Iterator for Entries<'a, F> {
160    type Item = Entry;
161
162    fn next(&mut self) -> Option<Entry> {
163        if let Some((parent, stream_id, visit_siblings)) = self.stack.pop() {
164            let minialloc = self.minialloc.read().unwrap();
165            let dir_entry = minialloc.dir_entry(stream_id);
166            let path = join_path(&parent, dir_entry);
167            if visit_siblings {
168                self.stack_left_spine(&parent, dir_entry.right_sibling);
169            }
170            if self.order == EntriesOrder::Preorder
171                && dir_entry.obj_type != ObjType::Stream
172                && dir_entry.child != consts::NO_STREAM
173            {
174                self.stack_left_spine(&path, dir_entry.child);
175            }
176            Some(Entry::new(dir_entry, path))
177        } else {
178            None
179        }
180    }
181}
182
183//===========================================================================//
184
185fn join_path(parent_path: &Path, dir_entry: &DirEntry) -> PathBuf {
186    if dir_entry.obj_type == ObjType::Root {
187        parent_path.to_path_buf()
188    } else {
189        parent_path.join(&dir_entry.name)
190    }
191}
192
193//===========================================================================//
194
195#[cfg(test)]
196mod tests {
197    use super::{Entries, EntriesOrder, Entry};
198    use crate::internal::consts::{self, NO_STREAM, ROOT_DIR_NAME};
199    use crate::internal::{
200        Allocator, DirEntry, Directory, MiniAllocator, ObjType, Sectors,
201        Timestamp, Validation, Version,
202    };
203    use std::path::{Path, PathBuf};
204    use std::sync::{Arc, RwLock};
205
206    fn make_entry(
207        name: &str,
208        obj_type: ObjType,
209        left: u32,
210        child: u32,
211        right: u32,
212    ) -> DirEntry {
213        let mut dir_entry = DirEntry::new(name, obj_type, Timestamp::zero());
214        dir_entry.left_sibling = left;
215        dir_entry.child = child;
216        dir_entry.right_sibling = right;
217        dir_entry
218    }
219
220    fn make_minialloc() -> Arc<RwLock<MiniAllocator<()>>> {
221        // Root contains:      3 contains:
222        //      5                  8
223        //     / \                / \
224        //    3   6              7   9
225        //   / \
226        //  1   4
227        //   \
228        //    2
229        let dir_entries = vec![
230            make_entry(ROOT_DIR_NAME, ObjType::Root, NO_STREAM, 5, NO_STREAM),
231            make_entry("1", ObjType::Stream, NO_STREAM, NO_STREAM, 2),
232            make_entry("2", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM),
233            make_entry("3", ObjType::Storage, 1, 8, 4),
234            make_entry("4", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM),
235            make_entry("5", ObjType::Stream, 3, NO_STREAM, 6),
236            make_entry("6", ObjType::Storage, NO_STREAM, NO_STREAM, NO_STREAM),
237            make_entry("7", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM),
238            make_entry("8", ObjType::Stream, 7, NO_STREAM, 9),
239            make_entry("9", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM),
240        ];
241        let version = Version::V3;
242        let sectors =
243            Sectors::new(version, 3 * version.sector_len() as u64, ());
244        let allocator = Allocator::new(
245            sectors,
246            vec![],
247            vec![0],
248            vec![consts::FAT_SECTOR, consts::END_OF_CHAIN],
249            Validation::Strict,
250        )
251        .unwrap();
252        let directory =
253            Directory::new(allocator, dir_entries, 1, Validation::Strict)
254                .unwrap();
255        let minialloc = MiniAllocator::new(
256            directory,
257            vec![],
258            consts::END_OF_CHAIN,
259            Validation::Strict,
260        )
261        .unwrap();
262        Arc::new(RwLock::new(minialloc))
263    }
264
265    fn paths_for_entries(entries: &[Entry]) -> Vec<&Path> {
266        entries.iter().map(|entry| entry.path()).collect()
267    }
268
269    #[test]
270    fn nonrecursive_entries_from_root() {
271        let minialloc = make_minialloc();
272        let entries: Vec<Entry> = Entries::new(
273            EntriesOrder::Nonrecursive,
274            &minialloc,
275            PathBuf::from("/"),
276            5,
277        )
278        .collect();
279        let paths = paths_for_entries(&entries);
280        assert_eq!(
281            paths,
282            vec![
283                Path::new("/1"),
284                Path::new("/2"),
285                Path::new("/3"),
286                Path::new("/4"),
287                Path::new("/5"),
288                Path::new("/6")
289            ]
290        );
291    }
292
293    #[test]
294    fn nonrecursive_entries_from_storage() {
295        let minialloc = make_minialloc();
296        let entries: Vec<Entry> = Entries::new(
297            EntriesOrder::Nonrecursive,
298            &minialloc,
299            PathBuf::from("/3"),
300            8,
301        )
302        .collect();
303        let paths = paths_for_entries(&entries);
304        assert_eq!(
305            paths,
306            vec![Path::new("/3/7"), Path::new("/3/8"), Path::new("/3/9")]
307        );
308    }
309
310    #[test]
311    fn preorder_entries_from_root() {
312        let minialloc = make_minialloc();
313        let entries: Vec<Entry> = Entries::new(
314            EntriesOrder::Preorder,
315            &minialloc,
316            PathBuf::from("/"),
317            0,
318        )
319        .collect();
320        let paths = paths_for_entries(&entries);
321        assert_eq!(
322            paths,
323            vec![
324                Path::new("/"),
325                Path::new("/1"),
326                Path::new("/2"),
327                Path::new("/3"),
328                Path::new("/3/7"),
329                Path::new("/3/8"),
330                Path::new("/3/9"),
331                Path::new("/4"),
332                Path::new("/5"),
333                Path::new("/6"),
334            ]
335        );
336    }
337
338    #[test]
339    fn preorder_entries_from_storage() {
340        let minialloc = make_minialloc();
341        let entries: Vec<Entry> = Entries::new(
342            EntriesOrder::Preorder,
343            &minialloc,
344            PathBuf::from("/"),
345            3,
346        )
347        .collect();
348        let paths = paths_for_entries(&entries);
349        assert_eq!(
350            paths,
351            vec![
352                Path::new("/3"),
353                Path::new("/3/7"),
354                Path::new("/3/8"),
355                Path::new("/3/9")
356            ]
357        );
358    }
359}
360
361//===========================================================================//