Skip to main content

cfb/
lib.rs

1//! A library for reading/writing [Compound File Binary](
2//! https://en.wikipedia.org/wiki/Compound_File_Binary_Format) (structured
3//! storage) files.  See [MS-CFB](
4//! https://msdn.microsoft.com/en-us/library/dd942138.aspx) for the format
5//! specification.
6//!
7//! A Compound File Binary (CFB) file, also called a *structured storage file*
8//! or simply a *compound file*, is a bit like a simple file system within a
9//! file.  A compound file contains a tree of *storage* objects
10//! (i.e. directories), each of which can contain *stream* objects (i.e. files)
11//! or other storage objects.  The format is designed to allow reasonably
12//! efficient in-place mutation and resizing of these stream and storage
13//! objects, without having to completely rewrite the CFB file on disk.
14//!
15//! # Example usage
16//!
17//! ```no_run
18//! use cfb;
19//! use std::io::{Read, Seek, SeekFrom, Write};
20//!
21//! // Open an existing compound file in read-write mode.
22//! let mut comp = cfb::open_rw("path/to/cfb/file").unwrap();
23//!
24//! // Read in all the data from one of the streams in that compound file.
25//! let data = {
26//!     let mut stream = comp.open_stream("/foo/bar").unwrap();
27//!     let mut buffer = Vec::new();
28//!     stream.read_to_end(&mut buffer).unwrap();
29//!     buffer
30//! };
31//!
32//! // Append that data to the end of another stream in the same file.
33//! {
34//!     let mut stream = comp.open_stream("/baz").unwrap();
35//!     stream.seek(SeekFrom::End(0)).unwrap();
36//!     stream.write_all(&data).unwrap();
37//! }
38//!
39//! // Now create a new compound file, and create a new stream with the data.
40//! let mut comp2 = cfb::create("some/other/path").unwrap();
41//! comp2.create_storage("/spam/").unwrap();
42//! let mut stream = comp2.create_stream("/spam/eggs").unwrap();
43//! stream.write_all(&data).unwrap();
44//! ```
45
46#![warn(missing_docs)]
47
48use std::fmt;
49use std::fs;
50use std::io::{self, Read, Seek, SeekFrom, Write};
51use std::mem::size_of;
52use std::path::{Path, PathBuf};
53use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
54
55use fnv::FnvHashSet;
56use uuid::Uuid;
57
58use crate::internal::consts;
59use crate::internal::DEFAULT_STREAM_MAX_BUFFER_SIZE;
60use crate::internal::{
61    Allocator, DirEntry, Directory, EntriesOrder, Header, MiniAllocator,
62    ObjType, SectorInit, Sectors, Timestamp, Validation,
63};
64pub use crate::internal::{Entries, Entry, Stream, Version};
65
66#[macro_use]
67mod internal;
68
69//===========================================================================//
70
71/// Opens an existing compound file at the given path in read-only mode.
72pub fn open<P: AsRef<Path>>(path: P) -> io::Result<CompoundFile<fs::File>> {
73    OpenOptions::new().open(path)
74}
75
76/// Opens an existing compound file at the given path in read-write mode.
77pub fn open_rw<P: AsRef<Path>>(path: P) -> io::Result<CompoundFile<fs::File>> {
78    OpenOptions::new().open_rw(path)
79}
80
81/// Creates a new compound file with no contents at the given path.
82///
83/// The returned `CompoundFile` object will be both readable and writable.  If
84/// a file already exists at the given path, this will overwrite it.
85pub fn create<P: AsRef<Path>>(path: P) -> io::Result<CompoundFile<fs::File>> {
86    OpenOptions::new().create(path)
87}
88
89//===========================================================================//
90
91/// Options for opening or creating a compound file.
92pub struct OpenOptions {
93    pub(crate) max_buffer_size: usize,
94    pub(crate) validation: Validation,
95}
96
97impl OpenOptions {
98    /// Creates a new `OpenOptions` with default settings.
99    pub fn new() -> Self {
100        OpenOptions::default()
101    }
102
103    /// Sets the maximum size of a stream's internal buffer.
104    ///
105    /// The buffer grows dynamically up to this limit. Larger limits can improve
106    /// throughput for large streams, while smaller limits reduce peak memory
107    /// usage. Values below the internal minimum are clamped up to that minimum.
108    pub fn max_buffer_size(mut self, size: usize) -> Self {
109        self.max_buffer_size = size;
110        self
111    }
112
113    /// Any violation of the CFB spec will be treated as an error when parsing.
114    pub fn strict(mut self) -> Self {
115        self.validation = Validation::Strict;
116        self
117    }
118
119    /// Opens an existing compound file at the given path in read-only mode.
120    pub fn open<P: AsRef<Path>>(
121        self,
122        path: P,
123    ) -> io::Result<CompoundFile<fs::File>> {
124        self.open_with(fs::File::open(path)?)
125    }
126
127    /// Opens an existing compound file at the given path in read-write mode.
128    pub fn open_rw<P: AsRef<Path>>(
129        self,
130        path: P,
131    ) -> io::Result<CompoundFile<fs::File>> {
132        let file = fs::OpenOptions::new().read(true).write(true).open(path)?;
133        self.open_with(file)
134    }
135
136    /// Creates a new compound file with no contents at the given path.
137    ///
138    /// The returned `CompoundFile` object will be both readable and writable.
139    /// If a file already exists at the given path, this will overwrite it.
140    pub fn create<P: AsRef<Path>>(
141        self,
142        path: P,
143    ) -> io::Result<CompoundFile<fs::File>> {
144        let file = fs::OpenOptions::new()
145            .read(true)
146            .write(true)
147            .create(true)
148            .truncate(true)
149            .open(path)?;
150        self.create_with(file)
151    }
152
153    /// Opens an existing compound file using the underlying reader.
154    pub fn open_with<F: Read + Seek>(
155        self,
156        inner: F,
157    ) -> io::Result<CompoundFile<F>> {
158        CompoundFile::open_internal(
159            inner,
160            self.validation,
161            self.max_buffer_size,
162        )
163    }
164
165    /// Creates a new compound file with no contents using the underlying writer.
166    pub fn create_with<F: Read + Write + Seek>(
167        self,
168        inner: F,
169    ) -> io::Result<CompoundFile<F>> {
170        CompoundFile::create_with_version_and_options(
171            Version::V4,
172            inner,
173            self.max_buffer_size,
174        )
175    }
176}
177
178impl Default for OpenOptions {
179    fn default() -> Self {
180        OpenOptions {
181            max_buffer_size: DEFAULT_STREAM_MAX_BUFFER_SIZE,
182            validation: Validation::Permissive,
183        }
184    }
185}
186
187//===========================================================================//
188
189/// A compound file, backed by an underlying reader/writer (such as a
190/// [`File`](https://doc.rust-lang.org/std/fs/struct.File.html) or
191/// [`Cursor`](https://doc.rust-lang.org/std/io/struct.Cursor.html)).
192pub struct CompoundFile<F> {
193    minialloc: Arc<RwLock<MiniAllocator<F>>>,
194    max_buffer_size: usize,
195}
196
197impl<F> CompoundFile<F> {
198    fn minialloc(&self) -> RwLockReadGuard<'_, MiniAllocator<F>> {
199        self.minialloc.read().unwrap()
200    }
201
202    fn minialloc_mut(&mut self) -> RwLockWriteGuard<'_, MiniAllocator<F>> {
203        self.minialloc.write().unwrap()
204    }
205
206    /// Returns the CFB format version used for this compound file.
207    pub fn version(&self) -> Version {
208        self.minialloc().version()
209    }
210
211    fn stream_id_for_name_chain(&self, names: &[&str]) -> Option<u32> {
212        self.minialloc().stream_id_for_name_chain(names)
213    }
214
215    /// Returns information about the root storage object.  This is equivalent
216    /// to `self.entry("/").unwrap()` (but always succeeds).
217    pub fn root_entry(&self) -> Entry {
218        Entry::new(self.minialloc().root_dir_entry(), PathBuf::from("/"))
219    }
220
221    /// Given a path within the compound file, get information about that
222    /// stream or storage object.
223    pub fn entry<P: AsRef<Path>>(&self, path: P) -> io::Result<Entry> {
224        self.entry_with_path(path.as_ref())
225    }
226
227    fn entry_with_path(&self, path: &Path) -> io::Result<Entry> {
228        let names = internal::path::name_chain_from_path(path)?;
229        let path = internal::path::path_from_name_chain(&names);
230        let stream_id = match self.stream_id_for_name_chain(&names) {
231            Some(stream_id) => stream_id,
232            None => not_found!("No such object: {:?}", path),
233        };
234        Ok(Entry::new(self.minialloc().dir_entry(stream_id), path))
235    }
236
237    /// Returns an iterator over the entries within the root storage object.
238    /// This is equivalent to `self.read_storage("/").unwrap()` (but always
239    /// succeeds).
240    pub fn read_root_storage(&self) -> Entries<'_, F> {
241        let start = self.minialloc().root_dir_entry().child;
242        Entries::new(
243            EntriesOrder::Nonrecursive,
244            &self.minialloc,
245            internal::path::path_from_name_chain(&[]),
246            start,
247        )
248    }
249
250    /// Returns an iterator over the entries within a storage object.
251    pub fn read_storage<P: AsRef<Path>>(
252        &self,
253        path: P,
254    ) -> io::Result<Entries<'_, F>> {
255        self.read_storage_with_path(path.as_ref())
256    }
257
258    fn read_storage_with_path(
259        &self,
260        path: &Path,
261    ) -> io::Result<Entries<'_, F>> {
262        let names = internal::path::name_chain_from_path(path)?;
263        let path = internal::path::path_from_name_chain(&names);
264        let stream_id = match self.stream_id_for_name_chain(&names) {
265            Some(stream_id) => stream_id,
266            None => not_found!("No such storage: {:?}", path),
267        };
268        let start = {
269            let minialloc = self.minialloc();
270            let dir_entry = minialloc.dir_entry(stream_id);
271            if dir_entry.obj_type == ObjType::Stream {
272                invalid_input!("Not a storage: {:?}", path);
273            }
274            debug_assert!(
275                dir_entry.obj_type == ObjType::Storage
276                    || dir_entry.obj_type == ObjType::Root
277            );
278            dir_entry.child
279        };
280        Ok(Entries::new(
281            EntriesOrder::Nonrecursive,
282            &self.minialloc,
283            path,
284            start,
285        ))
286    }
287
288    /// Returns an iterator over all entries within the compound file, starting
289    /// from and including the root entry.  The iterator walks the storage tree
290    /// in a preorder traversal.  This is equivalent to
291    /// `self.walk_storage("/").unwrap()` (but always succeeds).
292    pub fn walk(&self) -> Entries<'_, F> {
293        Entries::new(
294            EntriesOrder::Preorder,
295            &self.minialloc,
296            internal::path::path_from_name_chain(&[]),
297            consts::ROOT_STREAM_ID,
298        )
299    }
300
301    /// Returns an iterator over all entries under a storage subtree, including
302    /// the given path itself.  The iterator walks the storage tree in a
303    /// preorder traversal.
304    pub fn walk_storage<P: AsRef<Path>>(
305        &self,
306        path: P,
307    ) -> io::Result<Entries<'_, F>> {
308        self.walk_storage_with_path(path.as_ref())
309    }
310
311    fn walk_storage_with_path(
312        &self,
313        path: &Path,
314    ) -> io::Result<Entries<'_, F>> {
315        let mut names = internal::path::name_chain_from_path(path)?;
316        let stream_id = match self.stream_id_for_name_chain(&names) {
317            Some(stream_id) => stream_id,
318            None => not_found!(
319                "No such object: {:?}",
320                internal::path::path_from_name_chain(&names)
321            ),
322        };
323        names.pop();
324        let parent_path = internal::path::path_from_name_chain(&names);
325        Ok(Entries::new(
326            EntriesOrder::Preorder,
327            &self.minialloc,
328            parent_path,
329            stream_id,
330        ))
331    }
332
333    /// Returns true if there is an existing stream or storage at the given
334    /// path, or false if there is nothing at that path.
335    pub fn exists<P: AsRef<Path>>(&self, path: P) -> bool {
336        match internal::path::name_chain_from_path(path.as_ref()) {
337            Ok(names) => self.stream_id_for_name_chain(&names).is_some(),
338            Err(_) => false,
339        }
340    }
341
342    /// Returns true if there is an existing stream at the given path, or false
343    /// if there is a storage or nothing at that path.
344    pub fn is_stream<P: AsRef<Path>>(&self, path: P) -> bool {
345        match internal::path::name_chain_from_path(path.as_ref()) {
346            Ok(names) => match self.stream_id_for_name_chain(&names) {
347                Some(stream_id) => {
348                    self.minialloc().dir_entry(stream_id).obj_type
349                        == ObjType::Stream
350                }
351                None => false,
352            },
353            Err(_) => false,
354        }
355    }
356
357    /// Returns true if there is an existing storage at the given path, or
358    /// false if there is a stream or nothing at that path.
359    pub fn is_storage<P: AsRef<Path>>(&self, path: P) -> bool {
360        match internal::path::name_chain_from_path(path.as_ref()) {
361            Ok(names) => match self.stream_id_for_name_chain(&names) {
362                Some(stream_id) => {
363                    self.minialloc().dir_entry(stream_id).obj_type
364                        != ObjType::Stream
365                }
366                None => false,
367            },
368            Err(_) => false,
369        }
370    }
371
372    // TODO: pub fn copy_stream
373
374    // TODO: pub fn rename
375
376    /// Consumes the `CompoundFile`, returning the underlying reader/writer.
377    pub fn into_inner(self) -> F {
378        // We only ever retain Weak copies of the CompoundFile's minialloc Rc
379        // (e.g. in Stream structs), so the Rc::try_unwrap() should always
380        // succeed.
381        match Arc::try_unwrap(self.minialloc) {
382            Ok(ref_cell) => ref_cell.into_inner().unwrap().into_inner(),
383            Err(_) => unreachable!(),
384        }
385    }
386}
387
388impl<F: Seek> CompoundFile<F> {
389    /// Opens an existing stream in the compound file for reading and/or
390    /// writing (depending on what the underlying file supports).
391    pub fn open_stream<P: AsRef<Path>>(
392        &mut self,
393        path: P,
394    ) -> io::Result<Stream<F>> {
395        self.open_stream_with_path(path.as_ref())
396    }
397
398    fn open_stream_with_path(&mut self, path: &Path) -> io::Result<Stream<F>> {
399        let names = internal::path::name_chain_from_path(path)?;
400        let path = internal::path::path_from_name_chain(&names);
401        let stream_id = match self.stream_id_for_name_chain(&names) {
402            Some(stream_id) => stream_id,
403            None => not_found!("No such stream: {:?}", path),
404        };
405        if self.minialloc().dir_entry(stream_id).obj_type != ObjType::Stream {
406            invalid_input!("Not a stream: {:?}", path);
407        }
408        Ok(Stream::new(&self.minialloc, stream_id, self.max_buffer_size))
409    }
410}
411
412impl<F: Read + Seek> CompoundFile<F> {
413    /// Opens an existing compound file, using the underlying reader.  If the
414    /// underlying reader also supports the `Write` trait, then the
415    /// `CompoundFile` object will be writable as well.
416    pub fn open(inner: F) -> io::Result<CompoundFile<F>> {
417        OpenOptions::new().open_with(inner)
418    }
419
420    /// Like `open()`, but is stricter when parsing and will return an error if
421    /// the file violates the CFB spec in any way (which many CFB files in the
422    /// wild do).  This is mainly useful for validating a CFB file or
423    /// implementation (such as this crate itself) to help ensure compatibility
424    /// with other readers.
425    pub fn open_strict(inner: F) -> io::Result<CompoundFile<F>> {
426        OpenOptions::new().strict().open_with(inner)
427    }
428
429    fn open_internal(
430        mut inner: F,
431        validation: Validation,
432        max_buffer_size: usize,
433    ) -> io::Result<CompoundFile<F>> {
434        let inner_len = inner.seek(SeekFrom::End(0))?;
435        if inner_len < consts::HEADER_LEN as u64 {
436            invalid_data!(
437                "Invalid CFB file ({} bytes is too small)",
438                inner_len
439            );
440        }
441        inner.seek(SeekFrom::Start(0))?;
442
443        // 2.2 Compound File Header
444        let header = Header::read_from(&mut inner, validation)?;
445        // Major Version
446        let sector_len = header.version.sector_len();
447        if inner_len
448            > (consts::MAX_REGULAR_SECTOR as u64 + 1) * (sector_len as u64)
449        {
450            invalid_data!(
451                "Invalid CFB file ({} bytes is too large)",
452                inner_len
453            );
454        }
455
456        if inner_len < header.version.sector_len() as u64 {
457            invalid_data!(
458                "Invalid CFB file (length of {} < sector length of {})",
459                inner_len,
460                header.version.sector_len()
461            );
462        }
463        let mut sectors = Sectors::new(header.version, inner_len, inner);
464        let num_sectors = sectors.num_sectors();
465
466        // Read in DIFAT.
467        let mut difat = Vec::<u32>::new();
468        difat.extend_from_slice(&header.initial_difat_entries);
469        let mut seen_sector_ids = FnvHashSet::default();
470        let mut difat_sector_ids = Vec::new();
471        let mut current_difat_sector = header.first_difat_sector;
472        while current_difat_sector != consts::END_OF_CHAIN
473            && current_difat_sector != consts::FREE_SECTOR
474        {
475            if current_difat_sector > consts::MAX_REGULAR_SECTOR {
476                invalid_data!(
477                    "DIFAT chain includes invalid sector index {}",
478                    current_difat_sector
479                );
480            } else if current_difat_sector >= num_sectors {
481                invalid_data!(
482                    "DIFAT chain includes sector index {}, but sector count \
483                     is only {}",
484                    current_difat_sector,
485                    num_sectors
486                );
487            }
488            if seen_sector_ids.contains(&current_difat_sector) {
489                invalid_data!(
490                    "DIFAT chain includes duplicate sector index {}",
491                    current_difat_sector,
492                );
493            }
494            seen_sector_ids.insert(current_difat_sector);
495            difat_sector_ids.push(current_difat_sector);
496            let mut sector = sectors.seek_to_sector(current_difat_sector)?;
497            for _ in 0..(sector_len / size_of::<u32>() - 1) {
498                let next = sector.read_le_u32()?;
499                if next != consts::FREE_SECTOR
500                    && next > consts::MAX_REGULAR_SECTOR
501                {
502                    invalid_data!(
503                        "DIFAT refers to invalid sector index {}",
504                        next
505                    );
506                }
507                difat.push(next);
508            }
509            current_difat_sector = sector.read_le_u32()?;
510            if validation.is_strict()
511                && current_difat_sector == consts::FREE_SECTOR
512            {
513                invalid_data!(
514                    "DIFAT chain must terminate with {}, not {}",
515                    consts::END_OF_CHAIN,
516                    consts::FREE_SECTOR
517                );
518            }
519        }
520        if validation.is_strict()
521            && header.num_difat_sectors as usize != difat_sector_ids.len()
522        {
523            invalid_data!(
524                "Incorrect DIFAT chain length (header says {}, actual is {})",
525                header.num_difat_sectors,
526                difat_sector_ids.len()
527            );
528        }
529        // The DIFAT should be padded with FREE_SECTOR, but DIFAT sectors
530        // may instead instead be incorrectly zero padded (see
531        // https://github.com/mdsteele/rust-cfb/issues/41).
532        // In case num_fat_sectors is not reliable, only remove zeroes,
533        // and don't remove sectors from the header DIFAT.
534        if !validation.is_strict() {
535            while difat.len() > consts::NUM_DIFAT_ENTRIES_IN_HEADER
536                && difat.len() > header.num_fat_sectors as usize
537                && difat.last() == Some(&0)
538            {
539                difat.pop();
540            }
541        }
542        while difat.last() == Some(&consts::FREE_SECTOR) {
543            difat.pop();
544        }
545        if validation.is_strict()
546            && header.num_fat_sectors as usize != difat.len()
547        {
548            invalid_data!(
549                "Incorrect number of FAT sectors (header says {}, DIFAT says \
550                 {})",
551                header.num_fat_sectors,
552                difat.len()
553            );
554        }
555
556        // Read in FAT.
557        let mut fat = Vec::<u32>::new();
558        for &sector_index in difat.iter() {
559            if sector_index >= num_sectors {
560                invalid_data!(
561                    "DIFAT refers to sector {}, but sector count is only {}",
562                    sector_index,
563                    num_sectors
564                );
565            }
566            let mut sector = sectors.seek_to_sector(sector_index)?;
567            for _ in 0..(sector_len / size_of::<u32>()) {
568                fat.push(sector.read_le_u32()?);
569            }
570        }
571        // If the number of sectors in the file is not a multiple of the number
572        // of FAT entries per sector, then the last FAT sector must be padded
573        // with FREE_SECTOR entries (see MS-CFB section 2.3).  However, some
574        // CFB implementations incorrectly pad the last FAT sector with zeros
575        // (see https://github.com/mdsteele/rust-cfb/issues/8), so we allow
576        // this under Permissive validation.  Since zero is normally a
577        // meaningful FAT entry (referring to sector 0), we only want to strip
578        // zeros from the end of the FAT if they are beyond the number of
579        // sectors in the file.
580        // Files have been seen with erroneous other types of sectors beyond
581        // EOF, so strip those as well.
582        if !validation.is_strict() {
583            while fat.len() > num_sectors as usize {
584                if fat.last() == Some(&0)
585                    || fat.last() == Some(&consts::DIFAT_SECTOR)
586                    || fat.last() == Some(&consts::FAT_SECTOR)
587                    || fat.last() == Some(&consts::FREE_SECTOR)
588                {
589                    fat.pop();
590                } else {
591                    break;
592                }
593            }
594        }
595        // Strip FREE_SECTOR entries from the end of the FAT.
596        while fat.len() > num_sectors as usize
597            && fat.last() == Some(&consts::FREE_SECTOR)
598        {
599            fat.pop();
600        }
601        while fat.len() < num_sectors as usize {
602            fat.push(consts::FREE_SECTOR);
603        }
604
605        let mut allocator =
606            Allocator::new(sectors, difat_sector_ids, difat, fat, validation)?;
607
608        // Read in directory.
609        let mut dir_entries = Vec::<DirEntry>::new();
610        let mut seen_dir_sectors = FnvHashSet::default();
611        let mut current_dir_sector = header.first_dir_sector;
612        let mut dir_sector_count = 1;
613        while current_dir_sector != consts::END_OF_CHAIN {
614            if validation.is_strict()
615                && header.version == Version::V4
616                && dir_sector_count > header.num_dir_sectors
617            {
618                invalid_data!(
619                    "Directory chain includes at least {} sectors which is greater than header num_dir_sectors {}",
620                    dir_sector_count,
621                    header.num_dir_sectors
622                );
623            }
624            if current_dir_sector > consts::MAX_REGULAR_SECTOR {
625                invalid_data!(
626                    "Directory chain includes invalid sector index {}",
627                    current_dir_sector
628                );
629            } else if current_dir_sector >= num_sectors {
630                invalid_data!(
631                    "Directory chain includes sector index {}, but sector \
632                     count is only {}",
633                    current_dir_sector,
634                    num_sectors
635                );
636            }
637            if seen_dir_sectors.contains(&current_dir_sector) {
638                invalid_data!(
639                    "Directory chain includes duplicate sector index {}",
640                    current_dir_sector,
641                );
642            }
643            seen_dir_sectors.insert(current_dir_sector);
644            {
645                let mut sector =
646                    allocator.seek_to_sector(current_dir_sector)?;
647                for _ in 0..header.version.dir_entries_per_sector() {
648                    dir_entries.push(DirEntry::read_from(
649                        &mut sector,
650                        header.version,
651                        validation,
652                    )?);
653                }
654            }
655            current_dir_sector = allocator.next(current_dir_sector)?;
656            dir_sector_count += 1;
657        }
658
659        let mut directory = Directory::new(
660            allocator,
661            dir_entries,
662            header.first_dir_sector,
663            validation,
664        )?;
665
666        // Read in MiniFAT.
667        let minifat = {
668            let mut chain = directory
669                .open_chain(header.first_minifat_sector, SectorInit::Fat)?;
670            if validation.is_strict()
671                && header.num_minifat_sectors as usize != chain.num_sectors()
672            {
673                invalid_data!(
674                    "Incorrect MiniFAT chain length (header says {}, actual \
675                     is {})",
676                    header.num_minifat_sectors,
677                    chain.num_sectors()
678                );
679            }
680            let num_minifat_entries = (chain.len() / 4) as usize;
681            let mut minifat = Vec::<u32>::with_capacity(num_minifat_entries);
682            for _ in 0..num_minifat_entries {
683                minifat.push(chain.read_le_u32()?);
684            }
685            while minifat.last() == Some(&consts::FREE_SECTOR) {
686                minifat.pop();
687            }
688            minifat
689        };
690
691        let minialloc = MiniAllocator::new(
692            directory,
693            minifat,
694            header.first_minifat_sector,
695            validation,
696        )?;
697
698        Ok(CompoundFile {
699            minialloc: Arc::new(RwLock::new(minialloc)),
700            max_buffer_size,
701        })
702    }
703}
704
705impl<F: Read + Write + Seek> CompoundFile<F> {
706    /// Creates a new compound file with no contents, using the underlying
707    /// reader/writer.  The reader/writer should be initially empty.
708    pub fn create(inner: F) -> io::Result<CompoundFile<F>> {
709        OpenOptions::new().create_with(inner)
710    }
711
712    /// Creates a new compound file of the given version with no contents,
713    /// using the underlying writer.  The writer should be initially empty.
714    pub fn create_with_version(
715        version: Version,
716        inner: F,
717    ) -> io::Result<CompoundFile<F>> {
718        CompoundFile::create_with_version_and_options(
719            version,
720            inner,
721            DEFAULT_STREAM_MAX_BUFFER_SIZE,
722        )
723    }
724
725    fn create_with_version_and_options(
726        version: Version,
727        mut inner: F,
728        max_buffer_size: usize,
729    ) -> io::Result<CompoundFile<F>> {
730        let mut header = Header {
731            version,
732            // 2.2 requires this to be zero in V3
733            num_dir_sectors: if version == Version::V3 { 0 } else { 1 },
734            num_fat_sectors: 1,
735            first_dir_sector: 1,
736            first_minifat_sector: consts::END_OF_CHAIN,
737            num_minifat_sectors: 0,
738            first_difat_sector: consts::END_OF_CHAIN,
739            num_difat_sectors: 0,
740            initial_difat_entries: [consts::FREE_SECTOR;
741                consts::NUM_DIFAT_ENTRIES_IN_HEADER],
742        };
743        header.initial_difat_entries[0] = 0;
744        header.write_to(&mut inner)?;
745
746        // Pad the header with zeroes so it's the length of a sector.
747        let sector_len = version.sector_len();
748        debug_assert!(sector_len >= consts::HEADER_LEN);
749        if sector_len > consts::HEADER_LEN {
750            inner.write_all(&vec![0; sector_len - consts::HEADER_LEN])?;
751        }
752
753        // Write FAT sector:
754        let fat: Vec<u32> = vec![consts::FAT_SECTOR, consts::END_OF_CHAIN];
755        for &entry in fat.iter() {
756            inner.write_le_u32(entry)?;
757        }
758        for _ in fat.len()..(sector_len / size_of::<u32>()) {
759            inner.write_le_u32(consts::FREE_SECTOR)?;
760        }
761        let difat: Vec<u32> = vec![0];
762        let difat_sector_ids: Vec<u32> = vec![];
763
764        // Write directory sector:
765        let root_dir_entry = DirEntry::empty_root_entry();
766        root_dir_entry.write_to(&mut inner)?;
767        for _ in 1..version.dir_entries_per_sector() {
768            DirEntry::unallocated().write_to(&mut inner)?;
769        }
770
771        let sectors = Sectors::new(version, 3 * sector_len as u64, inner);
772        let allocator = Allocator::new(
773            sectors,
774            difat_sector_ids,
775            difat,
776            fat,
777            Validation::Strict,
778        )?;
779        let directory = Directory::new(
780            allocator,
781            vec![root_dir_entry],
782            1,
783            Validation::Strict,
784        )?;
785        let minialloc = MiniAllocator::new(
786            directory,
787            vec![],
788            consts::END_OF_CHAIN,
789            Validation::Strict,
790        )?;
791        Ok(CompoundFile {
792            minialloc: Arc::new(RwLock::new(minialloc)),
793            max_buffer_size,
794        })
795    }
796
797    /// Creates a new, empty storage object (i.e. "directory") at the provided
798    /// path.  The parent storage object must already exist.
799    pub fn create_storage<P: AsRef<Path>>(
800        &mut self,
801        path: P,
802    ) -> io::Result<()> {
803        self.create_storage_with_path(path.as_ref())
804    }
805
806    fn create_storage_with_path(&mut self, path: &Path) -> io::Result<()> {
807        let mut names = internal::path::name_chain_from_path(path)?;
808        if let Some(stream_id) = self.stream_id_for_name_chain(&names) {
809            let path = internal::path::path_from_name_chain(&names);
810            if self.minialloc().dir_entry(stream_id).obj_type
811                != ObjType::Stream
812            {
813                already_exists!(
814                    "Cannot create storage at {:?} because a \
815                                 storage already exists there",
816                    path
817                );
818            } else {
819                already_exists!(
820                    "Cannot create storage at {:?} because a \
821                                 stream already exists there",
822                    path
823                );
824            }
825        }
826        // If names is empty, that means we're trying to create the root.  But
827        // the root always already exists and will have been rejected above.
828        debug_assert!(!names.is_empty());
829        let name = names.pop().unwrap();
830        let parent_id = match self.stream_id_for_name_chain(&names) {
831            Some(stream_id) => stream_id,
832            None => not_found!("Parent storage doesn't exist"),
833        };
834        self.minialloc_mut().insert_dir_entry(
835            parent_id,
836            name,
837            ObjType::Storage,
838        )?;
839        Ok(())
840    }
841
842    /// Recursively creates a storage and all of its parent storages if they
843    /// are missing.
844    pub fn create_storage_all<P: AsRef<Path>>(
845        &mut self,
846        path: P,
847    ) -> io::Result<()> {
848        self.create_storage_all_with_path(path.as_ref())
849    }
850
851    fn create_storage_all_with_path(&mut self, path: &Path) -> io::Result<()> {
852        let names = internal::path::name_chain_from_path(path)?;
853        for length in 1..(names.len() + 1) {
854            let prefix_path =
855                internal::path::path_from_name_chain(&names[..length]);
856            if self.is_storage(&prefix_path) {
857                continue;
858            }
859            self.create_storage_with_path(&prefix_path)?;
860        }
861        Ok(())
862    }
863
864    /// Removes the storage object at the provided path.  The storage object
865    /// must exist and have no children.
866    pub fn remove_storage<P: AsRef<Path>>(
867        &mut self,
868        path: P,
869    ) -> io::Result<()> {
870        self.remove_storage_with_path(path.as_ref())
871    }
872
873    fn remove_storage_with_path(&mut self, path: &Path) -> io::Result<()> {
874        let mut names = internal::path::name_chain_from_path(path)?;
875        let stream_id = match self.stream_id_for_name_chain(&names) {
876            Some(parent_id) => parent_id,
877            None => not_found!("No such storage: {:?}", path),
878        };
879        {
880            let minialloc = self.minialloc();
881            let dir_entry = minialloc.dir_entry(stream_id);
882            if dir_entry.obj_type == ObjType::Root {
883                invalid_input!("Cannot remove the root storage object");
884            }
885            if dir_entry.obj_type == ObjType::Stream {
886                invalid_input!("Not a storage: {:?}", path);
887            }
888            debug_assert_eq!(dir_entry.obj_type, ObjType::Storage);
889            if dir_entry.child != consts::NO_STREAM {
890                invalid_input!("Storage is not empty: {:?}", path);
891            }
892        }
893        debug_assert!(!names.is_empty());
894        let name = names.pop().unwrap();
895        let parent_id = self.stream_id_for_name_chain(&names).unwrap();
896        self.minialloc_mut().remove_dir_entry(parent_id, name)?;
897        Ok(())
898    }
899
900    /// Recursively removes a storage and all of its children.  If called on
901    /// the root storage, recursively removes all of its children but not the
902    /// root storage itself (which cannot be removed).
903    pub fn remove_storage_all<P: AsRef<Path>>(
904        &mut self,
905        path: P,
906    ) -> io::Result<()> {
907        self.remove_storage_all_with_path(path.as_ref())
908    }
909
910    fn remove_storage_all_with_path(&mut self, path: &Path) -> io::Result<()> {
911        let mut stack = self.walk_storage(path)?.collect::<Vec<Entry>>();
912        while let Some(entry) = stack.pop() {
913            if entry.is_stream() {
914                self.remove_stream_with_path(entry.path())?;
915            } else if !entry.is_root() {
916                self.remove_storage_with_path(entry.path())?;
917            }
918        }
919        Ok(())
920    }
921
922    /// Sets the CLSID for the storage object at the provided path.  (To get
923    /// the current CLSID for a storage object, use
924    /// `self.entry(path)?.clsid()`.)
925    pub fn set_storage_clsid<P: AsRef<Path>>(
926        &mut self,
927        path: P,
928        clsid: Uuid,
929    ) -> io::Result<()> {
930        self.set_storage_clsid_with_path(path.as_ref(), clsid)
931    }
932
933    fn set_storage_clsid_with_path(
934        &mut self,
935        path: &Path,
936        clsid: Uuid,
937    ) -> io::Result<()> {
938        let names = internal::path::name_chain_from_path(path)?;
939        let stream_id = match self.stream_id_for_name_chain(&names) {
940            Some(stream_id) => stream_id,
941            None => not_found!(
942                "No such storage: {:?}",
943                internal::path::path_from_name_chain(&names)
944            ),
945        };
946        let mut minialloc = self.minialloc_mut();
947        if minialloc.dir_entry(stream_id).obj_type == ObjType::Stream {
948            invalid_input!(
949                "Not a storage: {:?}",
950                internal::path::path_from_name_chain(&names)
951            );
952        }
953        minialloc.with_dir_entry_mut(stream_id, |dir_entry| {
954            dir_entry.clsid = clsid;
955        })
956    }
957
958    /// Creates and returns a new, empty stream object at the provided path.
959    /// If a stream already exists at that path, it will be replaced by the new
960    /// stream.  The parent storage object must already exist.
961    pub fn create_stream<P: AsRef<Path>>(
962        &mut self,
963        path: P,
964    ) -> io::Result<Stream<F>> {
965        self.create_stream_with_path(path.as_ref(), true)
966    }
967
968    /// Creates and returns a new, empty stream object at the provided path.
969    /// Returns an error if a stream already exists at that path.  The parent
970    /// storage object must already exist.
971    pub fn create_new_stream<P: AsRef<Path>>(
972        &mut self,
973        path: P,
974    ) -> io::Result<Stream<F>> {
975        self.create_stream_with_path(path.as_ref(), false)
976    }
977
978    fn create_stream_with_path(
979        &mut self,
980        path: &Path,
981        overwrite: bool,
982    ) -> io::Result<Stream<F>> {
983        let mut names = internal::path::name_chain_from_path(path)?;
984        if let Some(stream_id) = self.stream_id_for_name_chain(&names) {
985            if self.minialloc().dir_entry(stream_id).obj_type
986                != ObjType::Stream
987            {
988                already_exists!(
989                    "Cannot create stream at {:?} because a \
990                                 storage already exists there",
991                    internal::path::path_from_name_chain(&names)
992                );
993            } else if !overwrite {
994                already_exists!(
995                    "Cannot create new stream at {:?} because a \
996                                 stream already exists there",
997                    internal::path::path_from_name_chain(&names)
998                );
999            } else {
1000                let mut stream = Stream::new(
1001                    &self.minialloc,
1002                    stream_id,
1003                    self.max_buffer_size,
1004                );
1005                stream.set_len(0)?;
1006                return Ok(stream);
1007            }
1008        }
1009        // If names is empty, that means we're trying to create the root.  But
1010        // the root always already exists and will have been rejected above.
1011        debug_assert!(!names.is_empty());
1012        let name = names.pop().unwrap();
1013        let parent_id = match self.stream_id_for_name_chain(&names) {
1014            Some(stream_id) => stream_id,
1015            None => not_found!("Parent storage doesn't exist"),
1016        };
1017        let new_stream_id = self.minialloc_mut().insert_dir_entry(
1018            parent_id,
1019            name,
1020            ObjType::Stream,
1021        )?;
1022        Ok(Stream::new(&self.minialloc, new_stream_id, self.max_buffer_size))
1023    }
1024
1025    /// Removes the stream object at the provided path.
1026    pub fn remove_stream<P: AsRef<Path>>(
1027        &mut self,
1028        path: P,
1029    ) -> io::Result<()> {
1030        self.remove_stream_with_path(path.as_ref())
1031    }
1032
1033    fn remove_stream_with_path(&mut self, path: &Path) -> io::Result<()> {
1034        let mut names = internal::path::name_chain_from_path(path)?;
1035        let stream_id = match self.stream_id_for_name_chain(&names) {
1036            Some(parent_id) => parent_id,
1037            None => not_found!("No such stream: {:?}", path),
1038        };
1039        let (start_sector_id, is_in_mini_stream) = {
1040            let minialloc = self.minialloc();
1041            let dir_entry = minialloc.dir_entry(stream_id);
1042            if dir_entry.obj_type != ObjType::Stream {
1043                invalid_input!("Not a stream: {:?}", path);
1044            }
1045            debug_assert_eq!(dir_entry.child, consts::NO_STREAM);
1046            (
1047                dir_entry.start_sector,
1048                dir_entry.stream_len < consts::MINI_STREAM_CUTOFF as u64,
1049            )
1050        };
1051        if is_in_mini_stream {
1052            self.minialloc_mut().free_mini_chain(start_sector_id)?;
1053        } else {
1054            self.minialloc_mut().free_chain(start_sector_id)?;
1055        }
1056        debug_assert!(!names.is_empty());
1057        let name = names.pop().unwrap();
1058        let parent_id = self.stream_id_for_name_chain(&names).unwrap();
1059        self.minialloc_mut().remove_dir_entry(parent_id, name)?;
1060        Ok(())
1061    }
1062
1063    /// Sets the user-defined bitflags for the object at the provided path.
1064    /// (To get the current state bits for an object, use
1065    /// `self.entry(path)?.state_bits()`.)
1066    pub fn set_state_bits<P: AsRef<Path>>(
1067        &mut self,
1068        path: P,
1069        bits: u32,
1070    ) -> io::Result<()> {
1071        self.set_entry_with_path(path.as_ref(), |dir_entry| {
1072            dir_entry.state_bits = bits
1073        })
1074    }
1075
1076    /// Sets the modified time for the object at the given path to now.  Has no
1077    /// effect when called on the root storage.
1078    pub fn touch<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
1079        self.set_modified_time(path, web_time::SystemTime::now())
1080    }
1081
1082    /// Sets the modified time for the object at the given path.
1083    /// Has no effect on streams due to requirements imposed by CFB spec.
1084    pub fn set_modified_time<P: AsRef<Path>>(
1085        &mut self,
1086        path: P,
1087        ts: web_time::SystemTime,
1088    ) -> io::Result<()> {
1089        self.set_entry_with_path(path.as_ref(), |dir_entry| {
1090            if dir_entry.obj_type != ObjType::Stream {
1091                dir_entry.modified_time = Timestamp::from_system_time(ts);
1092            }
1093        })
1094    }
1095
1096    /// Sets the created time for the object at the given path.
1097    /// Has no effect on streams due to requirements imposed by CFB spec.
1098    pub fn set_created_time<P: AsRef<Path>>(
1099        &mut self,
1100        path: P,
1101        ts: web_time::SystemTime,
1102    ) -> io::Result<()> {
1103        self.set_entry_with_path(path.as_ref(), |dir_entry| {
1104            if dir_entry.obj_type != ObjType::Stream {
1105                dir_entry.creation_time = Timestamp::from_system_time(ts);
1106            }
1107        })
1108    }
1109
1110    fn set_entry_with_path<G: FnMut(&mut DirEntry)>(
1111        &mut self,
1112        path: &Path,
1113        f: G,
1114    ) -> io::Result<()> {
1115        let names = internal::path::name_chain_from_path(path)?;
1116        let path = internal::path::path_from_name_chain(&names);
1117        let stream_id = match self.stream_id_for_name_chain(&names) {
1118            Some(stream_id) => stream_id,
1119            None => not_found!("No such object: {:?}", path),
1120        };
1121        self.minialloc_mut().with_dir_entry_mut(stream_id, f)?;
1122        Ok(())
1123    }
1124
1125    /// Flushes all changes to the underlying file.
1126    pub fn flush(&mut self) -> io::Result<()> {
1127        self.minialloc_mut().flush()
1128    }
1129}
1130
1131impl<F: fmt::Debug> fmt::Debug for CompoundFile<F> {
1132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1133        f.debug_tuple("CompoundFile").field(self.minialloc().inner()).finish()
1134    }
1135}
1136
1137trait ReadLeNumber: Read {
1138    fn read_le_u64(&mut self) -> Result<u64, std::io::Error> {
1139        let mut buf = [0u8; 8];
1140        self.read_exact(&mut buf)?;
1141        Ok(u64::from_le_bytes(buf))
1142    }
1143    fn read_le_u32(&mut self) -> Result<u32, std::io::Error> {
1144        let mut buf = [0u8; 4];
1145        self.read_exact(&mut buf)?;
1146        Ok(u32::from_le_bytes(buf))
1147    }
1148    fn read_le_u16(&mut self) -> Result<u16, std::io::Error> {
1149        let mut buf = [0u8; 2];
1150        self.read_exact(&mut buf)?;
1151        Ok(u16::from_le_bytes(buf))
1152    }
1153}
1154impl<T: Read> ReadLeNumber for T {}
1155
1156trait WriteLeNumber: Write {
1157    fn write_le_u64(&mut self, num: u64) -> Result<(), std::io::Error> {
1158        self.write_all(&num.to_le_bytes())
1159    }
1160    fn write_le_u32(&mut self, num: u32) -> Result<(), std::io::Error> {
1161        self.write_all(&num.to_le_bytes())
1162    }
1163    fn write_le_u16(&mut self, num: u16) -> Result<(), std::io::Error> {
1164        self.write_all(&num.to_le_bytes())
1165    }
1166}
1167impl<T: Write> WriteLeNumber for T {}
1168//===========================================================================//
1169
1170#[cfg(test)]
1171mod tests {
1172    use std::io::{self, Cursor, Seek, SeekFrom};
1173    use std::mem::size_of;
1174    use std::path::Path;
1175
1176    use crate::internal::{
1177        consts, DirEntry, Header, ObjType, Timestamp, Version,
1178    };
1179    use crate::{ReadLeNumber, WriteLeNumber};
1180
1181    use super::CompoundFile;
1182
1183    fn make_cfb_file_with_zero_padded_fat() -> io::Result<Vec<u8>> {
1184        let version = Version::V3;
1185        let mut data = Vec::<u8>::new();
1186        let mut header = Header {
1187            version,
1188            num_dir_sectors: 0,
1189            num_fat_sectors: 1,
1190            first_dir_sector: 1,
1191            first_minifat_sector: consts::END_OF_CHAIN,
1192            num_minifat_sectors: 0,
1193            first_difat_sector: consts::END_OF_CHAIN,
1194            num_difat_sectors: 0,
1195            initial_difat_entries: [consts::FREE_SECTOR;
1196                consts::NUM_DIFAT_ENTRIES_IN_HEADER],
1197        };
1198        header.initial_difat_entries[0] = 0;
1199        header.write_to(&mut data)?;
1200        // Write FAT sector:
1201        let fat: Vec<u32> = vec![consts::FAT_SECTOR, consts::END_OF_CHAIN];
1202        for &entry in fat.iter() {
1203            data.write_le_u32(entry)?;
1204        }
1205        // Pad the FAT sector with zeros instead of FREE_SECTOR.  Technically
1206        // this violates the MS-CFB spec (section 2.3), but apparently some CFB
1207        // implementations do this.
1208        for _ in fat.len()..(version.sector_len() / size_of::<u32>()) {
1209            data.write_le_u32(0)?;
1210        }
1211        // Write directory sector:
1212        DirEntry::empty_root_entry().write_to(&mut data)?;
1213        for _ in 1..version.dir_entries_per_sector() {
1214            DirEntry::unallocated().write_to(&mut data)?;
1215        }
1216        Ok(data)
1217    }
1218
1219    fn make_cfb_with_ts(ts: web_time::SystemTime) -> Vec<u8> {
1220        use std::io::Write;
1221
1222        let mut buf = Vec::new();
1223        let mut cfb = CompoundFile::create(io::Cursor::new(&mut buf)).unwrap();
1224
1225        cfb.create_storage("/foo/").unwrap();
1226        let mut stream = cfb.create_stream("/foo/bar").unwrap();
1227        stream.write_all(b"data").unwrap();
1228        drop(stream);
1229
1230        let entries: Vec<_> = cfb.walk().collect();
1231        for entr in entries {
1232            cfb.set_modified_time(entr.path(), ts).unwrap();
1233            cfb.set_created_time(entr.path(), ts).unwrap();
1234        }
1235        cfb.flush().unwrap();
1236        buf
1237    }
1238
1239    #[test]
1240    fn zero_padded_fat_strict() {
1241        let data = make_cfb_file_with_zero_padded_fat().unwrap();
1242        let result = CompoundFile::open_strict(Cursor::new(data));
1243        assert_eq!(
1244            result.err().unwrap().to_string(),
1245            "Malformed FAT (FAT has 128 entries, but file has only 2 sectors)"
1246        );
1247    }
1248
1249    // Regression test for https://github.com/mdsteele/rust-cfb/issues/8.
1250    #[test]
1251    fn zero_padded_fat_permissive() {
1252        let data = make_cfb_file_with_zero_padded_fat().unwrap();
1253        // Despite the zero-padded FAT, we should be able to read this file
1254        // under Permissive validation.
1255        CompoundFile::open(Cursor::new(data)).expect("open");
1256    }
1257
1258    fn make_cfb_file_with_zero_padded_difat() -> io::Result<Vec<u8>> {
1259        let version = Version::V3;
1260        let mut data = Vec::<u8>::new();
1261
1262        let dir_sector = 0;
1263        let difat_sector = 1;
1264        // The zero-padded DIFAT issue is only seen with a DIFAT sector
1265        let num_fat_sectors = consts::NUM_DIFAT_ENTRIES_IN_HEADER + 1;
1266        // Layout FAT sectors after the DIFAT sector
1267        let first_fat_sector = difat_sector + 1;
1268        let fat_sectors: Vec<u32> = (0..num_fat_sectors)
1269            .map(|i| (first_fat_sector + i) as u32)
1270            .collect();
1271
1272        // Construct header full of DIFAT entries
1273        let header = Header {
1274            version,
1275            num_dir_sectors: 0,
1276            num_fat_sectors: num_fat_sectors as u32,
1277            first_dir_sector: dir_sector as u32,
1278            first_minifat_sector: consts::END_OF_CHAIN,
1279            num_minifat_sectors: 0,
1280            first_difat_sector: difat_sector as u32,
1281            num_difat_sectors: 1,
1282            initial_difat_entries: std::array::from_fn(|difat_entry_i| {
1283                fat_sectors[difat_entry_i]
1284            }),
1285        };
1286        header.write_to(&mut data)?;
1287
1288        // Write the directory sector
1289        DirEntry::empty_root_entry().write_to(&mut data)?;
1290        for _ in 1..version.dir_entries_per_sector() {
1291            DirEntry::unallocated().write_to(&mut data)?;
1292        }
1293
1294        // Write the DIFAT sector
1295        let num_difat_entries_in_sector =
1296            version.sector_len() / size_of::<u32>() - 1;
1297        for i in 0..num_difat_entries_in_sector {
1298            let difat_entry_i = i + consts::NUM_DIFAT_ENTRIES_IN_HEADER;
1299
1300            let entry = if difat_entry_i < num_fat_sectors {
1301                fat_sectors[difat_entry_i]
1302            } else {
1303                // Pad with zeroes instead of FREE_SECTOR, this is
1304                // the point where it deviates from spec.
1305                0
1306            };
1307            data.write_le_u32(entry)?;
1308        }
1309        // End DIFAT chain
1310        data.write_le_u32(consts::END_OF_CHAIN)?;
1311
1312        // Write the first two FAT sectors, referencing the header data
1313        let num_fat_entries_in_sector =
1314            version.sector_len() / size_of::<u32>();
1315        let mut fat = vec![consts::FREE_SECTOR; num_fat_entries_in_sector * 2];
1316        fat[difat_sector] = consts::DIFAT_SECTOR;
1317        fat[dir_sector] = consts::END_OF_CHAIN;
1318        for fat_sector in fat_sectors {
1319            fat[fat_sector as usize] = consts::FAT_SECTOR;
1320        }
1321        for entry in fat {
1322            data.write_le_u32(entry)?;
1323        }
1324
1325        // Pad out the rest of the FAT sectors with FREE_SECTOR
1326        for _fat_sector in 2..num_fat_sectors {
1327            for _i in 0..num_fat_entries_in_sector {
1328                data.write_le_u32(consts::FREE_SECTOR)?;
1329            }
1330        }
1331
1332        Ok(data)
1333    }
1334
1335    #[test]
1336    fn zero_padded_difat_strict() {
1337        let data = make_cfb_file_with_zero_padded_difat().unwrap();
1338        let result = CompoundFile::open_strict(Cursor::new(data));
1339        assert_eq!(
1340            result.err().unwrap().to_string(),
1341            "Incorrect number of FAT sectors (header says 110, DIFAT says 236)",
1342        );
1343    }
1344
1345    // Regression test for https://github.com/mdsteele/rust-cfb/issues/41.
1346    #[test]
1347    fn zero_padded_difat_permissive() {
1348        let data = make_cfb_file_with_zero_padded_difat().unwrap();
1349        // Despite the zero-padded DIFAT, we should be able to read this file
1350        // under Permissive validation.
1351        CompoundFile::open(Cursor::new(data)).expect("open");
1352    }
1353
1354    // Regression test for https://github.com/mdsteele/rust-cfb/issues/52.
1355    #[test]
1356    fn update_num_dir_sectors() {
1357        // Create a CFB file with 2 sectors for the directory.
1358        let cursor = Cursor::new(Vec::new());
1359        let mut comp = CompoundFile::create(cursor).unwrap();
1360        // root + 31 entries in the first sector
1361        // 1 stream entry in the second sector
1362        for i in 0..32 {
1363            let path = format!("stream{i}");
1364            let path = Path::new(&path);
1365            comp.create_stream(path).unwrap();
1366        }
1367        comp.flush().unwrap();
1368
1369        // read num_dir_sectors from the header
1370        let mut cursor = comp.into_inner();
1371        cursor.seek(SeekFrom::Start(40)).unwrap();
1372        let num_dir_sectors = cursor.read_le_u32().unwrap();
1373        assert_eq!(num_dir_sectors, 2);
1374    }
1375
1376    #[test]
1377    fn deterministic_cfbs() {
1378        let ts = web_time::SystemTime::now();
1379        let cfb1 = make_cfb_with_ts(ts);
1380        let cfb2 = make_cfb_with_ts(ts);
1381        let ts = Timestamp::from_system_time(ts);
1382        assert_eq!(cfb1, cfb2);
1383
1384        let cfb = CompoundFile::open(Cursor::new(&cfb1)).unwrap();
1385
1386        let entry = cfb.entry("/foo").unwrap();
1387        assert_eq!(Timestamp::from_system_time(entry.created()), ts);
1388        assert_eq!(Timestamp::from_system_time(entry.modified()), ts);
1389
1390        let strict = CompoundFile::open_strict(Cursor::new(cfb1)).unwrap();
1391
1392        let entry = strict.entry("/foo").unwrap();
1393        assert_eq!(Timestamp::from_system_time(entry.created()), ts);
1394        assert_eq!(Timestamp::from_system_time(entry.modified()), ts);
1395    }
1396    fn make_cfb_with_inconsistent_difat_entries() -> io::Result<Vec<u8>> {
1397        let mut data = Vec::new();
1398        // cfb has spare DIFAT_SECTOR entries in FAT not accounted for in header
1399        let mut hdr = Header {
1400            version: Version::V3,
1401            num_dir_sectors: 0,
1402            num_fat_sectors: 1,
1403            first_dir_sector: 0,
1404            first_minifat_sector: consts::END_OF_CHAIN,
1405            num_minifat_sectors: 0,
1406            first_difat_sector: consts::END_OF_CHAIN,
1407            num_difat_sectors: 0,
1408            initial_difat_entries: [consts::FREE_SECTOR;
1409                consts::NUM_DIFAT_ENTRIES_IN_HEADER],
1410        };
1411        hdr.initial_difat_entries[0] = 1;
1412
1413        hdr.write_to(&mut data)?;
1414
1415        // write dir sector
1416        for entr in [
1417            DirEntry::new("Root Entry", ObjType::Root, Timestamp::now()),
1418            DirEntry::unallocated(),
1419            DirEntry::unallocated(),
1420            DirEntry::unallocated(),
1421        ] {
1422            entr.write_to(&mut data)?;
1423        }
1424
1425        // write FAT sector
1426        data.extend(&consts::END_OF_CHAIN.to_le_bytes());
1427        data.extend(&consts::FAT_SECTOR.to_le_bytes());
1428        // add a DIFAT_SECTOR to FAT, although inconsistent with header
1429        data.extend(&consts::DIFAT_SECTOR.to_le_bytes());
1430        for _ in (0..128).skip(3) {
1431            data.extend(&consts::FREE_SECTOR.to_le_bytes());
1432        }
1433
1434        Ok(data)
1435    }
1436
1437    #[test]
1438    fn too_many_fat_entries() {
1439        use std::io::Write;
1440
1441        let cfb = make_cfb_with_inconsistent_difat_entries().unwrap();
1442
1443        let mut cfb = CompoundFile::open(Cursor::new(cfb)).unwrap();
1444        let mut f = cfb.create_stream("stream").unwrap();
1445        f.write_all(&vec![0; 1024 * 1024]).unwrap();
1446    }
1447}
1448
1449//===========================================================================//