From b8c83ab5c133dc1330aa425a012d45f3c62e7ef1 Mon Sep 17 00:00:00 2001 From: Igor Tolmachov Date: Wed, 30 Aug 2023 23:33:11 +0900 Subject: Add zip archive datatypes --- src/datatypes.rs | 116 ++++++++++++++++++++++ src/lib.rs | 1 + src/result.rs | 8 ++ src/zip/datatypes.rs | 270 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/zip/mod.rs | 1 + 5 files changed, 396 insertions(+) create mode 100644 src/datatypes.rs create mode 100644 src/zip/datatypes.rs (limited to 'src') diff --git a/src/datatypes.rs b/src/datatypes.rs new file mode 100644 index 0000000..7712064 --- /dev/null +++ b/src/datatypes.rs @@ -0,0 +1,116 @@ +use crate::result::ArchiveResult; +use std::io::{Read, Write}; + +pub trait ArchiveDatatype +where + Self: Sized, +{ + const SIZE: usize; + + fn read(reader: impl Read) -> ArchiveResult; + + fn write(&self, writer: impl Write) -> ArchiveResult<()>; +} + +pub mod utils { + pub fn read_string(buf: &[u8]) -> String { + String::from_utf8(buf.into()).unwrap() + } + + pub fn write_string(string: &str) -> &[u8] { + string.as_bytes() + } + + pub fn vec(vec: &[u8]) -> &[u8] { + vec + } + + #[macro_export] + macro_rules! create_archive_datatype { + { + $vis:vis struct $struct_name:ident { + $($field:ident: $field_type:ty,)* + } + + $( + let $custom_field:ident: $custom_field_type:ty { + size: $custom_field_size: expr, + read: $custom_field_read: path, + write: $custom_field_write: path, + } + )* + + $( + const { + $($const_name:ident: $const_type:ty = $const_value:expr;)+ + } + )? + + $( + read $read_code:block + )? + + $( + write $write_code:block + )? + } + => { + #[derive(Debug)] + $vis struct $struct_name { + $($vis $field: $field_type),*, + $($vis $custom_field: $custom_field_type),* + } + + $( + impl $struct_name { + $(const $const_name: $const_type = $const_value;)* + } + )? + + impl ArchiveDatatype for $struct_name { + const SIZE: usize = ($((<$field_type>::BITS / 8)+)*0) as usize; + + fn read(mut reader: impl Read) -> ArchiveResult { + let mut buf = [0; Self::SIZE]; + reader.read(&mut buf)?; + + $( + let (bytes, buf) = buf.split_at((<$field_type>::BITS / 8) as usize); + let $field = <$field_type>::from_le_bytes(bytes.try_into().unwrap()); + )* + + $( + let $custom_field = { + let mut buf = vec![0; $custom_field_size as usize]; + reader.read(&mut buf)?; + $custom_field_read(&buf).into() + }; + )* + + #[allow(dropping_references)] + drop(buf); + + $($read_code;)? + + Ok( + Self { + $($field),*, + $($custom_field),* + } + ) + } + + fn write(&self, mut writer: impl Write) -> ArchiveResult<()> { + $($write_code;)? + writer.write(&[ + $(&self.$field.to_le_bytes()[..]),*, + $(&$custom_field_write(&self.$custom_field)),* + ].concat())?; + Ok(()) + } + } + }; + } + + pub use create_archive_datatype; +} diff --git a/src/lib.rs b/src/lib.rs index 163f760..abfd599 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod zip; mod result; mod archive; +mod datatypes; mod file; mod io; diff --git a/src/result.rs b/src/result.rs index 47302ae..715f10c 100644 --- a/src/result.rs +++ b/src/result.rs @@ -7,6 +7,7 @@ pub type ArchiveResult = Result; #[derive(Debug)] pub enum ArchiveError { IO(io::Error), + WrongSignature { expected: u32, received: u32 }, } impl From for ArchiveError { @@ -19,6 +20,12 @@ impl Display for ArchiveError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::IO(err) => write!(f, "{err}"), + Self::WrongSignature { expected, received } => { + write!( + f, + "Wrong signature. Expected: {expected}, received: {received}" + ) + } } } } @@ -27,6 +34,7 @@ impl Error for ArchiveError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { Self::IO(source) => Some(source), + _ => None, } } } diff --git a/src/zip/datatypes.rs b/src/zip/datatypes.rs new file mode 100644 index 0000000..b280e5d --- /dev/null +++ b/src/zip/datatypes.rs @@ -0,0 +1,270 @@ +use crate::datatypes::{utils, ArchiveDatatype}; +use crate::result::{ArchiveError, ArchiveResult}; +use std::io::{Read, Write}; + +utils::create_archive_datatype! { + pub struct LocalFileHeader { + signature: u32, + version_needed: u16, + general_purpose_bit_flag: u16, + compression_method: u16, + last_mod_file_time: u16, + last_mod_file_date: u16, + crc32: u32, + compressed_size: u32, + uncompressed_size: u32, + file_name_length: u16, + extra_field_length: u16, + } + + let file_name: String { + size: file_name_length, + read: utils::read_string, + write: utils::write_string, + } + + let extra_field: Vec { + size: file_name_length, + read: utils::vec, + write: utils::vec, + } + + const { + SIGNATURE: u32 = 0x04034b50; + } + + read { + if signature != Self::SIGNATURE { + return Err(ArchiveError::WrongSignature { expected: Self::SIGNATURE, received: signature }) + } + } +} + +pub struct DataDescriptor { + pub signature: Option, + pub crc32: u32, + pub compressed_size: u32, + pub uncompressed_size: u32, +} + +impl DataDescriptor { + const SIGNATURE: u32 = 0x04034b50; +} + +impl ArchiveDatatype for DataDescriptor { + const SIZE: usize = 12; + + fn read(mut reader: impl Read) -> ArchiveResult { + let mut buf = [0; Self::SIZE]; + reader.read(&mut buf)?; + + let signature = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]); + if signature == Self::SIGNATURE { + return Ok(Self { + signature: Some(signature), + crc32: u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]), + compressed_size: u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]), + uncompressed_size: { + let mut buf = [0; 4]; + reader.read(&mut buf)?; + u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]) + }, + }); + } else { + return Ok(Self { + signature: None, + crc32: signature, + compressed_size: u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]), + uncompressed_size: u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]), + }); + } + } + + fn write(&self, mut writer: impl Write) -> ArchiveResult<()> { + writer.write( + &[ + Self::SIGNATURE.to_le_bytes(), + self.crc32.to_le_bytes(), + self.compressed_size.to_le_bytes(), + self.uncompressed_size.to_le_bytes(), + ] + .concat(), + )?; + Ok(()) + } +} + +utils::create_archive_datatype! { + pub struct ArchiveExtraDataRecord { + signature: u32, + extra_field_length: u32, + } + + let extra_field: Vec { + size: extra_field_length, + read: utils::vec, + write: utils::vec, + } + + const { + SIGNATURE: u32 = 0x08064b50; + } + + read { + if signature != Self::SIGNATURE { + return Err(ArchiveError::WrongSignature { expected: Self::SIGNATURE, received: signature }) + } + } +} + +utils::create_archive_datatype! { + pub struct CentralDirectoryRecord { + signature: u32, + version_made_by: u16, + version_needed: u16, + general_purpose_bit_flag: u16, + compression_method: u16, + last_mod_file_time: u16, + last_mod_file_date: u16, + crc32: u32, + compressed_size: u32, + uncompressed_size: u32, + file_name_length: u16, + extra_field_length: u16, + file_comment_length: u16, + disk_number: u16, + internal_file_attributes: u16, + external_file_attributes: u32, + relative_header_offset: u32, + } + + let file_name: String { + size: file_name_length, + read: utils::read_string, + write: utils::write_string, + } + + let extra_field: Vec { + size: external_file_attributes, + read: utils::vec, + write: utils::vec, + } + + let file_comment: String { + size: file_comment_length, + read: utils::read_string, + write: utils::write_string, + } + + const { + SIGNATURE: u32 = 0x02014b50; + } + + read { + if signature != Self::SIGNATURE { + return Err(ArchiveError::WrongSignature { expected: Self::SIGNATURE, received: signature }) + } + } +} + +utils::create_archive_datatype! { + pub struct DigitalSignature{ + signature: u32, + data_size: u16, + } + + let data: Vec { + size: data_size, + read: utils::vec, + write: utils::vec, + } + + const { + SIGNATURE: u32 = 0x05054b50; + } + + read { + if signature != Self::SIGNATURE { + return Err(ArchiveError::WrongSignature { expected: Self::SIGNATURE, received: signature }) + } + } +} + +utils::create_archive_datatype! { + pub struct Zip64EndOfCentralDirectoryRecord { + signature: u32, + size_of_struct: u64, + version_made_by: u16, + version_needed: u16, + disk_number: u32, + disk_number_where_starts_central_directory: u32, + entries_in_central_directory_on_disk: u64, + entries_in_central_directory: u64, + size_of_central_directory_records: u64, + offset_of_central_directory_entries: u64, + } + + let extensible_data: Vec { + size: size_of_struct - Self::SIZE as u64 + 12, + read: utils::vec, + write: utils::vec, + } + + const { + SIGNATURE: u32 = 0x08064b50; + } + + read { + if signature != Self::SIGNATURE { + return Err(ArchiveError::WrongSignature { expected: Self::SIGNATURE, received: signature }) + } + } +} + +utils::create_archive_datatype! { + pub struct Zip64EndOfCentralDirectoryLocator { + signature: u32, + disk_number_where_starts_zip64_eocd: u32, + relative_offset_of_zip64_eocd: u64, + total_disks_number: u32, + } + + const { + SIGNATURE: u32 = 0x07064b50; + } + + read { + if signature != Self::SIGNATURE { + return Err(ArchiveError::WrongSignature { expected: Self::SIGNATURE, received: signature }) + } + } +} + +utils::create_archive_datatype! { + pub struct EndOfCentralDirectoryRecord { + signature: u32, + disk_number: u16, + disk_number_where_starts_central_directory: u16, + entries_in_central_directory_on_disk: u16, + entries_in_central_directory: u16, + size_of_central_directory_records: u32, + offset_of_central_directory_entries: u32, + comment_length: u16, + } + + let comment: String { + size: comment_length, + read: utils::read_string, + write: utils::write_string, + } + + const { + SIGNATURE: u32 = 0x06054b50; + } + + read { + if signature != Self::SIGNATURE { + return Err(ArchiveError::WrongSignature { expected: Self::SIGNATURE, received: signature }) + } + } +} diff --git a/src/zip/mod.rs b/src/zip/mod.rs index 5241c87..b668b6b 100644 --- a/src/zip/mod.rs +++ b/src/zip/mod.rs @@ -1,4 +1,5 @@ mod archive; +mod datatypes; mod file; mod io; -- cgit v1.2.3