From a191c4b435c683701b5e7b7c891f094da3071fd1 Mon Sep 17 00:00:00 2001 From: Rouven Spreckels Date: Wed, 16 Sep 2020 11:45:21 +0200 Subject: [PATCH] Support extra field. --- src/read.rs | 17 ++++++++++----- src/types.rs | 3 +++ src/write.rs | 51 ++++++++++++++++++++++++++++++++++++++------- tests/end_to_end.rs | 16 +++++++++++++- 4 files changed, 74 insertions(+), 13 deletions(-) diff --git a/src/read.rs b/src/read.rs index d19b353f..240bccba 100644 --- a/src/read.rs +++ b/src/read.rs @@ -502,6 +502,7 @@ fn central_header_to_zip_file( uncompressed_size: uncompressed_size as u64, file_name, file_name_raw, + extra_field, file_comment, header_start: offset, central_header_start, @@ -509,7 +510,7 @@ fn central_header_to_zip_file( external_attributes: external_file_attributes, }; - match parse_extra_field(&mut result, &*extra_field) { + match parse_extra_field(&mut result) { Ok(..) | Err(ZipError::Io(..)) => {} Err(e) => return Err(e), } @@ -520,10 +521,10 @@ fn central_header_to_zip_file( Ok(result) } -fn parse_extra_field(file: &mut ZipFileData, data: &[u8]) -> ZipResult<()> { - let mut reader = io::Cursor::new(data); +fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> { + let mut reader = io::Cursor::new(&file.extra_field); - while (reader.position() as usize) < data.len() { + while (reader.position() as usize) < file.extra_field.len() { let kind = reader.read_u16::()?; let len = reader.read_u16::()?; let mut len_left = len as i64; @@ -652,6 +653,11 @@ impl<'a> ZipFile<'a> { self.data.crc32 } + /// Get the extra data of the zip header for this file + pub fn extra_data(&self) -> &[u8] { + &self.data.extra_field + } + /// Get the starting offset of the data of the compressed file pub fn data_start(&self) -> u64 { self.data.data_start @@ -761,6 +767,7 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>( uncompressed_size: uncompressed_size as u64, file_name, file_name_raw, + extra_field, file_comment: String::new(), // file comment is only available in the central directory // header_start and data start are not available, but also don't matter, since seeking is // not available. @@ -773,7 +780,7 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>( external_attributes: 0, }; - match parse_extra_field(&mut result, &extra_field) { + match parse_extra_field(&mut result) { Ok(..) | Err(ZipError::Io(..)) => {} Err(e) => return Err(e), } diff --git a/src/types.rs b/src/types.rs index f4684027..1f4c13eb 100644 --- a/src/types.rs +++ b/src/types.rs @@ -230,6 +230,8 @@ pub struct ZipFileData { pub file_name: String, /// Raw file name. To be used when file_name was incorrectly decoded. pub file_name_raw: Vec, + /// Extra field usually used for storage expansion + pub extra_field: Vec, /// File comment pub file_comment: String, /// Specifies where the local header of the file starts @@ -310,6 +312,7 @@ mod test { uncompressed_size: 0, file_name: file_name.clone(), file_name_raw: file_name.into_bytes(), + extra_field: Vec::new(), file_comment: String::new(), header_start: 0, data_start: 0, diff --git a/src/write.rs b/src/write.rs index b8e97b08..d97080bf 100644 --- a/src/write.rs +++ b/src/write.rs @@ -209,9 +209,16 @@ impl ZipWriter { } /// Start a new file for with the requested options. - fn start_entry(&mut self, name: S, options: FileOptions) -> ZipResult<()> + fn start_entry( + &mut self, + name: S, + options: FileOptions, + extra_data: F, + ) -> ZipResult<()> where S: Into, + V: Into>, + F: FnOnce(u64) -> V, { self.finish_file()?; @@ -222,6 +229,7 @@ impl ZipWriter { let permissions = options.permissions.unwrap_or(0o100644); let file_name = name.into(); let file_name_raw = file_name.clone().into_bytes(); + let extra_field = extra_data(header_start + 30 + file_name_raw.len() as u64).into(); let mut file = ZipFileData { system: System::Unix, version_made_by: DEFAULT_VERSION, @@ -233,6 +241,7 @@ impl ZipWriter { uncompressed_size: 0, file_name, file_name_raw, + extra_field, file_comment: String::new(), header_start, data_start: 0, @@ -288,7 +297,7 @@ impl ZipWriter { options.permissions = Some(0o644); } *options.permissions.as_mut().unwrap() |= 0o100000; - self.start_entry(name, options)?; + self.start_entry(name, options, |_data_start| Vec::new())?; self.writing_to_file = true; Ok(()) } @@ -309,6 +318,30 @@ impl ZipWriter { self.start_file(path_to_string(path), options) } + /// Starts a file with extra data. + /// + /// Extra data is given by closure which provides a preliminary `ZipFile::data_start()` as it + /// would be without any extra data. + pub fn start_file_with_extra_data( + &mut self, + name: S, + mut options: FileOptions, + extra_data: F, + ) -> ZipResult<()> + where + S: Into, + V: Into>, + F: FnOnce(u64) -> V, + { + if options.permissions.is_none() { + options.permissions = Some(0o644); + } + *options.permissions.as_mut().unwrap() |= 0o100000; + self.start_entry(name, options, extra_data)?; + self.writing_to_file = true; + Ok(()) + } + /// Add a directory entry. /// /// You can't write data to the file afterwards. @@ -329,7 +362,7 @@ impl ZipWriter { _ => name_as_string + "/", }; - self.start_entry(name_with_slash, options)?; + self.start_entry(name_with_slash, options, |_data_start| Vec::new())?; self.writing_to_file = false; Ok(()) } @@ -611,10 +644,14 @@ fn write_central_directory_header(writer: &mut T, file: &ZipFileData) Ok(()) } -fn build_extra_field(_file: &ZipFileData) -> ZipResult> { - let writer = Vec::new(); - // Future work - Ok(writer) +fn build_extra_field(file: &ZipFileData) -> ZipResult> { + if file.extra_field.len() > std::u16::MAX as usize { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Extra data exceeds extra field", + ))?; + } + Ok(file.extra_field.clone()) } fn path_to_string(path: &std::path::Path) -> String { diff --git a/tests/end_to_end.rs b/tests/end_to_end.rs index 6268920a..ff25af16 100644 --- a/tests/end_to_end.rs +++ b/tests/end_to_end.rs @@ -28,6 +28,9 @@ fn write_to_zip_file(file: &mut Cursor>) -> zip::result::ZipResult<()> { zip.start_file("test/☃.txt", options)?; zip.write_all(b"Hello, World!\n")?; + zip.start_file_with_extra_data("test_with_extra_data/🐢.txt", options, |_| LOREM_IPSUM)?; + zip.write_all(b"Hello, World! Again.\n")?; + zip.start_file("test/lorem_ipsum.txt", Default::default())?; zip.write_all(LOREM_IPSUM)?; @@ -38,11 +41,22 @@ fn write_to_zip_file(file: &mut Cursor>) -> zip::result::ZipResult<()> { fn read_zip_file(zip_file: &mut Cursor>) -> zip::result::ZipResult { let mut archive = zip::ZipArchive::new(zip_file).unwrap(); - let expected_file_names = ["test/", "test/☃.txt", "test/lorem_ipsum.txt"]; + let expected_file_names = [ + "test/", + "test/☃.txt", + "test_with_extra_data/🐢.txt", + "test/lorem_ipsum.txt", + ]; let expected_file_names = HashSet::from_iter(expected_file_names.iter().map(|&v| v)); let file_names = archive.file_names().collect::>(); assert_eq!(file_names, expected_file_names); + { + let file_with_extra_data = archive.by_name("test_with_extra_data/🐢.txt")?; + let expected_extra_data = LOREM_IPSUM; + assert_eq!(file_with_extra_data.extra_data(), expected_extra_data); + } + let mut file = archive.by_name("test/lorem_ipsum.txt")?; let mut contents = String::new();