cfb-0.10.0/.cargo_vcs_info.json0000644000000001360000000000100116440ustar { "git": { "sha1": "6594a4732c1b7b51cc076353b4888c3beb864597" }, "path_in_vcs": "" }cfb-0.10.0/.github/workflows/tests.yml000064400000000000000000000017151046102023000157220ustar 00000000000000name: tests on: push: paths-ignore: - 'LICENSE-*' - '**.md' pull_request: paths-ignore: - 'LICENSE-*' - '**.md' jobs: tests: strategy: matrix: os: [ ubuntu-latest, windows-latest, macos-latest] rust: [ stable ] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Install rust toolchain uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - name: Test run: cargo test --verbose linters: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: submodules: true - name: Install rust toolchain uses: dtolnay/rust-toolchain@master with: toolchain: stable components: rustfmt, clippy - name: Cargo fmt run: cargo fmt --all -- --check - name: Cargo clippy run: cargo clippy --all-features -- -D warnings cfb-0.10.0/.gitignore000064400000000000000000000000401046102023000124160ustar 00000000000000*.rs.bk *~ /Cargo.lock /target/ cfb-0.10.0/Cargo.lock0000644000000237460000000000100076330ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "anstream" version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", "windows-sys", ] [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfb" version = "0.10.0" 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 = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] [[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.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06fddc2749e0528d2813f95e050e87e52c8cbbae56223b9babf73b3e53b0cc6" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[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 = "serde" version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "time" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "num-conv", "powerfmt", "serde", "time-core", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" cfb-0.10.0/Cargo.toml0000644000000021220000000000100076370ustar # 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.10.0" authors = ["Matthew D. Steele "] description = "Read/write Compound File Binary (structured storage) files" 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" [dependencies.uuid] version = "1" [dev-dependencies.clap] version = "4.4" features = ["derive"] [dev-dependencies.rand] version = "0.8" [dev-dependencies.rand_pcg] version = "0.3" [dev-dependencies.time] version = "0.3" cfb-0.10.0/Cargo.toml.orig000064400000000000000000000007531046102023000133300ustar 00000000000000[package] name = "cfb" version = "0.10.0" authors = ["Matthew D. Steele "] description = "Read/write Compound File Binary (structured storage) files" repository = "https://github.com/mdsteele/rust-cfb" keywords = ["cfb", "storage", "structured"] license = "MIT" readme = "README.md" edition = "2018" [dependencies] byteorder = "1" fnv = "1.0" uuid = "1" [dev-dependencies] clap = { version = "4.4", features = ["derive"] } rand = "0.8" rand_pcg = "0.3" time = "0.3" cfb-0.10.0/LICENSE000064400000000000000000000020621046102023000114410ustar 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.10.0/README.md000064400000000000000000000012431046102023000117130ustar 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. ## License rust-cfb is made available under the [MIT License](http://spdx.org/licenses/MIT.html). cfb-0.10.0/examples/cfbtool.rs000064400000000000000000000067261046102023000142630ustar 00000000000000use std::io; use std::path::PathBuf; use clap::{Parser, Subcommand}; use time::OffsetDateTime; use uuid::Uuid; #[derive(Parser, Debug)] #[clap(author, about, long_about = None)] struct Cli { #[clap(subcommand)] command: Command, } #[derive(Subcommand, Debug)] enum Command { /// Concatenates and prints streams Cat { path: Vec }, /// Changes storage CLSIDs Chcls { clsid: Uuid, path: Vec }, /// Lists storage contents Ls { #[clap(short, long)] /// Lists in long format long: bool, #[clap(short, long)] /// Includes . in output all: bool, path: Vec, }, } 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 cli = Cli::parse(); match cli.command { Command::Cat { path } => { for path in path { 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(); } } Command::Chcls { clsid, path } => { for path in path { 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(); } } Command::Ls { long, all, path } => { for path in path { 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 all { list_entry(".", &entry, long); } for subentry in comp.read_storage(&inner_path).unwrap() { list_entry(subentry.name(), &subentry, long); } } } } } } cfb-0.10.0/rustfmt.toml000064400000000000000000000001431046102023000130330ustar 00000000000000max_width = 79 newline_style = "Unix" use_field_init_shorthand = true use_small_heuristics = "Max" cfb-0.10.0/src/internal/alloc.rs000064400000000000000000000455041046102023000145070ustar 00000000000000use crate::internal::{ consts, Chain, Sector, SectorInit, Sectors, Validation, 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, validation: Validation, ) -> io::Result> { let mut alloc = Allocator { sectors, difat_sector_ids, difat, fat }; alloc.validate(validation)?; Ok(alloc) } pub fn version(&self) -> Version { self.sectors.version() } pub fn inner(&self) -> &F { self.sectors.inner() } 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(&mut self, validation: Validation) -> 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() { let difat_sector_index = difat_sector as usize; if difat_sector_index >= self.fat.len() { malformed!( "FAT has {} entries, but DIFAT lists {} as a DIFAT sector", self.fat.len(), difat_sector ); } if self.fat[difat_sector_index] != consts::DIFAT_SECTOR { if validation.is_strict() { malformed!( "DIFAT sector {} is not marked as such in the FAT", difat_sector ); } else { self.fat[difat_sector_index] = consts::DIFAT_SECTOR; } } } for &fat_sector in self.difat.iter() { let fat_sector_index = fat_sector as usize; if fat_sector_index >= self.fat.len() { malformed!( "FAT has {} entries, but DIFAT lists {} as a FAT sector", self.fat.len(), fat_sector ); } if self.fat[fat_sector_index] != consts::FAT_SECTOR { if validation.is_strict() { malformed!( "FAT sector {} is not marked as such in the FAT", fat_sector ); } else { self.fat[fat_sector_index] = consts::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, Validation, 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, validation: Validation, ) -> Allocator>> { Allocator::new( make_sectors(Version::V3, fat.len()), vec![], difat, fat, validation, ) .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, Validation::Strict) .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, Validation::Strict) .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_strict() { 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, Validation::Strict) .unwrap(); } #[test] fn difat_sector_not_marked_in_fat_permissive() { 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()); // Marking the DIFAT sector as END_OF_CHAIN instead of DIFAT_SECTOR is // a spec violation, but is tolerated under Permissive validation. let mut allocator = Allocator::new( sectors, difat_sectors, difat, fat, Validation::Permissive, ) .unwrap(); // We should repair the FAT entry, and the resulting Allocator should // now pass Strict validation. assert_eq!(allocator.fat[1], consts::DIFAT_SECTOR); allocator.validate(Validation::Strict).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, Validation::Permissive); } #[test] #[should_panic( expected = "Malformed FAT (FAT sector 1 is not marked as such in the \ FAT)" )] fn fat_sector_not_marked_in_fat_strict() { let difat = vec![0, 1]; let fat = vec![consts::FAT_SECTOR, consts::END_OF_CHAIN]; make_allocator(difat, fat, Validation::Strict); } // Regression test for https://github.com/mdsteele/rust-cfb/issues/30 #[test] fn fat_sector_not_marked_in_fat_permissive() { let difat = vec![0, 1]; let fat = vec![consts::FAT_SECTOR, consts::END_OF_CHAIN]; // Marking the second FAT sector as END_OF_CHAIN instead of FAT_SECTOR // is a spec violation, but is tolerated under Permissive validation. let mut allocator = make_allocator(difat, fat, Validation::Permissive); // We should repair the FAT entry, and the resulting Allocator should // now pass Strict validation. assert_eq!(allocator.fat[1], consts::FAT_SECTOR); allocator.validate(Validation::Strict).unwrap(); } #[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, Validation::Permissive); } #[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, Validation::Permissive); } #[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, Validation::Permissive); } } //===========================================================================// cfb-0.10.0/src/internal/chain.rs000064400000000000000000000163221046102023000144730ustar 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.10.0/src/internal/color.rs000064400000000000000000000021221046102023000145200ustar 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.10.0/src/internal/consts.rs000064400000000000000000000026471046102023000147270ustar 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.10.0/src/internal/directory.rs000064400000000000000000000557771046102023000154360ustar 00000000000000use crate::internal::{ self, consts, Allocator, Chain, Color, DirEntry, ObjType, Sector, SectorInit, Timestamp, Validation, Version, }; use byteorder::{LittleEndian, WriteBytesExt}; use fnv::FnvHashSet; use std::cmp::Ordering; use std::io::{self, Seek, SeekFrom, Write}; //===========================================================================// 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, validation: Validation, ) -> io::Result> { let directory = Directory { allocator, dir_entries, dir_start_sector }; directory.validate(validation)?; Ok(directory) } pub fn version(&self) -> Version { self.allocator.version() } pub fn inner(&self) -> &F { self.allocator.inner() } 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) } 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, validation: Validation) -> 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, false)]; while let Some((stream_id, parent_is_red)) = 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 node_is_red = dir_entry.color == Color::Red; // The MS-CFB spec section 2.6.4 says that two consecutive nodes in // the red-black tree for siblings within a storage object 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 only consider this an // error under Strict validation. if parent_is_red && node_is_red && validation.is_strict() { malformed!("RB tree has adjacent red nodes"); } 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, node_is_red)); } 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, node_is_red)); } 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, false)); } } 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()?; // 2.6.1 streams must have creation and modified time of 0 let mut ts = Timestamp::zero(); if obj_type == ObjType::Storage { ts = Timestamp::now(); } *self.dir_entry_mut(stream_id) = DirEntry::new(name, obj_type, ts); // 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)?; self.update_num_dir_sectors()?; } // 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) } /// Increase header num_dir_sectors if version V4 /// note: not updating this value breaks ole32 compatibility fn update_num_dir_sectors(&mut self) -> io::Result<()> { let start_sector = self.dir_start_sector; if self.version() == Version::V4 { let num_dir_sectors = self.count_directory_sectors(start_sector)?; self.seek_within_header(40)? .write_u32::(num_dir_sectors)?; } Ok(()) } fn count_directory_sectors( &mut self, start_sector: u32, ) -> io::Result { let mut num_dir_sectors = 1; let mut next_sector = self.allocator.next(start_sector)?; while next_sector != consts::END_OF_CHAIN { num_dir_sectors += 1; next_sector = self.allocator.next(next_sector)?; } Ok(num_dir_sectors) } /// 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. // In that case, also call update_num_dir_sectors() 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, Timestamp, Validation, Version, }; use std::io::Cursor; fn make_directory( entries: Vec, validation: Validation, ) -> 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, validation).unwrap(); Directory::new(allocator, entries, 1, validation).unwrap() } #[test] #[should_panic(expected = "Malformed directory (root entry is missing)")] fn no_root_entry() { make_directory(vec![], Validation::Permissive); } #[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], Validation::Permissive); } #[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, Timestamp::zero()); storage.child = 1; make_directory(vec![root_entry, storage], Validation::Permissive); } #[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], Validation::Permissive); } #[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, Timestamp::zero()); make_directory(vec![root_entry, storage], Validation::Permissive); } #[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], Validation::Permissive); } fn make_entries_with_adjacent_red_nodes() -> Vec { let mut root_entry = DirEntry::empty_root_entry(); root_entry.child = 1; let mut storage1 = DirEntry::new("foo", ObjType::Storage, Timestamp::zero()); storage1.color = Color::Red; storage1.left_sibling = 2; let mut storage2 = DirEntry::new("bar", ObjType::Storage, Timestamp::zero()); storage2.color = Color::Red; vec![root_entry, storage1, storage2] } #[test] #[should_panic( expected = "Malformed directory (RB tree has adjacent red nodes)" )] fn adjacent_red_nodes_strict() { make_directory( make_entries_with_adjacent_red_nodes(), Validation::Strict, ); } #[test] fn adjacent_red_nodes_permissive() { make_directory( make_entries_with_adjacent_red_nodes(), Validation::Permissive, ); } } //===========================================================================// cfb-0.10.0/src/internal/direntry.rs000064400000000000000000000712671046102023000152620ustar 00000000000000use crate::internal::consts::{self, MAX_REGULAR_STREAM_ID, NO_STREAM}; use crate::internal::{self, Color, ObjType, Timestamp, Validation, 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: Timestamp, pub modified_time: Timestamp, pub start_sector: u32, pub stream_len: u64, } impl DirEntry { pub fn new( name: &str, obj_type: ObjType, timestamp: Timestamp, ) -> 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: Timestamp::zero(), modified_time: Timestamp::zero(), start_sector: 0, stream_len: 0, } } pub fn empty_root_entry() -> DirEntry { DirEntry::new(consts::ROOT_DIR_NAME, ObjType::Root, Timestamp::zero()) } 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)) } 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, validation: Validation, ) -> 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 under Permissive validation we don't // enforce it. if validation.is_strict() && name_chars[name_len_chars] != 0 { malformed!("name not null-terminated"); } 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 under Permissive validation 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 { if name != consts::ROOT_DIR_NAME { if validation.is_strict() { malformed!( "root entry name is {:?}, but should be {:?}", name, consts::ROOT_DIR_NAME ); } 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 under Permissive validation 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 mut clsid = DirEntry::read_clsid(reader)?; if obj_type == ObjType::Stream && !clsid.is_nil() { if validation.is_strict() { malformed!("non-null stream CLSID: {:?}", clsid); } clsid = Uuid::nil(); } let state_bits = reader.read_u32::()?; // Section 2.6.1 of the MS-CFB spec states that "for a stream object, // [creation time and modified time] MUST be all zeroes." However, // under Permissive validation, we don't enforce this, but instead just // treat these fields as though they were zero. let mut creation_time = Timestamp::read_from(reader)?; if obj_type == ObjType::Stream && creation_time != Timestamp::zero() { if validation.is_strict() { malformed!( "non-zero stream creation time: {}", creation_time.value() ); } creation_time = Timestamp::zero(); } let mut modified_time = Timestamp::read_from(reader)?; if obj_type == ObjType::Stream && modified_time != Timestamp::zero() { if validation.is_strict() { malformed!( "non-zero stream modified time: {}", modified_time.value() ); } modified_time = Timestamp::zero(); } // 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 under Permissive validation 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 { if validation.is_strict() && start_sector != 0 { malformed!("non-zero storage start sector: {}", start_sector); } start_sector = 0; if validation.is_strict() && stream_len != 0 { malformed!("non-zero storage stream length: {}", stream_len); } 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)?; self.creation_time.write_to(writer)?; self.modified_time.write_to(writer)?; 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, Timestamp, Validation, Version, }; use std::time::UNIX_EPOCH; 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, Validation::Permissive, ) .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, Timestamp::zero()); assert_eq!(dir_entry.modified_time, Timestamp::zero()); 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, 128, 62, 213, 222, 177, 157, 1, // 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, Validation::Strict, ) .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, Timestamp::zero()); assert_eq!( dir_entry.modified_time, Timestamp::from_system_time(UNIX_EPOCH) ); 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, Validation::Permissive, ) .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, Validation::Permissive, ) .unwrap(); } const NON_ZERO_CREATION_TIME_ON_STREAM: [u8; consts::DIR_ENTRY_LEN] = [ 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, // name 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 37, 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 ]; #[test] #[should_panic( expected = "Malformed directory entry (non-zero stream creation time: \ 37)" )] fn non_zero_creation_time_on_stream_strict() { let mut input: &[u8] = &NON_ZERO_CREATION_TIME_ON_STREAM; DirEntry::read_from(&mut input, Version::V4, Validation::Strict) .unwrap(); } #[test] fn non_zero_creation_time_on_stream_permissive() { let mut input: &[u8] = &NON_ZERO_CREATION_TIME_ON_STREAM; let dir_entry = DirEntry::read_from( &mut input, Version::V4, Validation::Permissive, ) .unwrap(); assert_eq!(dir_entry.obj_type, ObjType::Stream); assert_eq!(dir_entry.creation_time, Timestamp::zero()); } const NON_ZERO_MODIFIED_TIME_ON_STREAM: [u8; consts::DIR_ENTRY_LEN] = [ 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, // name 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, 1, 0, 0, 0, 0, 0, 0, // modified 0, 0, 0, 0, // start sector 0, 0, 0, 0, 0, 0, 0, 0, // stream length ]; #[test] #[should_panic( expected = "Malformed directory entry (non-zero stream modified time: \ 256)" )] fn non_zero_modified_time_on_stream_strict() { let mut input: &[u8] = &NON_ZERO_MODIFIED_TIME_ON_STREAM; DirEntry::read_from(&mut input, Version::V4, Validation::Strict) .unwrap(); } #[test] fn non_zero_modified_time_on_stream_permissive() { let mut input: &[u8] = &NON_ZERO_MODIFIED_TIME_ON_STREAM; let dir_entry = DirEntry::read_from( &mut input, Version::V4, Validation::Permissive, ) .unwrap(); assert_eq!(dir_entry.obj_type, ObjType::Stream); assert_eq!(dir_entry.modified_time, Timestamp::zero()); } const NON_NULL_CLSID_ON_STREAM: [u8; consts::DIR_ENTRY_LEN] = [ 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, // name 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 ]; #[test] #[should_panic( expected = "Malformed directory entry (non-null stream CLSID: \ 04030201-0605-0807-0908-070605040302)" )] fn non_null_clsid_on_stream_strict() { let mut input: &[u8] = &NON_NULL_CLSID_ON_STREAM; DirEntry::read_from(&mut input, Version::V4, Validation::Strict) .unwrap(); } // Regression test for https://github.com/mdsteele/rust-cfb/issues/26 #[test] fn non_null_clsid_on_stream_permissive() { let mut input: &[u8] = &NON_NULL_CLSID_ON_STREAM; // 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 under Permissive validation, but we // ignore that CLSID and just set it to all zeroes. let dir_entry = DirEntry::read_from( &mut input, Version::V4, Validation::Permissive, ) .unwrap(); assert_eq!(dir_entry.obj_type, ObjType::Stream); assert!(dir_entry.clsid.is_nil()); } const NON_NULL_TERMINATED_NAME: [u8; consts::DIR_ENTRY_LEN] = [ 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, // name 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 ]; #[test] #[should_panic( expected = "Malformed directory entry (name not null-terminated)" )] fn non_null_terminated_name_strict() { let mut input: &[u8] = &NON_NULL_TERMINATED_NAME; DirEntry::read_from(&mut input, Version::V4, Validation::Strict) .unwrap(); } // Regression test for https://github.com/mdsteele/rust-cfb/issues/26 #[test] fn non_null_terminated_name_permissive() { let mut input: &[u8] = &NON_NULL_TERMINATED_NAME; // 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 under Permissive validation we // just rely on the name length field. let dir_entry = DirEntry::read_from( &mut input, Version::V4, Validation::Permissive, ) .unwrap(); assert_eq!(dir_entry.name, "Foobar"); } #[test] fn nonzero_storage_starting_sector_strict() { let mut dir_entry = DirEntry::new("Foobar", ObjType::Storage, Timestamp::zero()); dir_entry.start_sector = 58; let mut input = Vec::::new(); dir_entry.write_to(&mut input).unwrap(); let result = DirEntry::read_from( &mut input.as_slice(), Version::V4, Validation::Strict, ); assert_eq!( result.err().unwrap().to_string(), "Malformed directory entry (non-zero storage start sector: 58)" ); } #[test] fn nonzero_storage_stream_len_strict() { let mut dir_entry = DirEntry::new("Foobar", ObjType::Storage, Timestamp::zero()); dir_entry.stream_len = 574; let mut input = Vec::::new(); dir_entry.write_to(&mut input).unwrap(); let result = DirEntry::read_from( &mut input.as_slice(), Version::V4, Validation::Strict, ); assert_eq!( result.err().unwrap().to_string(), "Malformed directory entry (non-zero storage stream length: 574)" ); } // Regression test for https://github.com/mdsteele/rust-cfb/issues/27 #[test] fn nonzero_storage_starting_sector_and_stream_len_permissive() { let mut dir_entry = DirEntry::new("Foobar", ObjType::Storage, Timestamp::zero()); dir_entry.start_sector = 58; dir_entry.stream_len = 574; let mut input = Vec::::new(); dir_entry.write_to(&mut input).unwrap(); // 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 under Permissive validation, just // ignore those fields' values and pretend they're zero. let dir_entry = DirEntry::read_from( &mut (&input as &[u8]), Version::V4, Validation::Permissive, ) .unwrap(); assert_eq!(dir_entry.obj_type, ObjType::Storage); assert_eq!(dir_entry.start_sector, 0); assert_eq!(dir_entry.stream_len, 0); } const ROOT_ENTRY_WITH_INCORRECT_NAME: [u8; consts::DIR_ENTRY_LEN] = [ 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, // name 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 ]; #[test] #[should_panic( expected = "Malformed directory entry (root entry name is \ \\\"Foobar\\\", but should be \\\"Root Entry\\\")" )] fn root_entry_with_incorrect_name_strict() { let mut input: &[u8] = &ROOT_ENTRY_WITH_INCORRECT_NAME; DirEntry::read_from(&mut input, Version::V4, Validation::Strict) .unwrap(); } // Regression test for https://github.com/mdsteele/rust-cfb/issues/29 #[test] fn root_entry_with_incorrect_name_permissive() { let mut input: &[u8] = &ROOT_ENTRY_WITH_INCORRECT_NAME; // 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 // under Permissive validation, just ignore the name in the file and // pretend it's correct. let dir_entry = DirEntry::read_from( &mut input, Version::V4, Validation::Permissive, ) .unwrap(); assert_eq!(dir_entry.obj_type, ObjType::Root); assert_eq!(dir_entry.name, "Root Entry"); } } //===========================================================================// cfb-0.10.0/src/internal/entry.rs000064400000000000000000000255711046102023000145600ustar 00000000000000use crate::internal::{consts, DirEntry, MiniAllocator, ObjType, Timestamp}; use std::fmt; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; 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: Timestamp, modified_time: Timestamp, 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 { self.creation_time.to_system_time() } /// Returns the time when the object that this entry represents was last /// modified. pub fn modified(&self) -> SystemTime { self.modified_time.to_system_time() } } impl fmt::Debug for Entry { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( f, "{path} ({len} bytes)", path = self.path().display(), len = self.len() ) } } //===========================================================================// #[derive(Clone, Copy, Eq, PartialEq)] pub enum EntriesOrder { Nonrecursive, Preorder, } //===========================================================================// /// An iterator over the entries in a storage object. pub struct Entries<'a, F: 'a> { order: EntriesOrder, // TODO: Consider storing a Weak>> here instead of // a reference to the Rc. That would allow e.g. opening streams during // iteration. But we'd need to think about how the iterator should behave // if the CFB tree structure is modified during iteration. minialloc: &'a Arc>>, stack: Vec<(PathBuf, u32, bool)>, } impl<'a, F> Entries<'a, F> { pub(crate) fn new( order: EntriesOrder, minialloc: &'a Arc>>, parent_path: PathBuf, start: u32, ) -> Entries<'a, F> { let mut entries = Entries { order, minialloc, 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 stack_left_spine(&mut self, parent_path: &Path, mut current_id: u32) { let minialloc = self.minialloc.read().unwrap(); while current_id != consts::NO_STREAM { self.stack.push((parent_path.to_path_buf(), current_id, true)); current_id = minialloc.dir_entry(current_id).left_sibling; } } } impl<'a, F> Iterator for Entries<'a, F> { type Item = Entry; fn next(&mut self) -> Option { if let Some((parent, stream_id, visit_siblings)) = self.stack.pop() { let minialloc = self.minialloc.read().unwrap(); let dir_entry = minialloc.dir_entry(stream_id); let path = 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 } } } //===========================================================================// 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) } } //===========================================================================// #[cfg(test)] mod tests { use super::{Entries, EntriesOrder, Entry}; use crate::internal::consts::{self, NO_STREAM, ROOT_DIR_NAME}; use crate::internal::{ Allocator, DirEntry, Directory, MiniAllocator, ObjType, Sectors, Timestamp, Validation, Version, }; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; fn make_entry( name: &str, obj_type: ObjType, left: u32, child: u32, right: u32, ) -> DirEntry { let mut dir_entry = DirEntry::new(name, obj_type, Timestamp::zero()); dir_entry.left_sibling = left; dir_entry.child = child; dir_entry.right_sibling = right; dir_entry } fn make_minialloc() -> Arc>> { // Root contains: 3 contains: // 5 8 // / \ / \ // 3 6 7 9 // / \ // 1 4 // \ // 2 let dir_entries = 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), ]; let version = Version::V3; let sectors = Sectors::new(version, 3 * version.sector_len() as u64, ()); let allocator = Allocator::new( sectors, vec![], vec![0], vec![consts::FAT_SECTOR, consts::END_OF_CHAIN], Validation::Strict, ) .unwrap(); let directory = Directory::new(allocator, dir_entries, 1, Validation::Strict) .unwrap(); let minialloc = MiniAllocator::new( directory, vec![], consts::END_OF_CHAIN, Validation::Strict, ) .unwrap(); Arc::new(RwLock::new(minialloc)) } fn paths_for_entries(entries: &[Entry]) -> Vec<&Path> { entries.iter().map(|entry| entry.path()).collect() } #[test] fn nonrecursive_entries_from_root() { let minialloc = make_minialloc(); let entries: Vec = Entries::new( EntriesOrder::Nonrecursive, &minialloc, 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 minialloc = make_minialloc(); let entries: Vec = Entries::new( EntriesOrder::Nonrecursive, &minialloc, 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 minialloc = make_minialloc(); let entries: Vec = Entries::new( EntriesOrder::Preorder, &minialloc, 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 minialloc = make_minialloc(); let entries: Vec = Entries::new( EntriesOrder::Preorder, &minialloc, 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.10.0/src/internal/header.rs000064400000000000000000000264171046102023000146470ustar 00000000000000use std::io::{self, Read, Write}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use crate::internal::{consts, Validation, Version}; //===========================================================================// 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, validation: Validation, ) -> 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 ); } // TODO: require reserved field to be all zeros under strict validation reader.read_exact(&mut [0u8; 6])?; // reserved field // According to section 2.2 of the MS-CFB spec, "If Major Version is 3, // the Number of Directory Sectors MUST be zero." However, under // Permissive validation, we don't enforce this, but instead just treat // the field as though it were zero for V3 files. let mut num_dir_sectors = reader.read_u32::()?; if version == Version::V3 && num_dir_sectors != 0 { if validation.is_strict() { invalid_data!( "Invalid number of directory sectors field (must be zero \ for CFB version 3, found {})", num_dir_sectors ); } num_dir_sectors = 0; } 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 crate::internal::{consts, Validation, Version}; use super::Header; fn make_valid_header() -> Header { let mut header = Header { version: Version::V3, num_dir_sectors: 0, 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(), Validation::Strict) .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(), Validation::Strict).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(), Validation::Strict).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(), Validation::Strict).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(), Validation::Strict).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(), Validation::Strict).unwrap(); } #[test] #[should_panic( expected = "Invalid number of directory sectors field (must be zero \ for CFB version 3, found 37)" )] fn v3_non_zero_dir_sectors_strict() { let mut data = make_valid_header_data(); data[40] = 37; Header::read_from(&mut data.as_slice(), Validation::Strict).unwrap(); } #[test] fn v3_non_zero_dir_sectors_permissive() { let mut data = make_valid_header_data(); data[40] = 37; let header = Header::read_from(&mut data.as_slice(), Validation::Permissive) .unwrap(); assert_eq!(header.num_dir_sectors, 0); } #[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(), Validation::Strict).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(), Validation::Strict).unwrap(); } } //===========================================================================// cfb-0.10.0/src/internal/macros.rs000064400000000000000000000030441046102023000146720ustar 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.10.0/src/internal/minialloc.rs000064400000000000000000000375271046102023000153720ustar 00000000000000use std::io::{self, Seek, SeekFrom, Write}; use std::mem::size_of; use byteorder::{LittleEndian, WriteBytesExt}; use fnv::FnvHashSet; use crate::internal::{ consts, Chain, DirEntry, Directory, MiniChain, ObjType, Sector, SectorInit, Validation, Version, }; //===========================================================================// 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, validation: Validation, ) -> io::Result> { let mut minialloc = MiniAllocator { directory, minifat, minifat_start_sector }; minialloc.validate(validation)?; Ok(minialloc) } pub fn version(&self) -> Version { self.directory.version() } pub fn inner(&self) -> &F { self.directory.inner() } 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) } 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(&mut self, validation: Validation) -> 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) { if validation.is_strict() { malformed!( "MiniFAT has {} entries, but root stream has only {} mini \ sectors", self.minifat.len(), root_stream_mini_sectors ); } else { self.minifat.truncate(root_stream_mini_sectors as usize); } } 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 std::io::Cursor; use crate::internal::{ consts, Allocator, DirEntry, Directory, ObjType, Sectors, Timestamp, Validation, Version, }; use super::MiniAllocator; 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 validation = Validation::Strict; 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, validation).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, Timestamp::zero()); 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, validation).unwrap(); MiniAllocator::new(directory, minifat, 2, validation).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.10.0/src/internal/minichain.rs000064400000000000000000000142521046102023000153500ustar 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.10.0/src/internal/mod.rs000064400000000000000000000014231046102023000141640ustar 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; mod stream; mod timestamp; mod validate; 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::stream::Stream; pub use self::timestamp::Timestamp; pub use self::validate::Validation; pub use self::version::Version; cfb-0.10.0/src/internal/objtype.rs000064400000000000000000000030021046102023000150540ustar 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.10.0/src/internal/path.rs000064400000000000000000000120611046102023000143410ustar 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.10.0/src/internal/sector.rs000064400000000000000000000322511046102023000147070ustar 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 } pub fn 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, Validation, 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, Validation::Strict, ) .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.10.0/src/internal/stream.rs000064400000000000000000000447601046102023000147130ustar 00000000000000use crate::internal::{consts, MiniAllocator, ObjType, SectorInit}; use std::io::{self, BufRead, Read, Seek, SeekFrom, Write}; use std::sync::{Arc, RwLock, Weak}; //===========================================================================// const BUFFER_SIZE: usize = 8192; //===========================================================================// /// A stream entry in a compound file, much like a filesystem file. pub struct Stream { minialloc: Weak>>, 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 Stream { pub(crate) fn new( minialloc: &Arc>>, stream_id: u32, ) -> Stream { let total_len = minialloc.read().unwrap().dir_entry(stream_id).stream_len; Stream { minialloc: Arc::downgrade(minialloc), stream_id, total_len, buffer: Box::new([0; BUFFER_SIZE]), buf_pos: 0, buf_cap: 0, buf_offset_from_start: 0, flusher: None, } } fn minialloc(&self) -> io::Result>>> { self.minialloc.upgrade().ok_or_else(|| { io::Error::new(io::ErrorKind::Other, "CompoundFile was dropped") }) } /// 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 Stream { /// 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()?; let minialloc = self.minialloc()?; resize_stream( &mut minialloc.write().unwrap(), 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 BufRead for Stream { 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; let minialloc = self.minialloc()?; self.buf_cap = read_data_from_stream( &mut minialloc.write().unwrap(), 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 Read for Stream { fn read(&mut self, buf: &mut [u8]) -> io::Result { let mut buffered_data = self.fill_buf()?; let num_bytes = buffered_data.read(buf)?; self.consume(num_bytes); Ok(num_bytes) } } impl Seek for Stream { 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 Write for Stream { 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()?; let minialloc = self.minialloc()?; minialloc.write().unwrap().flush()?; Ok(()) } } impl Drop for Stream { 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<()> { let minialloc = stream.minialloc()?; write_data_to_stream( &mut minialloc.write().unwrap(), stream.stream_id, stream.buf_offset_from_start, &stream.buffer[..stream.buf_cap], )?; debug_assert_eq!( minialloc.read().unwrap().dir_entry(stream.stream_id).stream_len, stream.total_len ); Ok(()) } } //===========================================================================// fn read_data_from_stream( minialloc: &mut MiniAllocator, stream_id: u32, buf_offset_from_start: u64, buf: &mut [u8], ) -> io::Result { let (start_sector, stream_len) = { let dir_entry = 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 = 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 = 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) } fn write_data_to_stream( minialloc: &mut MiniAllocator, stream_id: u32, buf_offset_from_start: u64, buf: &[u8], ) -> io::Result<()> { let (old_start_sector, old_stream_len) = { let dir_entry = 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 = 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 = 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 = 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 = minialloc.open_mini_chain(old_start_sector)?; chain.read_exact(&mut tmp)?; chain.free()?; let mut chain = 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 = 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. minialloc.with_dir_entry_mut(stream_id, |dir_entry| { dir_entry.start_sector = new_start_sector; dir_entry.stream_len = new_stream_len; }) } /// 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( minialloc: &mut MiniAllocator, stream_id: u32, new_stream_len: u64, ) -> io::Result<()> { let (old_start_sector, old_stream_len) = { let dir_entry = 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 = 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 = 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. 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 = 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 = minialloc.open_mini_chain(old_start_sector)?; chain.read_exact(&mut tmp)?; chain.free()?; let mut chain = 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. 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 = minialloc.open_chain(old_start_sector, SectorInit::Zero)?; chain.read_exact(&mut tmp)?; chain.free()?; let mut chain = 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 = 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. minialloc.with_dir_entry_mut(stream_id, |dir_entry| { dir_entry.start_sector = new_start_sector; dir_entry.stream_len = new_stream_len; }) } //===========================================================================// cfb-0.10.0/src/internal/timestamp.rs000064400000000000000000000143471046102023000154210ustar 00000000000000use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use std::io::{self, Read, Write}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; //===========================================================================// /// A CFB file timestamp. This is represented as the number of 100-nanosecond /// intervals since January 1, 1601 UTC. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Timestamp(u64); impl Timestamp { pub(crate) fn value(self) -> u64 { self.0 } /// Returns a timestamp representing the CFB file epoch of January 1, 1601 /// UTC. This is an appropriate value to use for an uninitialized /// timestamp. pub fn zero() -> Timestamp { Timestamp(0) } /// Returns a timestamp representing the current system time. pub fn now() -> Timestamp { Timestamp::from_system_time(SystemTime::now()) } /// Returns a timestamp representing the given system time. pub fn from_system_time(system_time: SystemTime) -> Timestamp { Timestamp(timestamp_from_system_time(system_time)) } /// Returns the local system time that this timestamp represents. pub fn to_system_time(self) -> SystemTime { system_time_from_timestamp(self.0) } pub fn read_from(reader: &mut R) -> io::Result { Ok(Timestamp(reader.read_u64::()?)) } pub fn write_to(self, writer: &mut W) -> io::Result<()> { writer.write_u64::(self.0) } } //===========================================================================// /// The CFB timestamp value for the Unix epoch (Jan 1, 1970 UTC). const UNIX_EPOCH_TIMESTAMP: u64 = 116444736000000000; /// Converts a local `SystemTime` to a CFB file timestamp value. 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 value to a local `SystemTime`. 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.10.0/src/internal/validate.rs000064400000000000000000000013131046102023000151740ustar 00000000000000//===========================================================================// /// A parsing validation strategy. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum Validation { /// As much as possible, spec violations will be ignored when parsing. Permissive, /// Any violation of the CFB spec will be treated as an error when parsing. Strict, } impl Validation { /// Returns true for `Strict` validation, false otherwise. pub fn is_strict(self) -> bool { match self { Validation::Permissive => false, Validation::Strict => true, } } } //===========================================================================// cfb-0.10.0/src/internal/version.rs000064400000000000000000000042211046102023000150710ustar 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.10.0/src/lib.rs000064400000000000000000001177051046102023000123520ustar 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 std::fmt; use std::fs; use std::io::{self, Read, Seek, SeekFrom, Write}; use std::mem::size_of; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use fnv::FnvHashSet; use uuid::Uuid; use crate::internal::consts; use crate::internal::{ Allocator, DirEntry, Directory, EntriesOrder, Header, MiniAllocator, ObjType, SectorInit, Sectors, Timestamp, Validation, }; pub use crate::internal::{Entries, Entry, Stream, Version}; #[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: Arc>>, } impl CompoundFile { fn minialloc(&self) -> RwLockReadGuard> { self.minialloc.read().unwrap() } fn minialloc_mut(&mut self) -> RwLockWriteGuard> { self.minialloc.write().unwrap() } /// 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 { let start = self.minialloc().root_dir_entry().child; Entries::new( EntriesOrder::Nonrecursive, &self.minialloc, internal::path::path_from_name_chain(&[]), start, ) } /// Returns an iterator over the entries within a storage object. pub fn read_storage>( &self, path: P, ) -> io::Result> { self.read_storage_with_path(path.as_ref()) } fn read_storage_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 storage: {:?}", path), }; let start = { let minialloc = self.minialloc(); let dir_entry = minialloc.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.minialloc, 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. This is equivalent to /// `self.walk_storage("/").unwrap()` (but always succeeds). pub fn walk(&self) -> Entries { Entries::new( EntriesOrder::Preorder, &self.minialloc, 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: P, ) -> io::Result> { self.walk_storage_with_path(path.as_ref()) } fn walk_storage_with_path(&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.minialloc, parent_path, stream_id, )) } /// 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 { // We only ever retain Weak copies of the CompoundFile's minialloc Rc // (e.g. in Stream structs), so the Rc::try_unwrap() should always // succeed. match Arc::try_unwrap(self.minialloc) { Ok(ref_cell) => ref_cell.into_inner().unwrap().into_inner(), Err(_) => unreachable!(), } } } 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.minialloc, 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(inner: F) -> io::Result> { CompoundFile::open_internal(inner, Validation::Permissive) } /// Like `open()`, but is stricter when parsing and will return an error if /// the file violates the CFB spec in any way (which many CFB files in the /// wild do). This is mainly useful for validating a CFB file or /// implemention (such as this crate itself) to help ensure compatibility /// with other readers. pub fn open_strict(inner: F) -> io::Result> { CompoundFile::open_internal(inner, Validation::Strict) } fn open_internal( mut inner: F, validation: Validation, ) -> 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, validation)?; 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 && current_difat_sector != consts::FREE_SECTOR { 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 validation.is_strict() && current_difat_sector == consts::FREE_SECTOR { invalid_data!( "DIFAT chain must terminate with {}, not {}", consts::END_OF_CHAIN, consts::FREE_SECTOR ); } } if validation.is_strict() && 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 validation.is_strict() && 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), so we allow // this under Permissive validation. 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. if !validation.is_strict() { 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, validation)?; // 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; let mut dir_sector_count = 1; while current_dir_sector != consts::END_OF_CHAIN { if validation.is_strict() && header.version == Version::V4 && dir_sector_count > header.num_dir_sectors { invalid_data!( "Directory chain includes at least {} sectors which is greater than header num_dir_sectors {}", dir_sector_count, header.num_dir_sectors ); } 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, validation, )?); } } current_dir_sector = allocator.next(current_dir_sector)?; dir_sector_count += 1; } let mut directory = Directory::new( allocator, dir_entries, header.first_dir_sector, validation, )?; // Read in MiniFAT. let minifat = { let mut chain = directory .open_chain(header.first_minifat_sector, SectorInit::Fat)?; if validation.is_strict() && 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, validation, )?; Ok(CompoundFile { minialloc: Arc::new(RwLock::new(minialloc)) }) } } 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, // 2.2 requires this to be zero in V3 num_dir_sectors: if version == Version::V3 { 0 } else { 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, Validation::Strict, )?; let directory = Directory::new( allocator, vec![root_dir_entry], 1, Validation::Strict, )?; let minialloc = MiniAllocator::new( directory, vec![], consts::END_OF_CHAIN, Validation::Strict, )?; Ok(CompoundFile { minialloc: Arc::new(RwLock::new(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_mut().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 minialloc = self.minialloc(); let dir_entry = 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 != consts::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_mut().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 = self.walk_storage(path)?.collect::>(); 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) ), }; let mut minialloc = self.minialloc_mut(); if minialloc.dir_entry(stream_id).obj_type == ObjType::Stream { invalid_input!( "Not a storage: {:?}", internal::path::path_from_name_chain(&names) ); } 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.minialloc, 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_mut().insert_dir_entry( parent_id, name, ObjType::Stream, )?; Ok(Stream::new(&self.minialloc, 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 minialloc = self.minialloc(); let dir_entry = minialloc.dir_entry(stream_id); if dir_entry.obj_type != ObjType::Stream { invalid_input!("Not a stream: {:?}", path); } debug_assert_eq!(dir_entry.child, consts::NO_STREAM); ( dir_entry.start_sector, dir_entry.stream_len < consts::MINI_STREAM_CUTOFF as u64, ) }; if is_in_mini_stream { self.minialloc_mut().free_mini_chain(start_sector_id)?; } else { self.minialloc_mut().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_mut().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_mut().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 { let mut minialloc = self.minialloc_mut(); debug_assert_ne!( minialloc.dir_entry(stream_id).obj_type, ObjType::Root ); minialloc.with_dir_entry_mut(stream_id, |dir_entry| { dir_entry.modified_time = Timestamp::now(); })?; } Ok(()) } /// Flushes all changes to the underlying file. pub fn flush(&mut self) -> io::Result<()> { self.minialloc_mut().flush() } } impl fmt::Debug for CompoundFile { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("CompoundFile").field(self.minialloc().inner()).finish() } } //===========================================================================// #[cfg(test)] mod tests { use std::io::{self, Cursor, Seek, SeekFrom}; use std::mem::size_of; use std::path::Path; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use crate::internal::{consts, DirEntry, Header, Version}; use super::CompoundFile; fn make_cfb_file_with_zero_padded_fat() -> io::Result> { let version = Version::V3; let mut data = Vec::::new(); let mut header = Header { version, num_dir_sectors: 0, 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)?; } Ok(data) } #[test] fn zero_padded_fat_strict() { let data = make_cfb_file_with_zero_padded_fat().unwrap(); let result = CompoundFile::open_strict(Cursor::new(data)); assert_eq!( result.err().unwrap().to_string(), "Malformed FAT (FAT has 128 entries, but file has only 2 sectors)" ); } // Regression test for https://github.com/mdsteele/rust-cfb/issues/8. #[test] fn zero_padded_fat_permissive() { let data = make_cfb_file_with_zero_padded_fat().unwrap(); // Despite the zero-padded FAT, we should be able to read this file // under Permissive validation. CompoundFile::open(Cursor::new(data)).expect("open"); } // Regression test for https://github.com/mdsteele/rust-cfb/issues/52. #[test] fn update_num_dir_sectors() { // Create a CFB file with 2 sectors for the directory. let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).unwrap(); // root + 31 entries in the first sector // 1 stream entry in the second sector for i in 0..32 { let path = format!("stream{}", i); let path = Path::new(&path); comp.create_stream(path).unwrap(); } comp.flush().unwrap(); // read num_dir_sectors from the header let mut cursor = comp.into_inner(); cursor.seek(SeekFrom::Start(40)).unwrap(); let num_dir_sectors = cursor.read_u32::().unwrap(); assert_eq!(num_dir_sectors, 2); } } //===========================================================================// cfb-0.10.0/tests/basic.rs000064400000000000000000000706171046102023000132400ustar 00000000000000use cfb::{CompoundFile, Entry, Version}; use std::io::{self, 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_strict(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_strict(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_strict(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_strict(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_strict(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_strict(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_strict(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_strict(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_strict(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_strict(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_strict(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_strict(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.stream_position().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_strict(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.stream_position().unwrap(), 1500); stream.set_len(5000).unwrap(); assert_eq!(stream.len(), 5000); assert_eq!(stream.stream_position().unwrap(), 1500); stream.write_all(&vec![b'z'; 500]).unwrap(); assert_eq!(stream.len(), 5000); assert_eq!(stream.stream_position().unwrap(), 2000); } let cursor = comp.into_inner(); let mut comp = CompoundFile::open_strict(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]); } //===========================================================================// // Tests for opening multiple streams at once: #[test] fn multiple_open_streams() -> io::Result<()> { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor)?; // Create a stream and write multiple sectors worth of data into it. let mut stream1 = comp.create_stream("/foo")?; let mut data = Vec::::new(); for i in 1..150 { for j in 0..i { data.push(j); } } assert!(data.len() > comp.version().sector_len() * 2); stream1.write_all(&data)?; // Create a second stream and copy the first stream into it. Having two // open streams at once, and interleaving reads and writes between them, // should work fine. let mut stream2 = comp.create_stream("/bar")?; stream1.rewind()?; let num_bytes = io::copy(&mut stream1, &mut stream2)?; assert_eq!(num_bytes, data.len() as u64); // Read the copied data out of the second stream and verify that it matches // the original data. let mut copied = Vec::::new(); stream2.rewind()?; let num_bytes = stream2.read_to_end(&mut copied)?; assert_eq!(num_bytes, data.len()); assert_eq!(copied, data); Ok(()) } #[test] fn drop_compound_file_with_stream_open() -> io::Result<()> { let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor)?; let mut stream = comp.create_stream("/foobar")?; stream.write_all(b"Hello, world!")?; comp.into_inner(); let result = stream.flush(); assert_eq!(result.unwrap_err().to_string(), "CompoundFile was dropped"); Ok(()) } //===========================================================================// // Tests for asserting Send + Sync: #[test] fn test_compound_file_send() { fn assert_send() {} assert_send::>(); } #[test] fn test_compound_file_sync() { fn assert_sync() {} assert_sync::>(); } //===========================================================================// cfb-0.10.0/tests/infinite_loops_fuzzed/loop_in_alloc000064400000000000000000000334321046102023000207470ustar 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.10.0/tests/infinite_loops_fuzzed/loop_in_chain000064400000000000000000000410771046102023000207430ustar 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.10.0/tests/infinite_loops_fuzzed/loop_in_directory000064400000000000000000000332021046102023000216540ustar 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.10.0/tests/infinite_loops_fuzzed/loop_in_minialloc000064400000000000000000000340001046102023000216140ustar 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.10.0/tests/infinite_loops_fuzzed/loop_in_minichain000064400000000000000000000332501046102023000216120ustar 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.10.0/tests/infinite_loops_fuzzed/loop_in_open_1000064400000000000000000000020311046102023000210250ustar 00000000000000ࡱ>  > (ry  ϭ-'VAA ϭ- ϭ-ThisWorkbook ϭ- ϭ-ThisWorkbook  ϭ-'VAAcfb-0.10.0/tests/infinite_loops_fuzzed/loop_in_open_2000064400000000000000000000330061046102023000210340ustar 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.10.0/tests/large.rs000064400000000000000000000026331046102023000132420ustar 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_strict(cursor).expect("re-open"); } cfb-0.10.0/tests/malformed.rs000064400000000000000000000605301046102023000141160ustar 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)) } } #[rustfmt::skip] fn difat_terminate_in_freesect() -> Cursor> { let mut data = Vec::with_capacity(7_864_832); // Header // ============================= data.extend_from_slice(&[ 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1, // Header signature 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Header CLSID 0x3e, 0x00, // Minor Version 0x03, 0x00, // Major Version 0xfe, 0xff, // Byte Order 0x09, 0x00, // Sector Shift 0x06, 0x00, // Mini Sector Shift 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Reserved 0x00, 0x00, 0x00, 0x00, // Number of Directory Sectors 0x78, 0x00, 0x00, 0x00, // Number of FAT Sectors - 120 0x01, 0x00, 0x00, 0x00, // First Directory Sector Location 0x00, 0x00, 0x00, 0x00, // Transaction Signature Number 0x00, 0x10, 0x00, 0x00, // Mini Stream Cutoff Size 0xfe, 0xff, 0xff, 0xff, // First Mini FAT Sector Location - end of chain 0x00, 0x00, 0x00, 0x00, // Number of Mini FAT Sectors - None 0x02, 0x00, 0x00, 0x00, // First DIFAT Sector Location 0x01, 0x00, 0x00, 0x00, // Number of DIFAT Sectors ]); // First 109 DIFAT Sectors data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); for i in 0..108 { data.extend_from_slice(&[i + 3, 0x00, 0x00, 0x00]); } // First FAT Sector // ============================= data.extend_from_slice(&[ 0xfd, 0xff, 0xff, 0xff, // FAT sector - The current sector 0xfe, 0xff, 0xff, 0xff, // Directory - End of chain, just the one 0xfc, 0xff, 0xff, 0xff, // DIFAT #2 ]); // The remaining 119 FAT sectors for _ in 0..119 { data.extend_from_slice(&[0xfd, 0xff, 0xff, 0xff]); } // The first 6 sectors for content, make it all one stream for i in 0..6 { data.extend_from_slice(&[i + 123, 0x00, 0x00, 0x00]); } // Directory // ============================= // Root entry data.extend_from_slice(&[ b'R', 0x00, b'o', 0x00, b'o', 0x00, b't', 0x00, b' ', 0x00, b'E', 0x00, b'n', 0x00, b't', 0x00, b'r', 0x00, b'y', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Directory Entry Name 0x16, 0x00, // Directory Entry Name Length 0x05, // Object Type - Root Storage Object 0x00, // Color Flag - red 0xff, 0xff, 0xff, 0xff, // Left Sibling ID - None 0xff, 0xff, 0xff, 0xff, // Right Sibling ID - None 0x01, 0x00, 0x00, 0x00, // Child ID - Stream 1 0x00, 0x67, 0x61, 0x56, 0x54, 0xc1, 0xce, 0x11, 0x85, 0x53, 0x00, 0xaa, 0x00, 0xa1, 0xf9, 0x5b, // CLSID 0x00, 0x00, 0x00, 0x00, // State Bits 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Creation Time 0xb0, 0xfd, 0x97, 0x18, 0xab, 0xe9, 0xc4, 0x01, // Modification Time 0x00, 0x00, 0x00, 0x00, // Starting Sector Location - Mini-stream, None as not using 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Stream Size - Mini-stream, None as not using ]); // Stream 1 data.extend_from_slice(&[ b'S', 0x00, b't', 0x00, b'r', 0x00, b'e', 0x00, b'a', 0x00, b'm', 0x00, b' ', 0x00, b'1', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Directory Entry Name 0x12, 0x00, // Directory Entry Name Length 0x02, // Object Type - Stream Object 0x01, // Color Flag - black 0xff, 0xff, 0xff, 0xff, // Left Sibling ID - None 0xff, 0xff, 0xff, 0xff, // Right Sibling ID - None 0xff, 0xff, 0xff, 0xff, // Child ID - No stream 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // CLSID 0x00, 0x00, 0x00, 0x00, // State Bits 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Creation Time 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Modification Time 0x7c, 0x00, 0x00, 0x00, // Starting Sector Location - 124 0x00, 0x0c, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, // Stream Size - 7_801_856 bytes // ((119 FAT pages * 128 sectors per page) + 6 sectors from 1st page) * 512 bytes per sector ]); // Unused data.extend_from_slice(&[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Directory Entry Name 0x00, 0x00, // Directory Entry Name Length 0x00, // Object Type 0x00, // Color Flag 0xff, 0xff, 0xff, 0xff, // Left Sibling ID - None 0xff, 0xff, 0xff, 0xff, // Right Sibling ID - None 0xff, 0xff, 0xff, 0xff, // Child ID - No stream 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // CLSID 0x00, 0x00, 0x00, 0x00, // State Bits 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Creation Time 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Modification Time 0x00, 0x00, 0x00, 0x00, // Starting Sector Location 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Stream Size ]); // Unused data.extend_from_slice(&[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Directory Entry Name 0x00, 0x00, // Directory Entry Name Length 0x00, // Object Type 0x00, // Color Flag 0xff, 0xff, 0xff, 0xff, // Left Sibling ID - None 0xff, 0xff, 0xff, 0xff, // Right Sibling ID - None 0xff, 0xff, 0xff, 0xff, // Child ID - No stream 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // CLSID 0x00, 0x00, 0x00, 0x00, // State Bits 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Creation Time 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Modification Time 0x00, 0x00, 0x00, 0x00, // Starting Sector Location 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Stream Size ]); // DIFAT #2 // ============================= // DIFAT Sectors 110 to 120 for i in 0..11 { data.extend_from_slice(&[i + 111, 0x00, 0x00, 0x00]); } // Pad out the rest of the sector with free sector markers for _ in 0..116 { data.extend_from_slice(&[0xff, 0xff, 0xff, 0xff]); } // Next DIFAT Sector Location // NOTE: This is wrong and should be 0xfffffffe data.extend_from_slice(&[0xff, 0xff, 0xff, 0xff]); // Subsequent FAT Sectors // ============================= let mut lower = 128; let mut upper = 0; let range = (119 * 128) - 1; for _ in 0..range { if lower == 0xff { lower = 0x00; upper += 1; } else { lower += 1; } data.extend_from_slice(&[lower, upper, 0x00, 0x00]); } // End the chain with an end of chain marker data.extend_from_slice(&[0xfe, 0xff, 0xff, 0xff]); // The stream data // ============================= let sectors = (119 * 128) + 6; for _ in 0..sectors { data.extend_from_slice(&[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]); } Cursor::new(data) } #[test] fn open_difat_terminate_freesect() { CompoundFile::open(difat_terminate_in_freesect()).unwrap(); } #[test] #[should_panic( expected = "DIFAT chain must terminate with 4294967294, not 4294967295" )] fn open_strict_difat_terminate_freesect() { CompoundFile::open_strict(difat_terminate_in_freesect()).unwrap(); } /// Regression test for https://github.com/mdsteele/rust-cfb/issues/52. #[test] #[should_panic( expected = "Directory chain includes at least 2 sectors which is greater than header num_dir_sectors 1" )] fn invalid_num_dir_sectors_issue_52() { // Create a CFB file with 2 sectors for the directory. let cursor = Cursor::new(Vec::new()); let mut comp = CompoundFile::create(cursor).unwrap(); // root + 31 entries in the first sector // 1 stream entry in the second sector for i in 0..32 { let path = format!("stream{}", i); let path = Path::new(&path); comp.create_stream(path).unwrap(); } comp.flush().unwrap(); // update the header and set num_dir_sectors = 1 instead of 2 let mut cursor = comp.into_inner(); cursor.seek(SeekFrom::Start(40)).unwrap(); cursor.write_u32::(1).unwrap(); cursor.flush().unwrap(); // Read the file back in. CompoundFile::open_strict(cursor).unwrap(); } cfb-0.10.0/tests/panics_fuzzed/alloc_panic000064400000000000000000000410401046102023000166300ustar 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.10.0/tests/panics_fuzzed/minialloc_panic000064400000000000000000000410151046102023000175070ustar 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.10.0/tests/set_len.rs000064400000000000000000000050761046102023000136050ustar 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_strict(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_strict(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); } //===========================================================================//