From c911c4f9f1c3757c9c547080e15735548cd79ea0 Mon Sep 17 00:00:00 2001 From: alandev2 Date: Wed, 31 May 2023 08:56:55 -0300 Subject: [PATCH 1/5] feat: add support to iterator over encrypted zip --- examples/uncompress_iterator.rs | 2 +- scripts/generate-ffi | 1 + src/ffi/generated.rs | 6 +++ src/iterator.rs | 61 ++++++++++++++++++++++++++++--- src/lib.rs | 2 +- tests/fixtures/with-password.zip | Bin 0 -> 660 bytes tests/integration_test.rs | 52 ++++++++++++++++++++++++-- 7 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 tests/fixtures/with-password.zip diff --git a/examples/uncompress_iterator.rs b/examples/uncompress_iterator.rs index 73b16db..7a23dc4 100644 --- a/examples/uncompress_iterator.rs +++ b/examples/uncompress_iterator.rs @@ -16,7 +16,7 @@ fn main() -> compress_tools::Result<()> { let source = std::fs::File::open(cmd.source_path)?; - for content in ArchiveIterator::from_read(source)? { + for content in ArchiveIterator::from_read(source, Password::empty())? { if let ArchiveContents::StartOfEntry(name, stat) = content { println!("{name}: size={}", stat.st_size); } diff --git a/scripts/generate-ffi b/scripts/generate-ffi index 365dccd..3bfcbda 100755 --- a/scripts/generate-ffi +++ b/scripts/generate-ffi @@ -48,6 +48,7 @@ bindgen \ --whitelist-function "archive_read_data_block" \ --whitelist-function "archive_read_next_header" \ --whitelist-function "archive_read_open" \ + --whitelist-function "archive_read_add_passphrase" \ --whitelist-function "archive_write_disk_new" \ --whitelist-function "archive_write_disk_set_options" \ --whitelist-function "archive_write_disk_set_standard_lookup" \ diff --git a/src/ffi/generated.rs b/src/ffi/generated.rs index 50f04c4..92d3aa5 100644 --- a/src/ffi/generated.rs +++ b/src/ffi/generated.rs @@ -105,6 +105,12 @@ extern "C" { offset: *mut la_int64_t, ) -> ::std::os::raw::c_int; } +extern "C" { + pub(crate) fn archive_read_add_passphrase( + arg1: *mut archive, + arg2: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} extern "C" { pub(crate) fn archive_read_close(arg1: *mut archive) -> ::std::os::raw::c_int; } diff --git a/src/iterator.rs b/src/iterator.rs index 7b34a39..83b6168 100644 --- a/src/iterator.rs +++ b/src/iterator.rs @@ -44,6 +44,44 @@ pub enum ArchiveContents { /// The entry is processed on a return value of `true` and ignored on `false`. pub type EntryFilterCallbackFn = dyn Fn(&str, &libc::stat) -> bool; +pub struct Password(Option>); + +impl Password { + pub fn empty() -> Self { + Self(None) + } + + pub fn extract(&self) -> Option<*const i8> { + match &self.0 { + Some(res) => { + let result = res.as_ptr(); + Some(result as *const i8) + } + _ => None, + } + } +} + +impl From<&str> for Password { + fn from(s: &str) -> Self { + let password = s.as_bytes(); + Self(Some(password.to_vec())) + } +} + +impl From for Password { + fn from(s: String) -> Self { + let password = s.as_bytes(); + Self(Some(password.to_vec())) + } +} + +impl From<&[u8]> for Password { + fn from(s: &[u8]) -> Self { + Self(Some(s.to_vec())) + } +} + /// An iterator over the contents of an archive. #[allow(clippy::module_name_repetitions)] pub struct ArchiveIterator { @@ -119,6 +157,7 @@ impl ArchiveIterator { source: R, decode: DecodeCallback, filter: Option>, + password: Password, ) -> Result> where R: Read + Seek, @@ -132,6 +171,10 @@ impl ArchiveIterator { let archive_entry: *mut ffi::archive_entry = std::ptr::null_mut(); let archive_reader = ffi::archive_read_new(); + if let Some(password) = password.extract() { + ffi::archive_read_add_passphrase(archive_reader, password); + } + let res = (|| { archive_result( ffi::archive_read_support_filter_all(archive_reader), @@ -230,7 +273,7 @@ impl ArchiveIterator { where R: Read + Seek, { - Self::new(source, decode, None) + Self::new(source, decode, None, Password::empty()) } /// Iterate over the contents of an archive, streaming the contents of each @@ -245,7 +288,7 @@ impl ArchiveIterator { /// /// let mut name = String::default(); /// let mut size = 0; - /// let mut iter = ArchiveIterator::from_read(file)?; + /// let mut iter = ArchiveIterator::from_read(file, Password::empty())?; /// /// for content in &mut iter { /// match content { @@ -265,11 +308,11 @@ impl ArchiveIterator { /// # Ok(()) /// # } /// ``` - pub fn from_read(source: R) -> Result> + pub fn from_read(source: R, password: Password) -> Result> where R: Read + Seek, { - Self::new(source, crate::decode_utf8, None) + Self::new(source, crate::decode_utf8, None, password) } /// Close the iterator, freeing up the associated resources. @@ -392,6 +435,7 @@ where source: R, decoder: DecodeCallback, filter: Option>, + password: Password, } /// A builder to generate an archive iterator over the contents of an @@ -430,6 +474,7 @@ where source, decoder: crate::decode_utf8, filter: None, + password: Password::empty(), } } @@ -450,8 +495,14 @@ where self } + /// Set a custom password to decode content of archive entries. + pub fn with_password(mut self, password: Password) -> ArchiveIteratorBuilder { + self.password = password; + self + } + /// Finish the builder and generate the configured `ArchiveIterator`. pub fn build(self) -> Result> { - ArchiveIterator::new(self.source, self.decoder, self.filter) + ArchiveIterator::new(self.source, self.decoder, self.filter, self.password) } } diff --git a/src/lib.rs b/src/lib.rs index 16a4599..40ef1b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,7 +60,7 @@ pub mod tokio_support; use error::archive_result; pub use error::{Error, Result}; use io::{Seek, SeekFrom}; -pub use iterator::{ArchiveContents, ArchiveIterator, ArchiveIteratorBuilder}; +pub use iterator::{ArchiveContents, ArchiveIterator, ArchiveIteratorBuilder, Password}; use std::{ ffi::{CStr, CString}, io::{self, Read, Write}, diff --git a/tests/fixtures/with-password.zip b/tests/fixtures/with-password.zip new file mode 100644 index 0000000000000000000000000000000000000000..dce209396908efb6f093735810e835a3125286ca GIT binary patch literal 660 zcmWIWW@h1H0D*GH{b67Rl;8u2WtL><79x5w9K4TL%ouU62_VA3``7;uFM<^cPD*Ye`NBd&xL-@CeNZ4D>WN`INMjOvC~P$ zuwCWYXq$2x-K66Ya_@d$o2(zGcw6D;0iYtkfVV>079XK3>Sj}Se%hTf+0>wV#9{XHyP(G zc?G6ne2DqD!df3{ZUd0k0becML*v!1z9BL`*$OVj)U@%Vh7ny% = Vec::new(); + let mut current_file_content: Vec = vec![]; + let mut current_file_name = String::new(); + + let mut iter: ArchiveIterator = ArchiveIteratorBuilder::new(source) + .with_password(source_password) + .filter(|name, _| name.ends_with(".txt")) + .build() + .unwrap() + .into_iter(); + + for content in &mut iter { + match content { + ArchiveContents::StartOfEntry(name, _stat) => { + current_file_name = name; + } + ArchiveContents::DataChunk(dt) => { + current_file_content.extend(dt); + } + ArchiveContents::EndOfEntry => { + let content_raw = String::from_utf8(current_file_content.clone()).unwrap(); + current_file_content.clear(); + + let content = format!("{}={}", current_file_name, content_raw); + files_result.push(content); + } + _ => {} + } + } + + iter.close().unwrap(); + + assert_eq!(files_result.len(), 2); + assert_eq!( + files_result, + vec![ + "with-password/file1.txt=its encrypted file".to_string(), + "with-password/file2.txt=file 2 in archive encrypted!".to_string() + ] + ); +} From 1f3a8ac695f713eb3ba3d9aad20f1232098be34d Mon Sep 17 00:00:00 2001 From: Otavio Salvador Date: Wed, 31 May 2023 13:50:11 -0300 Subject: [PATCH 2/5] fixup! feat: add support to iterator over encrypted zip Signed-off-by: Otavio Salvador --- examples/uncompress_iterator.rs | 2 +- src/iterator.rs | 54 +++++++++------------------------ tests/integration_test.rs | 9 +++--- 3 files changed, 20 insertions(+), 45 deletions(-) diff --git a/examples/uncompress_iterator.rs b/examples/uncompress_iterator.rs index 7a23dc4..52d4ce1 100644 --- a/examples/uncompress_iterator.rs +++ b/examples/uncompress_iterator.rs @@ -16,7 +16,7 @@ fn main() -> compress_tools::Result<()> { let source = std::fs::File::open(cmd.source_path)?; - for content in ArchiveIterator::from_read(source, Password::empty())? { + for content in ArchiveIterator::from_read(source, None)? { if let ArchiveContents::StartOfEntry(name, stat) = content { println!("{name}: size={}", stat.st_size); } diff --git a/src/iterator.rs b/src/iterator.rs index 83b6168..3be8066 100644 --- a/src/iterator.rs +++ b/src/iterator.rs @@ -44,41 +44,17 @@ pub enum ArchiveContents { /// The entry is processed on a return value of `true` and ignored on `false`. pub type EntryFilterCallbackFn = dyn Fn(&str, &libc::stat) -> bool; -pub struct Password(Option>); +pub struct Password(Vec); impl Password { - pub fn empty() -> Self { - Self(None) - } - - pub fn extract(&self) -> Option<*const i8> { - match &self.0 { - Some(res) => { - let result = res.as_ptr(); - Some(result as *const i8) - } - _ => None, - } - } -} - -impl From<&str> for Password { - fn from(s: &str) -> Self { - let password = s.as_bytes(); - Self(Some(password.to_vec())) - } -} - -impl From for Password { - fn from(s: String) -> Self { - let password = s.as_bytes(); - Self(Some(password.to_vec())) + pub fn extract(&self) -> *const i8 { + self.0.as_ptr() as *const i8 } } -impl From<&[u8]> for Password { - fn from(s: &[u8]) -> Self { - Self(Some(s.to_vec())) +impl From for Password where T: AsRef { + fn from(s: T) -> Self { + Self(s.as_ref().as_bytes().to_vec()) } } @@ -157,7 +133,7 @@ impl ArchiveIterator { source: R, decode: DecodeCallback, filter: Option>, - password: Password, + password: Option, ) -> Result> where R: Read + Seek, @@ -171,8 +147,8 @@ impl ArchiveIterator { let archive_entry: *mut ffi::archive_entry = std::ptr::null_mut(); let archive_reader = ffi::archive_read_new(); - if let Some(password) = password.extract() { - ffi::archive_read_add_passphrase(archive_reader, password); + if let Some(password) = password { + ffi::archive_read_add_passphrase(archive_reader, password.extract()); } let res = (|| { @@ -273,7 +249,7 @@ impl ArchiveIterator { where R: Read + Seek, { - Self::new(source, decode, None, Password::empty()) + Self::new(source, decode, None, None) } /// Iterate over the contents of an archive, streaming the contents of each @@ -288,7 +264,7 @@ impl ArchiveIterator { /// /// let mut name = String::default(); /// let mut size = 0; - /// let mut iter = ArchiveIterator::from_read(file, Password::empty())?; + /// let mut iter = ArchiveIterator::from_read(file, None)?; /// /// for content in &mut iter { /// match content { @@ -308,7 +284,7 @@ impl ArchiveIterator { /// # Ok(()) /// # } /// ``` - pub fn from_read(source: R, password: Password) -> Result> + pub fn from_read(source: R, password: Option) -> Result> where R: Read + Seek, { @@ -435,7 +411,7 @@ where source: R, decoder: DecodeCallback, filter: Option>, - password: Password, + password: Option, } /// A builder to generate an archive iterator over the contents of an @@ -474,7 +450,7 @@ where source, decoder: crate::decode_utf8, filter: None, - password: Password::empty(), + password: None, } } @@ -497,7 +473,7 @@ where /// Set a custom password to decode content of archive entries. pub fn with_password(mut self, password: Password) -> ArchiveIteratorBuilder { - self.password = password; + self.password = Some(password); self } diff --git a/tests/integration_test.rs b/tests/integration_test.rs index a3080d7..35caf5d 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -641,7 +641,7 @@ fn iterate_zip_with_cjk_pathname() { #[test] fn iterate_truncated_archive() { let source = std::fs::File::open("tests/fixtures/truncated.log.gz").unwrap(); - for content in ArchiveIterator::from_read(source, Password::empty()).unwrap() { + for content in ArchiveIterator::from_read(source, None).unwrap() { if let ArchiveContents::Err(Error::Unknown) = content { return; } @@ -653,7 +653,7 @@ fn iterate_truncated_archive() { fn uncompress_bytes_helper(bytes: &[u8]) { let wrapper = Cursor::new(bytes); - for content in ArchiveIterator::from_read(wrapper, Password::empty()).unwrap() { + for content in ArchiveIterator::from_read(wrapper, None).unwrap() { if let ArchiveContents::Err(Error::Unknown) = content { return; } @@ -814,12 +814,11 @@ fn iterate_archive_with_password() { let mut current_file_content: Vec = vec![]; let mut current_file_name = String::new(); - let mut iter: ArchiveIterator = ArchiveIteratorBuilder::new(source) + let mut iter = ArchiveIteratorBuilder::new(source) .with_password(source_password) .filter(|name, _| name.ends_with(".txt")) .build() - .unwrap() - .into_iter(); + .unwrap(); for content in &mut iter { match content { From f01be1aa6e331355ec977b2e27a25831da3233ff Mon Sep 17 00:00:00 2001 From: Otavio Salvador Date: Wed, 31 May 2023 18:31:01 -0300 Subject: [PATCH 3/5] fixup! feat: add support to iterator over encrypted zip Signed-off-by: Otavio Salvador --- src/iterator.rs | 14 +++++++------- src/lib.rs | 2 +- tests/integration_test.rs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/iterator.rs b/src/iterator.rs index 3be8066..a85f887 100644 --- a/src/iterator.rs +++ b/src/iterator.rs @@ -44,15 +44,15 @@ pub enum ArchiveContents { /// The entry is processed on a return value of `true` and ignored on `false`. pub type EntryFilterCallbackFn = dyn Fn(&str, &libc::stat) -> bool; -pub struct Password(Vec); +pub struct ArchivePassword(Vec); -impl Password { +impl ArchivePassword { pub fn extract(&self) -> *const i8 { self.0.as_ptr() as *const i8 } } -impl From for Password where T: AsRef { +impl From for ArchivePassword where T: AsRef { fn from(s: T) -> Self { Self(s.as_ref().as_bytes().to_vec()) } @@ -133,7 +133,7 @@ impl ArchiveIterator { source: R, decode: DecodeCallback, filter: Option>, - password: Option, + password: Option, ) -> Result> where R: Read + Seek, @@ -284,7 +284,7 @@ impl ArchiveIterator { /// # Ok(()) /// # } /// ``` - pub fn from_read(source: R, password: Option) -> Result> + pub fn from_read(source: R, password: Option) -> Result> where R: Read + Seek, { @@ -411,7 +411,7 @@ where source: R, decoder: DecodeCallback, filter: Option>, - password: Option, + password: Option, } /// A builder to generate an archive iterator over the contents of an @@ -472,7 +472,7 @@ where } /// Set a custom password to decode content of archive entries. - pub fn with_password(mut self, password: Password) -> ArchiveIteratorBuilder { + pub fn with_password(mut self, password: ArchivePassword) -> ArchiveIteratorBuilder { self.password = Some(password); self } diff --git a/src/lib.rs b/src/lib.rs index 40ef1b9..9627d98 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,7 +60,7 @@ pub mod tokio_support; use error::archive_result; pub use error::{Error, Result}; use io::{Seek, SeekFrom}; -pub use iterator::{ArchiveContents, ArchiveIterator, ArchiveIteratorBuilder, Password}; +pub use iterator::{ArchiveContents, ArchiveIterator, ArchiveIteratorBuilder, ArchivePassword}; use std::{ ffi::{CStr, CString}, io::{self, Read, Write}, diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 35caf5d..adfb382 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -808,7 +808,7 @@ fn iterate_archive_with_filter_path() { #[test] fn iterate_archive_with_password() { let source = std::fs::File::open("tests/fixtures/with-password.zip").unwrap(); - let source_password: Password = "123".into(); + let source_password: ArchivePassword = "123".into(); let mut files_result: Vec = Vec::new(); let mut current_file_content: Vec = vec![]; From a6098c805a26405dee087b5db193376eae7de929 Mon Sep 17 00:00:00 2001 From: Otavio Salvador Date: Thu, 1 Jun 2023 16:25:11 -0300 Subject: [PATCH 4/5] fixup! feat: add support to iterator over encrypted zip Signed-off-by: Otavio Salvador --- src/iterator.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/iterator.rs b/src/iterator.rs index a85f887..440f2d2 100644 --- a/src/iterator.rs +++ b/src/iterator.rs @@ -44,7 +44,7 @@ pub enum ArchiveContents { /// The entry is processed on a return value of `true` and ignored on `false`. pub type EntryFilterCallbackFn = dyn Fn(&str, &libc::stat) -> bool; -pub struct ArchivePassword(Vec); +pub struct ArchivePassword(CString); impl ArchivePassword { pub fn extract(&self) -> *const i8 { @@ -54,7 +54,7 @@ impl ArchivePassword { impl From for ArchivePassword where T: AsRef { fn from(s: T) -> Self { - Self(s.as_ref().as_bytes().to_vec()) + Self(CString::new(s.as_ref()).unwrap()) } } From c775c930e6420d71b40afd185d980a55a5c12e81 Mon Sep 17 00:00:00 2001 From: Otavio Salvador Date: Thu, 1 Jun 2023 16:28:47 -0300 Subject: [PATCH 5/5] fixup! feat: add support to iterator over encrypted zip Signed-off-by: Otavio Salvador --- src/iterator.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/iterator.rs b/src/iterator.rs index 440f2d2..dbf616a 100644 --- a/src/iterator.rs +++ b/src/iterator.rs @@ -52,7 +52,10 @@ impl ArchivePassword { } } -impl From for ArchivePassword where T: AsRef { +impl From for ArchivePassword +where + T: AsRef, +{ fn from(s: T) -> Self { Self(CString::new(s.as_ref()).unwrap()) }