diff --git a/examples/write_dir.rs b/examples/write_dir.rs index e03e6c968..dcaebe3b5 100644 --- a/examples/write_dir.rs +++ b/examples/write_dir.rs @@ -3,7 +3,7 @@ use anyhow::Context; use clap::{Parser, ValueEnum}; use std::io::prelude::*; -use zip::{result::ZipError, write::SimpleFileOptions}; +use zip::{cfg_if_expr, result::ZipError, write::SimpleFileOptions}; use std::fs::File; use std::path::{Path, PathBuf}; @@ -30,59 +30,51 @@ enum CompressionMethod { Zstd, } -fn main() { - std::process::exit(real_main()); -} - -fn real_main() -> i32 { +fn main() -> ! { let args = Args::parse(); let src_dir = &args.source; let dst_file = &args.destination; let method = match args.compression_method { CompressionMethod::Stored => zip::CompressionMethod::Stored, - CompressionMethod::Deflated => { - #[cfg(not(feature = "deflate-flate2"))] - { + CompressionMethod::Deflated => cfg_if_expr! { + #[cfg(feature = "deflate-flate2")] => zip::CompressionMethod::Deflated, + _ => { println!("The `deflate-flate2` feature is not enabled"); - return 1; + std::process::exit(1) } - #[cfg(feature = "deflate-flate2")] - zip::CompressionMethod::Deflated - } - CompressionMethod::Bzip2 => { - #[cfg(not(feature = "bzip2"))] - { + }, + CompressionMethod::Bzip2 => cfg_if_expr! { + #[cfg(feature = "bzip2")] => zip::CompressionMethod::Bzip2, + _ => { println!("The `bzip2` feature is not enabled"); - return 1; + std::process::exit(1) } - #[cfg(feature = "bzip2")] - zip::CompressionMethod::Bzip2 - } - CompressionMethod::Xz => { - #[cfg(not(feature = "xz"))] - { + }, + CompressionMethod::Xz => cfg_if_expr! { + #[cfg(feature = "xz")] => zip::CompressionMethod::Xz, + _ => { println!("The `xz` feature is not enabled"); - return 1; + std::process::exit(1) } - #[cfg(feature = "xz")] - zip::CompressionMethod::Xz - } - CompressionMethod::Zstd => { - #[cfg(not(feature = "zstd"))] - { + }, + CompressionMethod::Zstd => cfg_if_expr! { + #[cfg(feature = "zstd")] => zip::CompressionMethod::Zstd, + _ => { println!("The `zstd` feature is not enabled"); - return 1; + std::process::exit(1) } - #[cfg(feature = "zstd")] - zip::CompressionMethod::Zstd - } + }, }; match doit(src_dir, dst_file, method) { - Ok(_) => println!("done: {src_dir:?} written to {dst_file:?}"), - Err(e) => eprintln!("Error: {e:?}"), + Ok(_) => { + println!("done: {src_dir:?} written to {dst_file:?}"); + std::process::exit(0); + } + Err(e) => { + eprintln!("Error: {e:?}"); + std::process::abort(); + } } - - 0 } fn zip_dir( diff --git a/src/compression.rs b/src/compression.rs index 2d3c999da..6aba53886 100644 --- a/src/compression.rs +++ b/src/compression.rs @@ -2,6 +2,8 @@ use std::{fmt, io}; +use crate::cfg_if_expr; + #[allow(deprecated)] /// Identifies the storage format used to compress a file within a ZIP archive. /// @@ -228,11 +230,10 @@ impl CompressionMethod { impl Default for CompressionMethod { fn default() -> Self { - #[cfg(feature = "_deflate-any")] - return CompressionMethod::Deflated; - - #[cfg(not(feature = "_deflate-any"))] - return CompressionMethod::Stored; + cfg_if_expr! { + #[cfg(feature = "_deflate-any")] => CompressionMethod::Deflated, + _ => CompressionMethod::Stored + } } } diff --git a/src/lib.rs b/src/lib.rs index 9471e9b19..42675badb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ //! #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(missing_docs)] -#![allow(unexpected_cfgs)] // Needed for cfg(fuzzing) on nightly as of 2024-05-06 +#![allow(unexpected_cfgs)] // Needed for cfg(fuzzing) pub use crate::compression::{CompressionMethod, SUPPORTED_COMPRESSION_METHODS}; pub use crate::read::HasZipMetadata; pub use crate::read::ZipArchive; @@ -71,3 +71,6 @@ zip = \"="] #[doc = "\"\n\ ```"] pub mod unstable; + +#[doc(hidden)] +pub mod macros; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 000000000..3d07efd7b --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,162 @@ +//! Macros used internally. +//! +//! These may technically be exported, but that's only to make them available to internal +//! project dependencies. The `#[doc(hidden)]` mark indicates that these are not stable or supported +//! APIs, and should not be relied upon by external dependees. + +/// The single macro export of the [`cfg-if`](https://docs.rs/cfg-if) crate. +/// +/// It is packaged here to avoid pulling in another dependency. The stdlib does the same[^1]. +/// +/// [^1]: https://github.com/rust-lang/rust/blob/a2db9280539229a3b8a084a09886670a57bc7e9c/library/compiler-builtins/libm/src/math/support/macros.rs#L1 +#[doc(hidden)] +#[macro_export] +macro_rules! cfg_if { + // match if/else chains with a final `else` + ( + $( + if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* } + ) else+ + else { $( $e_tokens:tt )* } + ) => { + $crate::cfg_if! { + @__items () ; + $( + (( $i_meta ) ( $( $i_tokens )* )) , + )+ + (() ( $( $e_tokens )* )) , + }; + }; + + // match if/else chains lacking a final `else` + ( + if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* } + $( + else if #[cfg( $e_meta:meta )] { $( $e_tokens:tt )* } + )* + ) => { + $crate::cfg_if! { + @__items () ; + (( $i_meta ) ( $( $i_tokens )* )) , + $( + (( $e_meta ) ( $( $e_tokens )* )) , + )* + }; + }; + + // Internal and recursive macro to emit all the items + // + // Collects all the previous cfgs in a list at the beginning, so they can be + // negated. After the semicolon are all the remaining items. + (@__items ( $( $_:meta , )* ) ; ) => {}; + ( + @__items ( $( $no:meta , )* ) ; + (( $( $yes:meta )? ) ( $( $tokens:tt )* )) , + $( $rest:tt , )* + ) => { + // Emit all items within one block, applying an appropriate #[cfg]. The + // #[cfg] will require all `$yes` matchers specified and must also negate + // all previous matchers. + #[cfg(all( + $( $yes , )? + not(any( $( $no ),* )) + ))] + $crate::cfg_if! { @__identity $( $tokens )* } + + // Recurse to emit all other items in `$rest`, and when we do so add all + // our `$yes` matchers to the list of `$no` matchers as future emissions + // will have to negate everything we just matched as well. + $crate::cfg_if! { + @__items ( $( $no , )* $( $yes , )? ) ; + $( $rest , )* + }; + }; + + // Internal macro to make __apply work out right for different match types, + // because of how macros match/expand stuff. + (@__identity $( $tokens:tt )* ) => { + $( $tokens )* + }; +} + +/// Similar to [`cfg_if`](cfg_if), but accepts a list of expressions, and generates an internal +/// closure to return each value. +/// +/// The main reason this is necessary is because attaching `#[cfg(...)]` annotations to certain +/// types of statements requires a nightly feature, or `cfg_if` would be enough on its own. This +/// macro's restricted interface allows it to generate a closure as a circumlocution that is legal +/// on stable rust. +/// +/// Note that any `return` operation within the expressions provided to this macro will apply to the +/// generated closure, not the enclosing scope--it cannot be used to interfere with external +/// control flow. +/// +/// The generated closure is non-[`const`](const@keyword), so cannot be used inside `const` methods. +#[doc(hidden)] +#[macro_export] +macro_rules! cfg_if_expr { + // Match =>, chains, maybe with a final _ => catchall clause. + ( + $( $ret_ty:ty : )? + $( + #[cfg( $i_meta:meta )] => $i_val:expr + ),+ , + _ => $rem_val:expr $(,)? + ) => { + (|| $( -> $ret_ty )? { + $crate::cfg_if_expr! { + @__items (); + $( + (( $i_meta ) ( + #[allow(unreachable_code)] + return $i_val ; + )) , + )+ + (() ( + #[allow(unreachable_code)] + return $rem_val ; + )) , + } + })() + }; + // Match =>, chains *without* any _ => clause. + ( + $( $ret_ty:ty : )? + $( + #[cfg( $i_meta:meta )] => $i_val:expr + ),+ $(,)? + ) => { + (|| $( -> $ret_ty )? { + $crate::cfg_if_expr! { + @__items (); + $( + (( $i_meta ) ( + #[allow(unreachable_code)] + return $i_val ; + )) , + )+ + } + })() + }; + + (@__items ( $( $_:meta , )* ) ; ) => {}; + ( + @__items ( $( $no:meta , )* ) ; + (( $( $yes:meta )? ) ( $( $tokens:tt )* )) , + $( $rest:tt , )* + ) => { + #[cfg(all( + $( $yes , )? + not(any( $( $no ),* )) + ))] + $crate::cfg_if_expr! { @__identity $( $tokens )* } + + $crate::cfg_if_expr! { + @__items ( $( $no , )* $( $yes , )? ) ; + $( $rest , )* + }; + }; + (@__identity $( $tokens:tt )* ) => { + $( $tokens )* + }; +} diff --git a/src/read.rs b/src/read.rs index 68f6331f7..514700c2a 100644 --- a/src/read.rs +++ b/src/read.rs @@ -2,6 +2,7 @@ #[cfg(feature = "aes-crypto")] use crate::aes::{AesReader, AesReaderValid}; +use crate::cfg_if; use crate::compression::{CompressionMethod, Decompressor}; use crate::cp437::FromCp437; use crate::crc32::Crc32Reader; @@ -171,17 +172,21 @@ impl<'a, R: Read> CryptoReader<'a, R> { } /// Returns `true` if the data is encrypted using AE2. + #[allow(clippy::needless_return)] pub const fn is_ae2_encrypted(&self) -> bool { - #[cfg(feature = "aes-crypto")] - return matches!( - self, - CryptoReader::Aes { - vendor_version: AesVendorVersion::Ae2, - .. + cfg_if! { + if #[cfg(feature = "aes-crypto")] { + return matches!( + self, + CryptoReader::Aes { + vendor_version: AesVendorVersion::Ae2, + .. + } + ); + } else { + return false; } - ); - #[cfg(not(feature = "aes-crypto"))] - false + } } } @@ -454,34 +459,32 @@ pub(crate) fn make_symlink( return Err(invalid!("Invalid UTF-8 as symlink target")); }; - #[cfg(not(any(unix, windows)))] - { - use std::fs::File; - let output = File::create(outpath); - output?.write_all(target)?; - } - #[cfg(unix)] - { - std::os::unix::fs::symlink(Path::new(&target_str), outpath)?; - } - #[cfg(windows)] - { - let target = Path::new(OsStr::new(&target_str)); - let target_is_dir_from_archive = - existing_files.contains_key(target_str) && is_dir(target_str); - let target_is_dir = if target_is_dir_from_archive { - true - } else if let Ok(meta) = std::fs::metadata(target) { - meta.is_dir() - } else { - false - }; - if target_is_dir { - std::os::windows::fs::symlink_dir(target, outpath)?; + cfg_if! { + if #[cfg(unix)] { + std::os::unix::fs::symlink(Path::new(&target_str), outpath)?; + } else if #[cfg(windows)] { + let target = Path::new(OsStr::new(&target_str)); + let target_is_dir_from_archive = + existing_files.contains_key(target_str) && is_dir(target_str); + let target_is_dir = if target_is_dir_from_archive { + true + } else if let Ok(meta) = std::fs::metadata(target) { + meta.is_dir() + } else { + false + }; + if target_is_dir { + std::os::windows::fs::symlink_dir(target, outpath)?; + } else { + std::os::windows::fs::symlink_file(target, outpath)?; + } } else { - std::os::windows::fs::symlink_file(target, outpath)?; + use std::fs::File; + let output = File::create(outpath); + output?.write_all(target)?; } } + Ok(()) } @@ -533,6 +536,33 @@ impl<'a> TryFrom<&'a CentralDirectoryEndInfo> for CentralDirectoryInfo { } } +/// Store all entries which specify a numeric "mode" which is familiar to POSIX operating systems. +#[cfg(unix)] +#[derive(Default, Debug)] +struct FilesByUnixMode { + map: std::collections::BTreeMap, +} + +#[cfg(unix)] +impl FilesByUnixMode { + pub fn add_mode(&mut self, path: PathBuf, mode: u32) { + // We don't print a warning or consider it remotely out of the ordinary to receive two + // separate modes for the same path: just take the later one. + let _ = self.map.insert(path, mode); + } + + // Child nodes will be sorted later lexicographically, so reversing the order puts them first. + pub fn all_perms_with_children_first( + self, + ) -> impl IntoIterator { + use std::os::unix::fs::PermissionsExt; + self.map + .into_iter() + .rev() + .map(|(p, m)| (p, std::fs::Permissions::from_mode(m))) + } +} + impl ZipArchive { pub(crate) fn from_finalized_writer( files: IndexMap, ZipFileData>, @@ -882,66 +912,60 @@ impl ZipArchive { .transpose()?; #[cfg(unix)] - let mut files_by_unix_mode = Vec::new(); + let mut files_by_unix_mode = FilesByUnixMode::default(); for i in 0..self.len() { let mut file = self.by_index(i)?; let mut outpath = directory.clone(); + /* TODO: the control flow of this method call and subsequent expectations about the + * values in this loop is extremely difficult to follow. It also appears to + * perform a nested loop upon extracting every single file entry? Why does it + * accept two arguments that point to the same directory path, one mutable? */ file.safe_prepare_path(directory.as_ref(), &mut outpath, root_dir.as_ref())?; - let symlink_target = if file.is_symlink() && (cfg!(unix) || cfg!(windows)) { + let mut symlink_target: Option> = None; + #[cfg(any(unix, windows))] + if file.is_symlink() { let mut target = Vec::with_capacity(file.size() as usize); file.read_to_end(&mut target)?; - Some(target) - } else { - if file.is_dir() { - crate::read::make_writable_dir_all(&outpath)?; - continue; - } - None - }; - - drop(file); + symlink_target = Some(target); + } + if symlink_target.is_none() && file.is_dir() { + crate::read::make_writable_dir_all(&outpath)?; + continue; + } if let Some(target) = symlink_target { + drop(file); make_symlink(&outpath, &target, &self.shared.files)?; continue; } - let mut file = self.by_index(i)?; let mut outfile = fs::File::create(&outpath)?; io::copy(&mut file, &mut outfile)?; + + // Check for real permissions, which we'll set in a second pass. #[cfg(unix)] - { - // Check for real permissions, which we'll set in a second pass - if let Some(mode) = file.unix_mode() { - files_by_unix_mode.push((outpath.clone(), mode)); - } + if let Some(mode) = file.unix_mode() { + files_by_unix_mode.add_mode(outpath, mode); } + + // Set original timestamp. #[cfg(feature = "chrono")] - { - // Set original timestamp. - if let Some(last_modified) = file.last_modified() { - if let Some(t) = datetime_to_systemtime(&last_modified) { - outfile.set_modified(t)?; - } + if let Some(last_modified) = file.last_modified() { + if let Some(t) = datetime_to_systemtime(&last_modified) { + outfile.set_modified(t)?; } } } - #[cfg(unix)] - { - use std::cmp::Reverse; - use std::os::unix::fs::PermissionsExt; - if files_by_unix_mode.len() > 1 { - // Ensure we update children's permissions before making a parent unwritable - files_by_unix_mode.sort_by_key(|(path, _)| Reverse(path.clone())); - } - for (path, mode) in files_by_unix_mode.into_iter() { - fs::set_permissions(&path, fs::Permissions::from_mode(mode))?; - } + // Ensure we update children's permissions before making a parent unwritable. + #[cfg(unix)] + for (path, perms) in files_by_unix_mode.all_perms_with_children_first() { + std::fs::set_permissions(path, perms)?; } + Ok(()) } diff --git a/src/types.rs b/src/types.rs index a076d1f4f..5c3018dd8 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,5 @@ //! Types that specify what is contained in a ZIP. +use crate::cfg_if_expr; use crate::cp437::FromCp437; use crate::write::{FileOptionExtension, FileOptions}; use path::{Component, Path, PathBuf}; @@ -713,16 +714,11 @@ impl ZipFileData { system: System::Unix, version_made_by: DEFAULT_VERSION, flags: 0, - encrypted: options.encrypt_with.is_some() || { - #[cfg(feature = "aes-crypto")] - { - options.aes_mode.is_some() - } - #[cfg(not(feature = "aes-crypto"))] - { - false - } - }, + encrypted: options.encrypt_with.is_some() + || cfg_if_expr! { + #[cfg(feature = "aes-crypto")] => options.aes_mode.is_some(), + _ => false + }, using_data_descriptor: false, is_utf8: !file_name.is_ascii(), compression_method, diff --git a/src/write.rs b/src/write.rs index 9c324e460..b0bac6b52 100644 --- a/src/write.rs +++ b/src/write.rs @@ -1692,35 +1692,35 @@ impl GenericZipWriter { ); } - { - #[allow(deprecated)] - #[allow(unreachable_code)] - match compression { - Stored => { - if compression_level.is_some() { - Err(UnsupportedArchive("Unsupported compression level")) - } else { - Ok(Box::new(|bare| Ok(Storer(bare)))) - } + match compression { + Stored => { + if compression_level.is_some() { + Err(UnsupportedArchive("Unsupported compression level")) + } else { + Ok(Box::new(|bare| Ok(Storer(bare)))) } - #[cfg(feature = "_deflate-any")] - CompressionMethod::Deflated => { - #[cfg(feature = "deflate-flate2")] - let default = Compression::default().level() as i64; - - #[cfg(all(feature = "deflate-zopfli", not(feature = "deflate-flate2")))] - let default = 24; + } + #[allow(unreachable_code)] + #[cfg(feature = "_deflate-any")] + CompressionMethod::Deflated => { + let default = crate::cfg_if_expr! { + i64: + #[cfg(feature = "deflate-flate2")] => Compression::default().level() as i64, + #[cfg(feature = "deflate-zopfli")] => 24, + _ => compile_error!("could not calculate default: unknown deflate variant"), + }; - let level = clamp_opt( - compression_level.unwrap_or(default), - deflate_compression_level_range(), - ) - .ok_or(UnsupportedArchive("Unsupported compression level"))? - as u32; + let level = clamp_opt( + compression_level.unwrap_or(default), + deflate_compression_level_range(), + ) + .ok_or(UnsupportedArchive("Unsupported compression level"))? + as u32; - #[cfg(feature = "deflate-zopfli")] + #[cfg(feature = "deflate-zopfli")] + { macro_rules! deflate_zopfli_and_return { - ($bare:expr, $best_non_zopfli:expr) => { + ($bare:expr, $best_non_zopfli:expr) => {{ let options = Options { iteration_count: NonZeroU64::try_from( (level - $best_non_zopfli) as u64, @@ -1728,7 +1728,7 @@ impl GenericZipWriter { .unwrap(), ..Default::default() }; - return Ok(Box::new(move |bare| { + Ok(Box::new(move |bare| { Ok(match zopfli_buffer_size { Some(size) => GenericZipWriter::BufferedZopfliDeflater( BufWriter::with_capacity( @@ -1748,148 +1748,143 @@ impl GenericZipWriter { ), ), }) - })); - }; + })) + }}; } - #[cfg(all(feature = "deflate-zopfli", feature = "deflate-flate2"))] - { - let best_non_zopfli = Compression::best().level(); - if level > best_non_zopfli { - deflate_zopfli_and_return!(bare, best_non_zopfli); + crate::cfg_if! { + if #[cfg(feature = "deflate-flate2")] { + let best_non_zopfli = Compression::best().level(); + if level > best_non_zopfli { + return deflate_zopfli_and_return!(bare, best_non_zopfli); + } + } else { + let best_non_zopfli = 9; + return deflate_zopfli_and_return!(bare, best_non_zopfli); } } - - #[cfg(all(feature = "deflate-zopfli", not(feature = "deflate-flate2")))] - { - let best_non_zopfli = 9; - deflate_zopfli_and_return!(bare, best_non_zopfli); - } - - #[cfg(feature = "deflate-flate2")] - { - Ok(Box::new(move |bare| { - Ok(GenericZipWriter::Deflater(DeflateEncoder::new( - bare, - Compression::new(level), - ))) - })) - } } - #[cfg(feature = "deflate64")] - CompressionMethod::Deflate64 => { - Err(UnsupportedArchive("Compressing Deflate64 is not supported")) - } - #[cfg(feature = "bzip2")] - CompressionMethod::Bzip2 => { - let level = clamp_opt( - compression_level.unwrap_or(bzip2::Compression::default().level() as i64), - bzip2_compression_level_range(), - ) - .ok_or(UnsupportedArchive("Unsupported compression level"))? - as u32; - Ok(Box::new(move |bare| { - Ok(GenericZipWriter::Bzip2(BzEncoder::new( + + crate::cfg_if_expr! { + ZipResult>: + #[cfg(feature = "deflate-flate2")] => Ok(Box::new(move |bare| { + Ok(GenericZipWriter::Deflater(DeflateEncoder::new( bare, - bzip2::Compression::new(level), + Compression::new(level), ))) - })) + })), + _ => unreachable!("deflate writer: have no fallback for this case") } - CompressionMethod::AES => Err(UnsupportedArchive( - "AES encryption is enabled through FileOptions::with_aes_encryption", - )), - #[cfg(feature = "zstd")] - CompressionMethod::Zstd => { - let level = clamp_opt( - compression_level.unwrap_or(zstd::DEFAULT_COMPRESSION_LEVEL as i64), - zstd::compression_level_range(), - ) - .ok_or(UnsupportedArchive("Unsupported compression level"))?; - Ok(Box::new(move |bare| { - Ok(GenericZipWriter::Zstd( - ZstdEncoder::new(bare, level as i32).map_err(ZipError::Io)?, - )) - })) - } - #[cfg(feature = "legacy-zip")] - CompressionMethod::Shrink => Err(ZipError::UnsupportedArchive( - "Shrink compression unsupported", - )), - #[cfg(feature = "legacy-zip")] - CompressionMethod::Reduce(_) => Err(ZipError::UnsupportedArchive( - "Reduce compression unsupported", - )), - #[cfg(feature = "legacy-zip")] - CompressionMethod::Implode => Err(ZipError::UnsupportedArchive( - "Implode compression unsupported", - )), - #[cfg(feature = "lzma")] - CompressionMethod::Lzma => { - Err(UnsupportedArchive("LZMA isn't supported for compression")) - } - #[cfg(feature = "xz")] - CompressionMethod::Xz => { - let level = clamp_opt(compression_level.unwrap_or(6), 0..=9) - .ok_or(UnsupportedArchive("Unsupported compression level"))? - as u32; - Ok(Box::new(move |bare| { - Ok(GenericZipWriter::Xz(Box::new( - lzma_rust2::XzWriter::new( - bare, - lzma_rust2::XzOptions::with_preset(level), - ) + } + #[cfg(feature = "deflate64")] + CompressionMethod::Deflate64 => { + Err(UnsupportedArchive("Compressing Deflate64 is not supported")) + } + #[cfg(feature = "bzip2")] + CompressionMethod::Bzip2 => { + let level = clamp_opt( + compression_level.unwrap_or(bzip2::Compression::default().level() as i64), + bzip2_compression_level_range(), + ) + .ok_or(UnsupportedArchive("Unsupported compression level"))? + as u32; + Ok(Box::new(move |bare| { + Ok(GenericZipWriter::Bzip2(BzEncoder::new( + bare, + bzip2::Compression::new(level), + ))) + })) + } + CompressionMethod::AES => Err(UnsupportedArchive( + "AES encryption is enabled through FileOptions::with_aes_encryption", + )), + #[cfg(feature = "zstd")] + CompressionMethod::Zstd => { + let level = clamp_opt( + compression_level.unwrap_or(zstd::DEFAULT_COMPRESSION_LEVEL as i64), + zstd::compression_level_range(), + ) + .ok_or(UnsupportedArchive("Unsupported compression level"))?; + Ok(Box::new(move |bare| { + Ok(GenericZipWriter::Zstd( + ZstdEncoder::new(bare, level as i32).map_err(ZipError::Io)?, + )) + })) + } + #[cfg(feature = "legacy-zip")] + CompressionMethod::Shrink => Err(ZipError::UnsupportedArchive( + "Shrink compression unsupported", + )), + #[cfg(feature = "legacy-zip")] + CompressionMethod::Reduce(_) => Err(ZipError::UnsupportedArchive( + "Reduce compression unsupported", + )), + #[cfg(feature = "legacy-zip")] + CompressionMethod::Implode => Err(ZipError::UnsupportedArchive( + "Implode compression unsupported", + )), + #[cfg(feature = "lzma")] + CompressionMethod::Lzma => { + Err(UnsupportedArchive("LZMA isn't supported for compression")) + } + #[cfg(feature = "xz")] + CompressionMethod::Xz => { + let level = clamp_opt(compression_level.unwrap_or(6), 0..=9) + .ok_or(UnsupportedArchive("Unsupported compression level"))? + as u32; + Ok(Box::new(move |bare| { + Ok(GenericZipWriter::Xz(Box::new( + lzma_rust2::XzWriter::new(bare, lzma_rust2::XzOptions::with_preset(level)) .map_err(ZipError::Io)?, - ))) - })) - } - #[cfg(feature = "ppmd")] - CompressionMethod::Ppmd => { - const ORDERS: [u32; 10] = [0, 4, 5, 6, 7, 8, 9, 10, 11, 12]; + ))) + })) + } + #[cfg(feature = "ppmd")] + CompressionMethod::Ppmd => { + const ORDERS: [u32; 10] = [0, 4, 5, 6, 7, 8, 9, 10, 11, 12]; - let level = clamp_opt(compression_level.unwrap_or(7), 1..=9) - .ok_or(UnsupportedArchive("Unsupported compression level"))? - as u32; + let level = clamp_opt(compression_level.unwrap_or(7), 1..=9) + .ok_or(UnsupportedArchive("Unsupported compression level"))? + as u32; - let order = ORDERS[level as usize]; - let memory_size = 1 << (level + 19); - let memory_size_mb = memory_size / 1024 / 1024; + let order = ORDERS[level as usize]; + let memory_size = 1 << (level + 19); + let memory_size_mb = memory_size / 1024 / 1024; - Ok(Box::new(move |mut bare| { - let parameter: u16 = (order as u16 - 1) - + ((memory_size_mb - 1) << 4) as u16 - + ((ppmd_rust::RestoreMethod::Restart as u16) << 12); + Ok(Box::new(move |mut bare| { + let parameter: u16 = (order as u16 - 1) + + ((memory_size_mb - 1) << 4) as u16 + + ((ppmd_rust::RestoreMethod::Restart as u16) << 12); - bare.write_all(¶meter.to_le_bytes()) - .map_err(ZipError::Io)?; + bare.write_all(¶meter.to_le_bytes()) + .map_err(ZipError::Io)?; - let encoder = ppmd_rust::Ppmd8Encoder::new( - bare, - order, - memory_size, - ppmd_rust::RestoreMethod::Restart, - ) - .map_err(|error| match error { - ppmd_rust::Error::RangeDecoderInitialization => { - ZipError::InvalidArchive( - "PPMd range coder initialization failed".into(), - ) - } - ppmd_rust::Error::InvalidParameter => { - ZipError::InvalidArchive("Invalid PPMd parameter".into()) - } - ppmd_rust::Error::IoError(io_error) => ZipError::Io(io_error), - ppmd_rust::Error::MemoryAllocation => ZipError::Io(io::Error::new( - ErrorKind::OutOfMemory, - "PPMd could not allocate memory", - )), - })?; - - Ok(GenericZipWriter::Ppmd(Box::new(encoder))) - })) - } - CompressionMethod::Unsupported(..) => { - Err(UnsupportedArchive("Unsupported compression")) - } + let encoder = ppmd_rust::Ppmd8Encoder::new( + bare, + order, + memory_size, + ppmd_rust::RestoreMethod::Restart, + ) + .map_err(|error| match error { + ppmd_rust::Error::RangeDecoderInitialization => ZipError::InvalidArchive( + "PPMd range coder initialization failed".into(), + ), + ppmd_rust::Error::InvalidParameter => { + ZipError::InvalidArchive("Invalid PPMd parameter".into()) + } + ppmd_rust::Error::IoError(io_error) => ZipError::Io(io_error), + ppmd_rust::Error::MemoryAllocation => ZipError::Io(io::Error::new( + ErrorKind::OutOfMemory, + "PPMd could not allocate memory", + )), + })?; + + Ok(GenericZipWriter::Ppmd(Box::new(encoder))) + })) + } + #[allow(deprecated)] + CompressionMethod::Unsupported(..) => { + Err(UnsupportedArchive("Unsupported compression")) } } } @@ -1971,15 +1966,19 @@ impl GenericZipWriter { #[cfg(feature = "_deflate-any")] fn deflate_compression_level_range() -> std::ops::RangeInclusive { - #[cfg(feature = "deflate-flate2")] - let min = Compression::fast().level() as i64; - #[cfg(all(feature = "deflate-zopfli", not(feature = "deflate-flate2")))] - let min = 1; + let min = crate::cfg_if_expr! { + i64: + #[cfg(feature = "deflate-flate2")] => Compression::fast().level() as i64, + #[cfg(feature = "deflate-zopfli")] => 1, + _ => compile_error!("min: unknown deflate variant"), + }; - #[cfg(feature = "deflate-zopfli")] - let max = 264; - #[cfg(all(feature = "deflate-flate2", not(feature = "deflate-zopfli")))] - let max = Compression::best().level() as i64; + let max = crate::cfg_if_expr! { + i64: + #[cfg(feature = "deflate-zopfli")] => 264, + #[cfg(feature = "deflate-flate2")] => Compression::best().level() as i64, + _ => compile_error!("max: unknown deflate variant"), + }; min..=max } diff --git a/src/zipcrypto.rs b/src/zipcrypto.rs index 7ad017c8c..cc95fad18 100644 --- a/src/zipcrypto.rs +++ b/src/zipcrypto.rs @@ -7,6 +7,7 @@ use std::fmt::{Debug, Formatter}; use std::hash::Hash; use std::num::Wrapping; +use crate::cfg_if_expr; use crate::result::ZipError; /// A container to hold the current key state @@ -21,19 +22,21 @@ pub(crate) struct ZipCryptoKeys { impl Debug for ZipCryptoKeys { #[allow(unreachable_code)] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - #[cfg(not(any(test, fuzzing)))] - { - use std::collections::hash_map::DefaultHasher; - use std::hash::Hasher; - let mut t = DefaultHasher::new(); - self.hash(&mut t); - f.write_fmt(format_args!("ZipCryptoKeys(hash {})", t.finish())) + cfg_if_expr! { + #[cfg(any(test, fuzzing))] => { + f.write_fmt(format_args!( + "ZipCryptoKeys::of({:#10x},{:#10x},{:#10x})", + self.key_0, self.key_1, self.key_2 + )) + }, + _ => { + use std::collections::hash_map::DefaultHasher; + use std::hash::Hasher; + let mut t = DefaultHasher::new(); + self.hash(&mut t); + f.write_fmt(format_args!("ZipCryptoKeys(hash {})", t.finish())) + } } - #[cfg(any(test, fuzzing))] - f.write_fmt(format_args!( - "ZipCryptoKeys::of({:#10x},{:#10x},{:#10x})", - self.key_0, self.key_1, self.key_2 - )) } }