summaryrefslogtreecommitdiffstats
path: root/vendor/zip/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:59:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:59:35 +0000
commitd1b2d29528b7794b41e66fc2136e395a02f8529b (patch)
treea4a17504b260206dec3cf55b2dca82929a348ac2 /vendor/zip/src
parentReleasing progress-linux version 1.72.1+dfsg1-1~progress7.99u1. (diff)
downloadrustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.tar.xz
rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.zip
Merging upstream version 1.73.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/zip/src')
-rw-r--r--vendor/zip/src/aes_ctr.rs11
-rw-r--r--vendor/zip/src/lib.rs11
-rw-r--r--vendor/zip/src/read.rs64
-rw-r--r--vendor/zip/src/read/stream.rs372
-rw-r--r--vendor/zip/src/types.rs72
-rw-r--r--vendor/zip/src/unstable.rs20
-rw-r--r--vendor/zip/src/write.rs67
-rw-r--r--vendor/zip/src/zipcrypto.rs46
8 files changed, 566 insertions, 97 deletions
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<C> AesCtrZipKeyStream<C>
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<Aes>(key: &[u8], ciphertext: &mut [u8], expected_plaintext: &[u8])
where
Aes: AesKind,
- Aes::Cipher: NewBlockCipher + BlockEncrypt,
+ Aes::Cipher: KeyInit + BlockEncrypt,
{
let mut key_stream = AesCtrZipKeyStream::<Aes>::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<R: Read + io::Seek>(
archive_offset: u64,
) -> ZipResult<ZipFileData> {
let central_header_start = reader.stream_position()?;
+
// Parse central header
let signature = reader.read_u32::<LittleEndian>()?;
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<R: Read>(
+ reader: &mut R,
+ archive_offset: u64,
+ central_header_start: u64,
+) -> ZipResult<ZipFileData> {
let version_made_by = reader.read_u16::<LittleEndian>()?;
let _version_to_extract = reader.read_u16::<LittleEndian>()?;
let flags = reader.read_u16::<LittleEndian>()?;
@@ -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<u32> {
- 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>(R);
+
+impl<R> ZipStreamReader<R> {
+ /// Create a new ZipStreamReader
+ pub fn new(reader: R) -> Self {
+ Self(reader)
+ }
+}
+
+impl<R: Read> ZipStreamReader<R> {
+ fn parse_central_directory(&mut self) -> ZipResult<Option<ZipStreamFileMetadata>> {
+ // 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::<LittleEndian>()?;
+ 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<V: ZipStreamVisitor>(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<P: AsRef<Path>>(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<u32> {
+ 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<Box<str>>,
+ }
+ 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<Box<str>>,
+ }
+ 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<u32> {
+ 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
@@ -510,27 +557,6 @@ mod test {
#[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() {
use std::convert::TryFrom;
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<W> {
+ Unencrypted(W),
+ Encrypted(crate::zipcrypto::ZipCryptoWriter<W>),
+}
+impl<W: Write> Write for MaybeEncrypted<W> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ 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<W: Write + io::Seek> {
Closed,
- Storer(W),
+ Storer(MaybeEncrypted<W>),
#[cfg(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib"
))]
- Deflater(DeflateEncoder<W>),
+ Deflater(DeflateEncoder<MaybeEncrypted<W>>),
#[cfg(feature = "bzip2")]
- Bzip2(BzEncoder<W>),
+ Bzip2(BzEncoder<MaybeEncrypted<W>>),
#[cfg(feature = "zstd")]
- Zstd(ZstdEncoder<'static, W>),
+ Zstd(ZstdEncoder<'static, MaybeEncrypted<W>>),
}
// 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<u32>,
large_file: bool,
+ encrypt_with: Option<crate::zipcrypto::ZipCryptoKeys>,
}
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<A: Read + Write + io::Seek> ZipWriter<A> {
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<W: Write + io::Seek> ZipWriter<W> {
/// Before writing to this object, the [`ZipWriter::start_file`] function should be called.
pub fn new(inner: W) -> ZipWriter<W> {
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<W: Write + io::Seek> ZipWriter<W> {
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<W: Write + io::Seek> ZipWriter<W> {
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<W: Write + io::Seek> ZipWriter<W> {
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<W: Write + io::Seek> ZipWriter<W> {
/// 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<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
where
S: Into<String>,
@@ -985,8 +1023,8 @@ impl<W: Write + io::Seek> GenericZipWriter<W> {
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<W: Write + io::Seek> GenericZipWriter<W> {
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<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipR
1u16 << 11
} else {
0
- };
+ } | if file.encrypted { 1u16 << 0 } else { 0 };
writer.write_u16::<LittleEndian>(flag)?;
// Compression method
#[allow(deprecated)]
@@ -1133,7 +1171,7 @@ fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData)
1u16 << 11
} else {
0
- };
+ } | if file.encrypted { 1u16 << 0 } else { 0 };
writer.write_u16::<LittleEndian>(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<u32>,
key_1: Wrapping<u32>,
key_2: Wrapping<u32>,
@@ -49,6 +50,13 @@ impl ZipCryptoKeys {
fn crc32(crc: Wrapping<u32>, input: u8) -> Wrapping<u32> {
(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<R: std::io::Read> ZipCryptoReader<R> {
/// 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<R> {
- 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<R: std::io::Read> ZipCryptoReader<R> {
Ok(Some(ZipCryptoReaderValid { reader: self }))
}
}
+pub(crate) struct ZipCryptoWriter<W> {
+ pub(crate) writer: W,
+ pub(crate) buffer: Vec<u8>,
+ pub(crate) keys: ZipCryptoKeys,
+}
+impl<W: std::io::Write> ZipCryptoWriter<W> {
+ pub(crate) fn finish(mut self, crc32: u32) -> std::io::Result<W> {
+ 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<W: std::io::Write> std::io::Write for ZipCryptoWriter<W> {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ 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<R> {