From d1b2d29528b7794b41e66fc2136e395a02f8529b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 30 May 2024 05:59:35 +0200 Subject: Merging upstream version 1.73.0+dfsg1. Signed-off-by: Daniel Baumann --- vendor/zip/src/aes_ctr.rs | 11 +- vendor/zip/src/lib.rs | 11 ++ vendor/zip/src/read.rs | 64 +++----- vendor/zip/src/read/stream.rs | 372 ++++++++++++++++++++++++++++++++++++++++++ vendor/zip/src/types.rs | 72 +++++--- vendor/zip/src/unstable.rs | 20 +++ vendor/zip/src/write.rs | 67 ++++++-- vendor/zip/src/zipcrypto.rs | 46 ++++-- 8 files changed, 566 insertions(+), 97 deletions(-) create mode 100644 vendor/zip/src/read/stream.rs create mode 100644 vendor/zip/src/unstable.rs (limited to 'vendor/zip/src') diff --git a/vendor/zip/src/aes_ctr.rs b/vendor/zip/src/aes_ctr.rs index 0f34335cb..211727c0a 100644 --- a/vendor/zip/src/aes_ctr.rs +++ b/vendor/zip/src/aes_ctr.rs @@ -2,10 +2,11 @@ //! //! This was implemented since the zip specification requires the mode to not use a nonce and uses a //! different byte order (little endian) than NIST (big endian). -//! See [AesCtrZipKeyStream](./struct.AesCtrZipKeyStream.html) for more information. +//! See [AesCtrZipKeyStream] for more information. use aes::cipher::generic_array::GenericArray; -use aes::{BlockEncrypt, NewBlockCipher}; +// use aes::{BlockEncrypt, NewBlockCipher}; +use aes::cipher::{BlockEncrypt, KeyInit}; use byteorder::WriteBytesExt; use std::{any, fmt}; @@ -82,7 +83,7 @@ where impl AesCtrZipKeyStream where C: AesKind, - C::Cipher: NewBlockCipher, + C::Cipher: KeyInit, { /// Creates a new zip variant AES-CTR key stream. /// @@ -150,14 +151,14 @@ fn xor(dest: &mut [u8], src: &[u8]) { #[cfg(test)] mod tests { use super::{Aes128, Aes192, Aes256, AesCipher, AesCtrZipKeyStream, AesKind}; - use aes::{BlockEncrypt, NewBlockCipher}; + use aes::cipher::{BlockEncrypt, KeyInit}; /// Checks whether `crypt_in_place` produces the correct plaintext after one use and yields the /// cipertext again after applying it again. fn roundtrip(key: &[u8], ciphertext: &mut [u8], expected_plaintext: &[u8]) where Aes: AesKind, - Aes::Cipher: NewBlockCipher + BlockEncrypt, + Aes::Cipher: KeyInit + BlockEncrypt, { let mut key_stream = AesCtrZipKeyStream::::new(key); diff --git a/vendor/zip/src/lib.rs b/vendor/zip/src/lib.rs index 0fee99cc8..e2228e5b1 100644 --- a/vendor/zip/src/lib.rs +++ b/vendor/zip/src/lib.rs @@ -42,3 +42,14 @@ mod spec; mod types; pub mod write; mod zipcrypto; + +/// Unstable APIs +/// +/// All APIs accessible by importing this module are unstable; They may be changed in patch releases. +/// You MUST you an exact version specifier in `Cargo.toml`, to indicate the version of this API you're using: +/// +/// ```toml +/// [dependencies] +/// zip = "=0.6.6" +/// ``` +pub mod unstable; diff --git a/vendor/zip/src/read.rs b/vendor/zip/src/read.rs index dad20c260..b702b4f21 100644 --- a/vendor/zip/src/read.rs +++ b/vendor/zip/src/read.rs @@ -13,7 +13,7 @@ use byteorder::{LittleEndian, ReadBytesExt}; use std::borrow::Cow; use std::collections::HashMap; use std::io::{self, prelude::*}; -use std::path::{Component, Path}; +use std::path::Path; use std::sync::Arc; #[cfg(any( @@ -29,10 +29,8 @@ use bzip2::read::BzDecoder; #[cfg(feature = "zstd")] use zstd::stream::read::Decoder as ZstdDecoder; -mod ffi { - pub const S_IFDIR: u32 = 0o0040000; - pub const S_IFREG: u32 = 0o0100000; -} +/// Provides high level API for reading from a stream. +pub(crate) mod stream; // Put the struct declaration in a private module to convince rustdoc to display ZipArchive nicely pub(crate) mod zip_archive { @@ -650,12 +648,22 @@ pub(crate) fn central_header_to_zip_file( archive_offset: u64, ) -> ZipResult { let central_header_start = reader.stream_position()?; + // Parse central header let signature = reader.read_u32::()?; if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE { - return Err(ZipError::InvalidArchive("Invalid Central Directory header")); + Err(ZipError::InvalidArchive("Invalid Central Directory header")) + } else { + central_header_to_zip_file_inner(reader, archive_offset, central_header_start) } +} +/// Parse a central directory entry to collect the information for the file. +fn central_header_to_zip_file_inner( + reader: &mut R, + archive_offset: u64, + central_header_start: u64, +) -> ZipResult { let version_made_by = reader.read_u16::()?; let _version_to_extract = reader.read_u16::()?; let flags = reader.read_u16::()?; @@ -896,20 +904,7 @@ impl<'a> ZipFile<'a> { /// to path-based exploits. It is recommended over /// [`ZipFile::mangled_name`]. pub fn enclosed_name(&self) -> Option<&Path> { - if self.data.file_name.contains('\0') { - return None; - } - let path = Path::new(&self.data.file_name); - let mut depth = 0usize; - for component in path.components() { - match component { - Component::Prefix(_) | Component::RootDir => return None, - Component::ParentDir => depth = depth.checked_sub(1)?, - Component::Normal(_) => depth += 1, - Component::CurDir => (), - } - } - Some(path) + self.data.enclosed_name() } /// Get the comment of the file @@ -952,27 +947,7 @@ impl<'a> ZipFile<'a> { /// Get unix mode for the file pub fn unix_mode(&self) -> Option { - if self.data.external_attributes == 0 { - return None; - } - - match self.data.system { - System::Unix => Some(self.data.external_attributes >> 16), - System::Dos => { - // Interpret MS-DOS directory bit - let mut mode = if 0x10 == (self.data.external_attributes & 0x10) { - ffi::S_IFDIR | 0o0775 - } else { - ffi::S_IFREG | 0o0664 - }; - if 0x01 == (self.data.external_attributes & 0x01) { - // Read-only bit; strip write permissions - mode &= 0o0555; - } - Some(mode) - } - _ => None, - } + self.data.unix_mode() } /// Get the CRC32 hash of the original file @@ -1029,10 +1004,9 @@ impl<'a> Drop for ZipFile<'a> { match reader.read(&mut buffer) { Ok(0) => break, Ok(_) => (), - Err(e) => panic!( - "Could not consume all of the output of the current ZipFile: {:?}", - e - ), + Err(e) => { + panic!("Could not consume all of the output of the current ZipFile: {e:?}") + } } } } diff --git a/vendor/zip/src/read/stream.rs b/vendor/zip/src/read/stream.rs new file mode 100644 index 000000000..5a01b23f9 --- /dev/null +++ b/vendor/zip/src/read/stream.rs @@ -0,0 +1,372 @@ +use std::fs; +use std::io::{self, Read}; +use std::path::Path; + +use super::{ + central_header_to_zip_file_inner, read_zipfile_from_stream, spec, ZipError, ZipFile, + ZipFileData, ZipResult, +}; + +use byteorder::{LittleEndian, ReadBytesExt}; + +/// Stream decoder for zip. +#[derive(Debug)] +pub struct ZipStreamReader(R); + +impl ZipStreamReader { + /// Create a new ZipStreamReader + pub fn new(reader: R) -> Self { + Self(reader) + } +} + +impl ZipStreamReader { + fn parse_central_directory(&mut self) -> ZipResult> { + // Give archive_offset and central_header_start dummy value 0, since + // they are not used in the output. + let archive_offset = 0; + let central_header_start = 0; + + // Parse central header + let signature = self.0.read_u32::()?; + if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE { + Ok(None) + } else { + central_header_to_zip_file_inner(&mut self.0, archive_offset, central_header_start) + .map(ZipStreamFileMetadata) + .map(Some) + } + } + + /// Iteraate over the stream and extract all file and their + /// metadata. + pub fn visit(mut self, visitor: &mut V) -> ZipResult<()> { + while let Some(mut file) = read_zipfile_from_stream(&mut self.0)? { + visitor.visit_file(&mut file)?; + } + + while let Some(metadata) = self.parse_central_directory()? { + visitor.visit_additional_metadata(&metadata)?; + } + + Ok(()) + } + + /// Extract a Zip archive into a directory, overwriting files if they + /// already exist. Paths are sanitized with [`ZipFile::enclosed_name`]. + /// + /// Extraction is not atomic; If an error is encountered, some of the files + /// may be left on disk. + pub fn extract>(self, directory: P) -> ZipResult<()> { + struct Extractor<'a>(&'a Path); + impl ZipStreamVisitor for Extractor<'_> { + fn visit_file(&mut self, file: &mut ZipFile<'_>) -> ZipResult<()> { + let filepath = file + .enclosed_name() + .ok_or(ZipError::InvalidArchive("Invalid file path"))?; + + let outpath = self.0.join(filepath); + + if file.name().ends_with('/') { + fs::create_dir_all(&outpath)?; + } else { + if let Some(p) = outpath.parent() { + fs::create_dir_all(p)?; + } + let mut outfile = fs::File::create(&outpath)?; + io::copy(file, &mut outfile)?; + } + + Ok(()) + } + + #[allow(unused)] + fn visit_additional_metadata( + &mut self, + metadata: &ZipStreamFileMetadata, + ) -> ZipResult<()> { + #[cfg(unix)] + { + let filepath = metadata + .enclosed_name() + .ok_or(ZipError::InvalidArchive("Invalid file path"))?; + + let outpath = self.0.join(filepath); + + use std::os::unix::fs::PermissionsExt; + if let Some(mode) = metadata.unix_mode() { + fs::set_permissions(outpath, fs::Permissions::from_mode(mode))?; + } + } + + Ok(()) + } + } + + self.visit(&mut Extractor(directory.as_ref())) + } +} + +/// Visitor for ZipStreamReader +pub trait ZipStreamVisitor { + /// * `file` - contains the content of the file and most of the metadata, + /// except: + /// - `comment`: set to an empty string + /// - `data_start`: set to 0 + /// - `external_attributes`: `unix_mode()`: will return None + fn visit_file(&mut self, file: &mut ZipFile<'_>) -> ZipResult<()>; + + /// This function is guranteed to be called after all `visit_file`s. + /// + /// * `metadata` - Provides missing metadata in `visit_file`. + fn visit_additional_metadata(&mut self, metadata: &ZipStreamFileMetadata) -> ZipResult<()>; +} + +/// Additional metadata for the file. +#[derive(Debug)] +pub struct ZipStreamFileMetadata(ZipFileData); + +impl ZipStreamFileMetadata { + /// Get the name of the file + /// + /// # Warnings + /// + /// It is dangerous to use this name directly when extracting an archive. + /// It may contain an absolute path (`/etc/shadow`), or break out of the + /// current directory (`../runtime`). Carelessly writing to these paths + /// allows an attacker to craft a ZIP archive that will overwrite critical + /// files. + /// + /// You can use the [`ZipFile::enclosed_name`] method to validate the name + /// as a safe path. + pub fn name(&self) -> &str { + &self.0.file_name + } + + /// Get the name of the file, in the raw (internal) byte representation. + /// + /// The encoding of this data is currently undefined. + pub fn name_raw(&self) -> &[u8] { + &self.0.file_name_raw + } + + /// Rewrite the path, ignoring any path components with special meaning. + /// + /// - Absolute paths are made relative + /// - [`ParentDir`]s are ignored + /// - Truncates the filename at a NULL byte + /// + /// This is appropriate if you need to be able to extract *something* from + /// any archive, but will easily misrepresent trivial paths like + /// `foo/../bar` as `foo/bar` (instead of `bar`). Because of this, + /// [`ZipFile::enclosed_name`] is the better option in most scenarios. + /// + /// [`ParentDir`]: `Component::ParentDir` + pub fn mangled_name(&self) -> ::std::path::PathBuf { + self.0.file_name_sanitized() + } + + /// Ensure the file path is safe to use as a [`Path`]. + /// + /// - It can't contain NULL bytes + /// - It can't resolve to a path outside the current directory + /// > `foo/../bar` is fine, `foo/../../bar` is not. + /// - It can't be an absolute path + /// + /// This will read well-formed ZIP files correctly, and is resistant + /// to path-based exploits. It is recommended over + /// [`ZipFile::mangled_name`]. + pub fn enclosed_name(&self) -> Option<&Path> { + self.0.enclosed_name() + } + + /// Returns whether the file is actually a directory + pub fn is_dir(&self) -> bool { + self.name() + .chars() + .rev() + .next() + .map_or(false, |c| c == '/' || c == '\\') + } + + /// Returns whether the file is a regular file + pub fn is_file(&self) -> bool { + !self.is_dir() + } + + /// Get the comment of the file + pub fn comment(&self) -> &str { + &self.0.file_comment + } + + /// Get the starting offset of the data of the compressed file + pub fn data_start(&self) -> u64 { + self.0.data_start.load() + } + + /// Get unix mode for the file + pub fn unix_mode(&self) -> Option { + self.0.unix_mode() + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::collections::BTreeSet; + use std::io; + + struct DummyVisitor; + impl ZipStreamVisitor for DummyVisitor { + fn visit_file(&mut self, _file: &mut ZipFile<'_>) -> ZipResult<()> { + Ok(()) + } + + fn visit_additional_metadata( + &mut self, + _metadata: &ZipStreamFileMetadata, + ) -> ZipResult<()> { + Ok(()) + } + } + + #[derive(Default, Debug, Eq, PartialEq)] + struct CounterVisitor(u64, u64); + impl ZipStreamVisitor for CounterVisitor { + fn visit_file(&mut self, _file: &mut ZipFile<'_>) -> ZipResult<()> { + self.0 += 1; + Ok(()) + } + + fn visit_additional_metadata( + &mut self, + _metadata: &ZipStreamFileMetadata, + ) -> ZipResult<()> { + self.1 += 1; + Ok(()) + } + } + + #[test] + fn invalid_offset() { + ZipStreamReader::new(io::Cursor::new(include_bytes!( + "../../tests/data/invalid_offset.zip" + ))) + .visit(&mut DummyVisitor) + .unwrap_err(); + } + + #[test] + fn invalid_offset2() { + ZipStreamReader::new(io::Cursor::new(include_bytes!( + "../../tests/data/invalid_offset2.zip" + ))) + .visit(&mut DummyVisitor) + .unwrap_err(); + } + + #[test] + fn zip_read_streaming() { + let reader = ZipStreamReader::new(io::Cursor::new(include_bytes!( + "../../tests/data/mimetype.zip" + ))); + + #[derive(Default)] + struct V { + filenames: BTreeSet>, + } + impl ZipStreamVisitor for V { + fn visit_file(&mut self, file: &mut ZipFile<'_>) -> ZipResult<()> { + if file.is_file() { + self.filenames.insert(file.name().into()); + } + + Ok(()) + } + fn visit_additional_metadata( + &mut self, + metadata: &ZipStreamFileMetadata, + ) -> ZipResult<()> { + if metadata.is_file() { + assert!( + self.filenames.contains(metadata.name()), + "{} is missing its file content", + metadata.name() + ); + } + + Ok(()) + } + } + + reader.visit(&mut V::default()).unwrap(); + } + + #[test] + fn file_and_dir_predicates() { + let reader = ZipStreamReader::new(io::Cursor::new(include_bytes!( + "../../tests/data/files_and_dirs.zip" + ))); + + #[derive(Default)] + struct V { + filenames: BTreeSet>, + } + impl ZipStreamVisitor for V { + fn visit_file(&mut self, file: &mut ZipFile<'_>) -> ZipResult<()> { + let full_name = file.enclosed_name().unwrap(); + let file_name = full_name.file_name().unwrap().to_str().unwrap(); + assert!( + (file_name.starts_with("dir") && file.is_dir()) + || (file_name.starts_with("file") && file.is_file()) + ); + + if file.is_file() { + self.filenames.insert(file.name().into()); + } + + Ok(()) + } + fn visit_additional_metadata( + &mut self, + metadata: &ZipStreamFileMetadata, + ) -> ZipResult<()> { + if metadata.is_file() { + assert!( + self.filenames.contains(metadata.name()), + "{} is missing its file content", + metadata.name() + ); + } + + Ok(()) + } + } + + reader.visit(&mut V::default()).unwrap(); + } + + /// test case to ensure we don't preemptively over allocate based on the + /// declared number of files in the CDE of an invalid zip when the number of + /// files declared is more than the alleged offset in the CDE + #[test] + fn invalid_cde_number_of_files_allocation_smaller_offset() { + ZipStreamReader::new(io::Cursor::new(include_bytes!( + "../../tests/data/invalid_cde_number_of_files_allocation_smaller_offset.zip" + ))) + .visit(&mut DummyVisitor) + .unwrap_err(); + } + + /// test case to ensure we don't preemptively over allocate based on the + /// declared number of files in the CDE of an invalid zip when the number of + /// files declared is less than the alleged offset in the CDE + #[test] + fn invalid_cde_number_of_files_allocation_greater_offset() { + ZipStreamReader::new(io::Cursor::new(include_bytes!( + "../../tests/data/invalid_cde_number_of_files_allocation_greater_offset.zip" + ))) + .visit(&mut DummyVisitor) + .unwrap_err(); + } +} diff --git a/vendor/zip/src/types.rs b/vendor/zip/src/types.rs index ad3a5700b..c3d0a45db 100644 --- a/vendor/zip/src/types.rs +++ b/vendor/zip/src/types.rs @@ -1,6 +1,6 @@ //! Types that specify what is contained in a ZIP. -#[cfg(feature = "time")] -use std::convert::{TryFrom, TryInto}; +use std::path; + #[cfg(not(any( all(target_arch = "arm", target_pointer_width = "32"), target_arch = "mips", @@ -12,6 +12,11 @@ use std::time::SystemTime; #[cfg(doc)] use {crate::read::ZipFile, crate::write::FileOptions}; +mod ffi { + pub const S_IFDIR: u32 = 0o0040000; + pub const S_IFREG: u32 = 0o0100000; +} + #[cfg(any( all(target_arch = "arm", target_pointer_width = "32"), target_arch = "mips", @@ -375,6 +380,48 @@ impl ZipFileData { }) } + pub(crate) fn enclosed_name(&self) -> Option<&path::Path> { + if self.file_name.contains('\0') { + return None; + } + let path = path::Path::new(&self.file_name); + let mut depth = 0usize; + for component in path.components() { + match component { + path::Component::Prefix(_) | path::Component::RootDir => return None, + path::Component::ParentDir => depth = depth.checked_sub(1)?, + path::Component::Normal(_) => depth += 1, + path::Component::CurDir => (), + } + } + Some(path) + } + + /// Get unix mode for the file + pub(crate) fn unix_mode(&self) -> Option { + if self.external_attributes == 0 { + return None; + } + + match self.system { + System::Unix => Some(self.external_attributes >> 16), + System::Dos => { + // Interpret MS-DOS directory bit + let mut mode = if 0x10 == (self.external_attributes & 0x10) { + ffi::S_IFDIR | 0o0775 + } else { + ffi::S_IFREG | 0o0664 + }; + if 0x01 == (self.external_attributes & 0x01) { + // Read-only bit; strip write permissions + mode &= 0o0555; + } + Some(mode) + } + _ => None, + } + } + pub fn zip64_extension(&self) -> bool { self.uncompressed_size > 0xFFFFFFFF || self.compressed_size > 0xFFFFFFFF @@ -508,27 +555,6 @@ mod test { #[cfg(feature = "time")] use time::{format_description::well_known::Rfc3339, OffsetDateTime}; - #[cfg(feature = "time")] - #[test] - fn datetime_from_time_bounds() { - use std::convert::TryFrom; - - use super::DateTime; - use time::macros::datetime; - - // 1979-12-31 23:59:59 - assert!(DateTime::try_from(datetime!(1979-12-31 23:59:59 UTC)).is_err()); - - // 1980-01-01 00:00:00 - assert!(DateTime::try_from(datetime!(1980-01-01 00:00:00 UTC)).is_ok()); - - // 2107-12-31 23:59:59 - assert!(DateTime::try_from(datetime!(2107-12-31 23:59:59 UTC)).is_ok()); - - // 2108-01-01 00:00:00 - assert!(DateTime::try_from(datetime!(2108-01-01 00:00:00 UTC)).is_err()); - } - #[cfg(feature = "time")] #[test] fn datetime_try_from_bounds() { diff --git a/vendor/zip/src/unstable.rs b/vendor/zip/src/unstable.rs new file mode 100644 index 000000000..f8b46a970 --- /dev/null +++ b/vendor/zip/src/unstable.rs @@ -0,0 +1,20 @@ +/// Provides high level API for reading from a stream. +pub mod stream { + pub use crate::read::stream::*; +} +/// Types for creating ZIP archives. +pub mod write { + use crate::write::FileOptions; + /// Unstable methods for [`FileOptions`]. + pub trait FileOptionsExt { + /// Write the file with the given password using the deprecated ZipCrypto algorithm. + /// + /// This is not recommended for new archives, as ZipCrypto is not secure. + fn with_deprecated_encryption(self, password: &[u8]) -> Self; + } + impl FileOptionsExt for FileOptions { + fn with_deprecated_encryption(self, password: &[u8]) -> Self { + self.with_deprecated_encryption(password) + } + } +} \ No newline at end of file diff --git a/vendor/zip/src/write.rs b/vendor/zip/src/write.rs index 14252b4d5..4cdc031b0 100644 --- a/vendor/zip/src/write.rs +++ b/vendor/zip/src/write.rs @@ -29,19 +29,37 @@ use time::OffsetDateTime; #[cfg(feature = "zstd")] use zstd::stream::write::Encoder as ZstdEncoder; +enum MaybeEncrypted { + Unencrypted(W), + Encrypted(crate::zipcrypto::ZipCryptoWriter), +} +impl Write for MaybeEncrypted { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self { + MaybeEncrypted::Unencrypted(w) => w.write(buf), + MaybeEncrypted::Encrypted(w) => w.write(buf), + } + } + fn flush(&mut self) -> io::Result<()> { + match self { + MaybeEncrypted::Unencrypted(w) => w.flush(), + MaybeEncrypted::Encrypted(w) => w.flush(), + } + } +} enum GenericZipWriter { Closed, - Storer(W), + Storer(MaybeEncrypted), #[cfg(any( feature = "deflate", feature = "deflate-miniz", feature = "deflate-zlib" ))] - Deflater(DeflateEncoder), + Deflater(DeflateEncoder>), #[cfg(feature = "bzip2")] - Bzip2(BzEncoder), + Bzip2(BzEncoder>), #[cfg(feature = "zstd")] - Zstd(ZstdEncoder<'static, W>), + Zstd(ZstdEncoder<'static, MaybeEncrypted>), } // Put the struct declaration in a private module to convince rustdoc to display ZipWriter nicely pub(crate) mod zip_writer { @@ -108,6 +126,7 @@ pub struct FileOptions { last_modified_time: DateTime, permissions: Option, large_file: bool, + encrypt_with: Option, } impl FileOptions { @@ -171,6 +190,10 @@ impl FileOptions { self.large_file = large; self } + pub(crate) fn with_deprecated_encryption(mut self, password: &[u8]) -> FileOptions { + self.encrypt_with = Some(crate::zipcrypto::ZipCryptoKeys::derive(password)); + self + } } impl Default for FileOptions { @@ -196,6 +219,7 @@ impl Default for FileOptions { last_modified_time: DateTime::default(), permissions: None, large_file: false, + encrypt_with: None, } } } @@ -284,7 +308,7 @@ impl ZipWriter { let _ = readwriter.seek(io::SeekFrom::Start(directory_start)); // seek directory_start to overwrite it Ok(ZipWriter { - inner: GenericZipWriter::Storer(readwriter), + inner: GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(readwriter)), files, stats: Default::default(), writing_to_file: false, @@ -302,7 +326,7 @@ impl ZipWriter { /// Before writing to this object, the [`ZipWriter::start_file`] function should be called. pub fn new(inner: W) -> ZipWriter { ZipWriter { - inner: GenericZipWriter::Storer(inner), + inner: GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(inner)), files: Vec::new(), stats: Default::default(), writing_to_file: false, @@ -355,7 +379,7 @@ impl ZipWriter { let mut file = ZipFileData { system: System::Unix, version_made_by: DEFAULT_VERSION, - encrypted: false, + encrypted: options.encrypt_with.is_some(), using_data_descriptor: false, compression_method: options.compression_method, compression_level: options.compression_level, @@ -385,7 +409,13 @@ impl ZipWriter { self.files.push(file); } + if let Some(keys) = options.encrypt_with { + let mut zipwriter = crate::zipcrypto::ZipCryptoWriter { writer: core::mem::replace(&mut self.inner, GenericZipWriter::Closed).unwrap(), buffer: vec![], keys }; + let mut crypto_header = [0u8; 12]; + zipwriter.write_all(&crypto_header)?; + self.inner = GenericZipWriter::Storer(MaybeEncrypted::Encrypted(zipwriter)); + } Ok(()) } @@ -395,6 +425,14 @@ impl ZipWriter { self.end_extra_data()?; } self.inner.switch_to(CompressionMethod::Stored, None)?; + match core::mem::replace(&mut self.inner, GenericZipWriter::Closed) { + GenericZipWriter::Storer(MaybeEncrypted::Encrypted(writer)) => { + let crc32 = self.stats.hasher.clone().finalize(); + self.inner = GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(writer.finish(crc32)?)) + } + GenericZipWriter::Storer(w) => self.inner = GenericZipWriter::Storer(w), + _ => unreachable!() + } let writer = self.inner.get_plain(); if !self.writing_raw { @@ -699,7 +737,7 @@ impl ZipWriter { /// Add a directory entry. /// - /// You can't write data to the file afterwards. + /// As directories have no content, you must not call [`ZipWriter::write`] before adding a new file. pub fn add_directory(&mut self, name: S, mut options: FileOptions) -> ZipResult<()> where S: Into, @@ -985,8 +1023,8 @@ impl GenericZipWriter { fn get_plain(&mut self) -> &mut W { match *self { - GenericZipWriter::Storer(ref mut w) => w, - _ => panic!("Should have switched to stored beforehand"), + GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(ref mut w)) => w, + _ => panic!("Should have switched to stored and unencrypted beforehand"), } } @@ -1009,8 +1047,8 @@ impl GenericZipWriter { fn unwrap(self) -> W { match self { - GenericZipWriter::Storer(w) => w, - _ => panic!("Should have switched to stored beforehand"), + GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(w)) => w, + _ => panic!("Should have switched to stored and unencrypted beforehand"), } } } @@ -1058,7 +1096,7 @@ fn write_local_file_header(writer: &mut T, file: &ZipFileData) -> ZipR 1u16 << 11 } else { 0 - }; + } | if file.encrypted { 1u16 << 0 } else { 0 }; writer.write_u16::(flag)?; // Compression method #[allow(deprecated)] @@ -1133,7 +1171,7 @@ fn write_central_directory_header(writer: &mut T, file: &ZipFileData) 1u16 << 11 } else { 0 - }; + } | if file.encrypted { 1u16 << 0 } else { 0 }; writer.write_u16::(flag)?; // compression method #[allow(deprecated)] @@ -1428,6 +1466,7 @@ mod test { last_modified_time: DateTime::default(), permissions: Some(33188), large_file: false, + encrypt_with: None, }; writer.start_file("mimetype", options).unwrap(); writer diff --git a/vendor/zip/src/zipcrypto.rs b/vendor/zip/src/zipcrypto.rs index 91d403951..c3696e4d7 100644 --- a/vendor/zip/src/zipcrypto.rs +++ b/vendor/zip/src/zipcrypto.rs @@ -6,7 +6,8 @@ use std::num::Wrapping; /// A container to hold the current key state -struct ZipCryptoKeys { +#[derive(Clone, Copy)] +pub(crate) struct ZipCryptoKeys { key_0: Wrapping, key_1: Wrapping, key_2: Wrapping, @@ -49,6 +50,13 @@ impl ZipCryptoKeys { fn crc32(crc: Wrapping, input: u8) -> Wrapping { (crc >> 8) ^ Wrapping(CRCTABLE[((crc & Wrapping(0xff)).0 as u8 ^ input) as usize]) } + pub(crate) fn derive(password: &[u8]) -> ZipCryptoKeys { + let mut keys = ZipCryptoKeys::new(); + for byte in password.iter() { + keys.update(*byte); + } + keys + } } /// A ZipCrypto reader with unverified password @@ -70,17 +78,10 @@ impl ZipCryptoReader { /// would be impossible to decrypt files that were encrypted with a /// password byte sequence that is unrepresentable in UTF-8. pub fn new(file: R, password: &[u8]) -> ZipCryptoReader { - let mut result = ZipCryptoReader { + ZipCryptoReader { file, - keys: ZipCryptoKeys::new(), - }; - - // Key the cipher by updating the keys with the password. - for byte in password.iter() { - result.keys.update(*byte); + keys: ZipCryptoKeys::derive(password), } - - result } /// Read the ZipCrypto header bytes and validate the password. @@ -122,6 +123,31 @@ impl ZipCryptoReader { Ok(Some(ZipCryptoReaderValid { reader: self })) } } +pub(crate) struct ZipCryptoWriter { + pub(crate) writer: W, + pub(crate) buffer: Vec, + pub(crate) keys: ZipCryptoKeys, +} +impl ZipCryptoWriter { + pub(crate) fn finish(mut self, crc32: u32) -> std::io::Result { + self.buffer[11] = (crc32 >> 24) as u8; + for byte in self.buffer.iter_mut() { + *byte = self.keys.encrypt_byte(*byte); + } + self.writer.write_all(&self.buffer)?; + self.writer.flush()?; + Ok(self.writer) + } +} +impl std::io::Write for ZipCryptoWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.buffer.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} /// A ZipCrypto reader with verified password pub struct ZipCryptoReaderValid { -- cgit v1.2.3