cfb-0.7.3/.cargo_vcs_info.json0000644000000001360000000000100115750ustar { "git": { "sha1": "df3678d0fa60e298f7b1532939b4dede1e2895e4" }, "path_in_vcs": "" }cfb-0.7.3/.github/workflows/tests.yml000064400000000000000000000025230072674642500157010ustar 00000000000000name: tests on: push: paths-ignore: - 'LICENSE-*' - '**.md' pull_request: paths-ignore: - 'LICENSE-*' - '**.md' jobs: linters: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: submodules: true - name: Install rust toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Install rustfmt and clippy run: rustup component add rustfmt clippy - name: Cargo fmt uses: actions-rs/cargo@v1 with: command: fmt args: -- --check - name: Cargo clippy uses: actions-rs/cargo@v1 with: command: clippy args: -- -D warnings tests: strategy: matrix: os: [ ubuntu-20.04, windows-2019, macos-10.15] rust: [ stable ] needs: [linters] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 with: submodules: true - name: Install rust toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - name: Test uses: actions-rs/cargo@v1 with: command: test toolchain: ${{ matrix.rust }} args: --verbose cfb-0.7.3/.gitignore000064400000000000000000000000400072674642500123770ustar 00000000000000*.rs.bk *~ /Cargo.lock /target/ cfb-0.7.3/Cargo.lock0000644000000140030000000000100075460ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ansi_term" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ "winapi", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cfb" version = "0.7.3" dependencies = [ "byteorder", "clap", "fnv", "rand", "rand_pcg", "time", "uuid", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", "bitflags", "strsim", "textwrap", "unicode-width", "vec_map", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "getrandom" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "libc" version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" [[package]] name = "num_threads" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ "libc", ] [[package]] name = "ppv-lite86" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "rand" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ "libc", "rand_chacha", "rand_core", "rand_hc", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ "getrandom", ] [[package]] name = "rand_hc" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" dependencies = [ "rand_core", ] [[package]] name = "rand_pcg" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" dependencies = [ "rand_core", ] [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[package]] name = "time" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" dependencies = [ "libc", "num_threads", ] [[package]] name = "unicode-width" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "uuid" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0" [[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" cfb-0.7.3/Cargo.toml0000644000000021640000000000100075760ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "cfb" version = "0.7.3" authors = ["Matthew D. Steele "] description = "Read/write Compound File Binary (structured storage) files" documentation = "http://mdsteele.github.io/rust-cfb/" readme = "README.md" keywords = [ "cfb", "storage", "structured", ] license = "MIT" repository = "https://github.com/mdsteele/rust-cfb" [dependencies.byteorder] version = "1" [dependencies.fnv] version = "1.0.7" [dependencies.uuid] version = "1" [dev-dependencies.clap] version = "2.27" [dev-dependencies.rand] version = "0.8" [dev-dependencies.rand_pcg] version = "0.3" [dev-dependencies.time] version = "0.3" cfb-0.7.3/Cargo.toml.orig000064400000000000000000000007760072674642500133160ustar 00000000000000[package] name = "cfb" version = "0.7.3" authors = ["Matthew D. Steele "] description = "Read/write Compound File Binary (structured storage) files" repository = "https://github.com/mdsteele/rust-cfb" documentation = "http://mdsteele.github.io/rust-cfb/" keywords = ["cfb", "storage", "structured"] license = "MIT" readme = "README.md" edition = "2018" [dependencies] byteorder = "1" fnv = "1.0.7" uuid = "1" [dev-dependencies] clap = "2.27" rand = "0.8" rand_pcg = "0.3" time = "0.3" cfb-0.7.3/LICENSE000064400000000000000000000020620072674642500114220ustar 00000000000000MIT License Copyright (c) 2017 Matthew D. Steele Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cfb-0.7.3/README.md000064400000000000000000000013300072674642500116710ustar 00000000000000# rust-cfb [![Build Status](https://github.com/mdsteele/rust-cfb/actions/workflows/tests.yml/badge.svg)](https://github.com/mdsteele/rust-cfb/actions/workflows/tests.yml) [![Crates.io](https://img.shields.io/crates/v/cfb.svg)](https://crates.io/crates/cfb) [![Documentation](https://docs.rs/cfb/badge.svg)](https://docs.rs/cfb) A Rust library for reading/writing [Compound File Binary]( https://en.wikipedia.org/wiki/Compound_File_Binary_Format) (structured storage) files. See [MS-CFB](https://msdn.microsoft.com/en-us/library/dd942138.aspx) for the format specification. Documentation: https://mdsteele.github.io/rust-cfb/ ## License rust-cfb is made available under the [MIT License](http://spdx.org/licenses/MIT.html). cfb-0.7.3/examples/cfbtool.rs000064400000000000000000000106640072674642500142400ustar 00000000000000use clap::{App, Arg, SubCommand}; use std::io; use std::path::PathBuf; use time::OffsetDateTime; use uuid::Uuid; fn split(path: &str) -> (PathBuf, PathBuf) { let mut pieces = path.splitn(2, ':'); if let Some(piece1) = pieces.next() { if let Some(piece2) = pieces.next() { (PathBuf::from(piece1), PathBuf::from(piece2)) } else { (PathBuf::from(piece1), PathBuf::new()) } } else { (PathBuf::new(), PathBuf::new()) } } fn list_entry(name: &str, entry: &cfb::Entry, long: bool) { if !long { println!("{}", entry.name()); return; } let length = if entry.len() >= 10_000_000_000 { format!("{} GB", entry.len() / (1 << 30)) } else if entry.len() >= 100_000_000 { format!("{} MB", entry.len() / (1 << 20)) } else if entry.len() >= 1_000_000 { format!("{} kB", entry.len() / (1 << 10)) } else { format!("{} B ", entry.len()) }; let last_modified = { let timestamp = entry.created().max(entry.modified()); let datetime = OffsetDateTime::from(timestamp); let (year, month, day) = datetime.to_calendar_date(); format!("{:04}-{:02}-{:02}", year, month as u8, day) }; println!( "{}{:08x} {:>10} {} {}", if entry.is_storage() { '+' } else { '-' }, entry.state_bits(), length, last_modified, name ); if entry.is_storage() { println!(" {}", entry.clsid().hyphenated()); } } fn main() { let matches = App::new("cfbtool") .version("0.1") .author("Matthew D. Steele ") .about("Inspects and modifies CFB files") .subcommand( SubCommand::with_name("cat") .about("Concatenates and prints streams") .arg(Arg::with_name("path").multiple(true)), ) .subcommand( SubCommand::with_name("chcls") .about("Changes storage CLSIDs") .arg(Arg::with_name("clsid").required(true)) .arg(Arg::with_name("path").multiple(true)), ) .subcommand( SubCommand::with_name("ls") .about("Lists storage contents") .arg( Arg::with_name("all") .short("a") .help("Includes . in output"), ) .arg( Arg::with_name("long") .short("l") .help("Lists in long format"), ) .arg(Arg::with_name("path").multiple(true)), ) .get_matches(); if let Some(submatches) = matches.subcommand_matches("cat") { if let Some(paths) = submatches.values_of("path") { for path in paths { let (comp_path, inner_path) = split(path); let mut comp = cfb::open(&comp_path).unwrap(); let mut stream = comp.open_stream(inner_path).unwrap(); io::copy(&mut stream, &mut io::stdout()).unwrap(); } } } else if let Some(submatches) = matches.subcommand_matches("chcls") { let clsid = Uuid::parse_str(submatches.value_of("clsid").unwrap()).unwrap(); if let Some(paths) = submatches.values_of("path") { for path in paths { let (comp_path, inner_path) = split(path); let mut comp = cfb::open(&comp_path).unwrap(); comp.set_storage_clsid(inner_path, clsid).unwrap(); comp.flush().unwrap(); } } } else if let Some(submatches) = matches.subcommand_matches("ls") { if let Some(paths) = submatches.values_of("path") { let long = submatches.is_present("long"); for path in paths { let (comp_path, inner_path) = split(path); let comp = cfb::open(&comp_path).unwrap(); let entry = comp.entry(&inner_path).unwrap(); if entry.is_stream() { list_entry(entry.name(), &entry, long); } else { if submatches.is_present("all") { list_entry(".", &entry, long); } for subentry in comp.read_storage(&inner_path).unwrap() { list_entry(subentry.name(), &subentry, long); } } } } } } cfb-0.7.3/rustfmt.toml000064400000000000000000000001430072674642500130140ustar 00000000000000max_width = 79 newline_style = "Unix" use_field_init_shorthand = true use_small_heuristics = "Max" cfb-0.7.3/src/internal/alloc.rs000064400000000000000000000406420072674642500144660ustar 00000000000000use crate::internal::{consts, Chain, Sector, SectorInit, Sectors, Version}; use byteorder::{LittleEndian, WriteBytesExt}; use fnv::FnvHashSet; use std::io::{self, Seek, Write}; use std::mem::size_of; //===========================================================================// macro_rules! malformed { ($e:expr) => { invalid_data!("Malformed FAT ({})", $e) }; ($fmt:expr, $($arg:tt)+) => { invalid_data!("Malformed FAT ({})", format!($fmt, $($arg)+)) }; } //===========================================================================// /// A wrapper around the sectors of a compound file, providing sector /// allocation via the FAT and DIFAT. pub struct Allocator { sectors: Sectors, difat_sector_ids: Vec, difat: Vec, fat: Vec, } impl Allocator { pub fn new( sectors: Sectors, difat_sector_ids: Vec, difat: Vec, fat: Vec, ) -> io::Result> { let alloc = Allocator { sectors, difat_sector_ids, difat, fat }; alloc.validate()?; Ok(alloc) } pub fn version(&self) -> Version { self.sectors.version() } pub fn sector_len(&self) -> usize { self.sectors.sector_len() } pub fn next(&self, sector_id: u32) -> io::Result { let index = sector_id as usize; if index >= self.fat.len() { invalid_data!( "Found reference to sector {}, but FAT has only {} entries", index, self.fat.len() ); } let next_id = self.fat[index]; if next_id != consts::END_OF_CHAIN && (next_id > consts::MAX_REGULAR_SECTOR || next_id as usize >= self.fat.len()) { invalid_data!("next_id ({}) is invalid", next_id); } Ok(next_id) } pub fn into_inner(self) -> F { self.sectors.into_inner() } pub fn open_chain( &mut self, start_sector_id: u32, init: SectorInit, ) -> io::Result> { Chain::new(self, start_sector_id, init) } fn validate(&self) -> io::Result<()> { if self.fat.len() > self.sectors.num_sectors() as usize { malformed!( "FAT has {} entries, but file has only {} sectors", self.fat.len(), self.sectors.num_sectors() ); } for &difat_sector in self.difat_sector_ids.iter() { if difat_sector as usize >= self.fat.len() { malformed!( "FAT has {} entries, but DIFAT lists {} as a DIFAT sector", self.fat.len(), difat_sector ); } if self.fat[difat_sector as usize] != consts::DIFAT_SECTOR { malformed!( "DIFAT sector {} is not marked as such in the FAT", difat_sector ); } } for &fat_sector in self.difat.iter() { if fat_sector as usize >= self.fat.len() { malformed!( "FAT has {} entries, but DIFAT lists {} as a FAT sector", self.fat.len(), fat_sector ); } if self.fat[fat_sector as usize] != consts::FAT_SECTOR { malformed!( "FAT sector {} is not marked as such in the FAT", fat_sector ); } } let mut pointees = FnvHashSet::default(); for (from_sector, &to_sector) in self.fat.iter().enumerate() { if to_sector <= consts::MAX_REGULAR_SECTOR { if to_sector as usize >= self.fat.len() { malformed!( "FAT has {} entries, but sector {} points to {}", self.fat.len(), from_sector, to_sector ); } if pointees.contains(&to_sector) { malformed!("sector {} pointed to twice", to_sector); } pointees.insert(to_sector); } else if to_sector == consts::INVALID_SECTOR { malformed!("0x{:08X} is not a valid FAT entry", to_sector); } } Ok(()) } } impl Allocator { pub fn seek_within_header( &mut self, offset_within_header: u64, ) -> io::Result> { self.sectors.seek_within_header(offset_within_header) } pub fn seek_to_sector(&mut self, sector_id: u32) -> io::Result> { self.sectors.seek_to_sector(sector_id) } pub fn seek_within_sector( &mut self, sector_id: u32, offset_within_sector: u64, ) -> io::Result> { self.sectors.seek_within_sector(sector_id, offset_within_sector) } pub fn seek_within_subsector( &mut self, sector_id: u32, subsector_index_within_sector: u32, subsector_len: usize, offset_within_subsector: u64, ) -> io::Result> { let subsector_start = subsector_index_within_sector as usize * subsector_len; let offset_within_sector = subsector_start as u64 + offset_within_subsector; let sector = self .sectors .seek_within_sector(sector_id, offset_within_sector)?; Ok(sector.subsector(subsector_start, subsector_len)) } } impl Allocator { /// Allocates a new chain with one sector, and returns the starting sector /// number. pub fn begin_chain(&mut self, init: SectorInit) -> io::Result { self.allocate_sector(init) } /// Given the starting sector (or any internal sector) of a chain, extends /// the end of that chain by one sector and returns the new sector number, /// updating the FAT as necessary. pub fn extend_chain( &mut self, start_sector_id: u32, init: SectorInit, ) -> io::Result { debug_assert_ne!(start_sector_id, consts::END_OF_CHAIN); let mut last_sector_id = start_sector_id; loop { let next = self.fat[last_sector_id as usize]; if next == consts::END_OF_CHAIN { break; } last_sector_id = next; } let new_sector_id = self.allocate_sector(init)?; self.set_fat(last_sector_id, new_sector_id)?; Ok(new_sector_id) } /// Allocates a new entry in the FAT, sets its value to `END_OF_CHAIN`, and /// returns the new sector number. fn allocate_sector(&mut self, init: SectorInit) -> io::Result { // If there's an existing free sector, use that. for sector_id in 0..self.fat.len() { if self.fat[sector_id] == consts::FREE_SECTOR { let sector_id = sector_id as u32; self.set_fat(sector_id, consts::END_OF_CHAIN)?; self.sectors.init_sector(sector_id, init)?; return Ok(sector_id); } } // Otherwise, we need a new sector; if there's not room in the FAT to // add it, then first we need to allocate a new FAT sector. let fat_entries_per_sector = self.sectors.sector_len() / size_of::(); if self.fat.len() % fat_entries_per_sector == 0 { self.append_fat_sector()?; } // Add a new sector to the end of the file and return it. let new_sector = self.fat.len() as u32; self.set_fat(new_sector, consts::END_OF_CHAIN)?; self.sectors.init_sector(new_sector, init)?; Ok(new_sector) } /// Adds a new sector to the FAT chain at the end of the file, and updates /// the FAT and DIFAT accordingly. fn append_fat_sector(&mut self) -> io::Result<()> { // Add a new FAT sector to the end of the file. let new_fat_sector_id = self.fat.len() as u32; self.sectors.init_sector(new_fat_sector_id, SectorInit::Fat)?; // Record this new FAT sector in the DIFAT and in the FAT itself. let difat_index = self.difat.len(); self.difat.push(new_fat_sector_id); self.set_fat(new_fat_sector_id, consts::FAT_SECTOR)?; debug_assert_eq!(self.fat.len(), new_fat_sector_id as usize + 1); // Write DIFAT changes to file. if difat_index < consts::NUM_DIFAT_ENTRIES_IN_HEADER { // This DIFAT entry goes in the file header. let offset = 76 + 4 * difat_index as u64; let mut header = self.sectors.seek_within_header(offset)?; header.write_u32::(new_fat_sector_id)?; } else { // This DIFAT entry goes in a DIFAT sector. let difat_entries_per_sector = (self.sector_len() - 4) / 4; let difat_sector_index = (difat_index - consts::NUM_DIFAT_ENTRIES_IN_HEADER) / difat_entries_per_sector; if difat_sector_index >= self.difat_sector_ids.len() { // Add a new DIFAT sector to the end of the file. let new_difat_sector_id = self.fat.len() as u32; self.sectors .init_sector(new_difat_sector_id, SectorInit::Difat)?; // Record this new DIFAT sector in the FAT. self.set_fat(new_difat_sector_id, consts::DIFAT_SECTOR)?; // Add this sector to the end of the DIFAT chain. if let Some(&last_sector_id) = self.difat_sector_ids.last() { let offset = self.sector_len() as u64 - 4; let mut sector = self .sectors .seek_within_sector(last_sector_id, offset)?; sector.write_u32::(new_difat_sector_id)?; } self.difat_sector_ids.push(new_difat_sector_id); // Update DIFAT chain fields in header. let mut header = self.sectors.seek_within_header(68)?; header.write_u32::(self.difat_sector_ids[0])?; header.write_u32::( self.difat_sector_ids.len() as u32, )?; } // Write the new entry into the DIFAT sector. let difat_sector_id = self.difat_sector_ids[difat_sector_index]; let index_within_difat_sector = difat_index - consts::NUM_DIFAT_ENTRIES_IN_HEADER - difat_sector_index * difat_entries_per_sector; let mut sector = self.sectors.seek_within_sector( difat_sector_id, 4 * index_within_difat_sector as u64, )?; sector.write_u32::(new_fat_sector_id)?; } // Update length of FAT chain in header. let mut header = self.sectors.seek_within_header(44)?; header.write_u32::(self.difat.len() as u32)?; Ok(()) } /// Sets the given sector to point to `END_OF_CHAIN`, and deallocates all /// subsequent sectors in the chain. pub fn free_chain_after(&mut self, sector_id: u32) -> io::Result<()> { let next = self.next(sector_id)?; self.set_fat(sector_id, consts::END_OF_CHAIN)?; self.free_chain(next)?; Ok(()) } /// Given the start sector of a chain, deallocates the entire chain. pub fn free_chain(&mut self, start_sector_id: u32) -> io::Result<()> { let mut sector_id = start_sector_id; while sector_id != consts::END_OF_CHAIN { let next = self.next(sector_id)?; self.free_sector(sector_id)?; sector_id = next; } Ok(()) } /// Deallocates the specified sector. fn free_sector(&mut self, sector_id: u32) -> io::Result<()> { self.set_fat(sector_id, consts::FREE_SECTOR)?; // TODO: Truncate FAT if last FAT sector is now all free. Ok(()) } /// Sets `self.fat[index] = value`, and also writes that change to the /// underlying file. The `index` must be <= `self.fat.len()`. fn set_fat(&mut self, index: u32, value: u32) -> io::Result<()> { let index = index as usize; debug_assert!(index <= self.fat.len()); let fat_entries_per_sector = self.sectors.sector_len() / size_of::(); let fat_sector_id = self.difat[index / fat_entries_per_sector]; let offset_within_sector = 4 * (index % fat_entries_per_sector) as u64; let mut sector = self .sectors .seek_within_sector(fat_sector_id, offset_within_sector)?; sector.write_u32::(value)?; if index == self.fat.len() { self.fat.push(value); } else { self.fat[index] = value; } Ok(()) } /// Flushes all changes to the underlying file. pub fn flush(&mut self) -> io::Result<()> { self.sectors.flush() } } //===========================================================================// #[cfg(test)] mod tests { use super::Allocator; use crate::internal::{consts, Sectors, Version}; use std::io::Cursor; fn make_sectors( version: Version, num_sectors: usize, ) -> Sectors>> { let data_len = (num_sectors + 1) * version.sector_len(); Sectors::new(version, data_len as u64, Cursor::new(vec![0; data_len])) } fn make_allocator( difat: Vec, fat: Vec, ) -> Allocator>> { Allocator::new( make_sectors(Version::V3, fat.len()), vec![], difat, fat, ) .unwrap() } #[test] #[should_panic( expected = "Malformed FAT (FAT has 3 entries, but file has only 2 \ sectors)" )] fn fat_longer_than_file() { let difat = vec![0]; let fat = vec![consts::FAT_SECTOR, 2, consts::END_OF_CHAIN]; let sectors = make_sectors(Version::V3, 2); Allocator::new(sectors, vec![], difat, fat).unwrap(); } #[test] #[should_panic( expected = "Malformed FAT (FAT has 2 entries, but DIFAT lists 3 as \ a DIFAT sector)" )] fn difat_sector_out_of_range() { let difat_sectors = vec![3]; let difat = vec![0]; let fat = vec![consts::FAT_SECTOR, consts::END_OF_CHAIN]; let sectors = make_sectors(Version::V3, fat.len()); Allocator::new(sectors, difat_sectors, difat, fat).unwrap(); } #[test] #[should_panic( expected = "Malformed FAT (DIFAT sector 1 is not marked as such in \ the FAT)" )] fn difat_sector_not_marked_in_fat() { let difat_sectors = vec![1]; let difat = vec![0]; let fat = vec![consts::FAT_SECTOR, consts::END_OF_CHAIN]; let sectors = make_sectors(Version::V3, fat.len()); Allocator::new(sectors, difat_sectors, difat, fat).unwrap(); } #[test] #[should_panic( expected = "Malformed FAT (FAT has 2 entries, but DIFAT lists 3 as a \ FAT sector)" )] fn fat_sector_out_of_range() { let difat = vec![0, 3]; let fat = vec![consts::FAT_SECTOR, consts::END_OF_CHAIN]; make_allocator(difat, fat); } #[test] #[should_panic( expected = "Malformed FAT (FAT sector 1 is not marked as such in the \ FAT)" )] fn fat_sector_not_marked_in_fat() { let difat = vec![0, 1]; let fat = vec![consts::FAT_SECTOR, consts::END_OF_CHAIN]; make_allocator(difat, fat); } #[test] #[should_panic( expected = "Malformed FAT (FAT has 2 entries, but sector 1 points to \ 2)" )] fn pointee_out_of_range() { let difat = vec![0]; let fat = vec![consts::FAT_SECTOR, 2]; make_allocator(difat, fat); } #[test] #[should_panic(expected = "Malformed FAT (sector 3 pointed to twice)")] fn double_pointee() { let difat = vec![0]; let fat = vec![consts::FAT_SECTOR, 3, 3, consts::END_OF_CHAIN]; make_allocator(difat, fat); } #[test] #[should_panic( expected = "Malformed FAT (0xFFFFFFFB is not a valid FAT entry)" )] fn invalid_pointee() { let difat = vec![0]; let fat = vec![consts::FAT_SECTOR, consts::INVALID_SECTOR]; make_allocator(difat, fat); } } //===========================================================================// cfb-0.7.3/src/internal/chain.rs000064400000000000000000000163220072674642500144540ustar 00000000000000use crate::internal::{consts, Allocator, Sector, SectorInit}; use std::cmp; use std::io::{self, Read, Seek, SeekFrom, Write}; //===========================================================================// pub struct Chain<'a, F: 'a> { allocator: &'a mut Allocator, init: SectorInit, sector_ids: Vec, offset_from_start: u64, } impl<'a, F> Chain<'a, F> { pub fn new( allocator: &'a mut Allocator, start_sector_id: u32, init: SectorInit, ) -> io::Result> { let mut sector_ids = Vec::::new(); let mut current_sector_id = start_sector_id; let first_sector_id = start_sector_id; while current_sector_id != consts::END_OF_CHAIN { sector_ids.push(current_sector_id); current_sector_id = allocator.next(current_sector_id)?; if current_sector_id == first_sector_id { invalid_data!( "Chain contained duplicate sector id {}", current_sector_id ); } } Ok(Chain { allocator, init, sector_ids, offset_from_start: 0 }) } pub fn start_sector_id(&self) -> u32 { self.sector_ids.first().copied().unwrap_or(consts::END_OF_CHAIN) } pub fn num_sectors(&self) -> usize { self.sector_ids.len() } pub fn len(&self) -> u64 { (self.allocator.sector_len() as u64) * (self.sector_ids.len() as u64) } } impl<'a, F: Seek> Chain<'a, F> { pub fn into_subsector( self, subsector_index: u32, subsector_len: usize, offset_within_subsector: u64, ) -> io::Result> { debug_assert!(offset_within_subsector <= subsector_len as u64); debug_assert_eq!(self.allocator.sector_len() % subsector_len, 0); let subsectors_per_sector = self.allocator.sector_len() / subsector_len; let sector_index_within_chain = subsector_index as usize / subsectors_per_sector; let subsector_index_within_sector = subsector_index % (subsectors_per_sector as u32); let sector_id = *self .sector_ids .get(sector_index_within_chain) .ok_or_else(|| { io::Error::new(io::ErrorKind::InvalidData, "invalid sector id") })?; self.allocator.seek_within_subsector( sector_id, subsector_index_within_sector, subsector_len, offset_within_subsector, ) } } impl<'a, F: Write + Seek> Chain<'a, F> { /// Resizes the chain to the minimum number of sectors large enough to old /// `new_len` bytes, allocating or freeing sectors as needed. pub fn set_len(&mut self, new_len: u64) -> io::Result<()> { let sector_len = self.allocator.sector_len() as u64; let new_num_sectors = ((sector_len + new_len - 1) / sector_len) as usize; if new_num_sectors == 0 { if let Some(&start_sector) = self.sector_ids.first() { self.allocator.free_chain(start_sector)?; } } else if new_num_sectors <= self.sector_ids.len() { if new_num_sectors < self.sector_ids.len() { self.allocator .free_chain_after(self.sector_ids[new_num_sectors - 1])?; } // TODO: init remainder of final sector } else { for _ in self.sector_ids.len()..new_num_sectors { let new_sector_id = if let Some(&last_sector_id) = self.sector_ids.last() { self.allocator.extend_chain(last_sector_id, self.init)? } else { self.allocator.begin_chain(self.init)? }; self.sector_ids.push(new_sector_id); } } Ok(()) } pub fn free(self) -> io::Result<()> { self.allocator.free_chain(self.start_sector_id()) } } impl<'a, F> Seek for Chain<'a, F> { fn seek(&mut self, pos: SeekFrom) -> io::Result { let length = self.len(); let new_offset = match pos { SeekFrom::Start(delta) => delta as i64, SeekFrom::End(delta) => delta + length as i64, SeekFrom::Current(delta) => delta + self.offset_from_start as i64, }; if new_offset < 0 || (new_offset as u64) > length { invalid_input!( "Cannot seek to {}, chain length is {} bytes", new_offset, length ); } self.offset_from_start = new_offset as u64; Ok(self.offset_from_start) } } impl<'a, F: Read + Seek> Read for Chain<'a, F> { fn read(&mut self, buf: &mut [u8]) -> io::Result { let total_len = self.len(); debug_assert!(self.offset_from_start <= total_len); let remaining_in_chain = total_len - self.offset_from_start; let max_len = cmp::min(buf.len() as u64, remaining_in_chain) as usize; if max_len == 0 { return Ok(0); } let sector_len = self.allocator.sector_len() as u64; let current_sector_index = (self.offset_from_start / sector_len) as usize; debug_assert!(current_sector_index < self.sector_ids.len()); let current_sector_id = self.sector_ids[current_sector_index]; let offset_within_sector = self.offset_from_start % sector_len; let mut sector = self .allocator .seek_within_sector(current_sector_id, offset_within_sector)?; let bytes_read = sector.read(&mut buf[0..max_len])?; self.offset_from_start += bytes_read as u64; debug_assert!(self.offset_from_start <= total_len); Ok(bytes_read) } } impl<'a, F: Write + Seek> Write for Chain<'a, F> { fn write(&mut self, buf: &[u8]) -> io::Result { if buf.is_empty() { return Ok(0); } let mut total_len = self.len(); let sector_len = self.allocator.sector_len() as u64; if self.offset_from_start == total_len { let new_sector_id = if let Some(&last_sector_id) = self.sector_ids.last() { self.allocator.extend_chain(last_sector_id, self.init)? } else { self.allocator.begin_chain(self.init)? }; self.sector_ids.push(new_sector_id); total_len += sector_len; debug_assert_eq!(total_len, self.len()); } let current_sector_index = (self.offset_from_start / sector_len) as usize; debug_assert!(current_sector_index < self.sector_ids.len()); let current_sector_id = self.sector_ids[current_sector_index]; let offset_within_sector = self.offset_from_start % sector_len; let mut sector = self .allocator .seek_within_sector(current_sector_id, offset_within_sector)?; let bytes_written = sector.write(buf)?; self.offset_from_start += bytes_written as u64; debug_assert!(self.offset_from_start <= total_len); Ok(bytes_written) } fn flush(&mut self) -> io::Result<()> { self.allocator.flush() } } //===========================================================================// cfb-0.7.3/src/internal/color.rs000064400000000000000000000021220072674642500145010ustar 00000000000000use crate::internal::consts; //===========================================================================// /// The "color" of a directory entry (which can be used for maintaining a /// red-black tree). #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Color { Red, Black, } impl Color { pub fn as_byte(&self) -> u8 { match self { Color::Red => consts::COLOR_RED, Color::Black => consts::COLOR_BLACK, } } pub fn from_byte(byte: u8) -> Option { if byte == consts::COLOR_RED { Some(Color::Red) } else if byte == consts::COLOR_BLACK { Some(Color::Black) } else { None } } } //===========================================================================// #[cfg(test)] mod tests { use super::Color; #[test] fn round_trip() { for &color in &[Color::Red, Color::Black] { assert_eq!(Color::from_byte(color.as_byte()), Some(color)); } } } //===========================================================================// cfb-0.7.3/src/internal/consts.rs000064400000000000000000000026470072674642500147100ustar 00000000000000// ========================================================================= // pub const HEADER_LEN: usize = 512; // length of CFB file header, in bytes pub const DIR_ENTRY_LEN: usize = 128; // length of directory entry, in bytes pub const NUM_DIFAT_ENTRIES_IN_HEADER: usize = 109; // Constants for CFB file header values: pub const MAGIC_NUMBER: [u8; 8] = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]; pub const MINOR_VERSION: u16 = 0x3e; pub const BYTE_ORDER_MARK: u16 = 0xfffe; pub const MINI_SECTOR_SHIFT: u16 = 6; // 64-byte mini sectors pub const MINI_SECTOR_LEN: usize = 1 << (MINI_SECTOR_SHIFT as usize); pub const MINI_STREAM_CUTOFF: u32 = 4096; // Constants for FAT entries: pub const MAX_REGULAR_SECTOR: u32 = 0xfffffffa; pub const INVALID_SECTOR: u32 = 0xfffffffb; pub const DIFAT_SECTOR: u32 = 0xfffffffc; pub const FAT_SECTOR: u32 = 0xfffffffd; pub const END_OF_CHAIN: u32 = 0xfffffffe; pub const FREE_SECTOR: u32 = 0xffffffff; // Constants for directory entries: pub const ROOT_DIR_NAME: &str = "Root Entry"; pub const OBJ_TYPE_UNALLOCATED: u8 = 0; pub const OBJ_TYPE_STORAGE: u8 = 1; pub const OBJ_TYPE_STREAM: u8 = 2; pub const OBJ_TYPE_ROOT: u8 = 5; pub const COLOR_RED: u8 = 0; pub const COLOR_BLACK: u8 = 1; pub const ROOT_STREAM_ID: u32 = 0; pub const MAX_REGULAR_STREAM_ID: u32 = 0xfffffffa; pub const NO_STREAM: u32 = 0xffffffff; // ========================================================================= // cfb-0.7.3/src/internal/directory.rs000064400000000000000000000563670072674642500154130ustar 00000000000000use crate::internal::{ self, consts, Allocator, Chain, DirEntry, Entries, EntriesOrder, ObjType, Sector, SectorInit, Version, }; use byteorder::{LittleEndian, WriteBytesExt}; use fnv::FnvHashSet; use std::cmp::Ordering; use std::io::{self, Seek, SeekFrom, Write}; use std::path::Path; //===========================================================================// macro_rules! malformed { ($e:expr) => { invalid_data!("Malformed directory ({})", $e) }; ($fmt:expr, $($arg:tt)+) => { invalid_data!("Malformed directory ({})", format!($fmt, $($arg)+)) }; } //===========================================================================// /// A wrapper around the sector allocator that additionally provides management /// of the CFB directory chain. pub struct Directory { allocator: Allocator, dir_entries: Vec, dir_start_sector: u32, } impl Directory { pub fn new( allocator: Allocator, dir_entries: Vec, dir_start_sector: u32, ) -> io::Result> { let directory = Directory { allocator, dir_entries, dir_start_sector }; directory.validate()?; Ok(directory) } pub fn version(&self) -> Version { self.allocator.version() } pub fn sector_len(&self) -> usize { self.allocator.sector_len() } pub fn into_inner(self) -> F { self.allocator.into_inner() } pub fn stream_id_for_name_chain(&self, names: &[&str]) -> Option { let mut stream_id = consts::ROOT_STREAM_ID; for name in names.iter() { stream_id = self.dir_entry(stream_id).child; loop { if stream_id == consts::NO_STREAM { return None; } let dir_entry = self.dir_entry(stream_id); match internal::path::compare_names(name, &dir_entry.name) { Ordering::Equal => break, Ordering::Less => stream_id = dir_entry.left_sibling, Ordering::Greater => stream_id = dir_entry.right_sibling, } } } Some(stream_id) } /// Returns an iterator over the entries within the root storage object. pub fn root_storage_entries(&self) -> Entries { let start = self.root_dir_entry().child; Entries::new( EntriesOrder::Nonrecursive, &self.dir_entries, internal::path::path_from_name_chain(&[]), start, ) } /// Returns an iterator over the entries within a storage object. pub fn storage_entries(&self, path: &Path) -> io::Result { let names = internal::path::name_chain_from_path(path)?; let path = internal::path::path_from_name_chain(&names); let stream_id = match self.stream_id_for_name_chain(&names) { Some(stream_id) => stream_id, None => not_found!("No such storage: {:?}", path), }; let start = { let dir_entry = self.dir_entry(stream_id); if dir_entry.obj_type == ObjType::Stream { invalid_input!("Not a storage: {:?}", path); } debug_assert!( dir_entry.obj_type == ObjType::Storage || dir_entry.obj_type == ObjType::Root ); dir_entry.child }; Ok(Entries::new( EntriesOrder::Nonrecursive, &self.dir_entries, path, start, )) } /// Returns an iterator over all entries within the compound file, starting /// from and including the root entry. The iterator walks the storage tree /// in a preorder traversal. pub fn walk(&self) -> Entries { Entries::new( EntriesOrder::Preorder, &self.dir_entries, internal::path::path_from_name_chain(&[]), consts::ROOT_STREAM_ID, ) } /// Returns an iterator over all entries under a storage subtree, including /// the given path itself. The iterator walks the storage tree in a /// preorder traversal. pub fn walk_storage(&self, path: &Path) -> io::Result { let mut names = internal::path::name_chain_from_path(path)?; let stream_id = match self.stream_id_for_name_chain(&names) { Some(stream_id) => stream_id, None => { not_found!( "No such object: {:?}", internal::path::path_from_name_chain(&names) ); } }; names.pop(); let parent_path = internal::path::path_from_name_chain(&names); Ok(Entries::new( EntriesOrder::Preorder, &self.dir_entries, parent_path, stream_id, )) } pub fn open_chain( &mut self, start_sector_id: u32, init: SectorInit, ) -> io::Result> { self.allocator.open_chain(start_sector_id, init) } pub fn root_dir_entry(&self) -> &DirEntry { self.dir_entry(consts::ROOT_STREAM_ID) } pub fn dir_entry(&self, stream_id: u32) -> &DirEntry { &self.dir_entries[stream_id as usize] } fn dir_entry_mut(&mut self, stream_id: u32) -> &mut DirEntry { &mut self.dir_entries[stream_id as usize] } fn validate(&self) -> io::Result<()> { if self.dir_entries.is_empty() { malformed!("root entry is missing"); } let root_entry = self.root_dir_entry(); if root_entry.stream_len % consts::MINI_SECTOR_LEN as u64 != 0 { malformed!( "root stream len is {}, but should be multiple of {}", root_entry.stream_len, consts::MINI_SECTOR_LEN ); } let mut visited = FnvHashSet::default(); let mut stack = vec![consts::ROOT_STREAM_ID]; while let Some(stream_id) = stack.pop() { if visited.contains(&stream_id) { malformed!("loop in tree"); } visited.insert(stream_id); let dir_entry = self.dir_entry(stream_id); if stream_id == consts::ROOT_STREAM_ID { if dir_entry.obj_type != ObjType::Root { malformed!( "root entry has object type {:?}", dir_entry.obj_type ); } } else if dir_entry.obj_type != ObjType::Storage && dir_entry.obj_type != ObjType::Stream { malformed!( "non-root entry with object type {:?}", dir_entry.obj_type ); } let left_sibling = dir_entry.left_sibling; if left_sibling != consts::NO_STREAM { if left_sibling as usize >= self.dir_entries.len() { malformed!( "left sibling index is {}, but directory entry count \ is {}", left_sibling, self.dir_entries.len() ); } let entry = &self.dir_entry(left_sibling); if internal::path::compare_names(&entry.name, &dir_entry.name) != Ordering::Less { malformed!( "name ordering, {:?} vs {:?}", dir_entry.name, entry.name ); } stack.push(left_sibling); } let right_sibling = dir_entry.right_sibling; if right_sibling != consts::NO_STREAM { if right_sibling as usize >= self.dir_entries.len() { malformed!( "right sibling index is {}, but directory entry count \ is {}", right_sibling, self.dir_entries.len()); } let entry = &self.dir_entry(right_sibling); if internal::path::compare_names(&dir_entry.name, &entry.name) != Ordering::Less { malformed!( "name ordering, {:?} vs {:?}", dir_entry.name, entry.name ); } stack.push(right_sibling); } let child = dir_entry.child; if child != consts::NO_STREAM { if child as usize >= self.dir_entries.len() { malformed!( "child index is {}, but directory entry count is {}", child, self.dir_entries.len() ); } stack.push(child); } } Ok(()) } } impl Directory { pub fn seek_within_header( &mut self, offset_within_header: u64, ) -> io::Result> { self.allocator.seek_within_header(offset_within_header) } fn seek_to_dir_entry(&mut self, stream_id: u32) -> io::Result> { self.seek_within_dir_entry(stream_id, 0) } fn seek_within_dir_entry( &mut self, stream_id: u32, offset_within_dir_entry: usize, ) -> io::Result> { let dir_entries_per_sector = self.version().dir_entries_per_sector() as u32; let index_within_sector = stream_id % dir_entries_per_sector; let mut directory_sector = self.dir_start_sector; for _ in 0..(stream_id / dir_entries_per_sector) { debug_assert_ne!(directory_sector, consts::END_OF_CHAIN); directory_sector = self.allocator.next(directory_sector)?; } self.allocator.seek_within_subsector( directory_sector, index_within_sector, consts::DIR_ENTRY_LEN, offset_within_dir_entry as u64, ) } } impl Directory { /// Allocates a new chain with one sector, and returns the starting sector /// number. pub fn begin_chain(&mut self, init: SectorInit) -> io::Result { self.allocator.begin_chain(init) } /// Given the starting sector (or any internal sector) of a chain, extends /// the end of that chain by one sector and returns the new sector number, /// updating the FAT as necessary. pub fn extend_chain( &mut self, start_sector_id: u32, init: SectorInit, ) -> io::Result { self.allocator.extend_chain(start_sector_id, init) } /// Given the start sector of a chain, deallocates the entire chain. pub fn free_chain(&mut self, start_sector_id: u32) -> io::Result<()> { self.allocator.free_chain(start_sector_id) } /// Inserts a new directory entry into the tree under the specified parent /// entry, then returns the new stream ID. pub fn insert_dir_entry( &mut self, parent_id: u32, name: &str, obj_type: ObjType, ) -> io::Result { debug_assert!( obj_type == ObjType::Storage || obj_type == ObjType::Stream ); // Create a new directory entry. let stream_id = self.allocate_dir_entry()?; let now = internal::time::current_timestamp(); *self.dir_entry_mut(stream_id) = DirEntry::new(name, obj_type, now); // Insert the new entry into the tree. let mut sibling_id = self.dir_entry(parent_id).child; let mut prev_sibling_id = parent_id; let mut ordering = Ordering::Equal; while sibling_id != consts::NO_STREAM { let sibling = self.dir_entry(sibling_id); prev_sibling_id = sibling_id; ordering = internal::path::compare_names(name, &sibling.name); sibling_id = match ordering { Ordering::Less => sibling.left_sibling, Ordering::Greater => sibling.right_sibling, Ordering::Equal => panic!("internal error: insert duplicate"), }; } match ordering { Ordering::Less => { self.dir_entry_mut(prev_sibling_id).left_sibling = stream_id; let mut sector = self.seek_within_dir_entry(prev_sibling_id, 68)?; sector.write_u32::(stream_id)?; } Ordering::Greater => { self.dir_entry_mut(prev_sibling_id).right_sibling = stream_id; let mut sector = self.seek_within_dir_entry(prev_sibling_id, 72)?; sector.write_u32::(stream_id)?; } Ordering::Equal => { debug_assert_eq!(prev_sibling_id, parent_id); self.dir_entry_mut(parent_id).child = stream_id; let mut sector = self.seek_within_dir_entry(parent_id, 76)?; sector.write_u32::(stream_id)?; } } // TODO: rebalance tree // Write new entry to underyling file. self.write_dir_entry(stream_id)?; Ok(stream_id) } /// Removes a directory entry from the tree and deallocates it. pub fn remove_dir_entry( &mut self, parent_id: u32, name: &str, ) -> io::Result<()> { // Find the directory entry with the given name below the parent. let mut stream_ids = Vec::new(); let mut stream_id = self.dir_entry(parent_id).child; loop { debug_assert_ne!(stream_id, consts::NO_STREAM); debug_assert!(!stream_ids.contains(&stream_id)); stream_ids.push(stream_id); let dir_entry = self.dir_entry(stream_id); match internal::path::compare_names(name, &dir_entry.name) { Ordering::Equal => break, Ordering::Less => stream_id = dir_entry.left_sibling, Ordering::Greater => stream_id = dir_entry.right_sibling, } } debug_assert_eq!(self.dir_entry(stream_id).child, consts::NO_STREAM); // Restructure the tree. let mut replacement_id = consts::NO_STREAM; loop { let left_sibling = self.dir_entry(stream_id).left_sibling; let right_sibling = self.dir_entry(stream_id).right_sibling; if left_sibling == consts::NO_STREAM && right_sibling == consts::NO_STREAM { break; } else if left_sibling == consts::NO_STREAM { replacement_id = right_sibling; break; } else if right_sibling == consts::NO_STREAM { replacement_id = left_sibling; break; } let mut predecessor_id = left_sibling; loop { stream_ids.push(predecessor_id); let next_id = self.dir_entry(predecessor_id).right_sibling; if next_id == consts::NO_STREAM { break; } predecessor_id = next_id; } let mut pred_entry = self.dir_entry(predecessor_id).clone(); debug_assert_eq!(pred_entry.right_sibling, consts::NO_STREAM); pred_entry.left_sibling = left_sibling; pred_entry.right_sibling = right_sibling; pred_entry.write_to(&mut self.seek_to_dir_entry(stream_id)?)?; *self.dir_entry_mut(stream_id) = pred_entry; stream_id = predecessor_id; } // TODO: recolor nodes // Remove the entry. debug_assert_eq!(stream_ids.last(), Some(&stream_id)); stream_ids.pop(); if let Some(&sibling_id) = stream_ids.last() { if self.dir_entry(sibling_id).left_sibling == stream_id { self.dir_entry_mut(sibling_id).left_sibling = replacement_id; let mut sector = self.seek_within_dir_entry(sibling_id, 68)?; sector.write_u32::(replacement_id)?; } else { debug_assert_eq!( self.dir_entry(sibling_id).right_sibling, stream_id ); self.dir_entry_mut(sibling_id).right_sibling = replacement_id; let mut sector = self.seek_within_dir_entry(sibling_id, 72)?; sector.write_u32::(replacement_id)?; } } else { self.dir_entry_mut(parent_id).child = replacement_id; let mut sector = self.seek_within_dir_entry(parent_id, 76)?; sector.write_u32::(replacement_id)?; } self.free_dir_entry(stream_id)?; Ok(()) } /// Adds a new (uninitialized) entry to the directory and returns the new /// stream ID. fn allocate_dir_entry(&mut self) -> io::Result { // If there's an existing unalloated directory entry, use that. for (stream_id, entry) in self.dir_entries.iter().enumerate() { if entry.obj_type == ObjType::Unallocated { return Ok(stream_id as u32); } } // Otherwise, we need a new entry; if there's not room in the directory // chain to add it, then first we need to add a new directory sector. let dir_entries_per_sector = self.version().dir_entries_per_sector(); let unallocated_dir_entry = DirEntry::unallocated(); if self.dir_entries.len() % dir_entries_per_sector == 0 { let start_sector = self.dir_start_sector; self.allocator.extend_chain(start_sector, SectorInit::Dir)?; } // Add a new entry to the end of the directory and return it. let stream_id = self.dir_entries.len() as u32; self.dir_entries.push(unallocated_dir_entry); Ok(stream_id) } /// Deallocates the specified directory entry. fn free_dir_entry(&mut self, stream_id: u32) -> io::Result<()> { debug_assert_ne!(stream_id, consts::ROOT_STREAM_ID); let dir_entry = DirEntry::unallocated(); dir_entry.write_to(&mut self.seek_to_dir_entry(stream_id)?)?; *self.dir_entry_mut(stream_id) = dir_entry; // TODO: Truncate directory chain if last directory sector is now all // unallocated. Ok(()) } /// Calls the given function with a mutable reference to the specified /// directory entry, then writes the updated directory entry to the /// underlying file once the function returns. pub fn with_dir_entry_mut( &mut self, stream_id: u32, func: W, ) -> io::Result<()> where W: FnOnce(&mut DirEntry), { func(&mut self.dir_entries[stream_id as usize]); self.write_dir_entry(stream_id) } /// Calls the given function with a mutable reference to the root directory /// entry, then writes the updated directory entry to the underlying file /// once the function returns. pub fn with_root_dir_entry_mut(&mut self, func: W) -> io::Result<()> where W: FnOnce(&mut DirEntry), { self.with_dir_entry_mut(consts::ROOT_STREAM_ID, func) } fn write_dir_entry(&mut self, stream_id: u32) -> io::Result<()> { let mut chain = self .allocator .open_chain(self.dir_start_sector, SectorInit::Dir)?; let offset = (consts::DIR_ENTRY_LEN as u64) * (stream_id as u64); chain.seek(SeekFrom::Start(offset))?; self.dir_entries[stream_id as usize].write_to(&mut chain) } /// Flushes all changes to the underlying file. pub fn flush(&mut self) -> io::Result<()> { self.allocator.flush() } } //===========================================================================// #[cfg(test)] mod tests { use super::Directory; use crate::internal::{ consts, Allocator, Color, DirEntry, ObjType, Sectors, Version, }; use std::io::Cursor; fn make_directory(entries: Vec) -> Directory>> { let version = Version::V3; let num_sectors = 3; let data_len = (1 + num_sectors) * version.sector_len(); let cursor = Cursor::new(vec![0; data_len]); let sectors = Sectors::new(version, data_len as u64, cursor); let mut fat = vec![consts::END_OF_CHAIN; num_sectors]; fat[0] = consts::FAT_SECTOR; let allocator = Allocator::new(sectors, vec![], vec![0], fat).unwrap(); Directory::new(allocator, entries, 1).unwrap() } #[test] #[should_panic(expected = "Malformed directory (root entry is missing)")] fn no_root_entry() { make_directory(vec![]); } #[test] #[should_panic( expected = "Malformed directory (root stream len is 147, but should \ be multiple of 64)" )] fn invalid_mini_stream_len() { let mut root_entry = DirEntry::empty_root_entry(); root_entry.start_sector = 2; root_entry.stream_len = 147; make_directory(vec![root_entry]); } #[test] #[should_panic(expected = "Malformed directory (loop in tree)")] fn storage_is_child_of_itself() { let mut root_entry = DirEntry::empty_root_entry(); root_entry.child = 1; let mut storage = DirEntry::new("foo", ObjType::Storage, 0); storage.child = 1; make_directory(vec![root_entry, storage]); } #[test] #[should_panic( expected = "Malformed directory (root entry has object type Storage)" )] fn root_has_wrong_type() { let mut root_entry = DirEntry::empty_root_entry(); root_entry.obj_type = ObjType::Storage; make_directory(vec![root_entry]); } #[test] #[should_panic( expected = "Malformed directory (non-root entry with object type Root)" )] fn nonroot_has_wrong_type() { let mut root_entry = DirEntry::empty_root_entry(); root_entry.child = 1; let storage = DirEntry::new("foo", ObjType::Root, 0); make_directory(vec![root_entry, storage]); } #[test] fn tolerate_red_root() { // The MS-CFB spec section 2.6.4 says the root entry MUST be colored // black, but apparently some implementations don't do this (see // https://social.msdn.microsoft.com/Forums/sqlserver/en-US/ // 9290d877-d91f-4509-ace9-cb4575c48514/red-black-tree-in-mscfb). So // we shouldn't complain if the root is red. let mut root_entry = DirEntry::empty_root_entry(); root_entry.color = Color::Red; make_directory(vec![root_entry]); } #[test] fn tolerate_two_red_nodes_in_a_row() { // The MS-CFB spec section 2.6.4 says that two consecutive nodes in the // tree MUST NOT both be red, but apparently some implementations don't // obey this (see https://github.com/mdsteele/rust-cfb/issues/10). We // still want to be able to read these files, so we shouldn't complain // if there are two red nodes in a row. let mut root_entry = DirEntry::empty_root_entry(); root_entry.child = 1; let mut storage1 = DirEntry::new("foo", ObjType::Storage, 0); storage1.color = Color::Red; storage1.left_sibling = 2; let mut storage2 = DirEntry::new("bar", ObjType::Storage, 0); storage2.color = Color::Red; make_directory(vec![root_entry, storage1, storage2]); } } //===========================================================================// cfb-0.7.3/src/internal/direntry.rs000064400000000000000000000522030072674642500152300ustar 00000000000000use crate::internal::consts::{self, MAX_REGULAR_STREAM_ID, NO_STREAM}; use crate::internal::{self, Color, ObjType, Version}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use std::io::{self, Read, Write}; use uuid::Uuid; //===========================================================================// macro_rules! malformed { ($e:expr) => { invalid_data!("Malformed directory entry ({})", $e) }; ($fmt:expr, $($arg:tt)+) => { invalid_data!("Malformed directory entry ({})", format!($fmt, $($arg)+)) }; } //===========================================================================// #[derive(Clone)] pub struct DirEntry { pub name: String, pub obj_type: ObjType, pub color: Color, pub left_sibling: u32, pub right_sibling: u32, pub child: u32, pub clsid: Uuid, pub state_bits: u32, pub creation_time: u64, pub modified_time: u64, pub start_sector: u32, pub stream_len: u64, } impl DirEntry { pub fn new(name: &str, obj_type: ObjType, timestamp: u64) -> DirEntry { debug_assert_ne!(obj_type, ObjType::Unallocated); DirEntry { name: name.to_string(), obj_type, color: Color::Black, left_sibling: consts::NO_STREAM, right_sibling: consts::NO_STREAM, child: consts::NO_STREAM, clsid: Uuid::nil(), state_bits: 0, creation_time: timestamp, modified_time: timestamp, start_sector: if obj_type == ObjType::Storage { // According to the MS-CFB spec section 2.6.3, the starting // sector should be set to zero for storage entries. 0 } else { consts::END_OF_CHAIN }, stream_len: 0, } } pub fn unallocated() -> DirEntry { // According to the MS-CFB spec section 2.6.3, unallocated directory // entries must consist of all zeros except for the sibling and child // fields, which must be NO_STREAM. DirEntry { name: String::new(), obj_type: ObjType::Unallocated, color: Color::Red, left_sibling: NO_STREAM, right_sibling: NO_STREAM, child: NO_STREAM, clsid: Uuid::nil(), state_bits: 0, creation_time: 0, modified_time: 0, start_sector: 0, stream_len: 0, } } pub fn empty_root_entry() -> DirEntry { DirEntry::new(consts::ROOT_DIR_NAME, ObjType::Root, 0) } pub fn read_clsid(reader: &mut R) -> io::Result { let d1 = reader.read_u32::()?; let d2 = reader.read_u16::()?; let d3 = reader.read_u16::()?; let mut d4 = [0u8; 8]; reader.read_exact(&mut d4)?; Ok(Uuid::from_fields(d1, d2, d3, &d4)) } pub fn write_clsid( writer: &mut W, clsid: &Uuid, ) -> io::Result<()> { let (d1, d2, d3, d4) = clsid.as_fields(); writer.write_u32::(d1)?; writer.write_u16::(d2)?; writer.write_u16::(d3)?; writer.write_all(d4)?; Ok(()) } pub fn read_from( reader: &mut R, version: Version, ) -> io::Result { let mut name: String = { let mut name_chars: Vec = Vec::with_capacity(32); for _ in 0..32 { name_chars.push(reader.read_u16::()?); } let name_len_bytes = reader.read_u16::()?; if name_len_bytes > 64 { malformed!("name length too large: {}", name_len_bytes); } else if name_len_bytes % 2 != 0 { malformed!("odd name length: {}", name_len_bytes); } let name_len_chars = if name_len_bytes > 0 { (name_len_bytes / 2 - 1) as usize } else { 0 }; debug_assert!(name_len_chars < name_chars.len()); // According to section 2.6.1 of the MS-CFB spec, "The name MUST be // terminated with a UTF-16 terminating null character." (Even // though the directory entry aready includes the length of the // name. And also, that length *includes* the null character? // Look, CFB is a weird format.) Anyway, some CFB files in the // wild don't do this, so we're not going to enforce it. match String::from_utf16(&name_chars[0..name_len_chars]) { Ok(name) => name, Err(_) => malformed!("name not valid UTF-16"), } }; let obj_type = { let obj_type_byte = reader.read_u8()?; match ObjType::from_byte(obj_type_byte) { Some(obj_type) => obj_type, None => malformed!("invalid object type: {}", obj_type_byte), } }; // According to section 2.6.2 of the MS-CFB spec, "The root directory // entry's Name field MUST contain the null-terminated string 'Root // Entry' in Unicode UTF-16." However, some CFB files in the wild // don't do this, so we don't enforce it; instead, for the root entry // we just ignore the actual name in the file and treat it as though it // were what it's supposed to be. if obj_type == ObjType::Root { name = consts::ROOT_DIR_NAME.to_string(); } else { internal::path::validate_name(&name)?; } let color = { let color_byte = reader.read_u8()?; match Color::from_byte(color_byte) { Some(color) => color, None => malformed!("invalid color: {}", color_byte), } }; let left_sibling = reader.read_u32::()?; if left_sibling != NO_STREAM && left_sibling > MAX_REGULAR_STREAM_ID { malformed!("invalid left sibling: {}", left_sibling); } let right_sibling = reader.read_u32::()?; if right_sibling != NO_STREAM && right_sibling > MAX_REGULAR_STREAM_ID { malformed!("invalid right sibling: {}", right_sibling); } let child = reader.read_u32::()?; if child != NO_STREAM { if obj_type == ObjType::Stream { malformed!("non-empty stream child: {}", child); } else if child > MAX_REGULAR_STREAM_ID { malformed!("invalid child: {}", child); } } // Section 2.6.1 of the MS-CFB spec states that "In a stream object, // this [CLSID] field MUST be set to all zeroes." However, some CFB // files in the wild violate this, so we don't enforce it; instead, for // non-storage objects we just ignore the CLSID data entirely and treat // it as though it were nil. let clsid = match obj_type { ObjType::Unallocated | ObjType::Stream => { reader.read_exact(&mut [0u8; 16])?; Uuid::nil() } ObjType::Storage | ObjType::Root => DirEntry::read_clsid(reader)?, }; let state_bits = reader.read_u32::()?; let creation_time = reader.read_u64::()?; let modified_time = reader.read_u64::()?; // According to the MS-CFB spec section 2.6.3, the starting sector and // stream length fields should both be set to zero for storage entries. // However, some CFB implementations use FREE_SECTOR or END_OF_CHAIN // instead for the starting sector, or even just leave these fields // with uninitialized garbage values, so we don't enforce this. // Instead, for storage objects we just treat these fields as though // they were zero. let mut start_sector = reader.read_u32::()?; let mut stream_len = reader.read_u64::()? & version.stream_len_mask(); if obj_type == ObjType::Storage { start_sector = 0; stream_len = 0; } Ok(DirEntry { name, obj_type, color, left_sibling, right_sibling, child, clsid, state_bits, creation_time, modified_time, start_sector, stream_len, }) } pub fn write_to(&self, writer: &mut W) -> io::Result<()> { debug_assert!(internal::path::validate_name(&self.name).is_ok()); let name_utf16: Vec = self.name.encode_utf16().collect(); debug_assert!(name_utf16.len() < 32); for &chr in name_utf16.iter() { writer.write_u16::(chr)?; } for _ in name_utf16.len()..32 { writer.write_u16::(0)?; } writer.write_u16::((name_utf16.len() as u16 + 1) * 2)?; writer.write_u8(self.obj_type.as_byte())?; writer.write_u8(self.color.as_byte())?; writer.write_u32::(self.left_sibling)?; writer.write_u32::(self.right_sibling)?; writer.write_u32::(self.child)?; DirEntry::write_clsid(writer, &self.clsid)?; writer.write_u32::(self.state_bits)?; writer.write_u64::(self.creation_time)?; writer.write_u64::(self.modified_time)?; writer.write_u32::(self.start_sector)?; writer.write_u64::(self.stream_len)?; Ok(()) } } //===========================================================================// #[cfg(test)] mod tests { use super::DirEntry; use crate::internal::{consts, Color, ObjType, Version}; use uuid::Uuid; #[test] fn parse_valid_storage_entry_with_end_of_chain_start() { let input: [u8; consts::DIR_ENTRY_LEN] = [ // Name: 70, 0, 111, 0, 111, 0, 98, 0, 97, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, // name length 1, // obj type 1, // color, 12, 0, 0, 0, // left sibling 34, 0, 0, 0, // right sibling 56, 0, 0, 0, // child 0xe0, 0x85, 0x9f, 0xf2, 0xf9, 0x4f, 0x68, 0x10, // CLSID 0xab, 0x91, 0x08, 0x00, 0x2b, 0x27, 0xb3, 0xd9, // CLSID 239, 190, 173, 222, // state bits 0, 0, 0, 0, 0, 0, 0, 0, // created 0, 0, 0, 0, 0, 0, 0, 0, // modified 0xfe, 0xff, 0xff, 0xff, // start sector 0, 0, 0, 0, 0, 0, 0, 0, // stream length ]; let dir_entry = DirEntry::read_from(&mut (&input as &[u8]), Version::V4).unwrap(); assert_eq!(&dir_entry.name, "Foobar"); assert_eq!(dir_entry.obj_type, ObjType::Storage); assert_eq!(dir_entry.color, Color::Black); assert_eq!(dir_entry.left_sibling, 12); assert_eq!(dir_entry.right_sibling, 34); assert_eq!(dir_entry.child, 56); assert_eq!( dir_entry.clsid, Uuid::parse_str("F29F85E0-4FF9-1068-AB91-08002B27B3D9").unwrap() ); assert_eq!(dir_entry.state_bits, 0xdeadbeef); assert_eq!(dir_entry.creation_time, 0); assert_eq!(dir_entry.modified_time, 0); assert_eq!(dir_entry.start_sector, 0); assert_eq!(dir_entry.stream_len, 0); } #[test] fn parse_valid_storage_entry() { let input: [u8; consts::DIR_ENTRY_LEN] = [ // Name: 70, 0, 111, 0, 111, 0, 98, 0, 97, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, // name length 1, // obj type 1, // color, 12, 0, 0, 0, // left sibling 34, 0, 0, 0, // right sibling 56, 0, 0, 0, // child 0xe0, 0x85, 0x9f, 0xf2, 0xf9, 0x4f, 0x68, 0x10, // CLSID 0xab, 0x91, 0x08, 0x00, 0x2b, 0x27, 0xb3, 0xd9, // CLSID 239, 190, 173, 222, // state bits 0, 0, 0, 0, 0, 0, 0, 0, // created 0, 0, 0, 0, 0, 0, 0, 0, // modified 0, 0, 0, 0, // start sector 0, 0, 0, 0, 0, 0, 0, 0, // stream length ]; let dir_entry = DirEntry::read_from(&mut (&input as &[u8]), Version::V4).unwrap(); assert_eq!(&dir_entry.name, "Foobar"); assert_eq!(dir_entry.obj_type, ObjType::Storage); assert_eq!(dir_entry.color, Color::Black); assert_eq!(dir_entry.left_sibling, 12); assert_eq!(dir_entry.right_sibling, 34); assert_eq!(dir_entry.child, 56); assert_eq!( dir_entry.clsid, Uuid::parse_str("F29F85E0-4FF9-1068-AB91-08002B27B3D9").unwrap() ); assert_eq!(dir_entry.state_bits, 0xdeadbeef); assert_eq!(dir_entry.creation_time, 0); assert_eq!(dir_entry.modified_time, 0); assert_eq!(dir_entry.start_sector, 0); assert_eq!(dir_entry.stream_len, 0); } #[test] #[should_panic(expected = "Malformed directory entry \ (invalid object type: 3)")] fn invalid_object_type() { let input: [u8; consts::DIR_ENTRY_LEN] = [ // Name: 70, 0, 111, 0, 111, 0, 98, 0, 97, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, // name length 3, // obj type 1, // color, 12, 0, 0, 0, // left sibling 34, 0, 0, 0, // right sibling 56, 0, 0, 0, // child 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // CLSID 239, 190, 173, 222, // state bits 0, 0, 0, 0, 0, 0, 0, 0, // created 0, 0, 0, 0, 0, 0, 0, 0, // modified 0, 0, 0, 0, // start sector 0, 0, 0, 0, 0, 0, 0, 0, // stream length ]; DirEntry::read_from(&mut (&input as &[u8]), Version::V4).unwrap(); } #[test] #[should_panic(expected = "Malformed directory entry (invalid color: 2)")] fn invalid_color() { let input: [u8; consts::DIR_ENTRY_LEN] = [ // Name: 70, 0, 111, 0, 111, 0, 98, 0, 97, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, // name length 1, // obj type 2, // color, 12, 0, 0, 0, // left sibling 34, 0, 0, 0, // right sibling 56, 0, 0, 0, // child 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // CLSID 239, 190, 173, 222, // state bits 0, 0, 0, 0, 0, 0, 0, 0, // created 0, 0, 0, 0, 0, 0, 0, 0, // modified 0, 0, 0, 0, // start sector 0, 0, 0, 0, 0, 0, 0, 0, // stream length ]; DirEntry::read_from(&mut (&input as &[u8]), Version::V4).unwrap(); } // Regression test for https://github.com/mdsteele/rust-cfb/issues/26 #[test] fn non_null_clsid_on_stream() { let input: [u8; consts::DIR_ENTRY_LEN] = [ // Name: 70, 0, 111, 0, 111, 0, 98, 0, 97, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, // name length 2, // obj type 1, // color, 12, 0, 0, 0, // left sibling 34, 0, 0, 0, // right sibling 0xff, 0xff, 0xff, 0xff, // child 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, // CLSID 0, 0, 0, 0, // state bits 0, 0, 0, 0, 0, 0, 0, 0, // created 0, 0, 0, 0, 0, 0, 0, 0, // modified 0, 0, 0, 0, // start sector 0, 0, 0, 0, 0, 0, 0, 0, // stream length ]; // Section 2.6.1 of the MS-CFB spec states that "In a stream object, // this [CLSID] field MUST be set to all zeroes." However, some CFB // files in the wild violate this. So we allow parsing a stream dir // entry with a non-nil CLSID, but we ignore that CLSID and just set it // to all zeroes. let dir_entry = DirEntry::read_from(&mut (&input as &[u8]), Version::V4).unwrap(); assert_eq!(dir_entry.obj_type, ObjType::Stream); assert!(dir_entry.clsid.is_nil()); } // Regression test for https://github.com/mdsteele/rust-cfb/issues/26 #[test] fn non_null_terminated_name() { let input: [u8; consts::DIR_ENTRY_LEN] = [ // Name: 70, 0, 111, 0, 111, 0, 98, 0, 97, 0, 114, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 14, 0, // name length 2, // obj type 1, // color, 12, 0, 0, 0, // left sibling 34, 0, 0, 0, // right sibling 0xff, 0xff, 0xff, 0xff, // child 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // CLSID 0, 0, 0, 0, // state bits 0, 0, 0, 0, 0, 0, 0, 0, // created 0, 0, 0, 0, 0, 0, 0, 0, // modified 0, 0, 0, 0, // start sector 0, 0, 0, 0, 0, 0, 0, 0, // stream length ]; // According to section 2.6.1 of the MS-CFB spec, "The name MUST be // terminated with a UTF-16 terminating null character." But some CFB // files in the wild don't do this, so we just rely on the name length // field. let dir_entry = DirEntry::read_from(&mut (&input as &[u8]), Version::V4).unwrap(); assert_eq!(dir_entry.name, "Foobar"); } // Regression test for https://github.com/mdsteele/rust-cfb/issues/27 #[test] fn nonzero_storage_starting_sector_and_stream_len() { let input: [u8; consts::DIR_ENTRY_LEN] = [ // Name: 70, 0, 111, 0, 111, 0, 98, 0, 97, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, // name length 1, // obj type 1, // color, 12, 0, 0, 0, // left sibling 34, 0, 0, 0, // right sibling 56, 0, 0, 0, // child 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // CLSID 239, 190, 173, 222, // state bits 0, 0, 0, 0, 0, 0, 0, 0, // created 0, 0, 0, 0, 0, 0, 0, 0, // modified 58, 0, 0, 0, // start sector 62, 2, 0, 0, 0, 0, 0, 0, // stream length ]; // According to section 2.6.3 of the MS-CFB spec, the starting sector // location and stream size fields should be set to zero in a storage // directory entry. But some CFB files in the wild don't do this, so // when parsing a storage entry, just ignore those fields' values and // pretend they're zero. let dir_entry = DirEntry::read_from(&mut (&input as &[u8]), Version::V4).unwrap(); assert_eq!(dir_entry.obj_type, ObjType::Storage); assert_eq!(dir_entry.start_sector, 0); assert_eq!(dir_entry.stream_len, 0); } // Regression test for https://github.com/mdsteele/rust-cfb/issues/29 #[test] fn root_entry_with_incorrect_name() { let input: [u8; consts::DIR_ENTRY_LEN] = [ // Name: 70, 0, 111, 0, 111, 0, 98, 0, 97, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, // name length 5, // obj type 1, // color, 12, 0, 0, 0, // left sibling 34, 0, 0, 0, // right sibling 56, 0, 0, 0, // child 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // CLSID 239, 190, 173, 222, // state bits 0, 0, 0, 0, 0, 0, 0, 0, // created 0, 0, 0, 0, 0, 0, 0, 0, // modified 0, 0, 0, 0, // start sector 0, 0, 0, 0, 0, 0, 0, 0, // stream length ]; // According to section 2.6.2 of the MS-CFB spec, the name field MUST // be set to "Root Entry" in the root directory entry. But some CFB // files in the wild don't do this, so when parsing the root entry, // just ignore the name in the file and pretend it's correct. let dir_entry = DirEntry::read_from(&mut (&input as &[u8]), Version::V4).unwrap(); assert_eq!(dir_entry.obj_type, ObjType::Root); assert_eq!(dir_entry.name, "Root Entry"); } } //===========================================================================// cfb-0.7.3/src/internal/entry.rs000064400000000000000000000224160072674642500145340ustar 00000000000000use crate::internal::{self, consts, DirEntry, ObjType}; use std::path::{Path, PathBuf}; use std::time::SystemTime; use uuid::Uuid; // ========================================================================= // /// Metadata about a single object (storage or stream) in a compound file. #[derive(Clone)] pub struct Entry { name: String, path: PathBuf, obj_type: ObjType, clsid: Uuid, state_bits: u32, creation_time: u64, modified_time: u64, stream_len: u64, } impl Entry { pub(crate) fn new(dir_entry: &DirEntry, path: PathBuf) -> Entry { Entry { name: dir_entry.name.clone(), path, obj_type: dir_entry.obj_type, clsid: dir_entry.clsid, state_bits: dir_entry.state_bits, creation_time: dir_entry.creation_time, modified_time: dir_entry.modified_time, stream_len: dir_entry.stream_len, } } /// Returns the name of the object that this entry represents. pub fn name(&self) -> &str { &self.name } /// Returns the full path to the object that this entry represents. pub fn path(&self) -> &Path { &self.path } /// Returns whether this entry is for a stream object (i.e. a "file" within /// the compound file). pub fn is_stream(&self) -> bool { self.obj_type == ObjType::Stream } /// Returns whether this entry is for a storage object (i.e. a "directory" /// within the compound file), either the root or a nested storage. pub fn is_storage(&self) -> bool { self.obj_type == ObjType::Storage || self.obj_type == ObjType::Root } /// Returns whether this entry is specifically for the root storage object /// of the compound file. pub fn is_root(&self) -> bool { self.obj_type == ObjType::Root } /// Returns the size, in bytes, of the stream that this metadata is for. pub fn len(&self) -> u64 { self.stream_len } /// Returns true if the stream is empty. pub fn is_empty(&self) -> bool { self.stream_len == 0 } /// Returns the CLSID (that is, the object class GUID) for this object. /// This will always be all zeros for stream objects. pub fn clsid(&self) -> &Uuid { &self.clsid } /// Returns the user-defined bitflags set for this object. pub fn state_bits(&self) -> u32 { self.state_bits } /// Returns the time when the object that this entry represents was /// created. pub fn created(&self) -> SystemTime { internal::time::system_time_from_timestamp(self.creation_time) } /// Returns the time when the object that this entry represents was last /// modified. pub fn modified(&self) -> SystemTime { internal::time::system_time_from_timestamp(self.modified_time) } } // ========================================================================= // #[derive(Clone, Copy, Eq, PartialEq)] pub enum EntriesOrder { Nonrecursive, Preorder, } // ========================================================================= // /// An iterator over the entries in a storage object. pub struct Entries<'a> { order: EntriesOrder, directory: &'a [DirEntry], stack: Vec<(PathBuf, u32, bool)>, } impl<'a> Entries<'a> { pub(crate) fn new( order: EntriesOrder, directory: &'a [DirEntry], parent_path: PathBuf, start: u32, ) -> Entries<'a> { let mut entries = Entries { order, directory, stack: Vec::new() }; match order { EntriesOrder::Nonrecursive => { entries.stack_left_spine(&parent_path, start); } EntriesOrder::Preorder => { entries.stack.push((parent_path, start, false)); } } entries } fn join_path(parent_path: &Path, dir_entry: &DirEntry) -> PathBuf { if dir_entry.obj_type == ObjType::Root { parent_path.to_path_buf() } else { parent_path.join(&dir_entry.name) } } fn stack_left_spine(&mut self, parent_path: &Path, mut current_id: u32) { while current_id != consts::NO_STREAM { let dir_entry = &self.directory[current_id as usize]; self.stack.push((parent_path.to_path_buf(), current_id, true)); current_id = dir_entry.left_sibling; } } } impl<'a> Iterator for Entries<'a> { type Item = Entry; fn next(&mut self) -> Option { if let Some((parent, stream_id, visit_siblings)) = self.stack.pop() { let dir_entry = &self.directory[stream_id as usize]; let path = Entries::join_path(&parent, dir_entry); if visit_siblings { self.stack_left_spine(&parent, dir_entry.right_sibling); } if self.order == EntriesOrder::Preorder && dir_entry.obj_type != ObjType::Stream && dir_entry.child != consts::NO_STREAM { self.stack_left_spine(&path, dir_entry.child); } Some(Entry::new(dir_entry, path)) } else { None } } } // ========================================================================= // #[cfg(test)] mod tests { use super::{Entries, EntriesOrder, Entry}; use crate::internal::consts::{NO_STREAM, ROOT_DIR_NAME}; use crate::internal::{DirEntry, ObjType}; use std::path::{Path, PathBuf}; fn make_entry( name: &str, obj_type: ObjType, left: u32, child: u32, right: u32, ) -> DirEntry { let mut dir_entry = DirEntry::new(name, obj_type, 0); dir_entry.left_sibling = left; dir_entry.child = child; dir_entry.right_sibling = right; dir_entry } fn make_directory() -> Vec { // Root contains: 3 contains: // 5 8 // / \ / \ // 3 6 7 9 // / \ // 1 4 // \ // 2 vec![ make_entry(ROOT_DIR_NAME, ObjType::Root, NO_STREAM, 5, NO_STREAM), make_entry("1", ObjType::Stream, NO_STREAM, NO_STREAM, 2), make_entry("2", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM), make_entry("3", ObjType::Storage, 1, 8, 4), make_entry("4", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM), make_entry("5", ObjType::Stream, 3, NO_STREAM, 6), make_entry("6", ObjType::Storage, NO_STREAM, NO_STREAM, NO_STREAM), make_entry("7", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM), make_entry("8", ObjType::Stream, 7, NO_STREAM, 9), make_entry("9", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM), ] } fn paths_for_entries(entries: &[Entry]) -> Vec<&Path> { entries.iter().map(|entry| entry.path()).collect() } #[test] fn nonrecursive_entries_from_root() { let directory = make_directory(); let entries: Vec = Entries::new( EntriesOrder::Nonrecursive, &directory, PathBuf::from("/"), 5, ) .collect(); let paths = paths_for_entries(&entries); assert_eq!( paths, vec![ Path::new("/1"), Path::new("/2"), Path::new("/3"), Path::new("/4"), Path::new("/5"), Path::new("/6") ] ); } #[test] fn nonrecursive_entries_from_storage() { let directory = make_directory(); let entries: Vec = Entries::new( EntriesOrder::Nonrecursive, &directory, PathBuf::from("/3"), 8, ) .collect(); let paths = paths_for_entries(&entries); assert_eq!( paths, vec![Path::new("/3/7"), Path::new("/3/8"), Path::new("/3/9")] ); } #[test] fn preorder_entries_from_root() { let directory = make_directory(); let entries: Vec = Entries::new( EntriesOrder::Preorder, &directory, PathBuf::from("/"), 0, ) .collect(); let paths = paths_for_entries(&entries); assert_eq!( paths, vec![ Path::new("/"), Path::new("/1"), Path::new("/2"), Path::new("/3"), Path::new("/3/7"), Path::new("/3/8"), Path::new("/3/9"), Path::new("/4"), Path::new("/5"), Path::new("/6"), ] ); } #[test] fn preorder_entries_from_storage() { let directory = make_directory(); let entries: Vec = Entries::new( EntriesOrder::Preorder, &directory, PathBuf::from("/"), 3, ) .collect(); let paths = paths_for_entries(&entries); assert_eq!( paths, vec![ Path::new("/3"), Path::new("/3/7"), Path::new("/3/8"), Path::new("/3/9") ] ); } } // ========================================================================= // cfb-0.7.3/src/internal/header.rs000064400000000000000000000231660072674642500146260ustar 00000000000000use crate::internal::{consts, Version}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use std::io::{self, Read, Write}; //===========================================================================// pub struct Header { pub version: Version, pub num_dir_sectors: u32, pub num_fat_sectors: u32, pub first_dir_sector: u32, pub first_minifat_sector: u32, pub num_minifat_sectors: u32, pub first_difat_sector: u32, pub num_difat_sectors: u32, pub initial_difat_entries: [u32; consts::NUM_DIFAT_ENTRIES_IN_HEADER], } impl Header { pub fn read_from(reader: &mut R) -> io::Result
{ let mut magic = [0u8; 8]; reader.read_exact(&mut magic)?; if magic != consts::MAGIC_NUMBER { invalid_data!("Invalid CFB file (wrong magic number)"); } reader.read_exact(&mut [0u8; 16])?; // reserved field // Read the version number, but don't try to interpret it until after // we've checked the byte order mark. let _minor_version = reader.read_u16::()?; let version_number = reader.read_u16::()?; let byte_order_mark = reader.read_u16::()?; if byte_order_mark != consts::BYTE_ORDER_MARK { invalid_data!( "Invalid CFB byte order mark (expected 0x{:04X}, found \ 0x{:04X})", consts::BYTE_ORDER_MARK, byte_order_mark ); } let version = match Version::from_number(version_number) { Some(version) => version, None => { invalid_data!( "CFB version {} is not supported", version_number ); } }; let sector_shift = reader.read_u16::()?; if sector_shift != version.sector_shift() { invalid_data!( "Incorrect sector shift for CFB version {} (expected {}, \ found {})", version.number(), version.sector_shift(), sector_shift ); } let mini_sector_shift = reader.read_u16::()?; if mini_sector_shift != consts::MINI_SECTOR_SHIFT { invalid_data!( "Incorrect mini sector shift (expected {}, found {})", consts::MINI_SECTOR_SHIFT, mini_sector_shift ); } reader.read_exact(&mut [0u8; 6])?; // reserved field let num_dir_sectors = reader.read_u32::()?; let num_fat_sectors = reader.read_u32::()?; let first_dir_sector = reader.read_u32::()?; let _transaction_signature = reader.read_u32::()?; let mini_stream_cutoff = reader.read_u32::()?; if mini_stream_cutoff != consts::MINI_STREAM_CUTOFF { invalid_data!( "Incorrect mini stream cutoff (expected {}, found {})", consts::MINI_STREAM_CUTOFF, mini_stream_cutoff ); } let first_minifat_sector = reader.read_u32::()?; let num_minifat_sectors = reader.read_u32::()?; let mut first_difat_sector = reader.read_u32::()?; let num_difat_sectors = reader.read_u32::()?; // Some CFB implementations use FREE_SECTOR to indicate END_OF_CHAIN. if first_difat_sector == consts::FREE_SECTOR { first_difat_sector = consts::END_OF_CHAIN; } let mut initial_difat_entries = [consts::FREE_SECTOR; consts::NUM_DIFAT_ENTRIES_IN_HEADER]; for entry in initial_difat_entries.iter_mut() { let next = reader.read_u32::()?; if next == consts::FREE_SECTOR { break; } else if next > consts::MAX_REGULAR_SECTOR { invalid_data!( "Initial DIFAT array refers to invalid sector index \ 0x{:08X}", next ); } *entry = next; } Ok(Header { version, num_dir_sectors, num_fat_sectors, first_dir_sector, first_minifat_sector, num_minifat_sectors, first_difat_sector, num_difat_sectors, initial_difat_entries, }) } pub fn write_to(&self, writer: &mut W) -> io::Result<()> { writer.write_all(&consts::MAGIC_NUMBER)?; writer.write_all(&[0; 16])?; // reserved field writer.write_u16::(consts::MINOR_VERSION)?; writer.write_u16::(self.version.number())?; writer.write_u16::(consts::BYTE_ORDER_MARK)?; writer.write_u16::(self.version.sector_shift())?; writer.write_u16::(consts::MINI_SECTOR_SHIFT)?; writer.write_all(&[0; 6])?; // reserved field writer.write_u32::(self.num_dir_sectors)?; writer.write_u32::(self.num_fat_sectors)?; writer.write_u32::(self.first_dir_sector)?; writer.write_u32::(0)?; // transaction signature (unused) writer.write_u32::(consts::MINI_STREAM_CUTOFF)?; writer.write_u32::(self.first_minifat_sector)?; writer.write_u32::(self.num_minifat_sectors)?; writer.write_u32::(self.first_difat_sector)?; writer.write_u32::(self.num_difat_sectors)?; for &entry in self.initial_difat_entries.iter() { writer.write_u32::(entry)?; } Ok(()) } } //===========================================================================// #[cfg(test)] mod tests { use super::Header; use crate::internal::{consts, Version}; fn make_valid_header() -> Header { let mut header = Header { version: Version::V3, num_dir_sectors: 2, num_fat_sectors: 1, first_dir_sector: 1, first_minifat_sector: 2, num_minifat_sectors: 3, first_difat_sector: consts::END_OF_CHAIN, num_difat_sectors: 0, initial_difat_entries: [consts::FREE_SECTOR; consts::NUM_DIFAT_ENTRIES_IN_HEADER], }; header.initial_difat_entries[0] = 0; header } fn make_valid_header_data() -> Vec { let header = make_valid_header(); let mut data = Vec::::new(); header.write_to(&mut data).unwrap(); data } #[test] fn round_trip() { let header1 = make_valid_header(); let mut data = Vec::::new(); header1.write_to(&mut data).unwrap(); let header2 = Header::read_from(&mut data.as_slice()).unwrap(); assert_eq!(header1.version, header2.version); assert_eq!(header1.num_dir_sectors, header2.num_dir_sectors); assert_eq!(header1.num_fat_sectors, header2.num_fat_sectors); assert_eq!(header1.first_dir_sector, header2.first_dir_sector); assert_eq!(header1.first_minifat_sector, header2.first_minifat_sector); assert_eq!(header1.num_minifat_sectors, header2.num_minifat_sectors); assert_eq!(header1.first_difat_sector, header2.first_difat_sector); assert_eq!(header1.num_difat_sectors, header2.num_difat_sectors); assert_eq!( header1.initial_difat_entries, header2.initial_difat_entries ); } #[test] #[should_panic(expected = "Invalid CFB file (wrong magic number)")] fn invalid_magic_number() { let mut data = make_valid_header_data(); data[2] = 255; Header::read_from(&mut data.as_slice()).unwrap(); } #[test] #[should_panic(expected = "CFB version 42 is not supported")] fn invalid_version() { let mut data = make_valid_header_data(); data[26] = 42; Header::read_from(&mut data.as_slice()).unwrap(); } #[test] #[should_panic( expected = "Invalid CFB byte order mark (expected 0xFFFE, found \ 0x07FE)" )] fn invalid_byte_order_mark() { let mut data = make_valid_header_data(); data[29] = 7; Header::read_from(&mut data.as_slice()).unwrap(); } #[test] #[should_panic( expected = "Incorrect sector shift for CFB version 3 (expected 9, \ found 12)" )] fn invalid_sector_shift() { let mut data = make_valid_header_data(); data[30] = 12; Header::read_from(&mut data.as_slice()).unwrap(); } #[test] #[should_panic( expected = "Incorrect mini sector shift (expected 6, found 7)" )] fn invalid_mini_sector_shift() { let mut data = make_valid_header_data(); data[32] = 7; Header::read_from(&mut data.as_slice()).unwrap(); } #[test] #[should_panic( expected = "Incorrect mini stream cutoff (expected 4096, found 2048)" )] fn invalid_mini_stream_cutoff() { let mut data = make_valid_header_data(); data[57] = 8; Header::read_from(&mut data.as_slice()).unwrap(); } #[test] #[should_panic( expected = "Initial DIFAT array refers to invalid sector index \ 0xFFFFFFFB" )] fn invalid_difat_array() { let mut data = make_valid_header_data(); data[80] = 0xFB; Header::read_from(&mut data.as_slice()).unwrap(); } } //===========================================================================// cfb-0.7.3/src/internal/macros.rs000064400000000000000000000030440072674642500146530ustar 00000000000000// ========================================================================= // macro_rules! already_exists { ($e:expr) => { return Err(::std::io::Error::new(::std::io::ErrorKind::AlreadyExists, $e)) }; ($fmt:expr, $($arg:tt)+) => { return Err(::std::io::Error::new(::std::io::ErrorKind::AlreadyExists, format!($fmt, $($arg)+))) }; } macro_rules! invalid_data { ($e:expr) => { return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, $e)) }; ($fmt:expr, $($arg:tt)+) => { return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, format!($fmt, $($arg)+))) }; } macro_rules! invalid_input { ($e:expr) => { return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidInput, $e)) }; ($fmt:expr, $($arg:tt)+) => { return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidInput, format!($fmt, $($arg)+))) }; } macro_rules! not_found { ($e:expr) => { return Err(::std::io::Error::new(::std::io::ErrorKind::NotFound, $e)) }; ($fmt:expr, $($arg:tt)+) => { return Err(::std::io::Error::new(::std::io::ErrorKind::NotFound, format!($fmt, $($arg)+))) }; } // ========================================================================= // cfb-0.7.3/src/internal/minialloc.rs000064400000000000000000000405050072674642500153410ustar 00000000000000use crate::internal::{ consts, Chain, DirEntry, Directory, Entries, MiniChain, ObjType, Sector, SectorInit, Version, }; use byteorder::{LittleEndian, WriteBytesExt}; use fnv::FnvHashSet; use std::io::{self, Seek, SeekFrom, Write}; use std::mem::size_of; use std::path::Path; //===========================================================================// macro_rules! malformed { ($e:expr) => { invalid_data!("Malformed MiniFAT ({})", $e) }; ($fmt:expr, $($arg:tt)+) => { invalid_data!("Malformed MiniFAT ({})", format!($fmt, $($arg)+)) }; } //===========================================================================// /// A wrapper around the directory manager that additionally provides /// mini-sector allocation via the MiniFAT. pub struct MiniAllocator { directory: Directory, minifat: Vec, minifat_start_sector: u32, } impl MiniAllocator { pub fn new( directory: Directory, minifat: Vec, minifat_start_sector: u32, ) -> io::Result> { let minialloc = MiniAllocator { directory, minifat, minifat_start_sector }; minialloc.validate()?; Ok(minialloc) } pub fn version(&self) -> Version { self.directory.version() } pub fn next_mini_sector(&self, sector_id: u32) -> io::Result { let index = sector_id as usize; if index >= self.minifat.len() { invalid_data!( "Found reference to mini sector {}, but MiniFAT has only {} \ entries", index, self.minifat.len() ); } let next_id = self.minifat[index]; if next_id != consts::END_OF_CHAIN && (next_id > consts::MAX_REGULAR_SECTOR || next_id as usize >= self.minifat.len()) { invalid_data!("next_id ({}) is invalid", next_id); } Ok(next_id) } pub fn into_inner(self) -> F { self.directory.into_inner() } pub fn stream_id_for_name_chain(&self, names: &[&str]) -> Option { self.directory.stream_id_for_name_chain(names) } /// Returns an iterator over the entries within the root storage object. pub fn root_storage_entries(&self) -> Entries { self.directory.root_storage_entries() } /// Returns an iterator over the entries within a storage object. pub fn storage_entries(&self, path: &Path) -> io::Result { self.directory.storage_entries(path) } /// Returns an iterator over all entries within the compound file, starting /// from and including the root entry. The iterator walks the storage tree /// in a preorder traversal. pub fn walk(&self) -> Entries { self.directory.walk() } /// Returns an iterator over all entries under a storage subtree, including /// the given path itself. The iterator walks the storage tree in a /// preorder traversal. pub fn walk_storage(&self, path: &Path) -> io::Result { self.directory.walk_storage(path) } pub fn open_chain( &mut self, start_sector_id: u32, init: SectorInit, ) -> io::Result> { self.directory.open_chain(start_sector_id, init) } pub fn open_mini_chain( &mut self, start_sector_id: u32, ) -> io::Result> { MiniChain::new(self, start_sector_id) } pub fn root_dir_entry(&self) -> &DirEntry { self.directory.root_dir_entry() } pub fn dir_entry(&self, stream_id: u32) -> &DirEntry { self.directory.dir_entry(stream_id) } fn validate(&self) -> io::Result<()> { let root_entry = self.directory.root_dir_entry(); let root_stream_mini_sectors = root_entry.stream_len / (consts::MINI_SECTOR_LEN as u64); if root_stream_mini_sectors < (self.minifat.len() as u64) { malformed!( "MiniFAT has {} entries, but root stream has only {} mini \ sectors", self.minifat.len(), root_stream_mini_sectors ); } let mut pointees = FnvHashSet::default(); for (from_mini_sector, &to_mini_sector) in self.minifat.iter().enumerate() { if to_mini_sector <= consts::MAX_REGULAR_SECTOR { if to_mini_sector as usize >= self.minifat.len() { malformed!( "MiniFAT has {} entries, but mini sector {} points to \ {}", self.minifat.len(), from_mini_sector, to_mini_sector ); } if pointees.contains(&to_mini_sector) { malformed!( "mini sector {} pointed to twice", to_mini_sector ); } pointees.insert(to_mini_sector); } } Ok(()) } } impl MiniAllocator { pub fn seek_within_mini_sector( &mut self, mini_sector: u32, offset_within_mini_sector: u64, ) -> io::Result> { debug_assert!( offset_within_mini_sector < consts::MINI_SECTOR_LEN as u64 ); let mini_stream_start_sector = self.directory.root_dir_entry().start_sector; let chain = self .directory .open_chain(mini_stream_start_sector, SectorInit::Fat)?; chain.into_subsector( mini_sector, consts::MINI_SECTOR_LEN, offset_within_mini_sector, ) } } impl MiniAllocator { /// Given the start sector of a chain, deallocates the entire chain. pub fn free_chain(&mut self, start_sector_id: u32) -> io::Result<()> { self.directory.free_chain(start_sector_id) } /// Inserts a new directory entry into the tree under the specified parent /// entry, then returns the new stream ID. pub fn insert_dir_entry( &mut self, parent_id: u32, name: &str, obj_type: ObjType, ) -> io::Result { self.directory.insert_dir_entry(parent_id, name, obj_type) } /// Removes a directory entry from the tree and deallocates it. pub fn remove_dir_entry( &mut self, parent_id: u32, name: &str, ) -> io::Result<()> { self.directory.remove_dir_entry(parent_id, name) } /// Calls the given function with a mutable reference to the specified /// directory entry, then writes the updated directory entry to the /// underlying file once the function returns. pub fn with_dir_entry_mut( &mut self, stream_id: u32, func: W, ) -> io::Result<()> where W: FnOnce(&mut DirEntry), { self.directory.with_dir_entry_mut(stream_id, func) } /// Allocates a new mini chain with one sector, and returns the starting /// sector number. pub fn begin_mini_chain(&mut self) -> io::Result { self.allocate_mini_sector(consts::END_OF_CHAIN) } /// Given the starting mini sector (or any internal mini sector) of a mini /// chain, extends the end of that chain by one mini sector and returns the /// new mini sector number, updating the MiniFAT as necessary. pub fn extend_mini_chain( &mut self, start_mini_sector: u32, ) -> io::Result { debug_assert_ne!(start_mini_sector, consts::END_OF_CHAIN); let mut last_mini_sector = start_mini_sector; loop { let next = self.minifat[last_mini_sector as usize]; if next == consts::END_OF_CHAIN { break; } last_mini_sector = next; } let new_mini_sector = self.allocate_mini_sector(consts::END_OF_CHAIN)?; self.set_minifat(last_mini_sector, new_mini_sector)?; Ok(new_mini_sector) } /// Allocates a new entry in the MiniFAT, sets its value to `value`, and /// returns the new mini sector number. fn allocate_mini_sector(&mut self, value: u32) -> io::Result { // If there's an existing free mini sector, use that. for mini_sector in 0..self.minifat.len() { if self.minifat[mini_sector] == consts::FREE_SECTOR { let mini_sector = mini_sector as u32; self.set_minifat(mini_sector, value)?; return Ok(mini_sector); } } // Otherwise, we need a new mini sector; if there's not room in the // MiniFAT to add it, then first we need to allocate a new MiniFAT // sector. let minifat_entries_per_sector = self.directory.sector_len() / 4; if self.minifat_start_sector == consts::END_OF_CHAIN { debug_assert!(self.minifat.is_empty()); self.minifat_start_sector = self.directory.begin_chain(SectorInit::Fat)?; let mut header = self.directory.seek_within_header(60)?; header.write_u32::(self.minifat_start_sector)?; header.write_u32::(1)?; } else if self.minifat.len() % minifat_entries_per_sector == 0 { let start = self.minifat_start_sector; self.directory.extend_chain(start, SectorInit::Fat)?; let num_minifat_sectors = self .directory .open_chain(start, SectorInit::Fat)? .num_sectors() as u32; let mut header = self.directory.seek_within_header(64)?; header.write_u32::(num_minifat_sectors)?; } // Add a new mini sector to the end of the mini stream and return it. let new_mini_sector = self.minifat.len() as u32; self.set_minifat(new_mini_sector, value)?; self.append_mini_sector()?; Ok(new_mini_sector) } /// Adds a new mini sector to the end of the mini stream. fn append_mini_sector(&mut self) -> io::Result<()> { let mini_stream_start_sector = self.directory.root_dir_entry().start_sector; let mini_stream_len = self.directory.root_dir_entry().stream_len; debug_assert_eq!(mini_stream_len % consts::MINI_SECTOR_LEN as u64, 0); let sector_len = self.directory.sector_len(); // If the mini stream doesn't have room for new mini sector, add // another regular sector to its chain. let new_start_sector = if mini_stream_start_sector == consts::END_OF_CHAIN { debug_assert_eq!(mini_stream_len, 0); self.directory.begin_chain(SectorInit::Zero)? } else { if mini_stream_len % sector_len as u64 == 0 { self.directory.extend_chain( mini_stream_start_sector, SectorInit::Zero, )?; } mini_stream_start_sector }; // Update length of mini stream in root directory entry. self.directory.with_root_dir_entry_mut(|dir_entry| { dir_entry.start_sector = new_start_sector; dir_entry.stream_len += consts::MINI_SECTOR_LEN as u64; }) } /// Deallocates the specified mini sector. fn free_mini_sector(&mut self, mini_sector: u32) -> io::Result<()> { self.set_minifat(mini_sector, consts::FREE_SECTOR)?; let mut mini_stream_len = self.directory.root_dir_entry().stream_len; debug_assert_eq!(mini_stream_len % consts::MINI_SECTOR_LEN as u64, 0); while self.minifat.last() == Some(&consts::FREE_SECTOR) { mini_stream_len -= consts::MINI_SECTOR_LEN as u64; self.minifat.pop(); // TODO: Truncate MiniFAT if last MiniFAT sector is now all free. } if mini_stream_len != self.directory.root_dir_entry().stream_len { self.directory.with_root_dir_entry_mut(|dir_entry| { dir_entry.stream_len = mini_stream_len; })?; } Ok(()) } /// Given the start sector of a mini chain, deallocates the entire chain. pub fn free_mini_chain( &mut self, start_mini_sector: u32, ) -> io::Result<()> { let mut mini_sector = start_mini_sector; while mini_sector != consts::END_OF_CHAIN { let next = self.minifat[mini_sector as usize]; self.free_mini_sector(mini_sector)?; mini_sector = next; } Ok(()) } /// Sets the given mini sector to point to `END_OF_CHAIN`, and deallocates /// all subsequent mini sectors in the chain. pub fn free_mini_chain_after( &mut self, mini_sector: u32, ) -> io::Result<()> { let next = self.minifat[mini_sector as usize]; self.set_minifat(mini_sector, consts::END_OF_CHAIN)?; self.free_mini_chain(next)?; Ok(()) } /// Sets `self.minifat[index] = value`, and also writes that change to the /// underlying file. The `index` must be <= `self.minifat.len()`. fn set_minifat(&mut self, index: u32, value: u32) -> io::Result<()> { debug_assert!(index as usize <= self.minifat.len()); let mut chain = self .directory .open_chain(self.minifat_start_sector, SectorInit::Fat)?; let offset = (index as u64) * size_of::() as u64; debug_assert!(chain.len() >= offset + size_of::() as u64); chain.seek(SeekFrom::Start(offset))?; chain.write_u32::(value)?; if (index as usize) == self.minifat.len() { self.minifat.push(value); } else { self.minifat[index as usize] = value; } Ok(()) } /// Flushes all changes to the underlying file. pub fn flush(&mut self) -> io::Result<()> { self.directory.flush() } } //===========================================================================// #[cfg(test)] mod tests { use super::MiniAllocator; use crate::internal::{ consts, Allocator, DirEntry, Directory, ObjType, Sectors, Version, }; use std::io::Cursor; fn make_minialloc(minifat: Vec) -> MiniAllocator>> { let root_stream_len = (consts::MINI_SECTOR_LEN * minifat.len()) as u64; make_minialloc_with_root_stream_len(minifat, root_stream_len) } fn make_minialloc_with_root_stream_len( minifat: Vec, root_stream_len: u64, ) -> MiniAllocator>> { let version = Version::V3; let num_sectors = 4; // FAT, Directory, MiniFAT, and mini chain let data_len = (1 + num_sectors) * version.sector_len(); let cursor = Cursor::new(vec![0; data_len]); let sectors = Sectors::new(version, data_len as u64, cursor); let mut fat = vec![consts::END_OF_CHAIN; num_sectors]; fat[0] = consts::FAT_SECTOR; let allocator = Allocator::new(sectors, vec![], vec![0], fat).unwrap(); let mut root_entry = DirEntry::empty_root_entry(); root_entry.child = 1; root_entry.start_sector = 3; root_entry.stream_len = root_stream_len; let mut stream_entry = DirEntry::new("foo", ObjType::Stream, 0); stream_entry.start_sector = 0; stream_entry.stream_len = root_entry.stream_len; let entries = vec![root_entry, stream_entry]; let directory = Directory::new(allocator, entries, 1).unwrap(); MiniAllocator::new(directory, minifat, 2).unwrap() } #[test] #[should_panic( expected = "Malformed MiniFAT (MiniFAT has 3 entries, but root stream \ has only 2 mini sectors)" )] fn root_stream_too_short() { let minifat = vec![1, 2, consts::END_OF_CHAIN]; let root_stream_len = (2 * consts::MINI_SECTOR_LEN) as u64; make_minialloc_with_root_stream_len(minifat, root_stream_len); } #[test] #[should_panic( expected = "Malformed MiniFAT (MiniFAT has 2 entries, but mini sector \ 1 points to 3)" )] fn pointee_out_of_range() { let minifat = vec![1, 3]; make_minialloc(minifat); } #[test] #[should_panic( expected = "Malformed MiniFAT (mini sector 1 pointed to twice)" )] fn double_pointee() { let minifat = vec![1, 2, 1]; make_minialloc(minifat); } } //===========================================================================// cfb-0.7.3/src/internal/minichain.rs000064400000000000000000000142520072674642500153310ustar 00000000000000use crate::internal::{consts, MiniAllocator}; use std::io::{self, Read, Seek, SeekFrom, Write}; //===========================================================================// pub struct MiniChain<'a, F: 'a> { minialloc: &'a mut MiniAllocator, sector_ids: Vec, offset_from_start: u64, } impl<'a, F> MiniChain<'a, F> { pub fn new( minialloc: &'a mut MiniAllocator, start_sector_id: u32, ) -> io::Result> { let mut sector_ids = Vec::::new(); let mut current_sector_id = start_sector_id; let first_sector_id = start_sector_id; while current_sector_id != consts::END_OF_CHAIN { sector_ids.push(current_sector_id); current_sector_id = minialloc.next_mini_sector(current_sector_id)?; if current_sector_id == first_sector_id { invalid_data!( "Minichain contained duplicate sector id {}", current_sector_id ); } } Ok(MiniChain { minialloc, sector_ids, offset_from_start: 0 }) } pub fn start_sector_id(&self) -> u32 { self.sector_ids.first().copied().unwrap_or(consts::END_OF_CHAIN) } pub fn len(&self) -> u64 { (consts::MINI_SECTOR_LEN as u64) * (self.sector_ids.len() as u64) } } impl<'a, F: Read + Write + Seek> MiniChain<'a, F> { /// Resizes the chain to the minimum number of sectors large enough to old /// `new_len` bytes, allocating or freeing sectors as needed. pub fn set_len(&mut self, new_len: u64) -> io::Result<()> { debug_assert!(new_len < consts::MINI_STREAM_CUTOFF as u64); let sector_len = consts::MINI_SECTOR_LEN as u64; let new_num_sectors = ((sector_len + new_len - 1) / sector_len) as usize; if new_num_sectors == 0 { if let Some(&start_sector) = self.sector_ids.first() { self.minialloc.free_mini_chain(start_sector)?; } } else if new_num_sectors <= self.sector_ids.len() { if new_num_sectors < self.sector_ids.len() { self.minialloc.free_mini_chain_after( self.sector_ids[new_num_sectors - 1], )?; } // TODO: zero remainder of final sector } else { for _ in self.sector_ids.len()..new_num_sectors { let new_sector_id = if let Some(&last_sector_id) = self.sector_ids.last() { self.minialloc.extend_mini_chain(last_sector_id)? } else { self.minialloc.begin_mini_chain()? }; self.sector_ids.push(new_sector_id); } } Ok(()) } pub fn free(self) -> io::Result<()> { self.minialloc.free_mini_chain(self.start_sector_id()) } } impl<'a, F> Seek for MiniChain<'a, F> { fn seek(&mut self, pos: SeekFrom) -> io::Result { let length = self.len(); let new_offset = match pos { SeekFrom::Start(delta) => delta as i64, SeekFrom::End(delta) => delta + length as i64, SeekFrom::Current(delta) => delta + self.offset_from_start as i64, }; if new_offset < 0 || (new_offset as u64) > length { invalid_input!( "Cannot seek to {}, chain length is {} bytes", new_offset, length ); } self.offset_from_start = new_offset as u64; Ok(self.offset_from_start) } } impl<'a, F: Read + Seek> Read for MiniChain<'a, F> { fn read(&mut self, buf: &mut [u8]) -> io::Result { let total_len = self.len(); debug_assert!(self.offset_from_start <= total_len); let remaining_in_chain = total_len - self.offset_from_start; let max_len = remaining_in_chain.min(buf.len() as u64) as usize; if max_len == 0 { return Ok(0); } let sector_len = consts::MINI_SECTOR_LEN as u64; let current_sector_index = (self.offset_from_start / sector_len) as usize; debug_assert!(current_sector_index < self.sector_ids.len()); let current_sector_id = self.sector_ids[current_sector_index]; let offset_within_sector = self.offset_from_start % sector_len; let mut sector = self.minialloc.seek_within_mini_sector( current_sector_id, offset_within_sector, )?; let bytes_read = sector.read(&mut buf[0..max_len])?; self.offset_from_start += bytes_read as u64; debug_assert!(self.offset_from_start <= total_len); Ok(bytes_read) } } impl<'a, F: Read + Write + Seek> Write for MiniChain<'a, F> { fn write(&mut self, buf: &[u8]) -> io::Result { if buf.is_empty() { return Ok(0); } let mut total_len = self.len(); let sector_len = consts::MINI_SECTOR_LEN as u64; if self.offset_from_start == total_len { let new_sector_id = if let Some(&last_sector_id) = self.sector_ids.last() { self.minialloc.extend_mini_chain(last_sector_id)? } else { self.minialloc.begin_mini_chain()? }; self.sector_ids.push(new_sector_id); total_len += sector_len; debug_assert_eq!(total_len, self.len()); } let current_sector_index = (self.offset_from_start / sector_len) as usize; debug_assert!(current_sector_index < self.sector_ids.len()); let current_sector_id = self.sector_ids[current_sector_index]; let offset_within_sector = self.offset_from_start % sector_len; let mut sector = self.minialloc.seek_within_mini_sector( current_sector_id, offset_within_sector, )?; let bytes_written = sector.write(buf)?; self.offset_from_start += bytes_written as u64; debug_assert!(self.offset_from_start <= total_len); Ok(bytes_written) } fn flush(&mut self) -> io::Result<()> { self.minialloc.flush() } } //===========================================================================// cfb-0.7.3/src/internal/mod.rs000064400000000000000000000012220072674642500141420ustar 00000000000000#[macro_use] mod macros; mod alloc; mod chain; mod color; pub mod consts; mod directory; mod direntry; mod entry; mod header; mod minialloc; mod minichain; mod objtype; pub mod path; mod sector; pub mod time; mod version; pub use self::alloc::Allocator; pub use self::chain::Chain; pub use self::color::Color; pub use self::directory::Directory; pub use self::direntry::DirEntry; pub use self::entry::{Entries, EntriesOrder, Entry}; pub use self::header::Header; pub use self::minialloc::MiniAllocator; pub use self::minichain::MiniChain; pub use self::objtype::ObjType; pub use self::sector::{Sector, SectorInit, Sectors}; pub use self::version::Version; cfb-0.7.3/src/internal/objtype.rs000064400000000000000000000030020072674642500150350ustar 00000000000000use crate::internal::consts; //===========================================================================// /// The type of a directory entry. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ObjType { Unallocated, Storage, Stream, Root, } impl ObjType { pub fn as_byte(&self) -> u8 { match self { ObjType::Unallocated => consts::OBJ_TYPE_UNALLOCATED, ObjType::Storage => consts::OBJ_TYPE_STORAGE, ObjType::Stream => consts::OBJ_TYPE_STREAM, ObjType::Root => consts::OBJ_TYPE_ROOT, } } pub fn from_byte(byte: u8) -> Option { if byte == consts::OBJ_TYPE_UNALLOCATED { Some(ObjType::Unallocated) } else if byte == consts::OBJ_TYPE_STORAGE { Some(ObjType::Storage) } else if byte == consts::OBJ_TYPE_STREAM { Some(ObjType::Stream) } else if byte == consts::OBJ_TYPE_ROOT { Some(ObjType::Root) } else { None } } } //===========================================================================// #[cfg(test)] mod tests { use super::ObjType; #[test] fn round_trip() { for &obj_type in &[ ObjType::Unallocated, ObjType::Storage, ObjType::Stream, ObjType::Root, ] { assert_eq!(ObjType::from_byte(obj_type.as_byte()), Some(obj_type)); } } } //===========================================================================// cfb-0.7.3/src/internal/path.rs000064400000000000000000000121330072674642500143220ustar 00000000000000use std::cmp::Ordering; use std::io; use std::path::{Component, Path, PathBuf}; // ========================================================================= // const MAX_NAME_LEN: usize = 31; // ========================================================================= // /// Compares two directory entry names according to CFB ordering, which is /// case-insensitive, and which always puts shorter names before longer names, /// as encoded in UTF-16 (i.e. [shortlex /// order](https://en.wikipedia.org/wiki/Shortlex_order), rather than /// dictionary order). pub fn compare_names(name1: &str, name2: &str) -> Ordering { match name1.encode_utf16().count().cmp(&name2.encode_utf16().count()) { // This is actually not 100% correct -- the MS-CFB spec specifies a // particular way of doing the uppercasing on individual UTF-16 code // units, along with a list of weird exceptions and corner cases. But // hopefully this is good enough for 99+% of the time. Ordering::Equal => name1.to_uppercase().cmp(&name2.to_uppercase()), other => other, } } /// Converts a storage/stream name to UTF-16, or returns an error if the name /// is invalid. pub fn validate_name(name: &str) -> io::Result> { let name_utf16: Vec = name.encode_utf16().take(MAX_NAME_LEN + 1).collect(); if name_utf16.len() > MAX_NAME_LEN { invalid_input!( "Object name cannot be more than {} UTF-16 code units \ (was {})", MAX_NAME_LEN, name.encode_utf16().count() ); } for &chr in &['/', '\\', ':', '!'] { if name.contains(chr) { invalid_input!("Object name cannot contain {} character", chr); } } Ok(name_utf16) } // ========================================================================= // /// Given a path within a compound file, turns it into a list of child names /// descending from the root. Returns an error if the name is invalid. pub fn name_chain_from_path(path: &Path) -> io::Result> { let mut names: Vec<&str> = Vec::new(); for component in path.components() { match component { Component::Prefix(_) => { invalid_input!("Invalid path (must not have prefix)"); } Component::RootDir => names.clear(), Component::CurDir => {} Component::ParentDir => { if names.pop().is_none() { invalid_input!("Invalid path (must be within root)"); } } Component::Normal(osstr) => match osstr.to_str() { Some(name) => names.push(name), None => invalid_input!("Non UTF-8 path"), }, } } Ok(names) } pub fn path_from_name_chain(names: &[&str]) -> PathBuf { let mut path = PathBuf::from("/"); for name in names { path.push(name); } path } // ========================================================================= // #[cfg(test)] mod tests { use super::{ compare_names, name_chain_from_path, path_from_name_chain, validate_name, }; use std::cmp::Ordering; use std::path::{Path, PathBuf}; #[test] fn name_ordering() { assert_eq!(compare_names("foobar", "FOOBAR"), Ordering::Equal); assert_eq!(compare_names("foo", "barfoo"), Ordering::Less); assert_eq!(compare_names("Foo", "bar"), Ordering::Greater); } #[test] fn short_name_is_valid() { assert_eq!( validate_name("Foobar").unwrap(), vec![70, 111, 111, 98, 97, 114] ); } #[test] #[should_panic( expected = "Object name cannot be more than 31 UTF-16 code \ units (was 35)" )] fn long_name_is_invalid() { validate_name("ThisNameIsMostDefinitelyMuchTooLong").unwrap(); } #[test] #[should_panic(expected = "Object name cannot contain / character")] fn name_with_slash_is_invalid() { validate_name("foo/bar").unwrap(); } #[test] fn absolute_path_is_valid() { assert_eq!( name_chain_from_path(&Path::new("/foo/bar/baz/")).unwrap(), vec!["foo", "bar", "baz"] ); } #[test] fn relative_path_is_valid() { assert_eq!( name_chain_from_path(&Path::new("foo/bar/baz")).unwrap(), vec!["foo", "bar", "baz"] ); } #[test] fn path_with_parents_is_valid() { assert_eq!( name_chain_from_path(&Path::new("foo/bar/../baz")).unwrap(), vec!["foo", "baz"] ); } #[test] #[should_panic(expected = "Invalid path (must be within root)")] fn parent_of_root_is_invalid() { name_chain_from_path(&Path::new("foo/../../baz")).unwrap(); } #[test] fn canonical_path_is_absolute() { let path = Path::new("foo/bar/../baz"); let names = name_chain_from_path(&path).unwrap(); assert_eq!(path_from_name_chain(&names), PathBuf::from("/foo/baz")); } } // ========================================================================= // cfb-0.7.3/src/internal/sector.rs000064400000000000000000000320020072674642500146620ustar 00000000000000use crate::internal::{consts, DirEntry, Version}; use byteorder::{LittleEndian, WriteBytesExt}; use std::cmp; use std::io::{self, Read, Seek, SeekFrom, Write}; // ========================================================================= // /// A wrapper around the underlying file of a CompoundFile struct, providing /// access to individual sectors of the file. pub struct Sectors { inner: F, version: Version, num_sectors: u32, } impl Sectors { pub fn new(version: Version, inner_len: u64, inner: F) -> Sectors { let sector_len = version.sector_len() as u64; debug_assert!(inner_len >= sector_len); let num_sectors = ((inner_len + sector_len - 1) / sector_len) as u32 - 1; Sectors { inner, version, num_sectors } } pub fn version(&self) -> Version { self.version } pub fn sector_len(&self) -> usize { self.version.sector_len() } pub fn num_sectors(&self) -> u32 { self.num_sectors } pub fn into_inner(self) -> F { self.inner } } impl Sectors { pub fn seek_within_header( &mut self, offset_within_header: u64, ) -> io::Result> { debug_assert!(offset_within_header < consts::HEADER_LEN as u64); self.inner.seek(SeekFrom::Start(offset_within_header))?; Ok(Sector { inner: &mut self.inner, sector_len: consts::HEADER_LEN, offset_within_sector: offset_within_header as usize, }) } pub fn seek_to_sector(&mut self, sector_id: u32) -> io::Result> { self.seek_within_sector(sector_id, 0) } pub fn seek_within_sector( &mut self, sector_id: u32, offset_within_sector: u64, ) -> io::Result> { debug_assert!(offset_within_sector <= self.sector_len() as u64); if sector_id >= self.num_sectors { invalid_data!( "Tried to seek to sector {}, but sector count is only {}", sector_id, self.num_sectors ); } let sector_len = self.sector_len(); self.inner.seek(SeekFrom::Start( (sector_id + 1) as u64 * sector_len as u64 + offset_within_sector, ))?; Ok(Sector { inner: &mut self.inner, sector_len, offset_within_sector: offset_within_sector as usize, }) } } impl Sectors { /// Creates or resets the specified sector using the given initializer. pub fn init_sector( &mut self, sector_id: u32, init: SectorInit, ) -> io::Result<()> { match sector_id.cmp(&self.num_sectors) { cmp::Ordering::Greater => invalid_data!( "Tried to initialize sector {}, but sector count is only {}", sector_id, self.num_sectors ), cmp::Ordering::Less => {} cmp::Ordering::Equal => self.num_sectors += 1, } let mut sector = self.seek_to_sector(sector_id)?; init.initialize(&mut sector)?; Ok(()) } /// Flushes all changes to the underlying file. pub fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } // ========================================================================= // /// A wrapper around a single sector or mini sector within a CFB file, allowing /// read and write access only within that sector. pub struct Sector<'a, F: 'a> { inner: &'a mut F, sector_len: usize, offset_within_sector: usize, } impl<'a, F> Sector<'a, F> { /// Returns the total length of this sector. pub fn len(&self) -> usize { self.sector_len } fn remaining(&self) -> usize { debug_assert!(self.offset_within_sector <= self.len()); self.len() - self.offset_within_sector } pub fn subsector(self, start: usize, len: usize) -> Sector<'a, F> { debug_assert!(self.offset_within_sector <= self.len()); debug_assert!(start <= self.offset_within_sector); debug_assert!(start + len >= self.offset_within_sector); debug_assert!(start + len <= self.len()); Sector { inner: self.inner, sector_len: len, offset_within_sector: self.offset_within_sector - start, } } } impl<'a, F: Read> Read for Sector<'a, F> { fn read(&mut self, buf: &mut [u8]) -> io::Result { let max_len = cmp::min(buf.len(), self.remaining()); if max_len == 0 { return Ok(0); } let bytes_read = self.inner.read(&mut buf[0..max_len])?; self.offset_within_sector += bytes_read; debug_assert!(self.offset_within_sector <= self.len()); Ok(bytes_read) } } impl<'a, F: Write> Write for Sector<'a, F> { fn write(&mut self, buf: &[u8]) -> io::Result { let max_len = cmp::min(buf.len(), self.remaining()); if max_len == 0 { return Ok(0); } let bytes_written = self.inner.write(&buf[0..max_len])?; self.offset_within_sector += bytes_written; debug_assert!(self.offset_within_sector <= self.len()); Ok(bytes_written) } fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } impl<'a, F: Seek> Seek for Sector<'a, F> { fn seek(&mut self, pos: SeekFrom) -> io::Result { let old_offset = self.offset_within_sector as i64; let new_offset = match pos { SeekFrom::Start(delta) => delta as i64, SeekFrom::End(delta) => self.len() as i64 + delta, SeekFrom::Current(delta) => { self.offset_within_sector as i64 + delta } }; if new_offset < 0 || new_offset > self.len() as i64 { panic!("Internal error: cannot seek outside of sector"); } self.inner.seek(SeekFrom::Current(new_offset - old_offset))?; self.offset_within_sector = new_offset as usize; Ok(new_offset as u64) } } // ========================================================================= // #[derive(Clone, Copy)] pub enum SectorInit { Zero, Fat, Difat, Dir, } impl SectorInit { fn initialize(self, sector: &mut Sector) -> io::Result<()> { debug_assert_eq!(sector.offset_within_sector, 0); match self { SectorInit::Zero => { io::copy( &mut io::repeat(0).take(sector.len() as u64), sector, )?; } SectorInit::Fat => { debug_assert_eq!(sector.len() % 4, 0); for _ in 0..(sector.len() / 4) { sector.write_u32::(consts::FREE_SECTOR)?; } } SectorInit::Difat => { debug_assert_eq!(sector.len() % 4, 0); debug_assert!(sector.len() >= 4); for _ in 0..((sector.len() - 4) / 4) { sector.write_u32::(consts::FREE_SECTOR)?; } sector.write_u32::(consts::END_OF_CHAIN)?; } SectorInit::Dir => { debug_assert_eq!(sector.len() % consts::DIR_ENTRY_LEN, 0); let dir_entry = DirEntry::unallocated(); for _ in 0..(sector.len() / consts::DIR_ENTRY_LEN) { dir_entry.write_to(sector)?; } } } Ok(()) } } // ========================================================================= // #[cfg(test)] mod tests { use super::{SectorInit, Sectors}; use crate::internal::{consts, DirEntry, ObjType, Version}; use byteorder::{LittleEndian, ReadBytesExt}; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; #[test] fn sector_read() { let mut data = vec![1u8; 512]; data.append(&mut vec![2; 512]); data.append(&mut vec![3; 512]); data.append(&mut vec![4; 512]); let mut sectors = Sectors::new(Version::V3, 2048, Cursor::new(data)); assert_eq!(sectors.sector_len(), 512); assert_eq!(sectors.num_sectors(), 3); let mut sector = sectors.seek_to_sector(1).unwrap(); assert_eq!(sector.len(), 512); { let mut buffer = vec![0; 400]; assert_eq!(sector.read(&mut buffer).unwrap(), 400); assert_eq!(buffer, vec![3; 400]) } { let mut buffer = vec![0; 400]; assert_eq!(sector.read(&mut buffer).unwrap(), 112); let mut expected_data = vec![3; 112]; expected_data.append(&mut vec![0; 288]); assert_eq!(buffer, expected_data); } { let mut buffer = vec![0; 400]; assert_eq!(sector.read(&mut buffer).unwrap(), 0); assert_eq!(buffer, vec![0; 400]) } } #[test] fn sector_write() { let cursor = Cursor::new(vec![0u8; 2048]); let mut sectors = Sectors::new(Version::V3, 2048, cursor); assert_eq!(sectors.sector_len(), 512); assert_eq!(sectors.num_sectors(), 3); { let mut sector = sectors.seek_to_sector(1).unwrap(); assert_eq!(sector.len(), 512); assert_eq!(sector.write(&vec![1; 400]).unwrap(), 400); assert_eq!(sector.write(&vec![2; 400]).unwrap(), 112); assert_eq!(sector.write(&vec![3; 400]).unwrap(), 0); } let actual_data = sectors.into_inner().into_inner(); let mut expected_data = vec![0u8; 1024]; expected_data.append(&mut vec![1; 400]); expected_data.append(&mut vec![2; 112]); expected_data.append(&mut vec![0; 512]); assert_eq!(actual_data, expected_data); } #[test] fn sector_seek() { let mut data = vec![0u8; 512]; data.append(&mut vec![1; 128]); data.append(&mut vec![2; 128]); data.append(&mut vec![3; 128]); data.append(&mut vec![4; 128]); assert_eq!(data.len(), 1024); let mut sectors = Sectors::new(Version::V3, 1536, Cursor::new(data)); assert_eq!(sectors.sector_len(), 512); assert_eq!(sectors.num_sectors(), 2); let mut sector = sectors.seek_to_sector(0).unwrap(); let mut buffer = vec![0; 128]; assert_eq!(sector.seek(SeekFrom::Start(128)).unwrap(), 128); sector.read_exact(&mut buffer).unwrap(); assert_eq!(buffer, vec![2; 128]); assert_eq!(sector.seek(SeekFrom::End(-128)).unwrap(), 384); sector.read_exact(&mut buffer).unwrap(); assert_eq!(buffer, vec![4; 128]); assert_eq!(sector.seek(SeekFrom::Current(-256)).unwrap(), 256); sector.read_exact(&mut buffer).unwrap(); assert_eq!(buffer, vec![3; 128]); } #[test] fn sector_init() { let data = vec![0u8; 512]; let mut sectors = Sectors::new(Version::V3, 512, Cursor::new(data)); assert_eq!(sectors.num_sectors(), 0); { sectors.init_sector(0, SectorInit::Zero).unwrap(); let mut sector = sectors.seek_to_sector(0).unwrap(); let mut buffer = vec![0xff; 512]; sector.read_exact(&mut buffer).unwrap(); assert_eq!(buffer, vec![0; 512]); } { sectors.init_sector(1, SectorInit::Fat).unwrap(); let mut sector = sectors.seek_to_sector(1).unwrap(); for _ in 0..128 { assert_eq!( sector.read_u32::().unwrap(), consts::FREE_SECTOR ); } } { sectors.init_sector(2, SectorInit::Difat).unwrap(); let mut sector = sectors.seek_to_sector(2).unwrap(); for _ in 0..127 { assert_eq!( sector.read_u32::().unwrap(), consts::FREE_SECTOR ); } assert_eq!( sector.read_u32::().unwrap(), consts::END_OF_CHAIN ); } { sectors.init_sector(3, SectorInit::Dir).unwrap(); let mut sector = sectors.seek_to_sector(3).unwrap(); for _ in 0..4 { let dir_entry = DirEntry::read_from(&mut sector, Version::V3).unwrap(); assert_eq!(dir_entry.obj_type, ObjType::Unallocated); assert_eq!(dir_entry.left_sibling, consts::NO_STREAM); assert_eq!(dir_entry.right_sibling, consts::NO_STREAM); assert_eq!(dir_entry.child, consts::NO_STREAM); } } } #[test] fn partial_final_sector() { let data = vec![0u8; 1124]; let len = data.len() as u64; let mut sectors = Sectors::new(Version::V3, len, Cursor::new(data)); assert_eq!(sectors.num_sectors(), 2); sectors.init_sector(2, SectorInit::Zero).unwrap(); let data: Vec = sectors.into_inner().into_inner(); assert_eq!(data, vec![0u8; 2048]); } } // ========================================================================= // cfb-0.7.3/src/internal/time.rs000064400000000000000000000117670072674642500143400ustar 00000000000000use std::time::{Duration, SystemTime, UNIX_EPOCH}; //===========================================================================// /// The CFB timestamp value for the Unix epoch (Jan 1, 1970 UTC). const UNIX_EPOCH_TIMESTAMP: u64 = 116444736000000000; /// Returns the current time as a CFB file timestamp (the number of /// 100-nanosecond intervals since January 1, 1601 UTC). pub fn current_timestamp() -> u64 { timestamp_from_system_time(SystemTime::now()) } /// Converts a local `SystemTime` to a CFB file timestamp. fn timestamp_from_system_time(system_time: SystemTime) -> u64 { match system_time.duration_since(UNIX_EPOCH) { Ok(duration) => { let delta = duration_to_timestamp_delta(duration); UNIX_EPOCH_TIMESTAMP.saturating_add(delta) } Err(err) => { let delta = duration_to_timestamp_delta(err.duration()); UNIX_EPOCH_TIMESTAMP.saturating_sub(delta) } } } /// Converts a CFB file timestamp to a local `SystemTime`. pub fn system_time_from_timestamp(timestamp: u64) -> SystemTime { // The maximum range of SystemTime varies by system, and some systems // (e.g. 32-bit Linux) can't represent, say, a zero CFB timestamp. So we // center our calculations around UNIX_EPOCH (the one value we can be sure // that SystemTime can represent), and use checked_add and checked_sub to // avoid panicking on overflow. // // TODO: If SystemTime ever gains saturating_add and saturing_sub (see // https://github.com/rust-lang/rust/issues/71224) we should use those // instead. let system_time = if timestamp >= UNIX_EPOCH_TIMESTAMP { UNIX_EPOCH.checked_add(timestamp_delta_to_duration( timestamp - UNIX_EPOCH_TIMESTAMP, )) } else { UNIX_EPOCH.checked_sub(timestamp_delta_to_duration( UNIX_EPOCH_TIMESTAMP - timestamp, )) }; // If overflow does occur, just return UNIX_EPOCH; this will be totally // wrong, but at least it will allow us to continue reading the CFB file // without panicking. system_time.unwrap_or(UNIX_EPOCH) } fn duration_to_timestamp_delta(duration: Duration) -> u64 { duration .as_secs() .saturating_mul(10_000_000) .saturating_add((duration.subsec_nanos() / 100) as u64) } fn timestamp_delta_to_duration(delta: u64) -> Duration { Duration::new(delta / 10_000_000, (delta % 10_000_000) as u32 * 100) } //===========================================================================// #[cfg(test)] mod tests { use super::{ duration_to_timestamp_delta, system_time_from_timestamp, timestamp_delta_to_duration, timestamp_from_system_time, UNIX_EPOCH_TIMESTAMP, }; use std::time::{Duration, UNIX_EPOCH}; #[test] fn extreme_timestamp_delta() { // The maximum representable CFB timestamp: let timestamp = u64::MAX; let duration = timestamp_delta_to_duration(timestamp); assert_eq!(duration.as_secs(), 1844674407370); assert_eq!(duration.subsec_nanos(), 955161500); assert_eq!(duration_to_timestamp_delta(duration), timestamp); } #[test] fn extreme_duration() { // The maximum representable duration: let duration = Duration::new(u64::MAX, 999_999_999); // This duration will not fit in a 64-bit CFB timestamp delta. Rather // than overflow, we should return a saturated result. assert_eq!(duration_to_timestamp_delta(duration), u64::MAX); } #[test] fn unix_epoch() { assert_eq!( UNIX_EPOCH_TIMESTAMP, timestamp_from_system_time(UNIX_EPOCH) ); assert_eq!( system_time_from_timestamp(UNIX_EPOCH_TIMESTAMP), UNIX_EPOCH ); } #[test] fn after_unix_epoch() { let sat_18_mar_2017_at_18_46_36_utc = UNIX_EPOCH + Duration::from_secs(1489862796); assert_eq!( timestamp_from_system_time(sat_18_mar_2017_at_18_46_36_utc), 131343363960000000, ); assert_eq!( system_time_from_timestamp(131343363960000000), sat_18_mar_2017_at_18_46_36_utc ); } #[test] fn before_unix_epoch() { let sun_20_jul_1969_at_20_17_00_utc = UNIX_EPOCH - Duration::from_secs(14182980); assert_eq!( timestamp_from_system_time(sun_20_jul_1969_at_20_17_00_utc), 116302906200000000, ); assert_eq!( system_time_from_timestamp(116302906200000000), sun_20_jul_1969_at_20_17_00_utc ); } #[test] fn extreme_timestamps() { // If the system we're on can't represent these timestamps in a // SystemTime, then we'll get incorrect values, but we shouldn't panic. let min_time = system_time_from_timestamp(u64::MIN); let max_time = system_time_from_timestamp(u64::MAX); assert!(min_time <= max_time); } } //===========================================================================// cfb-0.7.3/src/internal/version.rs000064400000000000000000000042210072674642500150520ustar 00000000000000use crate::internal::consts; // ========================================================================= // /// The CFB format version to use. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum Version { /// Version 3, which uses 512-byte sectors. V3, /// Version 4, which uses 4096-byte sectors. V4, } impl Version { /// Returns the version enum for the given version number, or `None`. pub fn from_number(number: u16) -> Option { match number { 3 => Some(Version::V3), 4 => Some(Version::V4), _ => None, } } /// Returns the version number for this version. pub fn number(self) -> u16 { match self { Version::V3 => 3, Version::V4 => 4, } } /// Returns the sector shift used in this version. pub fn sector_shift(self) -> u16 { match self { Version::V3 => 9, // 512-byte sectors Version::V4 => 12, // 4096-byte sectors } } /// Returns the length of sectors used in this version. /// /// ``` /// use cfb::Version; /// assert_eq!(Version::V3.sector_len(), 512); /// assert_eq!(Version::V4.sector_len(), 4096); /// ``` pub fn sector_len(self) -> usize { 1 << (self.sector_shift() as usize) } /// Returns the bitmask used for reading stream lengths in this version. pub fn stream_len_mask(self) -> u64 { match self { Version::V3 => 0xffffffff, Version::V4 => 0xffffffffffffffff, } } /// Returns the number of directory entries per sector in this version. pub fn dir_entries_per_sector(self) -> usize { self.sector_len() / consts::DIR_ENTRY_LEN } } // ========================================================================= // #[cfg(test)] mod tests { use super::Version; #[test] fn number_round_trip() { for &version in &[Version::V3, Version::V4] { assert_eq!(Version::from_number(version.number()), Some(version)); } } } // ========================================================================= // cfb-0.7.3/src/lib.rs000064400000000000000000001501530072674642500123250ustar 00000000000000//! A library for reading/writing [Compound File Binary]( //! https://en.wikipedia.org/wiki/Compound_File_Binary_Format) (structured //! storage) files. See [MS-CFB]( //! https://msdn.microsoft.com/en-us/library/dd942138.aspx) for the format //! specification. //! //! A Compound File Binary (CFB) file, also called a *structured storage file* //! or simply a *compound file*, is a bit like a simple file system within a //! file. A compound file contains a tree of *storage* objects //! (i.e. directories), each of which can contain *stream* objects (i.e. files) //! or other storage objects. The format is designed to allow reasonably //! efficient in-place mutation and resizing of these stream and storage //! objects, without having to completely rewrite the CFB file on disk. //! //! # Example usage //! //! ```no_run //! use cfb; //! use std::io::{Read, Seek, SeekFrom, Write}; //! //! // Open an existing compound file in read-write mode. //! let mut comp = cfb::open_rw("path/to/cfb/file").unwrap(); //! //! // Read in all the data from one of the streams in that compound file. //! let data = { //! let mut stream = comp.open_stream("/foo/bar").unwrap(); //! let mut buffer = Vec::new(); //! stream.read_to_end(&mut buffer).unwrap(); //! buffer //! }; //! //! // Append that data to the end of another stream in the same file. //! { //! let mut stream = comp.open_stream("/baz").unwrap(); //! stream.seek(SeekFrom::End(0)).unwrap(); //! stream.write_all(&data).unwrap(); //! } //! //! // Now create a new compound file, and create a new stream with the data. //! let mut comp2 = cfb::create("some/other/path").unwrap(); //! comp2.create_storage("/spam/").unwrap(); //! let mut stream = comp2.create_stream("/spam/eggs").unwrap(); //! stream.write_all(&data).unwrap(); //! ``` #![warn(missing_docs)] use crate::internal::consts::{self, NO_STREAM}; use crate::internal::{ Allocator, DirEntry, Directory, Header, MiniAllocator, ObjType, SectorInit, Sectors, }; pub use crate::internal::{Entries, Entry, Version}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use fnv::FnvHashSet; use std::fs; use std::io::{self, BufRead, Read, Seek, SeekFrom, Write}; use std::mem::size_of; use std::path::{Path, PathBuf}; use uuid::Uuid; #[macro_use] mod internal; //===========================================================================// /// Opens an existing compound file at the given path in read-only mode. pub fn open>(path: P) -> io::Result> { CompoundFile::open(fs::File::open(path)?) } /// Opens an existing compound file at the given path in read-write mode. pub fn open_rw>(path: P) -> io::Result> { open_rw_with_path(path.as_ref()) } fn open_rw_with_path(path: &Path) -> io::Result> { let file = fs::OpenOptions::new().read(true).write(true).open(path)?; CompoundFile::open(file) } /// Creates a new compound file with no contents at the given path. /// /// The returned `CompoundFile` object will be both readable and writable. If /// a file already exists at the given path, this will overwrite it. pub fn create>(path: P) -> io::Result> { create_with_path(path.as_ref()) } fn create_with_path(path: &Path) -> io::Result> { let file = fs::OpenOptions::new() .read(true) .write(true) .create(true) .truncate(true) .open(path)?; CompoundFile::create(file) } //===========================================================================// /// A compound file, backed by an underlying reader/writer (such as a /// [`File`](https://doc.rust-lang.org/std/fs/struct.File.html) or /// [`Cursor`](https://doc.rust-lang.org/std/io/struct.Cursor.html)). pub struct CompoundFile { minialloc: MiniAllocator, } impl CompoundFile { /// Returns the CFB format version used for this compound file. pub fn version(&self) -> Version { self.minialloc.version() } fn stream_id_for_name_chain(&self, names: &[&str]) -> Option { self.minialloc.stream_id_for_name_chain(names) } /// Returns information about the root storage object. This is equivalent /// to `self.entry("/").unwrap()` (but always succeeds). pub fn root_entry(&self) -> Entry { Entry::new(self.minialloc.root_dir_entry(), PathBuf::from("/")) } /// Given a path within the compound file, get information about that /// stream or storage object. pub fn entry>(&self, path: P) -> io::Result { self.entry_with_path(path.as_ref()) } fn entry_with_path(&self, path: &Path) -> io::Result { let names = internal::path::name_chain_from_path(path)?; let path = internal::path::path_from_name_chain(&names); let stream_id = match self.stream_id_for_name_chain(&names) { Some(stream_id) => stream_id, None => not_found!("No such object: {:?}", path), }; Ok(Entry::new(self.minialloc.dir_entry(stream_id), path)) } /// Returns an iterator over the entries within the root storage object. /// This is equivalent to `self.read_storage("/").unwrap()` (but always /// succeeds). pub fn read_root_storage(&self) -> Entries { self.minialloc.root_storage_entries() } /// Returns an iterator over the entries within a storage object. pub fn read_storage>( &self, path: P, ) -> io::Result { self.minialloc.storage_entries(path.as_ref()) } /// Returns an iterator over all entries within the compound file, starting /// from and including the root entry. The iterator walks the storage tree /// in a preorder traversal. This is equivalent to /// `self.walk_storage("/").unwrap()` (but always succeeds). pub fn walk(&self) -> Entries { self.minialloc.walk() } /// Returns an iterator over all entries under a storage subtree, including /// the given path itself. The iterator walks the storage tree in a /// preorder traversal. pub fn walk_storage>( &self, path: P, ) -> io::Result { self.minialloc.walk_storage(path.as_ref()) } /// Returns true if there is an existing stream or storage at the given /// path, or false if there is nothing at that path. pub fn exists>(&self, path: P) -> bool { match internal::path::name_chain_from_path(path.as_ref()) { Ok(names) => self.stream_id_for_name_chain(&names).is_some(), Err(_) => false, } } /// Returns true if there is an existing stream at the given path, or false /// if there is a storage or nothing at that path. pub fn is_stream>(&self, path: P) -> bool { match internal::path::name_chain_from_path(path.as_ref()) { Ok(names) => match self.stream_id_for_name_chain(&names) { Some(stream_id) => { self.minialloc.dir_entry(stream_id).obj_type == ObjType::Stream } None => false, }, Err(_) => false, } } /// Returns true if there is an existing storage at the given path, or /// false if there is a stream or nothing at that path. pub fn is_storage>(&self, path: P) -> bool { match internal::path::name_chain_from_path(path.as_ref()) { Ok(names) => match self.stream_id_for_name_chain(&names) { Some(stream_id) => { self.minialloc.dir_entry(stream_id).obj_type != ObjType::Stream } None => false, }, Err(_) => false, } } // TODO: pub fn copy_stream // TODO: pub fn rename /// Consumes the `CompoundFile`, returning the underlying reader/writer. pub fn into_inner(self) -> F { self.minialloc.into_inner() } } impl CompoundFile { /// Opens an existing stream in the compound file for reading and/or /// writing (depending on what the underlying file supports). pub fn open_stream>( &mut self, path: P, ) -> io::Result> { self.open_stream_with_path(path.as_ref()) } fn open_stream_with_path(&mut self, path: &Path) -> io::Result> { let names = internal::path::name_chain_from_path(path)?; let path = internal::path::path_from_name_chain(&names); let stream_id = match self.stream_id_for_name_chain(&names) { Some(stream_id) => stream_id, None => not_found!("No such stream: {:?}", path), }; if self.minialloc.dir_entry(stream_id).obj_type != ObjType::Stream { invalid_input!("Not a stream: {:?}", path); } Ok(Stream::new(self, stream_id)) } } impl CompoundFile { /// Opens an existing compound file, using the underlying reader. If the /// underlying reader also supports the `Write` trait, then the /// `CompoundFile` object will be writable as well. pub fn open(mut inner: F) -> io::Result> { let inner_len = inner.seek(SeekFrom::End(0))?; if inner_len < consts::HEADER_LEN as u64 { invalid_data!( "Invalid CFB file ({} bytes is too small)", inner_len ); } inner.seek(SeekFrom::Start(0))?; let header = Header::read_from(&mut inner)?; let sector_len = header.version.sector_len(); if inner_len > ((consts::MAX_REGULAR_SECTOR + 1) as u64) * (sector_len as u64) { invalid_data!( "Invalid CFB file ({} bytes is too large)", inner_len ); } if inner_len < header.version.sector_len() as u64 { invalid_data!( "Invalid CFB file (length of {} < sector length of {})", inner_len, header.version.sector_len() ); } let mut sectors = Sectors::new(header.version, inner_len, inner); let num_sectors = sectors.num_sectors(); // Read in DIFAT. let mut difat = Vec::::new(); difat.extend_from_slice(&header.initial_difat_entries); let mut seen_sector_ids = FnvHashSet::default(); let mut difat_sector_ids = Vec::new(); let mut current_difat_sector = header.first_difat_sector; while current_difat_sector != consts::END_OF_CHAIN { if current_difat_sector > consts::MAX_REGULAR_SECTOR { invalid_data!( "DIFAT chain includes invalid sector index {}", current_difat_sector ); } else if current_difat_sector >= num_sectors { invalid_data!( "DIFAT chain includes sector index {}, but sector count \ is only {}", current_difat_sector, num_sectors ); } if seen_sector_ids.contains(¤t_difat_sector) { invalid_data!( "DIFAT chain includes duplicate sector index {}", current_difat_sector, ); } seen_sector_ids.insert(current_difat_sector); difat_sector_ids.push(current_difat_sector); let mut sector = sectors.seek_to_sector(current_difat_sector)?; for _ in 0..(sector_len / size_of::() - 1) { let next = sector.read_u32::()?; if next != consts::FREE_SECTOR && next > consts::MAX_REGULAR_SECTOR { invalid_data!( "DIFAT refers to invalid sector index {}", next ); } difat.push(next); } current_difat_sector = sector.read_u32::()?; } if header.num_difat_sectors as usize != difat_sector_ids.len() { invalid_data!( "Incorrect DIFAT chain length (header says {}, actual is {})", header.num_difat_sectors, difat_sector_ids.len() ); } while difat.last() == Some(&consts::FREE_SECTOR) { difat.pop(); } if header.num_fat_sectors as usize != difat.len() { invalid_data!( "Incorrect number of FAT sectors (header says {}, DIFAT says \ {})", header.num_fat_sectors, difat.len() ); } // Read in FAT. let mut fat = Vec::::new(); for §or_index in difat.iter() { if sector_index >= num_sectors { invalid_data!( "DIFAT refers to sector {}, but sector count is only {}", sector_index, num_sectors ); } let mut sector = sectors.seek_to_sector(sector_index)?; for _ in 0..(sector_len / size_of::()) { fat.push(sector.read_u32::()?); } } // If the number of sectors in the file is not a multiple of the number // of FAT entries per sector, then the last FAT sector must be padded // with FREE_SECTOR entries (see MS-CFB section 2.3). However, some // CFB implementations incorrectly pad the last FAT sector with zeros // (see https://github.com/mdsteele/rust-cfb/issues/8). Since zero is // normally a meaningful FAT entry (referring to sector 0), we only // want to strip zeros from the end of the FAT if they are beyond the // number of sectors in the file. while fat.len() > num_sectors as usize && fat.last() == Some(&0) { fat.pop(); } // Strip FREE_SECTOR entries from the end of the FAT. Unlike the zero // case above, we can remove these even if it makes the number of FAT // entries less than the number of sectors in the file; the allocator // will implicitly treat these extra sectors as free. while fat.last() == Some(&consts::FREE_SECTOR) { fat.pop(); } let mut allocator = Allocator::new(sectors, difat_sector_ids, difat, fat)?; // Read in directory. let mut dir_entries = Vec::::new(); let mut seen_dir_sectors = FnvHashSet::default(); let mut current_dir_sector = header.first_dir_sector; while current_dir_sector != consts::END_OF_CHAIN { if current_dir_sector > consts::MAX_REGULAR_SECTOR { invalid_data!( "Directory chain includes invalid sector index {}", current_dir_sector ); } else if current_dir_sector >= num_sectors { invalid_data!( "Directory chain includes sector index {}, but sector \ count is only {}", current_dir_sector, num_sectors ); } if seen_dir_sectors.contains(¤t_dir_sector) { invalid_data!( "Directory chain includes duplicate sector index {}", current_dir_sector, ); } seen_dir_sectors.insert(current_dir_sector); { let mut sector = allocator.seek_to_sector(current_dir_sector)?; for _ in 0..header.version.dir_entries_per_sector() { dir_entries.push(DirEntry::read_from( &mut sector, header.version, )?); } } current_dir_sector = allocator.next(current_dir_sector)?; } let mut directory = Directory::new(allocator, dir_entries, header.first_dir_sector)?; // Read in MiniFAT. let minifat = { let mut chain = directory .open_chain(header.first_minifat_sector, SectorInit::Fat)?; if header.num_minifat_sectors as usize != chain.num_sectors() { invalid_data!( "Incorrect MiniFAT chain length (header says {}, actual \ is {})", header.num_minifat_sectors, chain.num_sectors() ); } let num_minifat_entries = (chain.len() / 4) as usize; let mut minifat = Vec::::with_capacity(num_minifat_entries); for _ in 0..num_minifat_entries { minifat.push(chain.read_u32::()?); } while minifat.last() == Some(&consts::FREE_SECTOR) { minifat.pop(); } minifat }; let minialloc = MiniAllocator::new( directory, minifat, header.first_minifat_sector, )?; Ok(CompoundFile { minialloc }) } fn read_data_from_stream( &mut self, stream_id: u32, buf_offset_from_start: u64, buf: &mut [u8], ) -> io::Result { let (start_sector, stream_len) = { let dir_entry = self.minialloc.dir_entry(stream_id); debug_assert_eq!(dir_entry.obj_type, ObjType::Stream); (dir_entry.start_sector, dir_entry.stream_len) }; let num_bytes = if buf_offset_from_start >= stream_len { 0 } else { let remaining = stream_len - buf_offset_from_start; if remaining < buf.len() as u64 { remaining as usize } else { buf.len() } }; if num_bytes > 0 { if stream_len < consts::MINI_STREAM_CUTOFF as u64 { let mut chain = self.minialloc.open_mini_chain(start_sector)?; chain.seek(SeekFrom::Start(buf_offset_from_start))?; chain.read_exact(&mut buf[..num_bytes])?; } else { let mut chain = self .minialloc .open_chain(start_sector, SectorInit::Zero)?; chain.seek(SeekFrom::Start(buf_offset_from_start))?; chain.read_exact(&mut buf[..num_bytes])?; } } Ok(num_bytes) } } impl CompoundFile { /// Creates a new compound file with no contents, using the underlying /// reader/writer. The reader/writer should be initially empty. pub fn create(inner: F) -> io::Result> { CompoundFile::create_with_version(Version::V4, inner) } /// Creates a new compound file of the given version with no contents, /// using the underlying writer. The writer should be initially empty. pub fn create_with_version( version: Version, mut inner: F, ) -> io::Result> { let mut header = Header { version, num_dir_sectors: 1, num_fat_sectors: 1, first_dir_sector: 1, first_minifat_sector: consts::END_OF_CHAIN, num_minifat_sectors: 0, first_difat_sector: consts::END_OF_CHAIN, num_difat_sectors: 0, initial_difat_entries: [consts::FREE_SECTOR; consts::NUM_DIFAT_ENTRIES_IN_HEADER], }; header.initial_difat_entries[0] = 0; header.write_to(&mut inner)?; // Pad the header with zeroes so it's the length of a sector. let sector_len = version.sector_len(); debug_assert!(sector_len >= consts::HEADER_LEN); if sector_len > consts::HEADER_LEN { inner.write_all(&vec![0; sector_len - consts::HEADER_LEN])?; } // Write FAT sector: let fat: Vec = vec![consts::FAT_SECTOR, consts::END_OF_CHAIN]; for &entry in fat.iter() { inner.write_u32::(entry)?; } for _ in fat.len()..(sector_len / size_of::()) { inner.write_u32::(consts::FREE_SECTOR)?; } let difat: Vec = vec![0]; let difat_sector_ids: Vec = vec![]; // Write directory sector: let root_dir_entry = DirEntry::empty_root_entry(); root_dir_entry.write_to(&mut inner)?; for _ in 1..version.dir_entries_per_sector() { DirEntry::unallocated().write_to(&mut inner)?; } let sectors = Sectors::new(version, 3 * sector_len as u64, inner); let allocator = Allocator::new(sectors, difat_sector_ids, difat, fat) .expect("allocator"); let directory = Directory::new(allocator, vec![root_dir_entry], 1) .expect("directory"); let minialloc = MiniAllocator::new(directory, vec![], consts::END_OF_CHAIN) .expect("minialloc"); Ok(CompoundFile { minialloc }) } /// Creates a new, empty storage object (i.e. "directory") at the provided /// path. The parent storage object must already exist. pub fn create_storage>( &mut self, path: P, ) -> io::Result<()> { self.create_storage_with_path(path.as_ref()) } fn create_storage_with_path(&mut self, path: &Path) -> io::Result<()> { let mut names = internal::path::name_chain_from_path(path)?; if let Some(stream_id) = self.stream_id_for_name_chain(&names) { let path = internal::path::path_from_name_chain(&names); if self.minialloc.dir_entry(stream_id).obj_type != ObjType::Stream { already_exists!( "Cannot create storage at {:?} because a \ storage already exists there", path ); } else { already_exists!( "Cannot create storage at {:?} because a \ stream already exists there", path ); } } // If names is empty, that means we're trying to create the root. But // the root always already exists and will have been rejected above. debug_assert!(!names.is_empty()); let name = names.pop().unwrap(); let parent_id = match self.stream_id_for_name_chain(&names) { Some(stream_id) => stream_id, None => { not_found!("Parent storage doesn't exist"); } }; self.minialloc.insert_dir_entry(parent_id, name, ObjType::Storage)?; Ok(()) } /// Recursively creates a storage and all of its parent storages if they /// are missing. pub fn create_storage_all>( &mut self, path: P, ) -> io::Result<()> { self.create_storage_all_with_path(path.as_ref()) } fn create_storage_all_with_path(&mut self, path: &Path) -> io::Result<()> { let names = internal::path::name_chain_from_path(path)?; for length in 1..(names.len() + 1) { let prefix_path = internal::path::path_from_name_chain(&names[..length]); if self.is_storage(&prefix_path) { continue; } self.create_storage_with_path(&prefix_path)?; } Ok(()) } /// Removes the storage object at the provided path. The storage object /// must exist and have no children. pub fn remove_storage>( &mut self, path: P, ) -> io::Result<()> { self.remove_storage_with_path(path.as_ref()) } fn remove_storage_with_path(&mut self, path: &Path) -> io::Result<()> { let mut names = internal::path::name_chain_from_path(path)?; let stream_id = match self.stream_id_for_name_chain(&names) { Some(parent_id) => parent_id, None => not_found!("No such storage: {:?}", path), }; { let dir_entry = self.minialloc.dir_entry(stream_id); if dir_entry.obj_type == ObjType::Root { invalid_input!("Cannot remove the root storage object"); } if dir_entry.obj_type == ObjType::Stream { invalid_input!("Not a storage: {:?}", path); } debug_assert_eq!(dir_entry.obj_type, ObjType::Storage); if dir_entry.child != NO_STREAM { invalid_input!("Storage is not empty: {:?}", path); } } debug_assert!(!names.is_empty()); let name = names.pop().unwrap(); let parent_id = self.stream_id_for_name_chain(&names).unwrap(); self.minialloc.remove_dir_entry(parent_id, name)?; Ok(()) } /// Recursively removes a storage and all of its children. If called on /// the root storage, recursively removes all of its children but not the /// root storage itself (which cannot be removed). pub fn remove_storage_all>( &mut self, path: P, ) -> io::Result<()> { self.remove_storage_all_with_path(path.as_ref()) } fn remove_storage_all_with_path(&mut self, path: &Path) -> io::Result<()> { let mut stack = Vec::::new(); for entry in self.minialloc.walk_storage(path)? { stack.push(entry); } while let Some(entry) = stack.pop() { if entry.is_stream() { self.remove_stream_with_path(entry.path())?; } else if !entry.is_root() { self.remove_storage_with_path(entry.path())?; } } Ok(()) } /// Sets the CLSID for the storage object at the provided path. (To get /// the current CLSID for a storage object, use /// `self.entry(path)?.clsid()`.) pub fn set_storage_clsid>( &mut self, path: P, clsid: Uuid, ) -> io::Result<()> { self.set_storage_clsid_with_path(path.as_ref(), clsid) } fn set_storage_clsid_with_path( &mut self, path: &Path, clsid: Uuid, ) -> io::Result<()> { let names = internal::path::name_chain_from_path(path)?; let stream_id = match self.stream_id_for_name_chain(&names) { Some(stream_id) => stream_id, None => not_found!( "No such storage: {:?}", internal::path::path_from_name_chain(&names) ), }; if self.minialloc.dir_entry(stream_id).obj_type == ObjType::Stream { invalid_input!( "Not a storage: {:?}", internal::path::path_from_name_chain(&names) ); } self.minialloc.with_dir_entry_mut(stream_id, |dir_entry| { dir_entry.clsid = clsid; }) } /// Creates and returns a new, empty stream object at the provided path. /// If a stream already exists at that path, it will be replaced by the new /// stream. The parent storage object must already exist. pub fn create_stream>( &mut self, path: P, ) -> io::Result> { self.create_stream_with_path(path.as_ref(), true) } /// Creates and returns a new, empty stream object at the provided path. /// Returns an error if a stream already exists at that path. The parent /// storage object must already exist. pub fn create_new_stream>( &mut self, path: P, ) -> io::Result> { self.create_stream_with_path(path.as_ref(), false) } fn create_stream_with_path( &mut self, path: &Path, overwrite: bool, ) -> io::Result> { let mut names = internal::path::name_chain_from_path(path)?; if let Some(stream_id) = self.stream_id_for_name_chain(&names) { if self.minialloc.dir_entry(stream_id).obj_type != ObjType::Stream { already_exists!( "Cannot create stream at {:?} because a \ storage already exists there", internal::path::path_from_name_chain(&names) ); } else if !overwrite { already_exists!( "Cannot create new stream at {:?} because a \ stream already exists there", internal::path::path_from_name_chain(&names) ); } else { let mut stream = Stream::new(self, stream_id); stream.set_len(0)?; return Ok(stream); } } // If names is empty, that means we're trying to create the root. But // the root always already exists and will have been rejected above. debug_assert!(!names.is_empty()); let name = names.pop().unwrap(); let parent_id = match self.stream_id_for_name_chain(&names) { Some(stream_id) => stream_id, None => { not_found!("Parent storage doesn't exist"); } }; let new_stream_id = self.minialloc.insert_dir_entry( parent_id, name, ObjType::Stream, )?; return Ok(Stream::new(self, new_stream_id)); } /// Removes the stream object at the provided path. pub fn remove_stream>( &mut self, path: P, ) -> io::Result<()> { self.remove_stream_with_path(path.as_ref()) } fn remove_stream_with_path(&mut self, path: &Path) -> io::Result<()> { let mut names = internal::path::name_chain_from_path(path)?; let stream_id = match self.stream_id_for_name_chain(&names) { Some(parent_id) => parent_id, None => not_found!("No such stream: {:?}", path), }; let (start_sector_id, is_in_mini_stream) = { let dir_entry = self.minialloc.dir_entry(stream_id); if dir_entry.obj_type != ObjType::Stream { invalid_input!("Not a stream: {:?}", path); } debug_assert_eq!(dir_entry.child, NO_STREAM); ( dir_entry.start_sector, dir_entry.stream_len < consts::MINI_STREAM_CUTOFF as u64, ) }; if is_in_mini_stream { self.minialloc.free_mini_chain(start_sector_id)?; } else { self.minialloc.free_chain(start_sector_id)?; } debug_assert!(!names.is_empty()); let name = names.pop().unwrap(); let parent_id = self.stream_id_for_name_chain(&names).unwrap(); self.minialloc.remove_dir_entry(parent_id, name)?; Ok(()) } /// Sets the user-defined bitflags for the object at the provided path. /// (To get the current state bits for an object, use /// `self.entry(path)?.state_bits()`.) pub fn set_state_bits>( &mut self, path: P, bits: u32, ) -> io::Result<()> { self.set_state_bits_with_path(path.as_ref(), bits) } fn set_state_bits_with_path( &mut self, path: &Path, bits: u32, ) -> io::Result<()> { let names = internal::path::name_chain_from_path(path)?; let stream_id = match self.stream_id_for_name_chain(&names) { Some(stream_id) => stream_id, None => not_found!( "No such object: {:?}", internal::path::path_from_name_chain(&names) ), }; self.minialloc.with_dir_entry_mut(stream_id, |dir_entry| { dir_entry.state_bits = bits; }) } /// Sets the modified time for the object at the given path to now. Has no /// effect when called on the root storage. pub fn touch>(&mut self, path: P) -> io::Result<()> { self.touch_with_path(path.as_ref()) } fn touch_with_path(&mut self, path: &Path) -> io::Result<()> { let names = internal::path::name_chain_from_path(path)?; let path = internal::path::path_from_name_chain(&names); let stream_id = match self.stream_id_for_name_chain(&names) { Some(stream_id) => stream_id, None => not_found!("No such object: {:?}", path), }; if stream_id != consts::ROOT_STREAM_ID { debug_assert_ne!( self.minialloc.dir_entry(stream_id).obj_type, ObjType::Root ); self.minialloc.with_dir_entry_mut(stream_id, |dir_entry| { dir_entry.modified_time = internal::time::current_timestamp(); })?; } Ok(()) } /// Flushes all changes to the underlying file. pub fn flush(&mut self) -> io::Result<()> { self.minialloc.flush() } fn write_data_to_stream( &mut self, stream_id: u32, buf_offset_from_start: u64, buf: &[u8], ) -> io::Result<()> { let (old_start_sector, old_stream_len) = { let dir_entry = self.minialloc.dir_entry(stream_id); debug_assert_eq!(dir_entry.obj_type, ObjType::Stream); (dir_entry.start_sector, dir_entry.stream_len) }; debug_assert!(buf_offset_from_start <= old_stream_len); let new_stream_len = old_stream_len.max(buf_offset_from_start + buf.len() as u64); let new_start_sector = if old_start_sector == consts::END_OF_CHAIN { // Case 1: The stream has no existing chain. The stream is empty, // and we are writing at the start. debug_assert_eq!(old_stream_len, 0); debug_assert_eq!(buf_offset_from_start, 0); if new_stream_len < consts::MINI_STREAM_CUTOFF as u64 { // Case 1a: The data we're writing is small enough that it // should be placed into a new mini chain. let mut chain = self.minialloc.open_mini_chain(consts::END_OF_CHAIN)?; chain.write_all(buf)?; chain.start_sector_id() } else { // Case 1b: The data we're writing is large enough that it // should be placed into a new regular chain. let mut chain = self .minialloc .open_chain(consts::END_OF_CHAIN, SectorInit::Zero)?; chain.write_all(buf)?; chain.start_sector_id() } } else if old_stream_len < consts::MINI_STREAM_CUTOFF as u64 { // Case 2: The stream currently exists in a mini chain. if new_stream_len < consts::MINI_STREAM_CUTOFF as u64 { // Case 2a: After the write, the stream will still be small // enough to stay in the mini stream. Therefore, we should // write into this stream's existing mini chain. let mut chain = self.minialloc.open_mini_chain(old_start_sector)?; chain.seek(SeekFrom::Start(buf_offset_from_start))?; chain.write_all(buf)?; debug_assert_eq!(chain.start_sector_id(), old_start_sector); old_start_sector } else { // Case 2b: After the write, the stream will be large enough // that it cannot be in the mini stream. Therefore, we should // migrate the stream into a new regular chain. debug_assert!( buf_offset_from_start < consts::MINI_STREAM_CUTOFF as u64 ); let mut tmp = vec![0u8; buf_offset_from_start as usize]; let mut chain = self.minialloc.open_mini_chain(old_start_sector)?; chain.read_exact(&mut tmp)?; chain.free()?; let mut chain = self .minialloc .open_chain(consts::END_OF_CHAIN, SectorInit::Zero)?; chain.write_all(&tmp)?; chain.write_all(buf)?; chain.start_sector_id() } } else { // Case 3: The stream currently exists in a regular chain. After // the write, it will of course still be too big to be in the mini // stream. Therefore, we should write into this stream's existing // chain. debug_assert!(new_stream_len >= consts::MINI_STREAM_CUTOFF as u64); let mut chain = self .minialloc .open_chain(old_start_sector, SectorInit::Zero)?; chain.seek(SeekFrom::Start(buf_offset_from_start))?; chain.write_all(buf)?; debug_assert_eq!(chain.start_sector_id(), old_start_sector); old_start_sector }; // Update the directory entry for this stream. self.minialloc.with_dir_entry_mut(stream_id, |dir_entry| { dir_entry.start_sector = new_start_sector; dir_entry.stream_len = new_stream_len; dir_entry.modified_time = internal::time::current_timestamp(); }) } /// If `new_stream_len` is less than the stream's current length, then the /// stream will be truncated. If it is greater than the stream's current /// size, then the stream will be padded with zero bytes. fn resize_stream( &mut self, stream_id: u32, new_stream_len: u64, ) -> io::Result<()> { let (old_start_sector, old_stream_len) = { let dir_entry = self.minialloc.dir_entry(stream_id); debug_assert_eq!(dir_entry.obj_type, ObjType::Stream); (dir_entry.start_sector, dir_entry.stream_len) }; let new_start_sector = if old_start_sector == consts::END_OF_CHAIN { // Case 1: The stream has no existing chain. We will allocate a // new chain that is all zeroes. debug_assert_eq!(old_stream_len, 0); if new_stream_len < consts::MINI_STREAM_CUTOFF as u64 { // Case 1a: The new length is small enough that it should be // placed into a new mini chain. let mut chain = self.minialloc.open_mini_chain(consts::END_OF_CHAIN)?; chain.set_len(new_stream_len)?; chain.start_sector_id() } else { // Case 1b: The new length is large enough that it should be // placed into a new regular chain. let mut chain = self .minialloc .open_chain(consts::END_OF_CHAIN, SectorInit::Zero)?; chain.set_len(new_stream_len)?; chain.start_sector_id() } } else if old_stream_len < consts::MINI_STREAM_CUTOFF as u64 { // Case 2: The stream currently exists in a mini chain. if new_stream_len == 0 { // Case 2a: The new length is zero. Free the existing mini // chain. self.minialloc.free_mini_chain(old_start_sector)?; consts::END_OF_CHAIN } else if new_stream_len < consts::MINI_STREAM_CUTOFF as u64 { // Case 2b: The new length is still small enough to fit in a // mini chain. Therefore, we just need to adjust the length of // the existing chain. let mut chain = self.minialloc.open_mini_chain(old_start_sector)?; chain.set_len(new_stream_len)?; debug_assert_eq!(chain.start_sector_id(), old_start_sector); old_start_sector } else { // Case 2c: The new length is too large to fit in a mini chain. // Therefore, we should migrate the stream into a new regular // chain. let mut tmp = vec![0u8; old_stream_len as usize]; let mut chain = self.minialloc.open_mini_chain(old_start_sector)?; chain.read_exact(&mut tmp)?; chain.free()?; let mut chain = self .minialloc .open_chain(consts::END_OF_CHAIN, SectorInit::Zero)?; chain.write_all(&tmp)?; chain.set_len(new_stream_len)?; chain.start_sector_id() } } else { // Case 3: The stream currently exists in a regular chain. if new_stream_len == 0 { // Case 3a: The new length is zero. Free the existing chain. self.minialloc.free_chain(old_start_sector)?; consts::END_OF_CHAIN } else if new_stream_len < consts::MINI_STREAM_CUTOFF as u64 { // Case 3b: The new length is small enough to fit in a mini // chain. Therefore, we should migrate the stream into a new // mini chain. debug_assert!(new_stream_len < old_stream_len); let mut tmp = vec![0u8; new_stream_len as usize]; let mut chain = self .minialloc .open_chain(old_start_sector, SectorInit::Zero)?; chain.read_exact(&mut tmp)?; chain.free()?; let mut chain = self.minialloc.open_mini_chain(consts::END_OF_CHAIN)?; chain.write_all(&tmp)?; chain.start_sector_id() } else { // Case 3c: The new length is still too large to fit in a mini // chain. Therefore, we just need to adjust the length of the // existing chain. let mut chain = self .minialloc .open_chain(old_start_sector, SectorInit::Zero)?; chain.set_len(new_stream_len)?; debug_assert_eq!(chain.start_sector_id(), old_start_sector); old_start_sector } }; // Update the directory entry for this stream. self.minialloc.with_dir_entry_mut(stream_id, |dir_entry| { dir_entry.start_sector = new_start_sector; dir_entry.stream_len = new_stream_len; dir_entry.modified_time = internal::time::current_timestamp(); }) } } //===========================================================================// const BUFFER_SIZE: usize = 8192; /// A stream entry in a compound file, much like a filesystem file. pub struct Stream<'a, F: 'a> { comp: &'a mut CompoundFile, stream_id: u32, total_len: u64, buffer: Box<[u8; BUFFER_SIZE]>, buf_pos: usize, buf_cap: usize, buf_offset_from_start: u64, flusher: Option>>, } impl<'a, F> Stream<'a, F> { pub(crate) fn new( comp: &'a mut CompoundFile, stream_id: u32, ) -> Stream<'a, F> { let total_len = comp.minialloc.dir_entry(stream_id).stream_len; Stream { comp, stream_id, total_len, buffer: Box::new([0; BUFFER_SIZE]), buf_pos: 0, buf_cap: 0, buf_offset_from_start: 0, flusher: None, } } /// Returns the current length of the stream, in bytes. pub fn len(&self) -> u64 { self.total_len } /// Returns true if the stream is empty. pub fn is_empty(&self) -> bool { self.total_len == 0 } fn current_position(&self) -> u64 { self.buf_offset_from_start + (self.buf_pos as u64) } fn flush_changes(&mut self) -> io::Result<()> { if let Some(flusher) = self.flusher.take() { flusher.flush_changes(self)?; } Ok(()) } } impl<'a, F: Read + Write + Seek> Stream<'a, F> { /// Truncates or extends the stream, updating the size of this stream to /// become `size`. /// /// If `size` is less than the stream's current size, then the stream will /// be shrunk. If it is greater than the stream's current size, then the /// stream will be padded with zero bytes. /// /// Does not change the current read/write position within the stream, /// unless the stream is truncated to before the current position, in which /// case the position becomes the new end of the stream. pub fn set_len(&mut self, size: u64) -> io::Result<()> { if size != self.total_len { let new_position = self.current_position().min(size); self.flush_changes()?; self.comp.resize_stream(self.stream_id, size)?; self.total_len = size; self.buf_offset_from_start = new_position; self.buf_pos = 0; self.buf_cap = 0; } Ok(()) } fn mark_modified(&mut self) { if self.flusher.is_none() { let flusher: Box> = Box::new(FlushBuffer); self.flusher = Some(flusher); } } } impl<'a, F: Read + Seek> BufRead for Stream<'a, F> { fn fill_buf(&mut self) -> io::Result<&[u8]> { if self.buf_pos >= self.buf_cap && self.current_position() < self.total_len { self.flush_changes()?; self.buf_offset_from_start += self.buf_pos as u64; self.buf_pos = 0; self.buf_cap = self.comp.read_data_from_stream( self.stream_id, self.buf_offset_from_start, &mut self.buffer[..], )?; } Ok(&self.buffer[self.buf_pos..self.buf_cap]) } fn consume(&mut self, amt: usize) { self.buf_pos = self.buf_cap.min(self.buf_pos + amt); } } impl<'a, F: Read + Seek> Read for Stream<'a, F> { fn read(&mut self, buf: &mut [u8]) -> io::Result { let num_bytes = { let mut buffered_data = self.fill_buf()?; buffered_data.read(buf)? }; self.consume(num_bytes); Ok(num_bytes) } } impl<'a, F: Read + Seek> Seek for Stream<'a, F> { fn seek(&mut self, pos: SeekFrom) -> io::Result { let new_pos: u64 = match pos { SeekFrom::Start(delta) => { if delta > self.total_len { invalid_input!( "Cannot seek to {} bytes from start, because stream \ length is only {} bytes", delta, self.total_len, ); } delta } SeekFrom::End(delta) => { if delta > 0 { invalid_input!( "Cannot seek to {} bytes past the end of the stream", delta, ); } else { let delta = (-delta) as u64; if delta > self.total_len { invalid_input!( "Cannot seek to {} bytes before end, because \ stream length is only {} bytes", delta, self.total_len, ); } self.total_len - delta } } SeekFrom::Current(delta) => { let old_pos = self.current_position(); debug_assert!(old_pos <= self.total_len); if delta < 0 { let delta = (-delta) as u64; if delta > old_pos { invalid_input!( "Cannot seek to {} bytes before current position, \ which is only {}", delta, old_pos, ); } old_pos - delta } else { let delta = delta as u64; let remaining = self.total_len - old_pos; if delta > remaining { invalid_input!( "Cannot seek to {} bytes after current position, \ because there are only {} bytes remaining in the \ stream", delta, remaining, ); } old_pos + delta } } }; if new_pos < self.buf_offset_from_start || new_pos > self.buf_offset_from_start + self.buf_cap as u64 { self.flush_changes()?; self.buf_offset_from_start = new_pos; self.buf_pos = 0; self.buf_cap = 0; } else { self.buf_pos = (new_pos - self.buf_offset_from_start) as usize; } Ok(new_pos) } } impl<'a, F: Read + Write + Seek> Write for Stream<'a, F> { fn write(&mut self, buf: &[u8]) -> io::Result { debug_assert!(self.buf_pos <= self.buffer.len()); if self.buf_pos >= self.buffer.len() { self.flush_changes()?; self.buf_offset_from_start += self.buf_pos as u64; self.buf_pos = 0; self.buf_cap = 0; } let num_bytes_written = (&mut self.buffer[self.buf_pos..]).write(buf)?; self.mark_modified(); self.buf_pos += num_bytes_written; debug_assert!(self.buf_pos <= self.buffer.len()); self.buf_cap = self.buf_cap.max(self.buf_pos); self.total_len = self .total_len .max(self.buf_offset_from_start + self.buf_cap as u64); Ok(num_bytes_written) } fn flush(&mut self) -> io::Result<()> { self.flush_changes()?; self.comp.minialloc.flush() } } impl<'a, F> Drop for Stream<'a, F> { fn drop(&mut self) { let _ = self.flush_changes(); } } //===========================================================================// trait Flusher { fn flush_changes(&self, stream: &mut Stream) -> io::Result<()>; } struct FlushBuffer; impl Flusher for FlushBuffer { fn flush_changes(&self, stream: &mut Stream) -> io::Result<()> { stream.comp.write_data_to_stream( stream.stream_id, stream.buf_offset_from_start, &stream.buffer[..stream.buf_cap], )?; debug_assert_eq!( stream.comp.minialloc.dir_entry(stream.stream_id).stream_len, stream.total_len ); Ok(()) } } //===========================================================================// #[cfg(test)] mod tests { use super::CompoundFile; use crate::internal::{consts, DirEntry, Header, Version}; use byteorder::{LittleEndian, WriteBytesExt}; use std::io::{self, Cursor}; use std::mem::size_of; /// Regression test for https://github.com/mdsteele/rust-cfb/issues/8. #[test] fn zero_padded_fat() -> io::Result<()> { let version = Version::V3; let mut data = Vec::::new(); let mut header = Header { version, num_dir_sectors: 1, num_fat_sectors: 1, first_dir_sector: 1, first_minifat_sector: consts::END_OF_CHAIN, num_minifat_sectors: 0, first_difat_sector: consts::END_OF_CHAIN, num_difat_sectors: 0, initial_difat_entries: [consts::FREE_SECTOR; consts::NUM_DIFAT_ENTRIES_IN_HEADER], }; header.initial_difat_entries[0] = 0; header.write_to(&mut data)?; // Write FAT sector: let fat: Vec = vec![consts::FAT_SECTOR, consts::END_OF_CHAIN]; for &entry in fat.iter() { data.write_u32::(entry)?; } // Pad the FAT sector with zeros instead of FREE_SECTOR. Technically // this violates the MS-CFB spec (section 2.3), but apparently some CFB // implementations do this. for _ in fat.len()..(version.sector_len() / size_of::()) { data.write_u32::(0)?; } // Write directory sector: DirEntry::empty_root_entry().write_to(&mut data)?; for _ in 1..version.dir_entries_per_sector() { DirEntry::unallocated().write_to(&mut data)?; } // Despite the zero-padded FAT, we should be able to read this file. CompoundFile::open(Cursor::new(data)).expect("open"); Ok(()) } } //===========================================================================// cfb-0.7.3/tests/basic.rs000064400000000000000000000645110072674642500132150ustar 00000000000000use cfb::{CompoundFile, Entry, Version}; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use std::path::Path; use uuid::Uuid; //===========================================================================// fn read_root_storage_to_vec(comp: &CompoundFile) -> Vec { comp.read_root_storage().map(|e| e.name().to_string()).collect() } fn read_storage_to_vec(comp: &CompoundFile, path: &str) -> Vec { comp.read_storage(path).unwrap().map(|e| e.name().to_string()).collect() } fn walk_to_vec(entries: &[Entry]) -> Vec<&Path> { entries.iter().map(|e| e.path()).collect() } //===========================================================================// // Tests for creating compound files: #[test] #[should_panic(expected = "Invalid CFB file (12 bytes is too small)")] fn file_too_small() { let cursor = Cursor::new([1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); CompoundFile::open(cursor).unwrap(); } #[test] fn create_empty_compound_file() { let version = Version::V3; let cursor = Cursor::new(Vec::new()); let comp = CompoundFile::create_with_version(version, cursor).expect("create"); assert_eq!(comp.version(), version); assert_eq!(comp.entry("/").unwrap().name(), "Root Entry"); let cursor = comp.into_inner(); assert_eq!(cursor.get_ref().len(), 3 * version.sector_len()); let comp = CompoundFile::open(cursor).expect("open"); assert_eq!(comp.version(), version); assert_eq!(comp.entry("/").unwrap().name(), "Root Entry"); } #[test] fn empty_compound_file_has_no_children() { let cursor = Cursor::new(Vec::new()); let comp = CompoundFile::create_with_version(Version::V4, cursor) .expect("create"); assert!(comp.entry("/").unwrap().is_root()); assert_eq!(comp.read_storage("/").unwrap().count(), 0); assert_eq!(comp.read_root_storage().count(), 0); } #[test] fn partial_final_sector() { // Create a CFB with 4096-byte sectors. let mut comp = CompoundFile::create_with_version( Version::V4, Cursor::new(Vec::new()), ) .unwrap(); // Create a stream with 10,000 bytes of data in it. This should take // up a little over two sectors. let stream_data = vec![b'x'; 10000]; comp.create_stream("s").unwrap().write_all(&stream_data).unwrap(); // Get the raw CFB data. Due to the way we constructed the CFB file, it // should consist of exactly six sectors (header, FAT, directory, and three // for the stream), and the final sector of the stream should be the final // sector of the file. However, we should still pad out the file to the // end of that sector, even though the stream doesn't use the whole last // sector, for compatibility with other tools that don't support partial // final sectors. let mut cfb_data = comp.into_inner().into_inner(); assert_eq!(cfb_data.len(), 6 * 4096); let mut expected_final_sector = vec![b'\0'; 4096]; for i in 0..(stream_data.len() % 4096) { expected_final_sector[i] = b'x'; } assert_eq!(&cfb_data[(5 * 4096)..], expected_final_sector.as_slice()); // Now, truncate the raw CFB data so that the final sector only // contains as much data as is actually needed by the stream, then read // it as a CFB file. For compatibility with other tools that create // partial final sectors, we should consider it valid and still be able // to read the stream. cfb_data.truncate(5 * 4096 + stream_data.len() % 4096); let mut comp = CompoundFile::open(Cursor::new(cfb_data)).unwrap(); assert_eq!(comp.entry("s").unwrap().len(), stream_data.len() as u64); let mut actual_data = Vec::new(); comp.open_stream("s").unwrap().read_to_end(&mut actual_data).unwrap(); assert_eq!(actual_data, stream_data); } //===========================================================================// // Tests for directory methods: #[test] fn root_entry() { let cursor = Cursor::new(Vec::new()); let comp = CompoundFile::create(cursor).expect("create"); assert_eq!(comp.root_entry().path(), comp.entry("/").unwrap().path()); assert_eq!(comp.root_entry().name(), comp.entry("/").unwrap().name()); assert!(comp.root_entry().is_root()); } #[test] fn create_directory_tree() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage("/foo").unwrap(); comp.create_storage("/baz").unwrap(); comp.create_storage("/foo/bar").unwrap(); let cursor = comp.into_inner(); let comp = CompoundFile::open(cursor).expect("open"); assert_eq!(read_root_storage_to_vec(&comp), vec!["baz", "foo"]); assert_eq!(read_storage_to_vec(&comp, "/"), vec!["baz", "foo"]); assert_eq!(read_storage_to_vec(&comp, "/foo"), vec!["bar"]); assert!(read_storage_to_vec(&comp, "/baz").is_empty()); assert!(read_storage_to_vec(&comp, "/foo/bar").is_empty()); } #[test] fn walk_directory_tree() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage("/foo").unwrap(); comp.create_stream("/baz").unwrap(); comp.create_storage("/quux").unwrap(); comp.create_stream("/foo/bar").unwrap(); let entries: Vec = comp.walk().collect(); assert_eq!( walk_to_vec(&entries), vec![ Path::new("/"), Path::new("/baz"), Path::new("/foo"), Path::new("/foo/bar"), Path::new("/quux") ] ); let entries: Vec = comp.walk_storage("/").unwrap().collect(); assert_eq!( walk_to_vec(&entries), vec![ Path::new("/"), Path::new("/baz"), Path::new("/foo"), Path::new("/foo/bar"), Path::new("/quux") ] ); let entries: Vec = comp.walk_storage("/foo").unwrap().collect(); assert_eq!( walk_to_vec(&entries), vec![Path::new("/foo"), Path::new("/foo/bar")] ); let entries: Vec = comp.walk_storage("/baz").unwrap().collect(); assert_eq!(walk_to_vec(&entries), vec![Path::new("/baz")]); } #[test] #[should_panic(expected = "Not a storage: \\\"/foo\\\"")] fn read_storage_on_stream() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_stream("/foo").unwrap().write_all(b"foobar").unwrap(); comp.read_storage("/foo").unwrap(); } #[test] #[should_panic(expected = "Not a stream: \\\"/foo\\\"")] fn open_stream_on_storage() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage("/foo").expect("storage"); comp.open_stream("/foo").unwrap(); } //===========================================================================// // Tests for path methods: #[test] fn path_exists() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_stream("/foo").unwrap().write_all(b"foo").unwrap(); comp.create_storage("/bar/").unwrap(); comp.create_stream("/bar/quux").unwrap().write_all(b"quux").unwrap(); assert!(comp.exists("/")); assert!(comp.exists("foo")); assert!(comp.exists("/bar")); assert!(!comp.exists("quux")); assert!(comp.exists("bar/quux")); assert!(!comp.exists("bar/foo")); assert!(comp.exists("/bar/../foo")); assert!(comp.exists("/bar/../bar")); assert!(!comp.exists("../../foo")); } #[test] fn path_is_stream() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_stream("/foo").unwrap().write_all(b"foo").unwrap(); comp.create_storage("/bar/").unwrap(); comp.create_stream("/bar/quux").unwrap().write_all(b"quux").unwrap(); assert!(!comp.is_stream("/")); assert!(comp.is_stream("foo")); assert!(!comp.is_stream("/bar")); assert!(!comp.is_stream("quux")); assert!(comp.is_stream("bar/quux")); assert!(!comp.is_stream("bar/foo")); assert!(comp.is_stream("/bar/../foo")); assert!(!comp.is_stream("/bar/../bar")); assert!(!comp.is_stream("../../foo")); } #[test] fn path_is_storage() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_stream("/foo").unwrap().write_all(b"foo").unwrap(); comp.create_storage("/bar/").unwrap(); comp.create_stream("/bar/quux").unwrap().write_all(b"quux").unwrap(); assert!(comp.is_storage("/")); assert!(!comp.is_storage("foo")); assert!(comp.is_storage("/bar")); assert!(!comp.is_storage("quux")); assert!(!comp.is_storage("bar/quux")); assert!(!comp.is_storage("bar/foo")); assert!(!comp.is_storage("/bar/../foo")); assert!(comp.is_storage("/bar/../bar")); assert!(!comp.is_storage("../../bar")); } //===========================================================================// // Tests for CLSIDs: #[test] fn storage_clsids() { let uuid1 = Uuid::from_bytes(*b"ABCDEFGHIJKLMNOP"); let uuid2 = Uuid::from_bytes(*b"0123456789abcdef"); let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage("/foo").unwrap(); comp.set_storage_clsid("/", uuid1).unwrap(); comp.set_storage_clsid("/foo", uuid2).unwrap(); assert_eq!(comp.root_entry().clsid(), &uuid1); assert_eq!(comp.entry("/foo").unwrap().clsid(), &uuid2); let cursor = comp.into_inner(); let comp = CompoundFile::open(cursor).expect("open"); assert_eq!(comp.root_entry().clsid(), &uuid1); assert_eq!(comp.entry("/foo").unwrap().clsid(), &uuid2); } #[test] #[should_panic(expected = "No such storage")] fn set_nonexistent_storage_clsid() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); let uuid = Uuid::from_bytes(*b"ABCDEFGHIJKLMNOP"); comp.set_storage_clsid("/foo", uuid).unwrap(); } #[test] #[should_panic(expected = "Not a storage")] fn set_storage_clsid_for_stream() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_new_stream("/foo").unwrap(); let uuid = Uuid::from_bytes(*b"ABCDEFGHIJKLMNOP"); comp.set_storage_clsid("/foo", uuid).unwrap(); } //===========================================================================// // Tests for state bits: #[test] fn state_bits() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_stream("foo").unwrap(); comp.create_storage("bar").unwrap(); comp.set_state_bits("foo", 0x12345678).unwrap(); comp.set_state_bits("bar", 0x0ABCDEF0).unwrap(); assert_eq!(comp.entry("foo").unwrap().state_bits(), 0x12345678); assert_eq!(comp.entry("bar").unwrap().state_bits(), 0x0ABCDEF0); let cursor = comp.into_inner(); let comp = CompoundFile::open(cursor).expect("open"); assert_eq!(comp.root_entry().state_bits(), 0); assert_eq!(comp.entry("foo").unwrap().state_bits(), 0x12345678); assert_eq!(comp.entry("bar").unwrap().state_bits(), 0x0ABCDEF0); } #[test] #[should_panic(expected = "No such object")] fn set_nonexistent_state_bits() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.set_state_bits("/foo", 0x12345678).unwrap(); } //===========================================================================// // Tests for creating storages: #[test] #[should_panic(expected = "Cannot create storage at \\\"/foobar\\\" \ because a stream already exists there")] fn create_storage_where_stream_exists() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); let data = vec![b'x'; 200]; comp.create_new_stream("/foobar").unwrap().write_all(&data).unwrap(); comp.create_storage("/foobar/").unwrap(); } #[test] #[should_panic(expected = "Cannot create storage at \\\"/foobar\\\" \ because a storage already exists there")] fn create_storage_where_storage_exists() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage("/foobar/").expect("create_storage"); comp.create_storage("/foobar/").unwrap(); } #[test] #[should_panic(expected = "Cannot create storage at \\\"/\\\" because a \ storage already exists there")] fn create_root_storage() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage("/").unwrap(); } #[test] fn create_storages_all() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage_all("/").unwrap(); comp.create_storage_all("/foo/bar").unwrap(); assert_eq!(read_storage_to_vec(&comp, "/"), vec!["foo"]); assert_eq!(read_storage_to_vec(&comp, "/foo"), vec!["bar"]); comp.create_storage_all("/foo").unwrap(); comp.create_storage_all("/foo/bar/baz").unwrap(); assert_eq!(read_storage_to_vec(&comp, "/foo/bar"), vec!["baz"]); assert!(comp.is_storage("/foo/bar/baz")); } #[test] #[should_panic(expected = "Cannot create storage")] fn create_storage_all_with_stream_in_the_way() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage("foo").unwrap(); comp.create_stream("foo/bar").unwrap(); comp.create_storage_all("foo/bar/baz").unwrap(); } //===========================================================================// // Tests for removing storages: #[test] fn remove_storages() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage("/foo").unwrap(); comp.create_storage("/foo/bar").unwrap(); comp.create_storage("/baz").unwrap(); comp.create_storage("/quux").unwrap(); comp.create_storage("/baz/blarg").unwrap(); comp.remove_storage("/foo/bar").unwrap(); comp.remove_storage("/foo").unwrap(); comp.remove_storage("/baz/blarg").unwrap(); let cursor = comp.into_inner(); let comp = CompoundFile::open(cursor).expect("open"); assert_eq!(read_storage_to_vec(&comp, "/"), vec!["baz", "quux"]); assert!(read_storage_to_vec(&comp, "/baz").is_empty()); } #[test] #[should_panic(expected = "No such storage: \\\"/foo\\\"")] fn remove_nonexistent_storage() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.remove_storage("/foo").unwrap(); } #[test] #[should_panic(expected = "Cannot remove the root storage object")] fn remove_root_storage() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.remove_storage("/").unwrap(); } #[test] #[should_panic(expected = "Not a storage: \\\"/foo\\\"")] fn remove_storage_on_stream() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_stream("/foo").unwrap(); comp.remove_storage("/foo").unwrap(); } #[test] #[should_panic(expected = "Storage is not empty: \\\"/foo\\\"")] fn remove_non_empty_storage() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage("/foo").unwrap(); comp.create_storage("/foo/bar").unwrap(); comp.remove_storage("/foo").unwrap(); } #[test] fn remove_storage_all_on_storage() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage("/foo").unwrap(); comp.create_storage("/foo/bar").unwrap(); comp.create_stream("/foo/bar/baz").unwrap(); comp.create_stream("/foo/bar/quux").unwrap(); comp.create_stream("/foo/blarg").unwrap(); comp.create_storage("/stuff").unwrap(); comp.create_stream("/stuff/foo").unwrap(); comp.remove_storage_all("foo").unwrap(); let cursor = comp.into_inner(); let comp = CompoundFile::open(cursor).expect("open"); assert_eq!(read_storage_to_vec(&comp, "/"), vec!["stuff"]); assert_eq!(read_storage_to_vec(&comp, "/stuff"), vec!["foo"]); } #[test] fn remove_storage_all_on_root() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage("/foo").unwrap(); comp.create_stream("/foo/bar").unwrap(); comp.create_storage("/stuff").unwrap(); comp.create_stream("/stuff/foo").unwrap(); comp.remove_storage_all("/").unwrap(); let cursor = comp.into_inner(); let comp = CompoundFile::open(cursor).expect("open"); assert!(read_storage_to_vec(&comp, "/").is_empty()); } //===========================================================================// // Tests for creating streams: #[test] fn create_streams() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_stream("/foo").unwrap().write_all(b"foobar").unwrap(); comp.create_stream("/baz").unwrap().write_all(b"baz!").unwrap(); let cursor = comp.into_inner(); let mut comp = CompoundFile::open(cursor).expect("open"); { let mut stream = comp.open_stream("/foo").unwrap(); let mut data = String::new(); stream.read_to_string(&mut data).unwrap(); assert_eq!(&data, "foobar"); } { let mut stream = comp.open_stream("/baz").unwrap(); let mut data = String::new(); stream.read_to_string(&mut data).unwrap(); assert_eq!(&data, "baz!"); } } #[test] fn create_small_stream() { let data = vec![b'x'; 500]; let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create_with_version(Version::V3, cursor) .expect("create"); comp.create_stream("foobar").unwrap().write_all(&data).unwrap(); let cursor = comp.into_inner(); let mut comp = CompoundFile::open(cursor).expect("open"); let mut stream = comp.open_stream("foobar").unwrap(); let mut actual_data = Vec::new(); stream.read_to_end(&mut actual_data).unwrap(); assert_eq!(actual_data, data); } #[test] fn create_large_stream() { let data = vec![b'x'; 5000]; let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create_with_version(Version::V3, cursor) .expect("create"); comp.create_stream("foobar").unwrap().write_all(&data).unwrap(); let cursor = comp.into_inner(); let mut comp = CompoundFile::open(cursor).expect("open"); let mut stream = comp.open_stream("foobar").unwrap(); let mut actual_data = Vec::new(); stream.read_to_end(&mut actual_data).unwrap(); assert_eq!(actual_data, data); } #[test] fn create_very_large_stream() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create_with_version(Version::V3, cursor) .expect("create"); { let mut stream = comp.create_stream("foobar").unwrap(); let data = vec![b'x'; 1000]; for _ in 0..1000 { stream.write_all(&data).unwrap(); } } let cursor = comp.into_inner(); let mut comp = CompoundFile::open(cursor).expect("open"); let mut stream = comp.open_stream("foobar").unwrap(); assert_eq!(stream.len(), 1_000_000); assert_eq!(stream.seek(SeekFrom::End(0)).unwrap(), 1_000_000); } #[test] fn create_stream_where_stream_exists() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); let data1 = vec![b'x'; 200]; comp.create_stream("/foobar").unwrap().write_all(&data1).unwrap(); let data2 = vec![b'y'; 100]; comp.create_stream("/foobar").unwrap().write_all(&data2).unwrap(); let mut stream = comp.open_stream("/foobar").expect("open"); assert_eq!(stream.len(), data2.len() as u64); let mut actual_data = Vec::new(); stream.read_to_end(&mut actual_data).unwrap(); assert_eq!(actual_data, data2); } #[test] #[should_panic(expected = "Cannot create stream at \\\"/foobar\\\" \ because a storage already exists there")] fn create_stream_where_storage_exists() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage("/foobar/").expect("create_storage"); comp.create_stream("/foobar").unwrap(); } #[test] #[should_panic(expected = "Cannot create new stream at \\\"/foobar\\\" \ because a stream already exists there")] fn create_new_stream_where_stream_exists() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); let data = vec![b'x'; 200]; comp.create_new_stream("/foobar").unwrap().write_all(&data).unwrap(); comp.create_new_stream("/foobar").unwrap(); } #[test] #[should_panic(expected = "Cannot create stream at \\\"/foobar\\\" \ because a storage already exists there")] fn create_new_stream_where_storage_exists() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage("/foobar/").expect("create_storage"); comp.create_new_stream("/foobar").unwrap(); } //===========================================================================// // Tests for removing streams: #[test] fn remove_streams() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_stream("/foo").unwrap().write_all(&vec![b'x'; 5000]).unwrap(); comp.create_storage("/baz").unwrap(); comp.create_storage("/quux").unwrap(); comp.create_stream("/baz/blarg").unwrap(); comp.remove_stream("/foo").unwrap(); comp.remove_stream("/baz/blarg").unwrap(); let cursor = comp.into_inner(); let comp = CompoundFile::open(cursor).expect("open"); assert_eq!(read_storage_to_vec(&comp, "/"), vec!["baz", "quux"]); assert!(read_storage_to_vec(&comp, "/baz").is_empty()); } #[test] fn remove_small_stream() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_stream("/foo").unwrap().write_all(&vec![b'x'; 500]).unwrap(); comp.remove_stream("/foo").unwrap(); } #[test] #[should_panic(expected = "No such stream: \\\"/foo\\\"")] fn remove_nonexistent_stream() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.remove_stream("/foo").unwrap(); } #[test] #[should_panic(expected = "Not a stream: \\\"/foo\\\"")] fn remove_stream_on_storage() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_storage("/foo").unwrap(); comp.remove_stream("/foo").unwrap(); } //===========================================================================// // Tests for navigating within streams: #[test] fn truncate_stream() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); { let mut stream = comp.create_stream("/foobar").unwrap(); stream.write_all(&vec![b'x'; 5000]).unwrap(); stream.write_all(&vec![b'y'; 5000]).unwrap(); assert_eq!(stream.len(), 10000); assert_eq!(stream.seek(SeekFrom::Start(6000)).unwrap(), 6000); stream.set_len(7000).unwrap(); assert_eq!(stream.len(), 7000); assert_eq!(stream.seek(SeekFrom::Current(0)).unwrap(), 6000); stream.set_len(5000).unwrap(); assert_eq!(stream.len(), 5000); stream.write_all(&vec![b'x'; 1000]).unwrap(); assert_eq!(stream.len(), 6000); } let cursor = comp.into_inner(); let mut comp = CompoundFile::open(cursor).expect("open"); let mut stream = comp.open_stream("/foobar").unwrap(); assert_eq!(stream.len(), 6000); let mut actual_data = Vec::new(); stream.read_to_end(&mut actual_data).unwrap(); assert_eq!(actual_data.len(), 6000); assert!(actual_data == vec![b'x'; 6000]); } #[test] fn extend_stream() { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); { let mut stream = comp.create_stream("/foobar").unwrap(); assert_eq!(stream.len(), 0); stream.write_all(&vec![b'x'; 2000]).unwrap(); assert_eq!(stream.len(), 2000); assert_eq!(stream.seek(SeekFrom::Start(1000)).unwrap(), 1000); stream.write_all(&vec![b'y'; 500]).unwrap(); assert_eq!(stream.len(), 2000); assert_eq!(stream.seek(SeekFrom::Current(0)).unwrap(), 1500); stream.set_len(5000).unwrap(); assert_eq!(stream.len(), 5000); assert_eq!(stream.seek(SeekFrom::Current(0)).unwrap(), 1500); stream.write_all(&vec![b'z'; 500]).unwrap(); assert_eq!(stream.len(), 5000); assert_eq!(stream.seek(SeekFrom::Current(0)).unwrap(), 2000); } let cursor = comp.into_inner(); let mut comp = CompoundFile::open(cursor).expect("open"); let mut stream = comp.open_stream("/foobar").unwrap(); assert_eq!(stream.len(), 5000); let mut actual_data = Vec::new(); stream.read_to_end(&mut actual_data).unwrap(); assert_eq!(actual_data.len(), 5000); assert_eq!(&actual_data[0..1000], &[b'x'; 1000] as &[u8]); assert_eq!(&actual_data[1000..1500], &[b'y'; 500] as &[u8]); assert_eq!(&actual_data[1500..2000], &[b'z'; 500] as &[u8]); assert_eq!(&actual_data[2000..5000], &[0u8; 3000] as &[u8]); } #[test] fn stream_seek() { let mut data = Vec::new(); data.append(&mut vec![1; 128]); data.append(&mut vec![2; 128]); data.append(&mut vec![3; 128]); data.append(&mut vec![4; 128]); assert_eq!(data.len(), 512); let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).expect("create"); comp.create_stream("foobar").unwrap().write_all(&data).unwrap(); let mut stream = comp.open_stream("foobar").expect("open"); assert_eq!(stream.len(), 512); let mut buffer = vec![0; 128]; assert_eq!(stream.seek(SeekFrom::Start(128)).unwrap(), 128); stream.read_exact(&mut buffer).unwrap(); assert_eq!(buffer, vec![2; 128]); assert_eq!(stream.seek(SeekFrom::End(-128)).unwrap(), 384); stream.read_exact(&mut buffer).unwrap(); assert_eq!(buffer, vec![4; 128]); assert_eq!(stream.seek(SeekFrom::Current(-256)).unwrap(), 256); stream.read_exact(&mut buffer).unwrap(); assert_eq!(buffer, vec![3; 128]); } //===========================================================================// cfb-0.7.3/tests/infinite_loops_fuzzed/loop_in_alloc000064400000000000000000000334320072674642500207300ustar 00000000000000ࡱ> 2 Root Entry  ϭ-'VBA ϭ- ϭ-ThisWorkbook!Sheet0 !"#$%&'()*+,-./012345679:;<=>@ACDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijlmnopqrsuvwxyz{|}~4By#xME(SLSS<N0{00020819-0000-0000-C000-000000000046}H 0Dr%@884dxAttribute VB_Name = "ThisWorkbook" Bas0{00020P819-0C$0046} |GlobalSpacFalse dCreatablPredeclaIdTru BExposeTemplateDerivBustomizD25By@#xME+(SLSS<N0{00020820-0000-0000-C000-000000000046}H 0%@884dxAttribute VB_Name = "She@et1" Bast0{00020820- C$0046} |Global!SpacFalse dCreatablPre declaIdTru BExposeTemplateDeriv$Bustom izD2Module1 0__SRP_0P_28__SRP_3 ?e_VBA_PROJECT__SRP_3sBtAxME(6 <<P .`(k ,%68pP@. `808@ @ @ @ @p @0X @@ @( 0`X4d$*\Rffff*06641af938*\R0*#17B L 0ip Hello worldA@0P[Attribute VB_Name = "Module1" Function test()  MsgBox ("Hello world"6End ` rU@@@@~x\ggL(XI(X(g\@j,X( a Z2rU@@@8`ba   *\G{000204EF-0000-0000-C000-000000000046}#4.2#9#C:\Program Files\Common Files\Microsoft Shared\VBA\VBA7.1\VBE7.DLL#Visual Basic For Applications*\G{00020813-0000-0000-C000-000000000046}#1.9#0#C:\Program Miles\Microsoft Office\root\Office16\EXCEL.EXE#Microsoft Excel 16.0 Object Library*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\System32\stdole2.tlb#OLE Automation(*\G{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}#2.8#0#C:\Program Files\Common Files\Microsoft Shared\OFFICE16\MSO.DLL#Microsoft Office 16.0 Object Library"4dBThisWorkbook03:41af92c'ThisWorkbooky; Sheet104641af92c+ Sheet1y@;Module106641af938,Module1tA0yP$0046} |GlobalSpacFalse dCreatablPredeclaIdTru BExposeTemplateDerivBustomizD25By@#xME+(SLSS<N0{00020820-0000-0000-C000-000000000046}H 0%@+-600JHH Hd VBAPr@ojectT@  = +r 8d J< 9stdole>stdole h%^*\G{00020430-C 0046}#2.0#0#C:\Windows\System32\e2.tlb#OLE Automation0E OfficEOficEE2DF8D04C-5BFA-101B-BDE5EAAC42Egram Files\CommonMicrosdirk4,P_0t__SRP_1 PROJECTwm oft Shared\OFFICE16\MSO.DLL#M 16.0 Ob Libra,ryK"BThisWorkbookGT isWYrkbo 2 HBj1Bx;B,Q!y"B+BB|Sheet1GS@#e@Xt14H2Ny@Module;dug2Oy#M9tA!C9K*rU@@@~~~~~~~X"tJ/SA    aaProject1 VBAProject ThisWorkbookSheet1Module1FBC:\Program Files\Common Files\Microsoft Shared\VBA\VBA7.1\VBE7.DLLVBA ! QPF 9C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXEExcel  p0FC:\Windows\System32\stdole2.tlbstdole QL-[DR?C:\Program Files\Common Files\Microsoft Shared\OFFICE16\MSO.DLLOffice Atest H㝒o worldVBE7.DLLaS  (~4rU@@@jThisWorkbookThisWorkbookSheet1Sheet1Module1Module1ID="{48AF7E82-3F4B-41E8-B592-916294188031}" Document=ThisWorkbook/&H00000000 les\Common Files\Microsoft Shared\VBA\VBA7.1\VBE7.DLLVBA ! QPF ="1614BA72DE96919791976E" [Host Extendecfb-0.7.3/tests/infinite_loops_fuzzed/loop_in_chain000064400000000000000000000410770072674642500207240ustar 00000000000000ࡱ> rkR C '  Root EntryF@ Ole EPRINTt-CompObje  !"#$%&'()*+, FMicrosoft Graph ChartGBiff5MSGraph.Chart.89q f2B""$"#,##0_);\("$"#,##0\)!"$"#,##0_);[Red]\("$"#,##0\)""$"#,##0.00_);\("$"#,##0.00\)'""$"#,##0.00_);[Red]\("$"#,##0.00\)7*2_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_).))_(* #,lv1 EMFt-X '$F, EMF+@XXF\PEMF+"@ @ $@ 0@?!@ @     !" !" !  Rp"Calibridv% % % % " !%   % " !%   % " !%    '% %     V0#5#75!#'% (   V0#7##77#V07577557&% #  6765#!6765#67r65r#6765#B673653#6765% % % " !% % %    % (    V0:5!#75&% ( V0<##77#V02:77557% % % " !% % %    &% ( 'LL%  LL LL  V0 '% (   V0INNN% ( 'ss%  ss ssV0I "aN&% ( 'L3% (  L3 L3V0'3f% (   3fV0% ( 's&L%  s&L s&LV0&% ( 'f% (  f fV0>R C '% (   V0MRR% ( '%   V0>WR CCRR&% ( 'LL% (  LL LLV0'% (   V0HMM% ( 'ss%  ss ssV0HaM&% ( 'L3% (  L3 L3V0y~'3f% (   3fV0% ( 's&L%  s&L s&LV0y~~&% ( 'f% (  f fV06J ; '% (   V0EJJ% ( '%   V06OJ ;;JJ&% ( 'LL% (  LL LLV0'% (   V0HMMM% ( 'ss%  ss ssV0H`M&% ( 'L3% (  L3 L3V0'3f% (   3fV0% ( 's&L%  s&L s&LV0&% ( 'f% (  f fV0FY K '% (   V0TYY% ( '%   V0F^Y KKYY&% ( 'LL% (  LL LLV0 '% (   V0GLLL% ( 'ss%  ss ssV0G "`L&% ( 'L3% (  L3 L3V0'3f% (   3fV0% ( 's&L%  s&L s&LV0&% ( 'f% (  f fV0Nb S '% (   V0]bb% ( '%   V0Ngb SSbb% ( % (  % #  6##6#!6!#6#6#B6B#6 TT UU@{D@LP0"TX EUU@{D@LP20""TXT UU@{D@TLP4'""TX UU@{D@LP60""TX fUU@{D@LP80""T`v UU@{D@vLT100"""#6!#6##6#"6""6"TxC1UU@{D@CL\1st Qtr".Tx8 1UU@{D@8L\2nd Qtr"$$.Tx=1UU@{D@=L\3rd Qtr"$.Tx>1UU@{D@>L\4th Qtr"$.% % " !% %   % % " !% %   %    +% % % " !% % %   % % % " !% % %   &% ( '%    +    TdwUU@{D@LTEast!! % % % " !% % %   % % % " !% % %   &% ( '3f% (   3f   k    Td/UU@{D@oLTWest=" % % % " !% % %   % % % " !% % %   &% ( '% (     +    TlUU@{D@LXNorth,$$ % % % " !% % %   % % % " !% % %   % ( % ( % " !  " !  ( " F4(EMF+*@$??FEMF+@ ObjIfoWo.8 ##0_);_(* \(#,##0\);_(* "-"_);_(@_)?,:_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)6+1_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)1Calibri1Calibri1Calibri=@ ,##0.00_, f2` J ` J 883ffff̙̙3f3fff3f3f33333f33333\R3&STU1st Qtr2nd Qtr3rd Qtr4th Qtr Eastffffff4@ffffff;@V@ffffff4@ West>@LC@LA@?@North33333F@33333sG@F@33333E@WY`T,= >X4P3d 3Q  EastQQQ3_4E4 3Q  WestQQQ3_4E4 3Q NorthQQQ3_4E4D $% M?3O&Q4$% M?3O&Q4FA 3O 3 b#M!  O43*#M! M!ࡱ> cfb-0.7.3/tests/infinite_loops_fuzzed/loop_in_directory000064400000000000000000000332020072674642500216350ustar 00000000000000ࡱ>  Root Entry  ϭ-'VBA ϭ- ϭ-ThisWorkbookSheet1 !"#$%&'()*+,-./012345679:;<=>@ACDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijlmnopqrsuvwxyz{|}~5By#xME(SLSS<N0{00020819-0000-0000-C000-000000000046}H 0Dr%@884dxAttribute VB_Name = "ThisWorkbook" Bas0{00020P819-0C$0046} |GlobalSpacFalse dCreatablPredeclaIdTru BExposeTemplateDerivBustomizD25By@#xME(SLSS<N0{00020820-0000-0000-C000-000000000046}H 0%@884dxAttribute VB_Name = "She@et1" Bast0{00020820- C$0046} |Global!SpacFalse dCreatablPre declaIdTru BExposeTemplateDeriv$Bustom izD2Module1 __SRP_28__SRP_3 ?_VBA_PROJECTB sBtAxME(6 <<P .`(k ,%88pP@. `808@ @ @ @ @p @0X @@ @( 0`X4d$*\Rffff*06641af938*\R0*#17B L 0ip Hello worldA@0P[Attribute VB_Name = "Module1" Function test()  MsgBox ("Hello world"6End ` rU@@@@~x\ggL(XI(X(g\@j,X( a Z2rU@@@8`ba   *\G{000204EF-0000-0000-C000-000000000046}#4.2#9#C:\Program Files\Common Files\Microsoft Shared\VBA\VBA7.1\VBE7.DLL#Visual Basic For Applications*\G{00020813-0000-0000-C000-000000000046}#1.9#0#C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE#Microsoft Excel 16.0 Object Library*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\System32\stdole2.tlb#OLE Automation(*\G{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}#2.8#0#C:\Program Files\Common Files\Microsoft Shared\OFFICE16\MSO.DLL#Microsoft Office 16.0 Object Library"4dBThisWorkbook03641af92c'ThisWorkbooky; Sheet104641af92c+ Sheet1y@;Module106641af938,Module1tA0yP0_H3K MkzB:c&qAzѦt=zI1Z̪Uc0+ Excel+ VBAWin16~Win32Win64xMacVBA6#VBA7#Project1  stdole` VBAProject Officeu ThisWorkbook| _valuate Sheet1 Module1b(test4MsgBoxRWorkbookk` "$'+-600JHH Hd VBAPr@ojectT@  = +r 4d J< 9stdole>stdole h%^*\G{00020430-C 0046}#2.0#0#C:\Windows\System32\e2.tlb#OLE Automation0E OfficEOficEE2DF8D04C-5BFA-101B-BDE5EAAC42Egram Files\CommonMicrosdirk4__SRP_0t__SRP_1 PROJECTwmVoft Shared\OFFICE16\MSO.DLL#M 16.0 Ob Libra,ryK"BThisWorkbookGT isWYrkbo 2 HBj1Bx;B,Q!y"B+BB|Sheet1GS@#e@Xt14H2Ny@Module;dug2Oy#M9tA!C9K*rU@@@~~~~~~~X"tJ/SA  0  aaProject1 VBAProject ThisWorkbookSheet1Module1FBC:\Program Files\Common Files\Microsoft Shared\VBA\VBA7.1\VBE7.DLLVBA ! QPF 9C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXEExcel  p0FC:\Windows\System32\stdole2.tlbstdole QL-[DR?C:\Program Files\Common Files\Microsoft Shared\OFFICE16\MSO.DLLOffice Atest Hello worldVBE7.DLLaS  (~9rU@@@jThisWorkbookThisWorkbookSheet1Sheet1Module1Module1ID="{48AF7E82-3F4B-41E8-B592-916294188031}" Document=ThisWorkbook/&H00000000 Document=Sheet1/&H00000000 Module=Module1 Name="VBAProject" HelpContextID="0" VersionCompatible32="393222000" CMG="B2B01EEED4F2D4F2D4F2D4F2" DPB="6466C8A0C853C953C953" GCPROJECT ="1614BA72DE96919791976E" [Hoscfb-0.7.3/tests/infinite_loops_fuzzed/loop_in_minialloc000064400000000000000000000340000072674642500215750ustar 00000000000000ࡱ>  Root Entry  ϭ-'VBA ϭ- ϭ-ThisWorkbookSheet1 !"#$%&'()*+,-./012345679:;<=>@ACDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijlmnopqrsuvwxyz{|!Sheet0 !"#$%&'()*+,-./012345679:;<=>@ACDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijlmnopqrsuvwxyz{|}~xAttribute VB_Name = "ThisWorkbook" Bas0{00020P819-0C$0046} |GlobalSpacFalse dCreatablPredeclaIdTru BExposeTemplateDerivBustomizD25By@#xMEࡱ>  ry  ϭ-'VAA$7\xAttribute VB_Name = "She@et1" Bast0{00020820- C$0046} |Global!SpacFalse dCreatablPre declaIdTru BExposeTemplateDeriv$Bustom izD2Mocule1 __SRP_28__SRP_2 ?_VBA_PROJECTࡱ> ip Hello worldA@0P[ilrosoft hared\OFFICE16\MSO.DLL#Microsoftx4dBThisWorkbook03641af92c'ThisWorkbooky; Sheet104641af92cpP@. `808@ @@ @ @p @0X @@ @( 0`X4d$*\Rffff*06641af938*\R0*#17B L 0ip Hello worldA@0P[Attribute VB_Name = "Module1" Function test()  MsgBox ("Hello world"6End ` rU@@@@~x\g__SRP_1(XI(X(g\@j,X( a Z2rU@@@8`ba   *\G{000204EF-0000-0000-C000-000000000046}#4.2#9#C:\Program Files\Common Files\Microsoft Shared\VBA\VBA7.1\VBE7.DLL#Visual Basic For Applications*\G{50020813-0000-0000-C000-000000000046}#1.9#0#C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE#Microsoft Excel 16.0 Object Library*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\System32\stdole2.tlb#OLE Automation(*\G{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}#2.8#0#C:\Program Files\Common Files\Microsoft Shared\OFFICE16\MSO.DLL#Microsoft Office 16.0 Object Library"4dBThisWorkbook03641af92c'ThisWorkbooky; Sheet104641af92c+ Sheet1y@;Module106641af938,Module1tA0yP0_H3K MkzB:c&qAzѦt=zI1Z̪Uc0+ Excel+ VBAWin16~Win32Win64xMacVBA6#VBA7#Project1  stdole` VBAProject Officeu ThisWorkbook| _Evaluate Sheet1 Module1b(test4MsgBoxRWorkbookk` "$'+-600JHH Hd VBAPr@ojectT@  = +r 4d J< 9stdole>stdole h%^*\G{00020430-C 0046}#2.0#0#C:\Windows\System32\e2.tlb#OLE Automation0E OfficEOficEE2DF8D04C-5BFA-101B-BDE5EAAC42Egram Files\CommonMicrosdirk4__SRP_0t__SRP_1 PROJECTwmVoft Shared\OFFICE16\MSO.DLL#M 16.0 Ob Libra,ryK"BThisWorkbookGT isWYrkbo 2 HBj1Bx;B,Q!y"B+BB|Sheet1GS@#e@Xt14H2Ny@Module;dug1Oy#M9tA!C9K*rU@@@~~~~~~~X"tJ/SA    aaProject1 VBAProject ThisWorkbookSheet1Module1FBC:\Program Files\Common Files\Microsoft Shared\VBA\VBA7.1\VBE7.DLLVBA ! QPF 9C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXEExcel  p0FC:\Windows\System32\stdole2.tlbstdole QL-[DR?C:\Program Files\Common Files\Microsoft Shared\OFFICE16\MSO.DLLOffice Atest Hello worldVBE7.DLLaS  (~4rU@@@jThisWorkbookThisWorkbookSheet1Sheet1Module1Module1ID="{48AF7E82-3F4B-41E8-B592-916294188031}" Document=ThisWorkbook/&H00000000 Document=Sheet1/&H00000000 Module=Module1 Name="VBAProject" HelpContextID="0" VersionCompatible32="393222000" CMG="B2B01EEED4F2D4F2D4F2D4F2" DPB="6466C8A0C853C953C953" GCPROJECT ="1614BA72DE96919791976E" [Host Extender Info] &H00000001={3832D640-CF90-11CF-8E43-00A0C911005A};VBE;&H00000000 [Workspace] ThisWorkbook=0, 0, 0, 0, C Sheet1=0, 0, 0, 0, C Module1=32, 32, 1305, 574, cfb-0.7.3/tests/infinite_loops_fuzzed/loop_in_minichain000064400000000000000000000332500072674642500215730ustar 00000000000000ࡱ>  Root Entry  ϭ-'VBA ϭ- ϭ-ThisWorkbookSheet1 !"#$%&'()*+,-./012345679:;<=>@ACDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijlmnopqrsuvwxyz{|}~5By#xME(SLSS<N0{00020819-0000-0000-C000-000000000046}H 0Dr%@884dxAttribute VB_Name = "ThisWorkbook" Bas0{00020P819-0C$0046} |GlobalSpacFalse dCreatablPredeclaIdTru BExposeTemplateDerivBustomizD25By@#xMEࡱ>  ry  ϭ-'VAA$7\xAttribute VB_Name = "She@et1" Bast0{00020820- C$0046} |Global!SpacFalse dCreatablPre declaIdTru BExposeTemplateDeriv$Bustom izD2Mocule1 __SRP_28__SRP_2 ?_VBA_PROJECTB -0000-C000-000000000046}#2.0#0#C:\Windows\System32\stdole2.tlb#OLE Automation(*\G{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}#2.8#0#C:\Program Files\Common Files\Microsoft Shared\OFFICE16\MSO.DLL#Microsoft Office 16.0 Object Library"4dBThisWorkbook03641af92c'ThisWorkbooky; Sheet104641af92cpP@. `808@ @@ @ @p @0X @@ @( 0`X4d$*\Rffff*06641af938*\R0*#17B L 0ip Hello worldA@0P[Attribute VB_Name = "Module1" Function test()  MsgBox ("Hello world"6End ` 0_H3K MkzB:c&qAzѦt=zI1Z̪Uc0+ Excel+ VBAWin16~Win32Win64xMacVBA6#VBA7#Project1  stdole` VBAProject Officeu ThisWorkbook| _Evaluate Sheet1 Module1b(test4MsgBoxRWorkbookk` "$'+-600JHH Hd VBAPr@ojectT@  = +r 4d J< 9stdole>stdole h%^*\G{00020430-C 0046}#2.0#0#C:\Windows\System32\e2.tlb#OLE Automation0E OfficEOficEE2DF8D04C-5BFA-101B-BDE5EAAC42Egram Files\CommonMicrosdirk4__SRP_0ࡱ> ࡱ#Root Entry ࡱ> P0_H3K MkzB:c&qAzѦt=zI1Z̪Uc0+ Excel+ VBAWin16~Win32Win64xMacVBA6#VBA7#Project1  stdole` VBAProject Officeu ThisWorkbook| _Evaluate Sheet1 Module1b(test4MsgBoxRWorkbookk` "$'+-600JHH Hd VBAPr@ojectT@  = +r 4d J< 9stdole>stdole h%^*\G{00020430-C 0046}#2.0#0#C:\Windows\System32\e2.tlb#OLE Automation0E OfficEOficEE2DF8D04C-5BFA-101B-BDE5EAAC42Egram Files\CommonMicrosdirk4__SRP_0t__SRP_1 PROJECTwmVoft Shared\OFFICE16\MSO.DLL#M 16.0 Ob Libra,ryK"BThisWorkbookGT isWYrkbo 2 HBj1Bx;B,Q!y"B+BB|Sheet1GS@#e@Xt14H2Ny@Module;dug2Oy#M9tA!C9K*rU@@@~~~~~~~X"tJ/SA    aaProject1 NBAProject ThisWorkbookSheet1Module1FBC:\Program Files\Common Files\Microsoft Shared\VBA\VBA7.1\VBE7.DLLVBA ! QPF 9C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXEExcel  p0FC:\Windows\System32\stdole2.tlbstdole QL-[DR?C:\Program Files\Common Files\Microsoft Shared\OFFICE16\MSO.DLLOffice Atest Hello worldVBE7.DLLaS  (~4rU@@@jThisWorkbookThisWorkbookSheet1Sheet1Module1Module1ID="{48AF7E82-3F4B-41E8-B592-916294188031}" Document=ThisWorkbook/&H00000000 Document=Sheet1/&H00000000 Module=Module1 Name="VBAProject" HelpContextID="0" VersionCompatible32="393222000" CMG="B2B01EEED4F2D4F2D4F2D4F2" DPB="6466C8A0C853C953C953" GCPROJECT cfb-0.7.3/tests/infinite_loops_fuzzed/loop_in_open_1000064400000000000000000000020310072674642500210060ustar 00000000000000ࡱ>  > (ry  ϭ-'VAA ϭ- ϭ-ThisWorkbook ϭ- ϭ-ThisWorkbook  ϭ-'VAAcfb-0.7.3/tests/infinite_loops_fuzzed/loop_in_open_2000064400000000000000000000330060072674642500210150ustar 00000000000000ࡱ>  Root Entry  ϭ-'VBA ϭ- ϭ-ThisWorkbookSheet1[ !"#$%&'()*+,-./012345679:;<=>@ACDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijlmnopqrsuvwxyz{|}~5By#xME(SLSS<N0{00020819-0000-0000-C000-000000000046}H 0Dr%@884dxAttribute VB_Name = "ThisWorkbook" Bas0{00020P819-0C$0046} |GlobalSpacFalse dCretaablPredeclaIdTru BExposeTemplateDerivBustomizD25By@#xME84dxAttribute VB_Name = "She@et1" Bast0{00020820- C$0046} |Global!SpacFalse dCreatablPre declaIdTru BExposeTemplateDeriv$Bustom izD2Module1 __SRP_28__SRP_3 ?_VBA_PROJECTB sBtAxME(6 <<P .`(k ,%8:8pP@. `858@ @ @ࡱ>  @ @p @0X @@ @( 0ࡱ> #C:\Program Files\Common Files\Microsoft Shared\VBA\VBA7.1\VBE7.DLL#Visual Basic For Applians*\G{00020813-0000-0000-C000-007.DLVL#isual BasiC:\Pr]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\ggL(XI(X(g\@j,X( a Z2rU@@@8`ba   *\G{000204EF-0000-0000-C000-000000000046}#4.2#9#C:\Program Files\Common Files\Microsoft Shared\VBA\VBA7.1\VBE7.DLL#Visual Basic For Applications*\G{00020813-0000-0000-C000-000000000046}#1.9#0#C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE#Microsoft Excel 16.0 Object Library*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\System32\stdole2.tlb#OLE Automation(*\G{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}#2.8#0#C:\Program Files\Common Files\Microsoft Shared\OFFICE16\MSO.DLL#Microsoft Office 16.0 Object Library"4dBThisWorkbook03641af92c'This#####################Workbooky; Sheet104641af92c+ Sheet1y@㝒Module106641af938,Module1tA0yP0_H3K MkzB:c&qAzѦt=zI1Z̪Uc0+ Excel+ VBAWin16~Win32Win64xMacVBA6#VBA7#Project1  stdole` VBAProject Officeu ThisWorkbook| _Evaluate Sheet1 Module1b(test4MsgBoxRWorkbookk` "$'+-600JHH Hd VBAPr@ojectT@  = +r 4d J< 9stdole>stdole h%^*\G{00020430-C 0046}#2.0#0#C:\Windows\System32\e2.tlb#OLE Automation0E OfficEOficEE2DF8D04C-5BFA-101B-BDE5EAAC42Egram Files\CommonMicrosdirk4__SRP_0t__SRP_1 PROJECTwmVoft Shared\OFFICE16\MSO.DLL#M 16.0 Ob Libra,ryK"BThisWorkbookGT isWYrkbo 2 HBj1Bx;B,Q!y"B+BB|Sheet1GS@#e@Xt14H2Ny@Module;dug2Oy#M9tA!C9K*rU@@@~~~~~~~X"tJ/SA    aaProject1 VBAProject ThisWorkbookSheet1Module1FBC:\Program Files\Common Files\Microsoft Shared\VBA\VBA7.1\VBE7.DLLVBA ! QPF 9C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXEExcel  p0FC:\Windows\System32\stdole2.tlbstdole QL-[DR?C:\Program Files\Common Files\Microsoft Shared\OFFICE16\MSO.DLLOffice Atest Hello worldVBE7.DLLaS  (~4rU@@@jThisWorkbookThisWorkbookSheet1Sheet1Module1Module1ID="{48AF7E82-3F4B-41E8-B592-916294188031}" Document=ThisWorkbook/&H00000000 Doc###############################################################ument=Sheet1/&H00000000 ###############################+############ࡳ# Module=Module1 Name="VBAProject" HelpContextID="0" VersionCompatible32="393222000" CMG="B2B01EEED4F2D4F2D4F2D4F2" DPB="6466C8A0C853C953C953" GCPROJECT cfb-0.7.3/tests/large.rs000064400000000000000000000026240072674642500132230ustar 00000000000000use cfb::CompoundFile; use rand::prelude::{Rng, SeedableRng}; use rand_pcg::Pcg32; use std::io::{Cursor, Seek, SeekFrom, Write}; use std::path::PathBuf; /// Regression test for https://github.com/mdsteele/rust-cfb/issues/12. #[test] fn large_file_issue_12() { // Use a reproducible PRNG sequence. let mut rng = Pcg32::from_seed(*b"1941039482934820"); let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).unwrap(); for _ in 0..50 { // Generate a random path. let mut path = PathBuf::new(); for _ in 0..6 { path.push(format!("{:08x}", rng.gen::())); } comp.create_storage_all(&path).unwrap(); path.push("file"); // Create a stream with a random amount of data, chosen such that // sometimes the length is below the mini-stream cutoff of 4096, and // sometimes it's above. let length: usize = rng.gen_range(4000..4200); let data = vec![0x11; length]; comp.create_stream(&path).unwrap().write_all(&data).unwrap(); // Open the same stream again, and append a bit more data to the end. let mut stream = comp.open_stream(&path).unwrap(); stream.seek(SeekFrom::End(0)).unwrap(); stream.write_all(b"additional test data").unwrap(); } let cursor = comp.into_inner(); let _comp = CompoundFile::open(cursor).expect("re-open"); } cfb-0.7.3/tests/malformed.rs000064400000000000000000000271530072674642500141030ustar 00000000000000use byteorder::{LittleEndian, WriteBytesExt}; use cfb::CompoundFile; use std::{ fs::read_dir, io::{Cursor, Read, Seek, SeekFrom, Write}, path::Path, sync::mpsc, thread, time::Duration, }; // Run function on another thread, panic if it takes too long fn panic_after(d: Duration, f: F) -> T where T: Send + 'static, F: FnOnce() -> T, F: Send + 'static, { let (done_tx, done_rx) = mpsc::channel(); let handle = thread::spawn(move || { let val = f(); done_tx.send(()).expect("Unable to send completion signal"); val }); match done_rx.recv_timeout(d) { Ok(_) => handle.join().expect("Thread panicked"), Err(_) => panic!("Thread took too long"), } } // Checks to see if a file can be walked over and read properly, or fail if it can not be read fn can_read(path: &Path) { let data = std::fs::read(&path).unwrap(); let cursor = Cursor::new(data); let mut cfb = match CompoundFile::open(cursor) { Ok(cfb) => cfb, Err(_) => return, }; #[allow(clippy::needless_collect)] let stream_paths = cfb .walk() .filter(|e| e.is_stream()) .map(|e| e.path().to_path_buf()) .collect::>(); let _unused = stream_paths .into_iter() .map(|s| -> Result, std::io::Error> { let mut data = Vec::new(); cfb.open_stream(&s)?.read_to_end(&mut data)?; Ok(data) }) .collect::>(); } /// Regression test for https://github.com/mdsteele/rust-cfb/issues/16. #[test] #[should_panic( expected = "Found reference to mini sector 123456789, but MiniFAT has \ only 2 entries" )] fn invalid_mini_sector_issue_16() { // Create a CFB file with a mini stream. let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).unwrap(); let version = comp.version(); comp.create_stream("foo").unwrap().write_all(&[0u8; 80]).unwrap(); let mut cursor = comp.into_inner(); // Corrupt the starting mini sector ID of the stream. Due to how we // constructed the CFB file, this will be at byte 116 of the second // 128-byte directory entry in the third sector of the CFB file. let offset = 116 + 128 * 1 + (version.sector_len() as u64) * 2; cursor.seek(SeekFrom::Start(offset)).unwrap(); cursor.write_u32::(123456789).unwrap(); // Re-open the CFB file and try to read the mini stream. let mut comp = CompoundFile::open(cursor).unwrap(); let mut data = Vec::new(); comp.open_stream("foo").unwrap().read_to_end(&mut data).unwrap(); } /// Regression test for https://github.com/mdsteele/rust-cfb/issues/17. #[test] #[should_panic(expected = "DIFAT chain includes duplicate sector index 0")] fn infinite_loop_difat_issue_17() { let data = vec![ 208, 207, 17, 224, 161, 177, 26, 225, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 62, 0, 3, 0, 254, 255, 9, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 255, 255, 255, 255, 255, 255, 255, 27, 80, 255, 255, 253, 0, 0, 0, 254, 255, 255, 255, 28, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 11, 0, 0, 0, 12, 0, 0, 0, 13, 0, 0, 0, 14, 0, 0, 0, 15, 208, 207, 17, 224, 161, 177, 26, 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 3, 0, 254, 255, 9, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 254, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 16, 0, 0, 0, 17, 0, 0, 0, 18, 0, 0, 0, 19, 0, 0, 0, 20, 0, 1, 0, 0, 0, 0, 0, 9, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253, 255, 255, 255, 27, 0, 0, 0, 254, 255, 255, 255, 28, 0, 0, 0, 5, 0, 0, 0, 0, 255, 27, 0, 0, 0, 254, 255, 255, 255, 28, 0, 0, 0, 5, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 62, 0, 3, 0, 254, 255, 9, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 254, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253, 255, 255, 255, 27, 0, 0, 0, 254, 255, 255, 255, 28, 0, 0, 0, 5, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253, 255, 255, 255, 27, 0, 0, 0, 254, 255, 255, 255, 28, 0, 0, 0, 5, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, ]; CompoundFile::open(Cursor::new(data)).unwrap(); } #[test] #[should_panic( expected = "Invalid CFB file (length of 512 < sector length of 4096)" )] fn sector_panic_pr_24() { let data = vec![ 208, 207, 17, 224, 161, 177, 26, 225, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 254, 255, 12, 0, 6, 0, 0, 0, 0, 0, 255, 255, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 16, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 5, 5, 5, 5, 59, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 176, 5, 5, 5, 5, 5, 5, 208, 207, 17, 224, 255, 177, 26, 225, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 176, 5, 5, 5, 5, 5, 5, 208, 207, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 229, 229, 229, 229, 229, 229, 231, 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, 0, 229, 255, 255, 255, 255, 255, 255, 223, 255, 5, 5, 5, 5, 5, 5, 15, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 176, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 13, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 255, 255, 255, 255, 255, 17, 224, 255, 255, 255, 255, 255, 255, 255, 255, 177, 26, 225, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 55, 50, 49, 51, 86, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 207, 207, 207, 207, 12, 0, 0, 0, 0, 0, 0, 0, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 5, 5, 5, 5, 5, 5, 5, 5, 12, 0, 6, 0, 0, 0, 0, 0, 255, 255, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 255, 255, 255, 255, 255, 17, 224, 255, 255, 255, 255, 255, 255, 255, 255, 177, 26, 225, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 8, 0, ]; CompoundFile::open(Cursor::new(data)).unwrap(); } #[test] #[should_panic(expected = "next_id (4294967293) is invalid")] fn alloc_panic_pr_24() { let mut cfb = cfb::open("tests/panics_fuzzed/alloc_panic").unwrap(); cfb.walk() .filter(|e| e.is_stream()) .map(|e| e.path().to_path_buf()) .collect::>() .into_iter() .map(|s| { let mut data = Vec::new(); cfb.open_stream(&s).unwrap().read_to_end(&mut data).unwrap(); Ok((s, data)) }) .collect::, std::io::Error>>() .unwrap(); } #[test] #[should_panic(expected = "next_id (4294967295) is invalid")] fn minialloc_panic_pr_24() { let mut cfb = cfb::open("tests/panics_fuzzed/minialloc_panic").unwrap(); cfb.walk() .filter(|e| e.is_stream()) .map(|e| e.path().to_path_buf()) .collect::>() .into_iter() .map(|s| { let mut data = Vec::new(); cfb.open_stream(&s).unwrap().read_to_end(&mut data).unwrap(); Ok((s, data)) }) .collect::, std::io::Error>>() .unwrap(); } #[test] fn check_for_infinite_loops() { // Loop through the provided files for entry in read_dir("tests/infinite_loops_fuzzed").unwrap() { let entry = entry.unwrap(); let path = entry.path(); // Test will panic if it takes more than 1 second to load the file ie. stuck in a loop panic_after(Duration::from_secs(1), move || can_read(&path)) } } cfb-0.7.3/tests/panics_fuzzed/alloc_panic000064400000000000000000000410400072674642500166110ustar 00000000000000ࡱ>   Root EntryF@ Ole EPRINTt-CompObje  !"#$%&'()*+, FMicrosoft Graph ChartGBiff5MSGraph.Chart.89q f2B""$"#,##0_);\("$"#,##0\)!"$"#,##0_);[Red]\("$"#,##0\)""$"#,##0.00_);\("$"#,##0.00\)'""$"#,##0.00_);[Red]\("$"#,##0.00\)7*2_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_).))_(* #,lv1 EMFt-X '$F, EMF+@XXF\PEMF+"@ @ $@ 0@?!@ @     !" !" !  Rp"Calibridv% % % % " !%   % " !%   % " !%    '% %     V0#5#75!#'% (   V0#7##77#V07577557&% #  6765#!6765#67r65r#6765#B673653#6765% % % " !% % %    % (    V0:5!#75&% ( V0<##77#V02:77557% % % " !% % %    &% ( 'LL%  LL LL  V0 '% (   V0INNN% ( 'ss%  ss ssV0I "aN&% ( 'L3% (  L3 L3V0'3f% (   3fV0% ( 's&L%  s&L s&LV0&% ( 'f% (  f fV0>R C '% (   V0MRR% ( '%   V0>WR CCRR&% ( 'LL% (  LL LLV0'% (   V0HMM% ( 'ss%  ss ssV0HaM&% ( 'L3% (  L3 L3V0y~'3f% (   3fV0% ( 's&L%  s&L s&LV0y~~&% ( 'f% (  f fV06J ; '% (   V0EJJ% ( '%   V06OJ xC1% ( 'LL% (  LL LLV0'% (   V0HMMM% ( 'ss%  ss ssV0H`M&% ( 'L3% (  L3 L3V0'3f% (   3fV0% ( 's&L%  s&L s&LV0&% ( 'f% (  f fV0FY K '% (   V0TYY% ( '%   V0F^Y KKYY&% ( 'LL% (  LL LLV0 '% (   V0GLLL% ( 'ss%  ss ssV0G "`L&% ( 'L3% (  L3 L3V0'3f% (   3fV0% ( 's&L%  s&L s&LV0&% ( 'f% (  f fV0Nb S '% (   V0]bb% ( '%   V0Ngb SSbb% ( % (  % #  6##6#!6!#6#6#B6B#6 TT UU@{D@LP0"TX EUU@{D@LP20""TXT UU@{D@TLP40""TX UU@{D@LP60""TX fUU@{D@LP80""T`v UU@{D@vLT100"""#6!#6##6#"6""6"TxC1UU@{D@CL\1st Qtr".Tx8 1UU@{D@8L\2nd Qtr"$$.Tx=1UU@{D@=L\3rd Qtr"$.Tx>1UU@{D@>L\4th Qtr"$.% % " !% %   % % " !% %   %    +% % % " !% % %   % % % " !% % %   &% ( '%    +    TdwUU@{D@LTEast!! % % % " !% % %   % % % " !% % %   &% ( '3f% (   3f   k    Td/UU@{D@oLTWest=" % % % " !% % %   % % % " !% % %   &% ( '% (     +    TlUU@{D@LXNorth,$$ % % % " !% % %   % % % " !% % %   % ( % ( % " !  " !  ( " F4(EMF+*@$??FEMF+@ ObjIfoWR C '.8 ##0_);_(* \(#,##0\);_(* "-"_);_(@_)?,:_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)6+1_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)1Calibri1Calibri1Calibri=@ ,##0.00_, f2` J ` J 883ffff̙ࡱ>   Root EntryF@ Ole EPRINTVR CCRR&% e cfb-0.7.3/tests/panics_fuzzed/minialloc_panic000064400000000000000000000410150072674642500174700ustar 00000000000000ࡱ>   Root EntryF@ OleEPRINTt-Co% bje  !"#$%&'()*+, FMicrosoft Graph ChartGBiff5MSGraph.Chart.89q f2B""$"#,##0_);\("$"#,##0\)!"$"#,##0);[Red]\("$"#,##0\)""$"#,##0.00_);\("$"#,##0.00\)'""$"#,##0.00_);[Red]\("$"#,##0.00\)7*2_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_).))_(* #,lv1 EMFt-X '$F, EMF+@XXF\PEMF+"@ @ $@ 0@?!@ @     !" !" !  Rp"Calibridv% % % % " !%   % " !%   % " !%    '% %     V0#5#75!#'% (   V0#7##77#V07577557&% #  6765#!6765#67r65r#6765#B673653#6765% % % " !% % %    % (    V0:5!#75&% ( V0<##77#V02:77557% % % " !% % %    &% ( 'LL%  LL LL  V0 '% (   V0INNN% ( 'ss%  ss ssV0I "/aN&V0HML3% (  L3 L3V0:'3f% (   3fV0% ( 's&L%  s&L s&LV0&% ( 'f% (  f fV0>R C '% (  3 V0MRR% ( '%   V0>WkR CCRR&% ( 'LL% (  LL LLV0'% (   V0HMMM% ( 'ss%  ss ssV0HaM&% ( 'L3% (  L3 L3V0y~'3f% (   3fV0% ( 's&L%  s&L s&LV0y~~&% ( 'f% (  f fV06J ; '% (   V0EJJ% ( '%   V06OJ ;;JJ&% ( 'LL% (  LL LLV0'% (   % ( 'MM% ( 'ss%  ss ssV0H`M&% ( 'L3% (  L3 L3V0'3f% (   3fV0% ( 's&L%  s&L s&LV0&% ( 'f% (  f fV0FY K '% (   V0TYY% ( '%   V0F^Y KKYY&% (LL% (  LL LLV0 '% (   V0GLLL% ( 'ss%  es ssV0G"`L&% ( 'L3% (  L3 L3V0'3f% (   3fV0% ( 's&L%  s&L s&LV0&% ( 'f% (  f fV0Nb S '% (   V0]bb% ( '%   V0Ngb SSbb% ( % ( &% #  6##6#!6!#6#6#B6B#6 TT UU@{D@LP0"TX EUU@{D@LP20""TXT UU@{D@TLP40""TX UU@{D@LP60""TX fUU@{D@LP80""T`v UU@{D@vLT100"""#6!#6##6#"6""6"TxC1UU@{D@CL\1st Qtr".Tx8 1UU@{D@8L\2nd Qtr"$$.Tx=1UU@{D@=L\3rd Qtr"$.Tx>1UU@{D@>L\4th Qtr"$.% % " !% %   % % " !% %   %    +% % % " !% % %   % % % " !% % %   &% ( '%    +    TdwUU@{D@LTEast!! % % % " !% % %   % % % " !% % %   &% ( '3f% (   3f  +Bk    Td/UU@{D@oLTWest=" % % % " !% % %   % z% % " !% % %   &% ( '% (     +    TlUU@{D@LXNorth,$$ % % % " !% % %   % % % " !% % %   % ( % ( % " !  " !  ( " F4(EMF+*@$??FEMF+@  'ObjInfoWorkbook8 ##0_);_(* \(#,##0\);_(* "-"_);_(@_)?,:_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)6+1_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)1Calibri1Calibri1Calibri=@ ,##0.00_, f2` J ` J 883ffff ( fff3f3f33333f33333\R3&STU1st Qtr2nd Qtr3rd Qtr4th Qtr Eastffffff4@ffffff;@V@ffffff4@ West>@LC@LA@?@North3333333sG@F@33333E@WY`T,= >X4P3d 3Q  EastQQQ3_4E4 3Q  WestQQQ3_4E4 3Q NorthQQQ3_4E4D $% M?3O&Q4$% M?3O&Q4FA 3O 3 b#M!  O43*#M! M! M cfb-0.7.3/tests/set_len.rs000064400000000000000000000050600072674642500135570ustar 00000000000000use cfb::{CompoundFile, Version}; use std::io::{Cursor, Read, Write}; //===========================================================================// fn create_data(len: usize) -> Vec { let mut data = Vec::::new(); let mut number = 0u32; while data.len() < len { data.extend_from_slice(number.to_string().as_bytes()); number += 1; } data.truncate(len); data } fn test_set_stream_len(initial_len: usize, resize_len: usize) { let data = create_data(initial_len); let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create_with_version(Version::V3, cursor) .expect("create"); { let mut stream = comp.create_stream("/foobar").unwrap(); stream.write_all(&data).unwrap(); } let cursor = comp.into_inner(); let mut comp = CompoundFile::open(cursor).expect("open"); { let mut stream = comp.open_stream("/foobar").unwrap(); assert_eq!(stream.len(), initial_len as u64); stream.set_len(resize_len as u64).unwrap(); } let cursor = comp.into_inner(); let mut comp = CompoundFile::open(cursor).expect("open"); { let mut stream = comp.open_stream("/foobar").unwrap(); assert_eq!(stream.len(), resize_len as u64); let mut actual_data = Vec::new(); stream.read_to_end(&mut actual_data).unwrap(); assert_eq!(actual_data.len(), resize_len); if resize_len <= initial_len { assert_eq!(actual_data, data[..resize_len]); } else { assert_eq!(actual_data[..initial_len], data); assert_eq!( actual_data[initial_len..], vec![0u8; resize_len - initial_len] ); } } } //===========================================================================// #[test] fn resize_zero_to_zero() { test_set_stream_len(0, 0); } #[test] fn resize_small_to_zero() { test_set_stream_len(1000, 0); } #[test] fn resize_large_to_zero() { test_set_stream_len(5000, 0); } #[test] fn resize_small_to_slightly_smaller() { test_set_stream_len(1000, 900); } #[test] fn resize_small_to_slightly_bigger() { test_set_stream_len(1000, 1100); } #[test] fn resize_small_to_large() { test_set_stream_len(1000, 5000); } #[test] fn resize_large_to_small() { test_set_stream_len(5000, 1000); } #[test] fn resize_large_to_huge() { test_set_stream_len(5000, 10000); } #[test] fn resize_huge_to_large() { test_set_stream_len(10000, 5000); } //===========================================================================//