From 7a5348b442a0532e0861b12560676eefc2e985e8 Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Tue, 11 Jul 2023 10:17:00 -0400 Subject: [PATCH] use indexmap --- Cargo.toml | 1 + src/read.rs | 49 ++++++++++++++++++++++--------------------------- src/write.rs | 38 +++++++++++++++++--------------------- 3 files changed, 40 insertions(+), 48 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fbf4c7aed..b8884b2ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ chrono = { version = "0.4.38", optional = true } constant_time_eq = { version = "0.3.0", optional = true } crc32fast = "1.4.0" flate2 = { version = "1.0.28", default-features = false, optional = true } +indexmap = "2" hmac = { version = "0.12.1", optional = true, features = ["reset"] } pbkdf2 = { version = "0.12.2", optional = true } sha1 = { version = "0.10.6", optional = true } diff --git a/src/read.rs b/src/read.rs index 0a39faef0..5e38f6d2a 100644 --- a/src/read.rs +++ b/src/read.rs @@ -11,8 +11,8 @@ use crate::spec; use crate::types::{AesMode, AesVendorVersion, DateTime, System, ZipFileData}; use crate::zipcrypto::{ZipCryptoReader, ZipCryptoReaderValid, ZipCryptoValidator}; use byteorder::{LittleEndian, ReadBytesExt}; -use std::borrow::{Borrow, Cow}; -use std::collections::HashMap; +use indexmap::IndexMap; +use std::borrow::Cow; use std::io::{self, prelude::*}; use std::ops::Deref; use std::path::{Path, PathBuf}; @@ -47,8 +47,7 @@ pub(crate) mod zip_archive { /// Extract immutable data from `ZipArchive` to make it cheap to clone #[derive(Debug)] pub(crate) struct Shared { - pub(crate) files: Box<[super::ZipFileData]>, - pub(crate) names_map: super::HashMap, usize>, + pub(crate) files: super::IndexMap, super::ZipFileData>, pub(super) offset: u64, pub(super) dir_start: u64, pub(super) dir_end: u64, @@ -487,21 +486,18 @@ impl ZipArchive { } else { dir_info.number_of_files }; - let mut files = Vec::with_capacity(file_capacity); - let mut names_map = HashMap::with_capacity(file_capacity); + let mut files = IndexMap::with_capacity(file_capacity); reader.seek(io::SeekFrom::Start(dir_info.directory_start))?; for _ in 0..dir_info.number_of_files { let file = central_header_to_zip_file(reader, dir_info.archive_offset)?; - names_map.insert(file.file_name.clone(), files.len()); - files.push(file); + files.insert(file.file_name.clone(), file); } let dir_end = reader.seek(io::SeekFrom::Start(dir_info.directory_start))?; if dir_info.disk_number != dir_info.disk_with_central_directory { unsupported_zip_error("Support for multi-disk files is not implemented") } else { Ok(Shared { - files: files.into(), - names_map, + files, offset: dir_info.archive_offset, dir_start: dir_info.directory_start, dir_end, @@ -606,7 +602,7 @@ impl ZipArchive { /// Returns an iterator over all the file and directory names in this archive. pub fn file_names(&self) -> impl Iterator { - self.shared.names_map.keys().map(Box::borrow) + self.shared.files.keys().map(|s| s.as_ref()) } /// Search for a file entry by name, decrypt with given password @@ -634,7 +630,7 @@ impl ZipArchive { /// Get the index of a file entry by name, if it's present. #[inline(always)] pub fn index_for_name(&self, name: &str) -> Option { - self.shared.names_map.get(name).copied() + self.shared.files.get_index_of(&*name) } /// Get the index of a file entry by path, if it's present. @@ -648,8 +644,8 @@ impl ZipArchive { pub fn name_for_index(&self, index: usize) -> Option<&str> { self.shared .files - .get(index) - .map(|file_data| &*file_data.file_name) + .get_index(index) + .map(|(name, _)| name.as_ref()) } fn by_name_with_optional_password<'a>( @@ -657,7 +653,7 @@ impl ZipArchive { name: &str, password: Option<&[u8]>, ) -> ZipResult> { - let Some(index) = self.index_for_name(name) else { + let Some(index) = self.shared.files.get_index_of(name) else { return Err(ZipError::FileNotFound); }; self.by_index_with_optional_password(index, password) @@ -692,17 +688,16 @@ impl ZipArchive { /// Get a contained file by index without decompressing it pub fn by_index_raw(&mut self, file_number: usize) -> ZipResult> { let reader = &mut self.reader; - self.shared + let (_, data) = self + .shared .files - .get(file_number) - .ok_or(ZipError::FileNotFound) - .and_then(move |data| { - Ok(ZipFile { - crypto_reader: None, - reader: ZipFileReader::Raw(find_content(data, reader)?), - data: Cow::Borrowed(data), - }) - }) + .get_index(file_number) + .ok_or(ZipError::FileNotFound)?; + Ok(ZipFile { + crypto_reader: None, + reader: ZipFileReader::Raw(find_content(data, reader)?), + data: Cow::Borrowed(data), + }) } fn by_index_with_optional_password( @@ -710,10 +705,10 @@ impl ZipArchive { file_number: usize, mut password: Option<&[u8]>, ) -> ZipResult> { - let data = self + let (_, data) = self .shared .files - .get(file_number) + .get_index(file_number) .ok_or(ZipError::FileNotFound)?; match (password, data.encrypted) { diff --git a/src/write.rs b/src/write.rs index 0051f253d..e6741406d 100644 --- a/src/write.rs +++ b/src/write.rs @@ -9,7 +9,7 @@ use byteorder::{LittleEndian, WriteBytesExt}; #[cfg(any(feature = "_deflate-any", feature = "bzip2", feature = "zstd",))] use core::num::NonZeroU64; use crc32fast::Hasher; -use std::collections::HashMap; +use indexmap::IndexMap; use std::default::Default; use std::io; use std::io::prelude::*; @@ -111,8 +111,7 @@ pub(crate) mod zip_writer { /// ``` pub struct ZipWriter { pub(super) inner: GenericZipWriter, - pub(super) files: Vec, - pub(super) files_by_name: HashMap, usize>, + pub(super) files: IndexMap, ZipFileData>, pub(super) stats: ZipWriterStats, pub(super) writing_to_file: bool, pub(super) writing_raw: bool, @@ -435,7 +434,7 @@ impl Write for ZipWriter { if let Ok(count) = write_result { self.stats.update(&buf[0..count]); if self.stats.bytes_written > spec::ZIP64_BYTES_THR - && !self.files.last_mut().unwrap().large_file + && !self.files.last_mut().unwrap().1.large_file { self.abort_file().unwrap(); return Err(io::Error::new( @@ -479,8 +478,7 @@ impl ZipWriter { Ok(ZipWriter { inner: Storer(MaybeEncrypted::Unencrypted(readwriter)), - files: metadata.files.into(), - files_by_name: metadata.names_map, + files: metadata.files, stats: Default::default(), writing_to_file: false, comment: footer.zip_file_comment, @@ -608,8 +606,7 @@ impl ZipWriter { pub fn new(inner: W) -> ZipWriter { ZipWriter { inner: Storer(MaybeEncrypted::Unencrypted(inner)), - files: Vec::new(), - files_by_name: HashMap::new(), + files: IndexMap::new(), stats: Default::default(), writing_to_file: false, writing_raw: false, @@ -808,15 +805,12 @@ impl ZipWriter { } fn insert_file_data(&mut self, file: ZipFileData) -> ZipResult { - let name = &file.file_name; - if self.files_by_name.contains_key(name) { + if self.files.contains_key(&file.file_name) { return Err(InvalidArchive("Duplicate filename")); } - let name = name.to_owned(); - self.files.push(file); - let index = self.files.len() - 1; - self.files_by_name.insert(name, index); - Ok(index) + let name = file.file_name.to_owned(); + self.files.insert(name.clone(), file); + Ok(self.files.get_index_of(&name).unwrap()) } fn finish_file(&mut self) -> ZipResult<()> { @@ -837,7 +831,7 @@ impl ZipWriter { if !self.writing_raw { let file = match self.files.last_mut() { None => return Ok(()), - Some(f) => f, + Some((_, f)) => f, }; file.crc32 = self.stats.hasher.clone().finalize(); file.uncompressed_size = self.stats.bytes_written; @@ -877,8 +871,7 @@ impl ZipWriter { /// Removes the file currently being written from the archive if there is one, or else removes /// the file most recently written. pub fn abort_file(&mut self) -> ZipResult<()> { - let last_file = self.files.pop().ok_or(ZipError::FileNotFound)?; - self.files_by_name.remove(&last_file.file_name); + let (_, last_file) = self.files.pop().ok_or(ZipError::FileNotFound)?; let make_plain_writer = self.inner.prepare_next_writer( Stored, None, @@ -891,7 +884,7 @@ impl ZipWriter { // overwrite a valid file and corrupt the archive let rewind_safe: bool = match last_file.data_start.get() { None => self.files.is_empty(), - Some(last_file_start) => self.files.iter().all(|file| { + Some(last_file_start) => self.files.values().all(|file| { file.data_start .get() .is_some_and(|start| start < last_file_start) @@ -1184,7 +1177,7 @@ impl ZipWriter { let writer = self.inner.get_plain(); let central_start = writer.stream_position()?; - for file in self.files.iter() { + for file in self.files.values() { write_central_directory_header(writer, file)?; } let central_size = writer.stream_position()? - central_start; @@ -1230,7 +1223,10 @@ impl ZipWriter { } fn index_by_name(&self, name: &str) -> ZipResult { - Ok(*self.files_by_name.get(name).ok_or(ZipError::FileNotFound)?) + Ok(self + .files + .get_index_of(name) + .ok_or(ZipError::FileNotFound)?) } /// Adds another entry to the central directory referring to the same content as an existing