From 42a954206db8add37df6d400cbcb563a74bfdff9 Mon Sep 17 00:00:00 2001 From: Adrian Date: Wed, 10 Apr 2024 09:40:19 +0200 Subject: [PATCH 01/11] add append example --- README.md | 1 + examples/append.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 examples/append.rs diff --git a/README.md b/README.md index f06cdbb5..a45234ac 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ See the [examples directory](examples) for: * How to extract a zip file. * How to extract a single file from a zip. * How to read a zip from the standard input. + * How to append a directory to an existing archive Fuzzing ------- diff --git a/examples/append.rs b/examples/append.rs new file mode 100644 index 00000000..eb79e111 --- /dev/null +++ b/examples/append.rs @@ -0,0 +1,68 @@ +use std::{ + fs::{File, OpenOptions}, + io::{Read, Write}, + path::{Path, PathBuf}, + str::FromStr, +}; +use zip; + +fn gather_files<'a, T: Into<&'a Path>>(path: T, files: &mut Vec) { + let path: &Path = path.into(); + + for entry in path.read_dir().unwrap() { + match entry { + Ok(e) => { + if e.path().is_dir() { + gather_files(e.path().as_ref(), files); + } else if e.path().is_file() { + files.push(e.path()); + } + } + Err(_) => todo!(), + } + } +} + +fn real_main() -> i32 { + let args: Vec<_> = std::env::args().collect(); + if args.len() < 3 { + println!("Usage: {} ", args[0]); + return 1; + } + + let existing_archive_path = &*args[1]; + let append_dir_path = &*args[2]; + let archive = PathBuf::from_str(existing_archive_path).unwrap(); + let to_append = PathBuf::from_str(append_dir_path).unwrap(); + + let existing_zip = OpenOptions::new() + .read(true) + .write(true) + .open(&archive) + .unwrap(); + let mut append_zip = zip::ZipWriter::new_append(existing_zip).unwrap(); + + let mut files: Vec = vec![]; + gather_files(to_append.as_ref(), &mut files); + + let mut buf: Vec = vec![]; + for file in files { + append_zip + .start_file(file.to_string_lossy(), Default::default()) + .unwrap(); + + let mut f = File::open(file).unwrap(); + f.read_to_end(&mut buf).unwrap(); + + append_zip.write_all(&buf).unwrap(); + buf.clear(); + } + + append_zip.finish().unwrap(); + + 0 +} + +fn main() { + std::process::exit(real_main()); +} From f6a34093fcd2c32a976890fc98e795f75b64eaee Mon Sep 17 00:00:00 2001 From: Adrian Date: Mon, 15 Apr 2024 10:34:49 +0200 Subject: [PATCH 02/11] use io::copy instead of read_to_end --- examples/append.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/append.rs b/examples/append.rs index eb79e111..560136d3 100644 --- a/examples/append.rs +++ b/examples/append.rs @@ -45,17 +45,13 @@ fn real_main() -> i32 { let mut files: Vec = vec![]; gather_files(to_append.as_ref(), &mut files); - let mut buf: Vec = vec![]; for file in files { append_zip .start_file(file.to_string_lossy(), Default::default()) .unwrap(); let mut f = File::open(file).unwrap(); - f.read_to_end(&mut buf).unwrap(); - - append_zip.write_all(&buf).unwrap(); - buf.clear(); + let _ = std::io::copy(&mut f, &mut append_zip); } append_zip.finish().unwrap(); From b944c3ad8600b978871e53764fcabe20e1ec7e65 Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Wed, 1 May 2024 09:55:07 -0700 Subject: [PATCH 03/11] Fix failing build Signed-off-by: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> --- examples/append.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/append.rs b/examples/append.rs index 560136d3..8bb1e4d8 100644 --- a/examples/append.rs +++ b/examples/append.rs @@ -1,6 +1,5 @@ use std::{ fs::{File, OpenOptions}, - io::{Read, Write}, path::{Path, PathBuf}, str::FromStr, }; @@ -47,7 +46,7 @@ fn real_main() -> i32 { for file in files { append_zip - .start_file(file.to_string_lossy(), Default::default()) + .start_file_from_path(file, Default::default()) .unwrap(); let mut f = File::open(file).unwrap(); From 3d6621236698861d0382f2262fa62488484de077 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Thu, 2 May 2024 20:58:34 +0200 Subject: [PATCH 04/11] Remove unnecessary "mut" roundtrip() takes a &mut, but only uses this argument non-mutably. --- src/aes_ctr.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/aes_ctr.rs b/src/aes_ctr.rs index d9f04b64..85f3ab7d 100644 --- a/src/aes_ctr.rs +++ b/src/aes_ctr.rs @@ -154,7 +154,7 @@ mod tests { /// 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]) + fn roundtrip(key: &[u8], ciphertext: &[u8], expected_plaintext: &[u8]) where Aes: AesKind, Aes::Cipher: KeyInit + BlockEncrypt, @@ -182,7 +182,7 @@ mod tests { // `7z a -phelloworld -mem=AES256 -mx=0 aes256_40byte.zip 40byte_data.txt` #[test] fn crypt_aes_256_0_byte() { - let mut ciphertext = []; + let ciphertext = []; let expected_plaintext = &[]; let key = [ 0x0b, 0xec, 0x2e, 0xf2, 0x46, 0xf0, 0x7e, 0x35, 0x16, 0x54, 0xe0, 0x98, 0x10, 0xb3, @@ -190,36 +190,36 @@ mod tests { 0x5c, 0xd0, 0xc0, 0x54, ]; - roundtrip::(&key, &mut ciphertext, expected_plaintext); + roundtrip::(&key, &ciphertext, expected_plaintext); } #[test] fn crypt_aes_128_5_byte() { - let mut ciphertext = [0x98, 0xa9, 0x8c, 0x26, 0x0e]; + let ciphertext = [0x98, 0xa9, 0x8c, 0x26, 0x0e]; let expected_plaintext = b"asdf\n"; let key = [ 0xe0, 0x25, 0x7b, 0x57, 0x97, 0x6a, 0xa4, 0x23, 0xab, 0x94, 0xaa, 0x44, 0xfd, 0x47, 0x4f, 0xa5, ]; - roundtrip::(&key, &mut ciphertext, expected_plaintext); + roundtrip::(&key, &ciphertext, expected_plaintext); } #[test] fn crypt_aes_192_5_byte() { - let mut ciphertext = [0x36, 0x55, 0x5c, 0x61, 0x3c]; + let ciphertext = [0x36, 0x55, 0x5c, 0x61, 0x3c]; let expected_plaintext = b"asdf\n"; let key = [ 0xe4, 0x4a, 0x88, 0x52, 0x8f, 0xf7, 0x0b, 0x81, 0x7b, 0x75, 0xf1, 0x74, 0x21, 0x37, 0x8c, 0x90, 0xad, 0xbe, 0x4a, 0x65, 0xa8, 0x96, 0x0e, 0xcc, ]; - roundtrip::(&key, &mut ciphertext, expected_plaintext); + roundtrip::(&key, &ciphertext, expected_plaintext); } #[test] fn crypt_aes_256_5_byte() { - let mut ciphertext = [0xc2, 0x47, 0xc0, 0xdc, 0x56]; + let ciphertext = [0xc2, 0x47, 0xc0, 0xdc, 0x56]; let expected_plaintext = b"asdf\n"; let key = [ 0x79, 0x5e, 0x17, 0xf2, 0xc6, 0x3d, 0x28, 0x9b, 0x4b, 0x4b, 0xbb, 0xa9, 0xba, 0xc9, @@ -227,12 +227,12 @@ mod tests { 0x15, 0xb2, 0x86, 0xab, ]; - roundtrip::(&key, &mut ciphertext, expected_plaintext); + roundtrip::(&key, &ciphertext, expected_plaintext); } #[test] fn crypt_aes_128_40_byte() { - let mut ciphertext = [ + let ciphertext = [ 0xcf, 0x72, 0x6b, 0xa1, 0xb2, 0x0f, 0xdf, 0xaa, 0x10, 0xad, 0x9c, 0x7f, 0x6d, 0x1c, 0x8d, 0xb5, 0x16, 0x7e, 0xbb, 0x11, 0x69, 0x52, 0x8c, 0x89, 0x80, 0x32, 0xaa, 0x76, 0xa6, 0x18, 0x31, 0x98, 0xee, 0xdd, 0x22, 0x68, 0xb7, 0xe6, 0x77, 0xd2, @@ -243,12 +243,12 @@ mod tests { 0x81, 0xb6, ]; - roundtrip::(&key, &mut ciphertext, expected_plaintext); + roundtrip::(&key, &ciphertext, expected_plaintext); } #[test] fn crypt_aes_192_40_byte() { - let mut ciphertext = [ + let ciphertext = [ 0xa6, 0xfc, 0x52, 0x79, 0x2c, 0x6c, 0xfe, 0x68, 0xb1, 0xa8, 0xb3, 0x07, 0x52, 0x8b, 0x82, 0xa6, 0x87, 0x9c, 0x72, 0x42, 0x3a, 0xf8, 0xc6, 0xa9, 0xc9, 0xfb, 0x61, 0x19, 0x37, 0xb9, 0x56, 0x62, 0xf4, 0xfc, 0x5e, 0x7a, 0xdd, 0x55, 0x0a, 0x48, @@ -259,12 +259,12 @@ mod tests { 0xfe, 0xae, 0x1b, 0xba, 0x01, 0x97, 0x97, 0x79, 0xbb, 0xa6, ]; - roundtrip::(&key, &mut ciphertext, expected_plaintext); + roundtrip::(&key, &ciphertext, expected_plaintext); } #[test] fn crypt_aes_256_40_byte() { - let mut ciphertext = [ + let ciphertext = [ 0xa9, 0x99, 0xbd, 0xea, 0x82, 0x9b, 0x8f, 0x2f, 0xb7, 0x52, 0x2f, 0x6b, 0xd8, 0xf6, 0xab, 0x0e, 0x24, 0x51, 0x9e, 0x18, 0x0f, 0xc0, 0x8f, 0x54, 0x15, 0x80, 0xae, 0xbc, 0xa0, 0x5c, 0x8a, 0x11, 0x8d, 0x14, 0x7e, 0xc5, 0xb4, 0xae, 0xd3, 0x37, @@ -276,6 +276,6 @@ mod tests { 0xc2, 0x07, 0x36, 0xb6, ]; - roundtrip::(&key, &mut ciphertext, expected_plaintext); + roundtrip::(&key, &ciphertext, expected_plaintext); } } From da3bbc87d7cde34bff29ca191d9fa43662b76c0a Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Thu, 2 May 2024 12:58:31 -0700 Subject: [PATCH 05/11] doc(examples): Fix a bug where type SimpleFileOptions must be specified --- examples/append.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/append.rs b/examples/append.rs index 8bb1e4d8..6c5e41d9 100644 --- a/examples/append.rs +++ b/examples/append.rs @@ -1,9 +1,11 @@ use std::{ fs::{File, OpenOptions}, + io::{Read, Write}, path::{Path, PathBuf}, str::FromStr, }; use zip; +use zip::write::SimpleFileOptions; fn gather_files<'a, T: Into<&'a Path>>(path: T, files: &mut Vec) { let path: &Path = path.into(); @@ -46,7 +48,7 @@ fn real_main() -> i32 { for file in files { append_zip - .start_file_from_path(file, Default::default()) + .start_file(file.to_string_lossy(), SimpleFileOptions::default()) .unwrap(); let mut f = File::open(file).unwrap(); From 08d30e976aeee41ef501e2d4ae3a05488db54027 Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Thu, 2 May 2024 13:31:38 -0700 Subject: [PATCH 06/11] style: Fix Clippy warnings: unused imports --- examples/append.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/append.rs b/examples/append.rs index 6c5e41d9..88a5147e 100644 --- a/examples/append.rs +++ b/examples/append.rs @@ -1,10 +1,8 @@ use std::{ fs::{File, OpenOptions}, - io::{Read, Write}, path::{Path, PathBuf}, str::FromStr, }; -use zip; use zip::write::SimpleFileOptions; fn gather_files<'a, T: Into<&'a Path>>(path: T, files: &mut Vec) { @@ -39,7 +37,7 @@ fn real_main() -> i32 { let existing_zip = OpenOptions::new() .read(true) .write(true) - .open(&archive) + .open(archive) .unwrap(); let mut append_zip = zip::ZipWriter::new_append(existing_zip).unwrap(); From 84ae5fc157a2ac17847ec5608f50b213f2eca1ea Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Thu, 2 May 2024 12:41:47 -0700 Subject: [PATCH 07/11] refactor: Remove byteorder dependency (#83) --- Cargo.toml | 1 - src/aes_ctr.rs | 4 +- src/read.rs | 84 +++++++++++++++++++------------------- src/read/stream.rs | 5 +-- src/spec.rs | 92 +++++++++++++++++++++--------------------- src/unstable.rs | 49 +++++++++++++++++++++++ src/write.rs | 98 ++++++++++++++++++++++----------------------- tests/end_to_end.rs | 5 +-- 8 files changed, 193 insertions(+), 145 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d2d4ab72..0094b84e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ time = { version = "0.3.36", default-features = false } [dependencies] aes = { version = "0.8.4", optional = true } -byteorder = "1.5.0" bzip2 = { version = "0.4.4", optional = true } chrono = { version = "0.4.38", optional = true } constant_time_eq = { version = "0.3.0", optional = true } diff --git a/src/aes_ctr.rs b/src/aes_ctr.rs index d9f04b64..9bc3748e 100644 --- a/src/aes_ctr.rs +++ b/src/aes_ctr.rs @@ -4,9 +4,9 @@ //! different byte order (little endian) than NIST (big endian). //! See [AesCtrZipKeyStream] for more information. +use crate::unstable::LittleEndianWriteExt; use aes::cipher::generic_array::GenericArray; use aes::cipher::{BlockEncrypt, KeyInit}; -use byteorder::WriteBytesExt; use std::{any, fmt}; /// Internal block size of an AES cipher. @@ -112,7 +112,7 @@ where // Note: AES block size is always 16 bytes, same as u128. self.buffer .as_mut() - .write_u128::(self.counter) + .write_u128_le(self.counter) .expect("did not expect u128 le conversion to fail"); self.cipher .encrypt_block(GenericArray::from_mut_slice(&mut self.buffer)); diff --git a/src/read.rs b/src/read.rs index 25ff7f4d..a58537ad 100644 --- a/src/read.rs +++ b/src/read.rs @@ -11,7 +11,6 @@ use crate::result::{ZipError, ZipResult}; 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 std::io::{self, prelude::*}; @@ -87,6 +86,7 @@ pub(crate) mod zip_archive { use crate::read::lzma::LzmaDecoder; use crate::result::ZipError::InvalidPassword; use crate::spec::path_to_string; +use crate::unstable::LittleEndianReadExt; pub use zip_archive::ZipArchive; #[allow(clippy::large_enum_variant)] @@ -210,15 +210,15 @@ pub(crate) fn find_content<'a>( ) -> ZipResult> { // Parse local header reader.seek(io::SeekFrom::Start(data.header_start))?; - let signature = reader.read_u32::()?; + let signature = reader.read_u32_le()?; if signature != spec::LOCAL_FILE_HEADER_SIGNATURE { return Err(ZipError::InvalidArchive("Invalid local file header")); } let data_start = match data.data_start.get() { None => { reader.seek(io::SeekFrom::Current(22))?; - let file_name_length = reader.read_u16::()? as u64; - let extra_field_length = reader.read_u16::()? as u64; + let file_name_length = reader.read_u16_le()? as u64; + let extra_field_length = reader.read_u16_le()? as u64; let magic_and_header = 4 + 22 + 2 + 2; let data_start = data.header_start + magic_and_header + file_name_length + extra_field_length; @@ -762,7 +762,7 @@ pub(crate) fn central_header_to_zip_file( let central_header_start = reader.stream_position()?; // Parse central header - let signature = reader.read_u32::()?; + let signature = reader.read_u32_le()?; if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE { Err(ZipError::InvalidArchive("Invalid Central Directory header")) } else { @@ -776,25 +776,25 @@ fn central_header_to_zip_file_inner( 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::()?; + let version_made_by = reader.read_u16_le()?; + let _version_to_extract = reader.read_u16_le()?; + let flags = reader.read_u16_le()?; let encrypted = flags & 1 == 1; let is_utf8 = flags & (1 << 11) != 0; let using_data_descriptor = flags & (1 << 3) != 0; - let compression_method = reader.read_u16::()?; - let last_mod_time = reader.read_u16::()?; - let last_mod_date = reader.read_u16::()?; - let crc32 = reader.read_u32::()?; - let compressed_size = reader.read_u32::()?; - let uncompressed_size = reader.read_u32::()?; - let file_name_length = reader.read_u16::()? as usize; - let extra_field_length = reader.read_u16::()? as usize; - let file_comment_length = reader.read_u16::()? as usize; - let _disk_number = reader.read_u16::()?; - let _internal_file_attributes = reader.read_u16::()?; - let external_file_attributes = reader.read_u32::()?; - let offset = reader.read_u32::()? as u64; + let compression_method = reader.read_u16_le()?; + let last_mod_time = reader.read_u16_le()?; + let last_mod_date = reader.read_u16_le()?; + let crc32 = reader.read_u32_le()?; + let compressed_size = reader.read_u32_le()?; + let uncompressed_size = reader.read_u32_le()?; + let file_name_length = reader.read_u16_le()? as usize; + let extra_field_length = reader.read_u16_le()? as usize; + let file_comment_length = reader.read_u16_le()? as usize; + let _disk_number = reader.read_u16_le()?; + let _internal_file_attributes = reader.read_u16_le()?; + let external_file_attributes = reader.read_u32_le()?; + let offset = reader.read_u32_le()? as u64; let mut file_name_raw = vec![0; file_name_length]; reader.read_exact(&mut file_name_raw)?; let mut extra_field = vec![0; extra_field_length]; @@ -868,24 +868,24 @@ fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> { let mut reader = io::Cursor::new(extra_field.as_ref()); while (reader.position() as usize) < extra_field.len() { - let kind = reader.read_u16::()?; - let len = reader.read_u16::()?; + let kind = reader.read_u16_le()?; + let len = reader.read_u16_le()?; let mut len_left = len as i64; match kind { // Zip64 extended information extra field 0x0001 => { if file.uncompressed_size == spec::ZIP64_BYTES_THR { file.large_file = true; - file.uncompressed_size = reader.read_u64::()?; + file.uncompressed_size = reader.read_u64_le()?; len_left -= 8; } if file.compressed_size == spec::ZIP64_BYTES_THR { file.large_file = true; - file.compressed_size = reader.read_u64::()?; + file.compressed_size = reader.read_u64_le()?; len_left -= 8; } if file.header_start == spec::ZIP64_BYTES_THR { - file.header_start = reader.read_u64::()?; + file.header_start = reader.read_u64_le()?; len_left -= 8; } } @@ -896,10 +896,12 @@ fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> { "AES extra data field has an unsupported length", )); } - let vendor_version = reader.read_u16::()?; - let vendor_id = reader.read_u16::()?; - let aes_mode = reader.read_u8()?; - let compression_method = reader.read_u16::()?; + let vendor_version = reader.read_u16_le()?; + let vendor_id = reader.read_u16_le()?; + let mut out = [0u8]; + reader.read_exact(&mut out)?; + let aes_mode = out[0]; + let compression_method = reader.read_u16_le()?; if vendor_id != 0x4541 { return Err(ZipError::InvalidArchive("Invalid AES vendor")); @@ -1163,7 +1165,7 @@ impl<'a> Drop for ZipFile<'a> { /// * `data_start`: set to 0 /// * `external_attributes`: `unix_mode()`: will return None pub fn read_zipfile_from_stream<'a, R: Read>(reader: &'a mut R) -> ZipResult>> { - let signature = reader.read_u32::()?; + let signature = reader.read_u32_le()?; match signature { spec::LOCAL_FILE_HEADER_SIGNATURE => (), @@ -1171,20 +1173,20 @@ pub fn read_zipfile_from_stream<'a, R: Read>(reader: &'a mut R) -> ZipResult return Err(ZipError::InvalidArchive("Invalid local file header")), } - let version_made_by = reader.read_u16::()?; - let flags = reader.read_u16::()?; + let version_made_by = reader.read_u16_le()?; + let flags = reader.read_u16_le()?; let encrypted = flags & 1 == 1; let is_utf8 = flags & (1 << 11) != 0; let using_data_descriptor = flags & (1 << 3) != 0; #[allow(deprecated)] - let compression_method = CompressionMethod::from_u16(reader.read_u16::()?); - let last_mod_time = reader.read_u16::()?; - let last_mod_date = reader.read_u16::()?; - let crc32 = reader.read_u32::()?; - let compressed_size = reader.read_u32::()?; - let uncompressed_size = reader.read_u32::()?; - let file_name_length = reader.read_u16::()? as usize; - let extra_field_length = reader.read_u16::()? as usize; + let compression_method = CompressionMethod::from_u16(reader.read_u16_le()?); + let last_mod_time = reader.read_u16_le()?; + let last_mod_date = reader.read_u16_le()?; + let crc32 = reader.read_u32_le()?; + let compressed_size = reader.read_u32_le()?; + let uncompressed_size = reader.read_u32_le()?; + let file_name_length = reader.read_u16_le()? as usize; + let extra_field_length = reader.read_u16_le()? as usize; let mut file_name_raw = vec![0; file_name_length]; reader.read_exact(&mut file_name_raw)?; diff --git a/src/read/stream.rs b/src/read/stream.rs index f99a0e5a..40cb9efc 100644 --- a/src/read/stream.rs +++ b/src/read/stream.rs @@ -1,3 +1,4 @@ +use crate::unstable::LittleEndianReadExt; use std::fs; use std::io::{self, Read}; use std::path::{Path, PathBuf}; @@ -7,8 +8,6 @@ use super::{ ZipFileData, ZipResult, }; -use byteorder::{LittleEndian, ReadBytesExt}; - /// Stream decoder for zip. #[derive(Debug)] pub struct ZipStreamReader(R); @@ -28,7 +27,7 @@ impl ZipStreamReader { let central_header_start = 0; // Parse central header - let signature = self.0.read_u32::()?; + let signature = self.0.read_u32_le()?; if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE { Ok(None) } else { diff --git a/src/spec.rs b/src/spec.rs index b620c01e..8c5da7d8 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -1,5 +1,5 @@ use crate::result::{ZipError, ZipResult}; -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use crate::unstable::{LittleEndianReadExt, LittleEndianWriteExt}; use std::borrow::Cow; use std::io; use std::io::prelude::*; @@ -26,17 +26,17 @@ pub struct CentralDirectoryEnd { impl CentralDirectoryEnd { pub fn parse(reader: &mut T) -> ZipResult { - let magic = reader.read_u32::()?; + let magic = reader.read_u32_le()?; if magic != CENTRAL_DIRECTORY_END_SIGNATURE { return Err(ZipError::InvalidArchive("Invalid digital signature header")); } - let disk_number = reader.read_u16::()?; - let disk_with_central_directory = reader.read_u16::()?; - let number_of_files_on_this_disk = reader.read_u16::()?; - let number_of_files = reader.read_u16::()?; - let central_directory_size = reader.read_u32::()?; - let central_directory_offset = reader.read_u32::()?; - let zip_file_comment_length = reader.read_u16::()? as usize; + let disk_number = reader.read_u16_le()?; + let disk_with_central_directory = reader.read_u16_le()?; + let number_of_files_on_this_disk = reader.read_u16_le()?; + let number_of_files = reader.read_u16_le()?; + let central_directory_size = reader.read_u32_le()?; + let central_directory_offset = reader.read_u32_le()?; + let zip_file_comment_length = reader.read_u16_le()? as usize; let mut zip_file_comment = vec![0; zip_file_comment_length]; reader.read_exact(&mut zip_file_comment)?; @@ -65,7 +65,7 @@ impl CentralDirectoryEnd { let mut pos = file_length - HEADER_SIZE; while pos >= search_upper_bound { reader.seek(io::SeekFrom::Start(pos))?; - if reader.read_u32::()? == CENTRAL_DIRECTORY_END_SIGNATURE { + if reader.read_u32_le()? == CENTRAL_DIRECTORY_END_SIGNATURE { reader.seek(io::SeekFrom::Current( BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE as i64, ))?; @@ -85,14 +85,14 @@ impl CentralDirectoryEnd { } pub fn write(&self, writer: &mut T) -> ZipResult<()> { - writer.write_u32::(CENTRAL_DIRECTORY_END_SIGNATURE)?; - writer.write_u16::(self.disk_number)?; - writer.write_u16::(self.disk_with_central_directory)?; - writer.write_u16::(self.number_of_files_on_this_disk)?; - writer.write_u16::(self.number_of_files)?; - writer.write_u32::(self.central_directory_size)?; - writer.write_u32::(self.central_directory_offset)?; - writer.write_u16::(self.zip_file_comment.len() as u16)?; + writer.write_u32_le(CENTRAL_DIRECTORY_END_SIGNATURE)?; + writer.write_u16_le(self.disk_number)?; + writer.write_u16_le(self.disk_with_central_directory)?; + writer.write_u16_le(self.number_of_files_on_this_disk)?; + writer.write_u16_le(self.number_of_files)?; + writer.write_u32_le(self.central_directory_size)?; + writer.write_u32_le(self.central_directory_offset)?; + writer.write_u16_le(self.zip_file_comment.len() as u16)?; writer.write_all(&self.zip_file_comment)?; Ok(()) } @@ -106,15 +106,15 @@ pub struct Zip64CentralDirectoryEndLocator { impl Zip64CentralDirectoryEndLocator { pub fn parse(reader: &mut T) -> ZipResult { - let magic = reader.read_u32::()?; + let magic = reader.read_u32_le()?; if magic != ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE { return Err(ZipError::InvalidArchive( "Invalid zip64 locator digital signature header", )); } - let disk_with_central_directory = reader.read_u32::()?; - let end_of_central_directory_offset = reader.read_u64::()?; - let number_of_disks = reader.read_u32::()?; + let disk_with_central_directory = reader.read_u32_le()?; + let end_of_central_directory_offset = reader.read_u64_le()?; + let number_of_disks = reader.read_u32_le()?; Ok(Zip64CentralDirectoryEndLocator { disk_with_central_directory, @@ -124,10 +124,10 @@ impl Zip64CentralDirectoryEndLocator { } pub fn write(&self, writer: &mut T) -> ZipResult<()> { - writer.write_u32::(ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE)?; - writer.write_u32::(self.disk_with_central_directory)?; - writer.write_u64::(self.end_of_central_directory_offset)?; - writer.write_u32::(self.number_of_disks)?; + writer.write_u32_le(ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE)?; + writer.write_u32_le(self.disk_with_central_directory)?; + writer.write_u64_le(self.end_of_central_directory_offset)?; + writer.write_u32_le(self.number_of_disks)?; Ok(()) } } @@ -156,20 +156,20 @@ impl Zip64CentralDirectoryEnd { while pos >= nominal_offset { reader.seek(io::SeekFrom::Start(pos))?; - if reader.read_u32::()? == ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE { + if reader.read_u32_le()? == ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE { let archive_offset = pos - nominal_offset; - let _record_size = reader.read_u64::()?; + let _record_size = reader.read_u64_le()?; // We would use this value if we did anything with the "zip64 extensible data sector". - let version_made_by = reader.read_u16::()?; - let version_needed_to_extract = reader.read_u16::()?; - let disk_number = reader.read_u32::()?; - let disk_with_central_directory = reader.read_u32::()?; - let number_of_files_on_this_disk = reader.read_u64::()?; - let number_of_files = reader.read_u64::()?; - let central_directory_size = reader.read_u64::()?; - let central_directory_offset = reader.read_u64::()?; + let version_made_by = reader.read_u16_le()?; + let version_needed_to_extract = reader.read_u16_le()?; + let disk_number = reader.read_u32_le()?; + let disk_with_central_directory = reader.read_u32_le()?; + let number_of_files_on_this_disk = reader.read_u64_le()?; + let number_of_files = reader.read_u64_le()?; + let central_directory_size = reader.read_u64_le()?; + let central_directory_offset = reader.read_u64_le()?; results.push(( Zip64CentralDirectoryEnd { @@ -201,16 +201,16 @@ impl Zip64CentralDirectoryEnd { } pub fn write(&self, writer: &mut T) -> ZipResult<()> { - writer.write_u32::(ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE)?; - writer.write_u64::(44)?; // record size - writer.write_u16::(self.version_made_by)?; - writer.write_u16::(self.version_needed_to_extract)?; - writer.write_u32::(self.disk_number)?; - writer.write_u32::(self.disk_with_central_directory)?; - writer.write_u64::(self.number_of_files_on_this_disk)?; - writer.write_u64::(self.number_of_files)?; - writer.write_u64::(self.central_directory_size)?; - writer.write_u64::(self.central_directory_offset)?; + writer.write_u32_le(ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE)?; + writer.write_u64_le(44)?; // record size + writer.write_u16_le(self.version_made_by)?; + writer.write_u16_le(self.version_needed_to_extract)?; + writer.write_u32_le(self.disk_number)?; + writer.write_u32_le(self.disk_with_central_directory)?; + writer.write_u64_le(self.number_of_files_on_this_disk)?; + writer.write_u64_le(self.number_of_files)?; + writer.write_u64_le(self.central_directory_size)?; + writer.write_u64_le(self.central_directory_offset)?; Ok(()) } } diff --git a/src/unstable.rs b/src/unstable.rs index beb35a41..f6ea888a 100644 --- a/src/unstable.rs +++ b/src/unstable.rs @@ -1,3 +1,8 @@ +#![allow(missing_docs)] + +use std::io; +use std::io::{Read, Write}; + /// Provides high level API for reading from a stream. pub mod stream { pub use crate::read::stream::*; @@ -18,3 +23,47 @@ pub mod write { } } } + +/// Helper methods for writing unsigned integers in little-endian form. +pub trait LittleEndianWriteExt: Write { + fn write_u16_le(&mut self, input: u16) -> io::Result<()> { + self.write_all(&input.to_le_bytes()) + } + + fn write_u32_le(&mut self, input: u32) -> io::Result<()> { + self.write_all(&input.to_le_bytes()) + } + + fn write_u64_le(&mut self, input: u64) -> io::Result<()> { + self.write_all(&input.to_le_bytes()) + } + + fn write_u128_le(&mut self, input: u128) -> io::Result<()> { + self.write_all(&input.to_le_bytes()) + } +} + +impl LittleEndianWriteExt for W {} + +/// Helper methods for reading unsigned integers in little-endian form. +pub trait LittleEndianReadExt: Read { + fn read_u16_le(&mut self) -> io::Result { + let mut out = [0u8; 2]; + self.read_exact(&mut out)?; + Ok(u16::from_le_bytes(out)) + } + + fn read_u32_le(&mut self) -> io::Result { + let mut out = [0u8; 4]; + self.read_exact(&mut out)?; + Ok(u32::from_le_bytes(out)) + } + + fn read_u64_le(&mut self) -> io::Result { + let mut out = [0u8; 8]; + self.read_exact(&mut out)?; + Ok(u64::from_le_bytes(out)) + } +} + +impl LittleEndianReadExt for R {} diff --git a/src/write.rs b/src/write.rs index 7179487f..2606fe97 100644 --- a/src/write.rs +++ b/src/write.rs @@ -5,7 +5,6 @@ use crate::read::{find_content, ZipArchive, ZipFile, ZipFileReader}; use crate::result::{ZipError, ZipResult}; use crate::spec; use crate::types::{ffi, DateTime, System, ZipFileData, DEFAULT_VERSION}; -use byteorder::{LittleEndian, WriteBytesExt}; #[cfg(any(feature = "_deflate-any", feature = "bzip2", feature = "zstd",))] use core::num::NonZeroU64; use crc32fast::Hasher; @@ -126,6 +125,7 @@ use crate::result::ZipError::InvalidArchive; #[cfg(feature = "lzma")] use crate::result::ZipError::UnsupportedArchive; use crate::spec::path_to_string; +use crate::unstable::LittleEndianWriteExt; use crate::write::GenericZipWriter::{Closed, Storer}; use crate::zipcrypto::ZipCryptoKeys; use crate::CompressionMethod::Stored; @@ -378,8 +378,8 @@ impl FileOptions { } }; vec.reserve_exact(data.len() + 4); - vec.write_u16::(header_id)?; - vec.write_u16::(data.len() as u16)?; + vec.write_u16_le(header_id)?; + vec.write_u16_le(data.len() as u16)?; vec.write_all(data)?; Ok(()) } @@ -701,34 +701,34 @@ impl ZipWriter { let index = self.insert_file_data(file)?; let file = &mut self.files[index]; let writer = self.inner.get_plain(); - writer.write_u32::(spec::LOCAL_FILE_HEADER_SIGNATURE)?; + writer.write_u32_le(spec::LOCAL_FILE_HEADER_SIGNATURE)?; // version needed to extract - writer.write_u16::(file.version_needed())?; + writer.write_u16_le(file.version_needed())?; // general purpose bit flag let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 } | if file.encrypted { 1u16 << 0 } else { 0 }; - writer.write_u16::(flag)?; + writer.write_u16_le(flag)?; // Compression method #[allow(deprecated)] - writer.write_u16::(file.compression_method.to_u16())?; + writer.write_u16_le(file.compression_method.to_u16())?; // last mod file time and last mod file date - writer.write_u16::(file.last_modified_time.timepart())?; - writer.write_u16::(file.last_modified_time.datepart())?; + writer.write_u16_le(file.last_modified_time.timepart())?; + writer.write_u16_le(file.last_modified_time.datepart())?; // crc-32 - writer.write_u32::(file.crc32)?; + writer.write_u32_le(file.crc32)?; // compressed size and uncompressed size if file.large_file { - writer.write_u32::(spec::ZIP64_BYTES_THR as u32)?; - writer.write_u32::(spec::ZIP64_BYTES_THR as u32)?; + writer.write_u32_le(spec::ZIP64_BYTES_THR as u32)?; + writer.write_u32_le(spec::ZIP64_BYTES_THR as u32)?; } else { - writer.write_u32::(file.compressed_size as u32)?; - writer.write_u32::(file.uncompressed_size as u32)?; + writer.write_u32_le(file.compressed_size as u32)?; + writer.write_u32_le(file.uncompressed_size as u32)?; } // file name length - writer.write_u16::(file.file_name.as_bytes().len() as u16)?; + writer.write_u16_le(file.file_name.as_bytes().len() as u16)?; // extra field length let mut extra_field_length = file.extra_field_len(); if file.large_file { @@ -739,7 +739,7 @@ impl ZipWriter { return Err(InvalidArchive("Extra data field is too large")); } let extra_field_length = extra_field_length as u16; - writer.write_u16::(extra_field_length)?; + writer.write_u16_le(extra_field_length)?; // file name writer.write_all(file.file_name.as_bytes())?; // zip64 extra field @@ -768,7 +768,7 @@ impl ZipWriter { let pad_body = vec![0; pad_length - 4]; writer.write_all(b"za").map_err(ZipError::from)?; // 0x617a writer - .write_u16::(pad_body.len() as u16) + .write_u16_le(pad_body.len() as u16) .map_err(ZipError::from)?; writer.write_all(&pad_body).map_err(ZipError::from)?; } else { @@ -781,7 +781,7 @@ impl ZipWriter { // Update extra field length in local file header. writer.seek(SeekFrom::Start(file.header_start + 28))?; - writer.write_u16::(new_extra_field_length)?; + writer.write_u16_le(new_extra_field_length)?; writer.seek(SeekFrom::Start(header_end))?; debug_assert_eq!(header_end % align, 0); } @@ -1522,7 +1522,7 @@ fn clamp_opt>( fn update_local_file_header(writer: &mut T, file: &ZipFileData) -> ZipResult<()> { const CRC32_OFFSET: u64 = 14; writer.seek(SeekFrom::Start(file.header_start + CRC32_OFFSET))?; - writer.write_u32::(file.crc32)?; + writer.write_u32_le(file.crc32)?; if file.large_file { update_local_zip64_extra_field(writer, file)?; } else { @@ -1533,9 +1533,9 @@ fn update_local_file_header(writer: &mut T, file: &ZipFileData) "Large file option has not been set", ))); } - writer.write_u32::(file.compressed_size as u32)?; + writer.write_u32_le(file.compressed_size as u32)?; // uncompressed size is already checked on write to catch it as soon as possible - writer.write_u32::(file.uncompressed_size as u32)?; + writer.write_u32_le(file.uncompressed_size as u32)?; } Ok(()) } @@ -1547,49 +1547,49 @@ fn write_central_directory_header(writer: &mut T, file: &ZipFileData) write_central_zip64_extra_field(&mut zip64_extra_field.as_mut(), file)?; // central file header signature - writer.write_u32::(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE)?; + writer.write_u32_le(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE)?; // version made by let version_made_by = (file.system as u16) << 8 | (file.version_made_by as u16); - writer.write_u16::(version_made_by)?; + writer.write_u16_le(version_made_by)?; // version needed to extract - writer.write_u16::(file.version_needed())?; + writer.write_u16_le(file.version_needed())?; // general purpose bit flag let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 } | if file.encrypted { 1u16 << 0 } else { 0 }; - writer.write_u16::(flag)?; + writer.write_u16_le(flag)?; // compression method #[allow(deprecated)] - writer.write_u16::(file.compression_method.to_u16())?; + writer.write_u16_le(file.compression_method.to_u16())?; // last mod file time + date - writer.write_u16::(file.last_modified_time.timepart())?; - writer.write_u16::(file.last_modified_time.datepart())?; + writer.write_u16_le(file.last_modified_time.timepart())?; + writer.write_u16_le(file.last_modified_time.datepart())?; // crc-32 - writer.write_u32::(file.crc32)?; + writer.write_u32_le(file.crc32)?; // compressed size - writer.write_u32::(file.compressed_size.min(spec::ZIP64_BYTES_THR) as u32)?; + writer.write_u32_le(file.compressed_size.min(spec::ZIP64_BYTES_THR) as u32)?; // uncompressed size - writer.write_u32::(file.uncompressed_size.min(spec::ZIP64_BYTES_THR) as u32)?; + writer.write_u32_le(file.uncompressed_size.min(spec::ZIP64_BYTES_THR) as u32)?; // file name length - writer.write_u16::(file.file_name.as_bytes().len() as u16)?; + writer.write_u16_le(file.file_name.as_bytes().len() as u16)?; // extra field length - writer.write_u16::( + writer.write_u16_le( zip64_extra_field_length + file.extra_field_len() as u16 + file.central_extra_field_len() as u16, )?; // file comment length - writer.write_u16::(0)?; + writer.write_u16_le(0)?; // disk number start - writer.write_u16::(0)?; + writer.write_u16_le(0)?; // internal file attributes - writer.write_u16::(0)?; + writer.write_u16_le(0)?; // external file attributes - writer.write_u32::(file.external_attributes)?; + writer.write_u32_le(file.external_attributes)?; // relative offset of local header - writer.write_u32::(file.header_start.min(spec::ZIP64_BYTES_THR) as u32)?; + writer.write_u32_le(file.header_start.min(spec::ZIP64_BYTES_THR) as u32)?; // file name writer.write_all(file.file_name.as_bytes())?; // zip64 extra field @@ -1643,10 +1643,10 @@ fn validate_extra_data(header_id: u16, data: &[u8]) -> ZipResult<()> { fn write_local_zip64_extra_field(writer: &mut T, file: &ZipFileData) -> ZipResult<()> { // This entry in the Local header MUST include BOTH original // and compressed file size fields. - writer.write_u16::(0x0001)?; - writer.write_u16::(16)?; - writer.write_u64::(file.uncompressed_size)?; - writer.write_u64::(file.compressed_size)?; + writer.write_u16_le(0x0001)?; + writer.write_u16_le(16)?; + writer.write_u64_le(file.uncompressed_size)?; + writer.write_u64_le(file.compressed_size)?; // Excluded fields: // u32: disk start number Ok(()) @@ -1658,8 +1658,8 @@ fn update_local_zip64_extra_field( ) -> ZipResult<()> { let zip64_extra_field = file.header_start + 30 + file.file_name.as_bytes().len() as u64; writer.seek(SeekFrom::Start(zip64_extra_field + 4))?; - writer.write_u64::(file.uncompressed_size)?; - writer.write_u64::(file.compressed_size)?; + writer.write_u64_le(file.uncompressed_size)?; + writer.write_u64_le(file.compressed_size)?; // Excluded fields: // u32: disk start number Ok(()) @@ -1684,18 +1684,18 @@ fn write_central_zip64_extra_field(writer: &mut T, file: &ZipFileData) size += 8; } if size > 0 { - writer.write_u16::(0x0001)?; - writer.write_u16::(size)?; + writer.write_u16_le(0x0001)?; + writer.write_u16_le(size)?; size += 4; if uncompressed_size { - writer.write_u64::(file.uncompressed_size)?; + writer.write_u64_le(file.uncompressed_size)?; } if compressed_size { - writer.write_u64::(file.compressed_size)?; + writer.write_u64_le(file.compressed_size)?; } if header_start { - writer.write_u64::(file.header_start)?; + writer.write_u64_le(file.header_start)?; } // Excluded fields: // u32: disk start number diff --git a/tests/end_to_end.rs b/tests/end_to_end.rs index faad769c..ee342382 100644 --- a/tests/end_to_end.rs +++ b/tests/end_to_end.rs @@ -1,4 +1,3 @@ -use byteorder::{LittleEndian, WriteBytesExt}; use std::collections::HashSet; use std::io::prelude::*; use std::io::Cursor; @@ -159,8 +158,8 @@ fn check_test_archive(zip_file: R) -> ZipResult(0xbeef)?; - extra_data.write_u16::(EXTRA_DATA.len() as u16)?; + extra_data.write_u16(0xbeef)?; + extra_data.write_u16(EXTRA_DATA.len() as u16)?; extra_data.write_all(EXTRA_DATA)?; assert_eq!( file_with_extra_data.extra_data(), From b520c7f5171bd4c9c4a05129ffa38188c83cc268 Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Thu, 2 May 2024 13:24:50 -0700 Subject: [PATCH 08/11] test: Fix end-to-end test --- tests/end_to_end.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/end_to_end.rs b/tests/end_to_end.rs index ee342382..fc2059c1 100644 --- a/tests/end_to_end.rs +++ b/tests/end_to_end.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use std::io::prelude::*; use std::io::Cursor; use zip::result::ZipResult; +use zip::unstable::LittleEndianWriteExt; use zip::write::ExtendedFileOptions; use zip::write::FileOptions; use zip::write::SimpleFileOptions; @@ -158,8 +159,8 @@ fn check_test_archive(zip_file: R) -> ZipResult Date: Thu, 2 May 2024 17:55:13 -0700 Subject: [PATCH 09/11] chore: Update due to merge of #82 --- src/extra_fields/extended_timestamp.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/extra_fields/extended_timestamp.rs b/src/extra_fields/extended_timestamp.rs index e5e3fb70..f173e944 100644 --- a/src/extra_fields/extended_timestamp.rs +++ b/src/extra_fields/extended_timestamp.rs @@ -1,9 +1,6 @@ -use std::io::Read; - -use byteorder::LittleEndian; -use byteorder::ReadBytesExt; - use crate::result::{ZipError, ZipResult}; +use crate::unstable::LittleEndianReadExt; +use std::io::Read; /// extended timestamp, as described in @@ -23,7 +20,9 @@ impl ExtendedTimestamp { where R: Read, { - let flags = reader.read_u8()?; + let mut flags = [0u8]; + reader.read_exact(&mut flags)?; + let flags = flags[0]; // the `flags` field refers to the local headers and might not correspond // to the len field. If the length field is 1+4, we assume that only @@ -48,19 +47,19 @@ impl ExtendedTimestamp { } let mod_time = if (flags & 0b00000001u8 == 0b00000001u8) || len == 5 { - Some(reader.read_u32::()?) + Some(reader.read_u32_le()?) } else { None }; let ac_time = if flags & 0b00000010u8 == 0b00000010u8 && len > 5 { - Some(reader.read_u32::()?) + Some(reader.read_u32_le()?) } else { None }; let cr_time = if flags & 0b00000100u8 == 0b00000100u8 && len > 5 { - Some(reader.read_u32::()?) + Some(reader.read_u32_le()?) } else { None }; From 5e7939002b72862a0947cbe00ce12ab8edad6b6f Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Thu, 2 May 2024 19:42:49 -0700 Subject: [PATCH 10/11] ci: run checks on release branches, since they don't run on release PRs --- .github/workflows/ci.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 674a3b37..e50a61df 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,7 +6,6 @@ on: - 'master' push: branches-ignore: - - 'release-plz-**' - 'gh-readonly-queue/**' workflow_dispatch: merge_group: From b6caa1c377adc598c99dc148d706dff11b1c11a4 Mon Sep 17 00:00:00 2001 From: Chris Hennick <4961925+Pr0methean@users.noreply.github.com> Date: Thu, 2 May 2024 20:12:43 -0700 Subject: [PATCH 11/11] ci: Run unit tests on multiple feature sets --- .github/workflows/ci.yaml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e50a61df..b441c409 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,6 +21,7 @@ jobs: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] rustalias: [stable, nightly, msrv] + feature_flag: ["--all-features", "--no-default-features", ""] include: - rustalias: stable rust: stable @@ -28,7 +29,7 @@ jobs: rust: '1.70' - rustalias: nightly rust: nightly - name: 'Build and test: ${{ matrix.os }}, ${{ matrix.rustalias }}' + name: 'Build and test ${{ matrix.feature_flag }}: ${{ matrix.os }}, ${{ matrix.rustalias }}' runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@master @@ -43,19 +44,13 @@ jobs: uses: actions-rs/cargo@v1 with: command: check - args: --all --bins --examples + args: --all ${{ matrix.feature_flag }} --bins --examples - name: Tests uses: actions-rs/cargo@v1 with: command: test - args: --all - - - name: Tests (no features) - uses: actions-rs/cargo@v1 - with: - command: test - args: --all --no-default-features + args: --all ${{ matrix.feature_flag }} style_and_docs: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name