From 7bcdc3b4ca460aec2b98fb2dca6165788c562b05 Mon Sep 17 00:00:00 2001 From: Igor Tolmachev Date: Sat, 20 Jul 2024 16:52:39 +0900 Subject: Partial aes implementation and others improvements --- .vscode/settings.json | 3 + Cargo.toml | 3 + src/archive.rs | 18 ++--- src/driver/driver.rs | 13 ++-- src/error.rs | 55 -------------- src/lib.rs | 2 - src/structs/de.rs | 33 ++++----- src/structs/error.rs | 41 ++++++----- src/structs/ser.rs | 47 +++++------- src/utils/read.rs | 10 +-- src/zip/datetime.rs | 45 ++++++++++++ src/zip/driver.rs | 179 ++++++++++++++++++++++----------------------- src/zip/encryption.rs | 89 ---------------------- src/zip/encryption/aes.rs | 46 ++++++++++++ src/zip/encryption/mod.rs | 5 ++ src/zip/encryption/weak.rs | 105 ++++++++++++++++++++++++++ src/zip/error.rs | 38 +++++++--- src/zip/file/info.rs | 43 +++++------ src/zip/file/read.rs | 99 ++++++++++++++++++++++--- src/zip/mod.rs | 1 + src/zip/structs.rs | 14 ++++ src/zip/tests.rs | 19 +++++ 22 files changed, 538 insertions(+), 370 deletions(-) delete mode 100644 src/error.rs create mode 100644 src/zip/datetime.rs delete mode 100644 src/zip/encryption.rs create mode 100644 src/zip/encryption/aes.rs create mode 100644 src/zip/encryption/mod.rs create mode 100644 src/zip/encryption/weak.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 8ab0a05..4a76d39 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,11 +4,13 @@ "bincode", "chrono", "datatypes", + "datelike", "datetime", "decompressor", "eocdr", "flate", "hasher", + "hmac", "lzma", "newtype", "ntfs", @@ -17,6 +19,7 @@ "repr", "rposition", "seekable", + "timelike", "unseekable", "xorout" ], diff --git a/Cargo.toml b/Cargo.toml index 96900a5..ea16168 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,13 @@ categories = ["compression", "filesystem"] exclude = [".vscode/"] [dependencies] +aes = "0.8.4" bzip2 = "0.4.4" chrono = "0.4.29" crc32fast = "1.4.2" flate2 = "1.0.30" liblzma = "0.3.2" +pbkdf2 = "0.12.2" serde = { version = "1.0.203", features = ["derive"] } +sha1 = "0.10.6" zstd = "0.13.2" diff --git a/src/archive.rs b/src/archive.rs index d49689d..d03f143 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -1,7 +1,6 @@ use crate::driver::{ArchiveRead, ArchiveWrite, Driver}; -use crate::ArchiveResult; use std::fs::File; -use std::io::{Read, Write}; +use std::io::{Error as IoError, Read, Write}; use std::path::Path; pub struct Archive { @@ -12,16 +11,17 @@ impl Archive where D::Io: std::io::Read, { - pub fn read(io: D::Io) -> ArchiveResult { + pub fn read(io: D::Io) -> Result { Ok(Self { driver: D::read(io)?, }) } #[inline] - pub fn read_from_file(path: impl AsRef) -> ArchiveResult + pub fn read_from_file(path: impl AsRef) -> Result where D: ArchiveRead, + D::Error: From, { Self::read(File::open(path)?) } @@ -35,23 +35,23 @@ where self.files().len() } - pub fn get_file_index(&self, name: &str) -> ArchiveResult { + pub fn get_file_index(&self, name: &str) -> Result { self.driver.get_file_index(name) } - pub fn get_file_info_by_index(&self, index: usize) -> ArchiveResult<&D::FileInfo, D::Error> { + pub fn get_file_info_by_index(&self, index: usize) -> Result<&D::FileInfo, D::Error> { self.driver.get_file_info(index) } #[inline] - pub fn get_file_info_by_name(&self, name: &str) -> ArchiveResult<&D::FileInfo, D::Error> { + pub fn get_file_info_by_name(&self, name: &str) -> Result<&D::FileInfo, D::Error> { self.get_file_info_by_index(self.get_file_index(name)?) } pub fn get_file_reader_by_index<'d>( &'d mut self, index: usize, - ) -> ArchiveResult, D::Error> { + ) -> Result, D::Error> { self.driver.get_file_reader(index) } @@ -59,7 +59,7 @@ where pub fn get_file_reader_by_name<'d>( &'d mut self, name: &str, - ) -> ArchiveResult, D::Error> { + ) -> Result, D::Error> { self.get_file_reader_by_index(self.get_file_index(name)?) } } diff --git a/src/driver/driver.rs b/src/driver/driver.rs index 359793d..755380f 100644 --- a/src/driver/driver.rs +++ b/src/driver/driver.rs @@ -1,5 +1,4 @@ use crate::driver::{ArchiveFileInfo, FileDriver}; -use crate::ArchiveResult; use std::error::Error; use std::io::{Read, Write}; @@ -19,22 +18,20 @@ where Self: 'd; // Create driver instance - fn read(io: Self::Io) -> ArchiveResult; + fn read(io: Self::Io) -> Result; // Return vec of file infos fn files(&self) -> &Vec; // Return file index by name - fn get_file_index(&self, name: &str) -> ArchiveResult; + fn get_file_index(&self, name: &str) -> Result; // Return file info by index - fn get_file_info(&self, index: usize) -> ArchiveResult<&Self::FileInfo, Self::Error>; + fn get_file_info(&self, index: usize) -> Result<&Self::FileInfo, Self::Error>; // Return file reader by index - fn get_file_reader<'d>( - &'d mut self, - index: usize, - ) -> ArchiveResult, Self::Error>; + fn get_file_reader<'d>(&'d mut self, index: usize) + -> Result, Self::Error>; } pub trait ArchiveWrite: Driver diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index b7866d7..0000000 --- a/src/error.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::error::Error; -use std::fmt::Display; -use std::io; - -pub type ArchiveResult = Result>; - -#[derive(Debug)] -pub enum ArchiveError { - Io { error: io::Error }, - Serde { message: String }, - Archivator { module: &'static str, error: E }, -} - -impl From for ArchiveError { - fn from(value: io::Error) -> Self { - Self::Io { error: value } - } -} - -impl PartialEq for ArchiveError { - fn eq(&self, other: &E) -> bool { - match self { - Self::Archivator { error, .. } => error == other, - _ => false, - } - } -} - -impl Display for ArchiveError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Io { error } => writeln!(f, "IO: {error}"), - Self::Serde { message } => writeln!(f, "Serde: {message}"), - Self::Archivator { module, error } => writeln!(f, "{module}: {error}"), - } - } -} - -impl Error for ArchiveError {} - -impl serde::ser::Error for ArchiveError { - fn custom(msg: T) -> Self { - Self::Serde { - message: msg.to_string(), - } - } -} - -impl serde::de::Error for ArchiveError { - fn custom(msg: T) -> Self { - Self::Serde { - message: msg.to_string(), - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 7490cc8..5dc36b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,10 @@ mod archive; mod driver; -mod error; mod structs; mod utils; pub mod zip; pub use archive::Archive; -pub use error::{ArchiveError, ArchiveResult}; pub use zip::Zip; diff --git a/src/structs/de.rs b/src/structs/de.rs index edd8011..8b2130e 100644 --- a/src/structs/de.rs +++ b/src/structs/de.rs @@ -1,5 +1,4 @@ use crate::structs::{Settings, StructError, StructResult}; -use crate::ArchiveError; use serde::de; pub struct ArchiveDeserializer<'de> { @@ -60,14 +59,14 @@ impl<'a, 'de> EnumDeserializer<'a, 'de> { } impl<'de> de::Deserializer<'de> for &mut ArchiveDeserializer<'de> { - type Error = ArchiveError; + type Error = StructError; #[inline] fn deserialize_any(self, _visitor: V) -> StructResult where V: de::Visitor<'de>, { - Err(StructError::DeserializationNotSupported { type_name: "any" }.into()) + Err(StructError::DeserializationNotSupported("any")) } fn deserialize_bool(self, visitor: V) -> StructResult @@ -160,7 +159,7 @@ impl<'de> de::Deserializer<'de> for &mut ArchiveDeserializer<'de> { where V: de::Visitor<'de>, { - Err(StructError::DeserializationNotSupported { type_name: "char" }.into()) + Err(StructError::DeserializationNotSupported("char")) } #[inline] @@ -168,7 +167,7 @@ impl<'de> de::Deserializer<'de> for &mut ArchiveDeserializer<'de> { where V: de::Visitor<'de>, { - Err(StructError::DeserializationNotSupported { type_name: "str" }.into()) + Err(StructError::DeserializationNotSupported("str")) } #[inline] @@ -176,10 +175,7 @@ impl<'de> de::Deserializer<'de> for &mut ArchiveDeserializer<'de> { where V: de::Visitor<'de>, { - Err(StructError::DeserializationNotSupported { - type_name: "string", - } - .into()) + Err(StructError::DeserializationNotSupported("string")) } #[inline] @@ -187,7 +183,7 @@ impl<'de> de::Deserializer<'de> for &mut ArchiveDeserializer<'de> { where V: de::Visitor<'de>, { - Err(StructError::DeserializationNotSupported { type_name: "bytes" }.into()) + Err(StructError::DeserializationNotSupported("bytes")) } #[inline] @@ -195,7 +191,7 @@ impl<'de> de::Deserializer<'de> for &mut ArchiveDeserializer<'de> { where V: de::Visitor<'de>, { - Err(StructError::DeserializationNotSupported { type_name: "bytes" }.into()) + Err(StructError::DeserializationNotSupported("bytes")) } #[inline] @@ -203,10 +199,7 @@ impl<'de> de::Deserializer<'de> for &mut ArchiveDeserializer<'de> { where V: de::Visitor<'de>, { - Err(StructError::DeserializationNotSupported { - type_name: "optional", - } - .into()) + Err(StructError::DeserializationNotSupported("optional")) } #[inline] @@ -242,7 +235,7 @@ impl<'de> de::Deserializer<'de> for &mut ArchiveDeserializer<'de> { where V: de::Visitor<'de>, { - Err(StructError::DeserializationNotSupported { type_name: "seq" }.into()) + Err(StructError::DeserializationNotSupported("seq")) } #[inline] @@ -271,7 +264,7 @@ impl<'de> de::Deserializer<'de> for &mut ArchiveDeserializer<'de> { where V: de::Visitor<'de>, { - Err(StructError::DeserializationNotSupported { type_name: "map" }.into()) + Err(StructError::DeserializationNotSupported("map")) } #[inline] @@ -322,7 +315,7 @@ impl<'de> de::Deserializer<'de> for &mut ArchiveDeserializer<'de> { } impl<'a, 'de> de::SeqAccess<'de> for SeqDeserializer<'a, 'de> { - type Error = ArchiveError; + type Error = StructError; fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> where @@ -336,7 +329,7 @@ impl<'a, 'de> de::SeqAccess<'de> for SeqDeserializer<'a, 'de> { } impl<'a, 'de> de::EnumAccess<'de> for EnumDeserializer<'a, 'de> { - type Error = ArchiveError; + type Error = StructError; type Variant = Self; fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> @@ -348,7 +341,7 @@ impl<'a, 'de> de::EnumAccess<'de> for EnumDeserializer<'a, 'de> { } impl<'a, 'de> de::VariantAccess<'de> for EnumDeserializer<'a, 'de> { - type Error = ArchiveError; + type Error = StructError; #[inline] fn unit_variant(self) -> Result<(), Self::Error> { diff --git a/src/structs/error.rs b/src/structs/error.rs index f07163c..deb10b3 100644 --- a/src/structs/error.rs +++ b/src/structs/error.rs @@ -1,37 +1,42 @@ -use crate::{ArchiveError, ArchiveResult}; +use serde::{de, ser}; use std::error::Error; use std::fmt::Display; -pub type StructResult = ArchiveResult; +pub type StructResult = Result; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum StructError { - SerializationNotSupported { type_name: &'static str }, - DeserializationNotSupported { type_name: &'static str }, + SerializationNotSupported(&'static str), + DeserializationNotSupported(&'static str), + Serde(String), UnexpectedEOF, } -impl From for ArchiveError { - fn from(value: StructError) -> Self { - Self::Archivator { - module: "Struct serializer", - error: value, - } - } -} - impl Display for StructError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::SerializationNotSupported { type_name } => { - writeln!(f, "Serialization for type '{type_name}' not supported") + Self::SerializationNotSupported(name) => { + writeln!(f, "Serialization for type '{}' not supported", name) } - Self::DeserializationNotSupported { type_name } => { - writeln!(f, "Deserialization for type '{type_name}' not supported") + Self::DeserializationNotSupported(name) => { + writeln!(f, "Deserialization for type '{}' not supported", name) } + Self::Serde(message) => writeln!(f, "Serde error: {}", message), Self::UnexpectedEOF => writeln!(f, "Unexpected EOF"), } } } impl Error for StructError {} + +impl ser::Error for StructError { + fn custom(msg: T) -> Self { + Self::Serde(msg.to_string()) + } +} + +impl de::Error for StructError { + fn custom(msg: T) -> Self { + Self::Serde(msg.to_string()) + } +} diff --git a/src/structs/ser.rs b/src/structs/ser.rs index f7e694a..b633dff 100644 --- a/src/structs/ser.rs +++ b/src/structs/ser.rs @@ -1,5 +1,4 @@ use crate::structs::{Settings, StructError, StructResult}; -use crate::ArchiveError; use serde::{ser, Serialize}; pub struct ArchiveSerializer { @@ -22,7 +21,7 @@ impl ArchiveSerializer { impl ser::Serializer for &mut ArchiveSerializer { type Ok = (); - type Error = ArchiveError; + type Error = StructError; type SerializeSeq = Self; type SerializeTuple = Self; @@ -89,25 +88,22 @@ impl ser::Serializer for &mut ArchiveSerializer { #[inline] fn serialize_char(self, _v: char) -> StructResult<()> { - Err(StructError::SerializationNotSupported { type_name: "char" }.into()) + Err(StructError::SerializationNotSupported("char")) } #[inline] fn serialize_str(self, _v: &str) -> StructResult<()> { - Err(StructError::SerializationNotSupported { type_name: "str" }.into()) + Err(StructError::SerializationNotSupported("str")) } #[inline] fn serialize_bytes(self, _v: &[u8]) -> StructResult<()> { - Err(StructError::SerializationNotSupported { type_name: "bytes" }.into()) + Err(StructError::SerializationNotSupported("bytes")) } #[inline] fn serialize_none(self) -> StructResult<()> { - Err(StructError::SerializationNotSupported { - type_name: "optional", - } - .into()) + Err(StructError::SerializationNotSupported("optional")) } #[inline] @@ -115,10 +111,7 @@ impl ser::Serializer for &mut ArchiveSerializer { where T: ?Sized + Serialize, { - Err(StructError::SerializationNotSupported { - type_name: "optional", - } - .into()) + Err(StructError::SerializationNotSupported("optional")) } #[inline] @@ -175,7 +168,7 @@ impl ser::Serializer for &mut ArchiveSerializer { #[inline] fn serialize_seq(self, _len: Option) -> StructResult { - Err(StructError::SerializationNotSupported { type_name: "seq" }.into()) + Err(StructError::SerializationNotSupported("seq")) } #[inline] @@ -210,7 +203,7 @@ impl ser::Serializer for &mut ArchiveSerializer { #[inline] fn serialize_map(self, _len: Option) -> StructResult { - Err(StructError::SerializationNotSupported { type_name: "map" }.into()) + Err(StructError::SerializationNotSupported("map")) } #[inline] @@ -241,25 +234,25 @@ impl ser::Serializer for &mut ArchiveSerializer { impl ser::SerializeSeq for &mut ArchiveSerializer { type Ok = (); - type Error = ArchiveError; + type Error = StructError; #[inline] fn serialize_element(&mut self, _value: &T) -> StructResult<()> where T: ?Sized + Serialize, { - Err(StructError::SerializationNotSupported { type_name: "seq" }.into()) + Err(StructError::SerializationNotSupported("seq")) } #[inline] fn end(self) -> StructResult<()> { - Err(StructError::SerializationNotSupported { type_name: "seq" }.into()) + Err(StructError::SerializationNotSupported("seq")) } } impl ser::SerializeTuple for &mut ArchiveSerializer { type Ok = (); - type Error = ArchiveError; + type Error = StructError; #[inline] fn serialize_element(&mut self, value: &T) -> StructResult<()> @@ -277,7 +270,7 @@ impl ser::SerializeTuple for &mut ArchiveSerializer { impl ser::SerializeTupleStruct for &mut ArchiveSerializer { type Ok = (); - type Error = ArchiveError; + type Error = StructError; #[inline] fn serialize_field(&mut self, value: &T) -> StructResult<()> @@ -295,7 +288,7 @@ impl ser::SerializeTupleStruct for &mut ArchiveSerializer { impl ser::SerializeTupleVariant for &mut ArchiveSerializer { type Ok = (); - type Error = ArchiveError; + type Error = StructError; #[inline] fn serialize_field(&mut self, value: &T) -> StructResult<()> @@ -313,14 +306,14 @@ impl ser::SerializeTupleVariant for &mut ArchiveSerializer { impl ser::SerializeMap for &mut ArchiveSerializer { type Ok = (); - type Error = ArchiveError; + type Error = StructError; #[inline] fn serialize_key(&mut self, _key: &T) -> StructResult<()> where T: ?Sized + Serialize, { - Err(StructError::SerializationNotSupported { type_name: "map" }.into()) + Err(StructError::SerializationNotSupported("map")) } #[inline] @@ -328,18 +321,18 @@ impl ser::SerializeMap for &mut ArchiveSerializer { where T: ?Sized + Serialize, { - Err(StructError::SerializationNotSupported { type_name: "map" }.into()) + Err(StructError::SerializationNotSupported("map")) } #[inline] fn end(self) -> StructResult<()> { - Err(StructError::SerializationNotSupported { type_name: "map" }.into()) + Err(StructError::SerializationNotSupported("map")) } } impl ser::SerializeStruct for &mut ArchiveSerializer { type Ok = (); - type Error = ArchiveError; + type Error = StructError; #[inline] fn serialize_field(&mut self, _key: &'static str, value: &T) -> StructResult<()> @@ -357,7 +350,7 @@ impl ser::SerializeStruct for &mut ArchiveSerializer { impl ser::SerializeStructVariant for &mut ArchiveSerializer { type Ok = (); - type Error = ArchiveError; + type Error = StructError; #[inline] fn serialize_field(&mut self, _key: &'static str, value: &T) -> StructResult<()> diff --git a/src/utils/read.rs b/src/utils/read.rs index 491c89c..2a46308 100644 --- a/src/utils/read.rs +++ b/src/utils/read.rs @@ -1,21 +1,21 @@ -use std::io::{Read, Result as IOResult}; +use std::io::{Read, Result as IoResult}; pub trait ReadUtils { - fn read_arr(&mut self) -> IOResult<[u8; S]>; + fn read_arr(&mut self) -> IoResult<[u8; S]>; - fn read_vec(&mut self, size: usize) -> IOResult>; + fn read_vec(&mut self, size: usize) -> IoResult>; } impl ReadUtils for R { #[inline] - fn read_arr(&mut self) -> Result<[u8; S], std::io::Error> { + fn read_arr(&mut self) -> IoResult<[u8; S]> { let mut arr = [0; S]; self.read_exact(&mut arr)?; Ok(arr) } #[inline] - fn read_vec(&mut self, size: usize) -> Result, std::io::Error> { + fn read_vec(&mut self, size: usize) -> IoResult> { let mut vec = vec![0; size]; self.read_exact(&mut vec)?; Ok(vec) diff --git a/src/zip/datetime.rs b/src/zip/datetime.rs new file mode 100644 index 0000000..a4b1b55 --- /dev/null +++ b/src/zip/datetime.rs @@ -0,0 +1,45 @@ +use crate::zip::{ZipError, ZipResult}; +use chrono::{DateTime, Datelike, NaiveDate, TimeZone, Timelike}; + +pub trait DosDateTime: Sized { + fn from_dos_date_time(date: u16, time: u16, tz: Tz) -> ZipResult; + + #[allow(dead_code)] + fn to_dos_date(&self) -> u16; + + fn to_dos_time(&self) -> u16; +} + +impl DosDateTime for DateTime { + #[inline] + fn from_dos_date_time(date: u16, time: u16, tz: Tz) -> ZipResult { + Ok(NaiveDate::from_ymd_opt( + (date as i32 >> 9 & 0x7F) + 1980, + date as u32 >> 5 & 0xF, + date as u32 & 0x1F, + ) + .ok_or(ZipError::InvalidDate)? + .and_hms_opt( + (time as u32 >> 11) & 0x1F, + (time as u32 >> 5) & 0x3F, + (time as u32 & 0x1F) * 2, + ) + .ok_or(ZipError::InvalidTime)? + .and_local_timezone(tz) + .unwrap()) + } + + #[inline] + fn to_dos_date(&self) -> u16 { + (self.year() as u16 - 1980 & 0x7F) << 9 + | (self.month() as u16 & 0xF) << 5 + | self.day() as u16 & 0x1F + } + + #[inline] + fn to_dos_time(&self) -> u16 { + (self.hour() as u16 & 0x1F) << 11 + | (self.minute() as u16 & 0x3F) << 5 + | self.second() as u16 / 2 & 0x1F + } +} diff --git a/src/zip/driver.rs b/src/zip/driver.rs index e7878cf..8dc902f 100644 --- a/src/zip/driver.rs +++ b/src/zip/driver.rs @@ -1,48 +1,51 @@ use crate::driver::{ArchiveRead, ArchiveWrite, Driver}; use crate::utils::ReadUtils; use crate::zip::cp437::FromCp437; -use crate::zip::structs::{deserialize, Cdr, Eocdr, Eocdr64, Eocdr64Locator, ExtraHeader}; +use crate::zip::datetime::DosDateTime; +use crate::zip::structs::{ + deserialize, AesField, Cdr, Eocdr, Eocdr64, Eocdr64Locator, ExtraHeader, CDR_SIGNATURE, + EOCDR64_LOCATOR_SIGNATURE, EOCDR64_SIGNATURE, EOCDR_SIGNATURE, +}; use crate::zip::{ BitFlag, CompressionMethod, EncryptionMethod, ZipError, ZipFileInfo, ZipFileReader, ZipFileWriter, ZipResult, }; -use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime}; +use chrono::{DateTime, Local}; use std::collections::HashMap as Map; use std::fs::File; use std::io::{BufReader, Read, Seek, SeekFrom, Write}; -fn dos_to_local(date: u16, time: u16) -> ZipResult> { - Ok(NaiveDateTime::new( - NaiveDate::from_ymd_opt( - (date as i32 >> 9 & 0x7F) + 1980, - date as u32 >> 5 & 0xF, - date as u32 & 0x1F, - ) - .ok_or(ZipError::InvalidDate)?, - NaiveTime::from_hms_opt( - (time as u32 >> 11) & 0x1F, - (time as u32 >> 5) & 0x3F, - (time as u32 & 0x1F) * 2, - ) - .ok_or(ZipError::InvalidTime)?, - ) - .and_local_timezone(Local) - .unwrap()) +#[inline] +fn split_fields(bytes: &[u8]) -> Option> { + let mut fields = Vec::new(); + + let mut p = 0; + while p < bytes.len() { + let header: ExtraHeader = deserialize(bytes.get(p..p + 4)?).unwrap(); + p += 4; + let data = bytes.get(p..p + header.size as usize)?; + p += header.size as usize; + + fields.push((header.id, data)); + } + + Some(fields) } -fn ntfs_to_local(time: u64) -> ZipResult> { - Ok(DateTime::from_timestamp( - (time / 10000000 - 11644473600) as i64, - (time % 10000000) as u32, +#[inline] +fn ntfs_to_local(time: u64) -> Option> { + Some( + DateTime::from_timestamp( + (time / 10000000 - 11644473600) as i64, + (time % 10000000) as u32, + )? + .with_timezone(&Local), ) - .ok_or(ZipError::InvalidTime)? - .with_timezone(&Local)) } -fn timestamp_to_local(time: i32) -> ZipResult> { - Ok(DateTime::from_timestamp(time as i64, 0) - .ok_or(ZipError::InvalidTime)? - .with_timezone(&Local)) +#[inline] +fn timestamp_to_local(time: i32) -> Option> { + Some(DateTime::from_timestamp(time as i64, 0)?.with_timezone(&Local)) } pub struct Zip { @@ -74,7 +77,7 @@ impl ArchiveRead for Zip { .ok_or(ZipError::EocdrNotFound)?, )? .windows(4) - .rposition(|v| u32::from_le_bytes(v.try_into().unwrap()) == 0x06054b50) + .rposition(|v| v == EOCDR_SIGNATURE) .ok_or(ZipError::EocdrNotFound)? as u64; // Read eocdr @@ -88,13 +91,13 @@ impl ArchiveRead for Zip { let buf = io.read_arr::<20>()?; let (cd_pointer, cd_size, cd_records) = // If locator found then read eocdr64 - if u32::from_le_bytes(buf[0..4].try_into().unwrap()) == 0x07064b50 { + if buf[0..4] == EOCDR64_LOCATOR_SIGNATURE { let eocdr64locator: Eocdr64Locator = deserialize(&buf[4..]).unwrap(); io.seek(SeekFrom::Start(eocdr64locator.eocdr64_pointer))?; let buf = io.read_arr::<56>()?; - if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != 0x06064b50 { - return Err(ZipError::InvalidEOCDR64Signature.into()); + if buf[0..4] != EOCDR64_SIGNATURE { + return Err(ZipError::InvalidEOCDR64Signature); } let eocdr64: Eocdr64 = deserialize(&buf[4..]).unwrap(); @@ -116,8 +119,8 @@ impl ArchiveRead for Zip { for i in 0..cd_records as usize { let buf = buf_reader.read_arr::<46>()?; - if u32::from_le_bytes(buf[..4].try_into().unwrap()) != 0x02014b50 { - return Err(ZipError::InvalidCDRSignature.into()); + if buf[..4] != CDR_SIGNATURE { + return Err(ZipError::InvalidCDRSignature); } let cdr: Cdr = deserialize(&buf[4..46]).unwrap(); let bit_flag = BitFlag::new(cdr.bit_flag); @@ -138,91 +141,87 @@ impl ArchiveRead for Zip { String::from_cp437(comment) }; + let mut compression_method = cdr.compression_method; + let mut encryption_method = if !bit_flag.is_encrypted() { + EncryptionMethod::None + } else if !bit_flag.is_strong_encryption() { + EncryptionMethod::Weak + } else { + EncryptionMethod::Unsupported + }; + let mut compressed_size = cdr.compressed_size as u64; let mut size = cdr.size as u64; let mut header_pointer = cdr.header_pointer as u64; - let mut mtime = dos_to_local(cdr.dos_date, cdr.dos_time)?; + let mut mtime = DateTime::from_dos_date_time(cdr.dos_date, cdr.dos_time, Local)?; let mut atime = None; let mut ctime = None; // Parse extensible data fields - let mut ep: usize = 0; - while ep < cdr.extra_field_len as usize { - let header: ExtraHeader = deserialize(&extra_fields[ep..ep + 4]).unwrap(); - ep += 4; - - match header.id { + for (id, mut data) in split_fields(&extra_fields).ok_or(ZipError::InvalidExtraFields)? { + match id { // Zip64 0x0001 => { if size == 0xFFFFFFFF { - compressed_size = - u64::from_le_bytes(extra_fields[ep..ep + 8].try_into().unwrap()); - ep += 8; + size = u64::from_le_bytes(data.read_arr()?); } if compressed_size == 0xFFFFFFFF { - size = u64::from_le_bytes(extra_fields[ep..ep + 8].try_into().unwrap()); - ep += 8; + compressed_size = u64::from_le_bytes(data.read_arr()?); } if header_pointer == 0xFFFFFFFF { - header_pointer = - u64::from_le_bytes(extra_fields[ep..ep + 8].try_into().unwrap()); - ep += 8; - } - if cdr.disk == 0xFFFF { - ep += 4 + header_pointer = u64::from_le_bytes(data.read_arr()?); } } // NTFS 0x000a => { - let mut tp = ep + 4; - ep += header.size as usize; - - while tp < ep { - let header: ExtraHeader = - deserialize(&extra_fields[tp..tp + 4]).unwrap(); - tp += 4; - - match header.id { + for (id, mut data) in + split_fields(&data).ok_or(ZipError::InvalidExtraFields)? + { + match id { 0x0001 => { - mtime = ntfs_to_local(u64::from_le_bytes( - extra_fields[tp..tp + 8].try_into().unwrap(), - ))?; - tp += 8; - atime = Some(ntfs_to_local(u64::from_le_bytes( - extra_fields[tp..tp + 8].try_into().unwrap(), - ))?); - tp += 8; - ctime = Some(ntfs_to_local(u64::from_le_bytes( - extra_fields[tp..tp + 8].try_into().unwrap(), - ))?); - tp += 8; - } - _ => { - tp += header.size as usize; + mtime = ntfs_to_local(u64::from_le_bytes(data.read_arr()?)) + .unwrap_or(mtime); + atime = ntfs_to_local(u64::from_le_bytes(data.read_arr()?)); + ctime = ntfs_to_local(u64::from_le_bytes(data.read_arr()?)); } + _ => {} } } } // Unix 0x000d => { - atime = Some(timestamp_to_local(i32::from_le_bytes( - extra_fields[ep..ep + 4].try_into().unwrap(), - ))?); - mtime = timestamp_to_local(i32::from_le_bytes( - extra_fields[ep + 4..ep + 8].try_into().unwrap(), - ))?; - ep += header.size as usize + atime = timestamp_to_local(i32::from_le_bytes(data.read_arr()?)); + mtime = timestamp_to_local(i32::from_le_bytes(data.read_arr()?)) + .unwrap_or(mtime); + } + // AES + 0x9901 => { + let aes: AesField = deserialize(&data.read_arr::<7>()?).unwrap(); + if aes.id != 0x4541 { + return Err(ZipError::InvalidExtraFields); + } + encryption_method = match aes.strength { + 0x01 => EncryptionMethod::Aes128, + 0x02 => EncryptionMethod::Aes192, + 0x03 => EncryptionMethod::Aes256, + _ => EncryptionMethod::Unsupported, + }; + compression_method = aes.compression_method; } // Skip unrecognized header - _ => ep += header.size as usize, + _ => {} } } + if compression_method == 99 { + return Err(ZipError::AesExtraFieldNotFound); + } + indexes.insert(name.clone(), i); files.push(ZipFileInfo::new( - CompressionMethod::from_struct_id(cdr.compression_method)?, - EncryptionMethod::from_bif_flag(bit_flag, cdr.crc, cdr.dos_time), + CompressionMethod::from_struct_id(compression_method), + encryption_method, bit_flag, mtime, atime, @@ -248,15 +247,15 @@ impl ArchiveRead for Zip { &self.files } - fn get_file_index(&self, name: &str) -> crate::ArchiveResult { + fn get_file_index(&self, name: &str) -> ZipResult { self.indexes .get(name) - .ok_or(ZipError::FileNotFound.into()) + .ok_or(ZipError::FileNotFound) .copied() } fn get_file_info(&self, index: usize) -> ZipResult<&Self::FileInfo> { - self.files.get(index).ok_or(ZipError::FileNotFound.into()) + self.files.get(index).ok_or(ZipError::FileNotFound) } #[inline] diff --git a/src/zip/encryption.rs b/src/zip/encryption.rs deleted file mode 100644 index f317245..0000000 --- a/src/zip/encryption.rs +++ /dev/null @@ -1,89 +0,0 @@ -use crate::utils::ReadUtils; -use crate::zip::{ZipError, ZipResult}; -use std::io::{Read, Result as IoResult}; - -const TABLE: [u32; 256] = generate_table(); - -const fn generate_table() -> [u32; 256] { - let mut table = [0; 256]; - - let mut i = 0; - while i <= 255 { - let mut t = i as u32; - - let mut j = 0; - while j < 8 { - if (t & 1) > 0 { - t = (t >> 1) ^ 0xEDB88320 - } else { - t >>= 1 - } - j += 1; - } - - table[i] = t; - i += 1 - } - - table -} - -fn crc32(byte: u8, crc: u32) -> u32 { - (crc >> 8) ^ TABLE[((crc & 0xFF) as u8 ^ byte) as usize] -} - -pub struct WeakDecoder { - key0: u32, - key1: u32, - key2: u32, - io: Io, -} - -impl WeakDecoder { - pub fn new(io: Io, check: u8, password: &[u8]) -> ZipResult { - let mut decoder = Self { - key0: 305419896, - key1: 591751049, - key2: 878082192, - io, - }; - - for c in password { - decoder.update_keys(*c) - } - - let buf = decoder.read_arr::<12>()?; - if check != buf[11] { - return Err(ZipError::IncorrectPassword.into()); - } - - Ok(decoder) - } - - fn update_keys(&mut self, byte: u8) { - self.key0 = crc32(byte, self.key0); - self.key1 = self - .key1 - .wrapping_add(self.key0 & 0xFF) - .wrapping_mul(134775813) - .wrapping_add(1); - self.key2 = crc32((self.key1 >> 24) as u8, self.key2); - } - - fn decode_byte(&mut self, byte: u8) -> u8 { - let key = self.key2 | 2; - let byte = byte ^ ((key.wrapping_mul(key ^ 1)) >> 8) as u8; - self.update_keys(byte); - byte - } -} - -impl Read for WeakDecoder { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - let bytes = self.io.read(buf)?; - for i in 0..bytes { - buf[i] = self.decode_byte(buf[i]); - } - Ok(bytes) - } -} diff --git a/src/zip/encryption/aes.rs b/src/zip/encryption/aes.rs new file mode 100644 index 0000000..6f41aaa --- /dev/null +++ b/src/zip/encryption/aes.rs @@ -0,0 +1,46 @@ +use crate::utils::ReadUtils; +use aes::cipher::generic_array::GenericArray; +use aes::cipher::BlockEncrypt; +use std::io::{Read, Result as IoResult}; + +#[allow(dead_code)] +pub struct AesDecoder { + io: Io, + aes: Aes, + + counter: u128, + block: [u8; 16], + cursor: usize, +} + +impl AesDecoder { + pub fn new(mut io: Io, aes: Aes) -> IoResult { + let block = io.read_arr::<16>()?; + let mut decoder = Self { + io, + aes, + counter: 1, + block, + cursor: 0, + }; + decoder.decrypt_block(); + Ok(decoder) + } + + #[inline] + fn decrypt_block(&mut self) { + let mut mask = self.counter.to_le_bytes(); + self.aes + .encrypt_block(GenericArray::from_mut_slice(&mut mask)); + for (b, m) in self.block.iter_mut().zip(mask) { + *b ^= m + } + self.counter += 1; + } +} + +impl Read for AesDecoder { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + todo!() + } +} diff --git a/src/zip/encryption/mod.rs b/src/zip/encryption/mod.rs new file mode 100644 index 0000000..8cacaf9 --- /dev/null +++ b/src/zip/encryption/mod.rs @@ -0,0 +1,5 @@ +mod aes; +mod weak; + +pub use aes::AesDecoder; +pub use weak::{Keys, WeakDecoder}; diff --git a/src/zip/encryption/weak.rs b/src/zip/encryption/weak.rs new file mode 100644 index 0000000..144cd53 --- /dev/null +++ b/src/zip/encryption/weak.rs @@ -0,0 +1,105 @@ +use std::io::{Read, Result as IoResult}; + +const TABLE: [u32; 256] = generate_table(); + +const fn generate_table() -> [u32; 256] { + let mut table = [0; 256]; + + let mut i = 0; + while i <= 255 { + let mut t = i as u32; + + let mut j = 0; + while j < 8 { + if (t & 1) > 0 { + t = (t >> 1) ^ 0xEDB88320 + } else { + t >>= 1 + } + j += 1; + } + + table[i] = t; + i += 1 + } + + table +} + +fn crc32(byte: u8, crc: u32) -> u32 { + (crc >> 8) ^ TABLE[((crc & 0xFF) as u8 ^ byte) as usize] +} + +pub struct Keys { + key0: u32, + key1: u32, + key2: u32, +} + +impl Keys { + pub fn new() -> Self { + Self { + key0: 305419896, + key1: 591751049, + key2: 878082192, + } + } + + fn update(&mut self, byte: u8) { + self.key0 = crc32(byte, self.key0); + self.key1 = self + .key1 + .wrapping_add(self.key0 & 0xFF) + .wrapping_mul(134775813) + .wrapping_add(1); + self.key2 = crc32((self.key1 >> 24) as u8, self.key2); + } + + pub fn set_password(&mut self, passwd: &[u8]) { + for b in passwd { + self.update(*b) + } + } + + pub fn set_header(&mut self, header: [u8; 12]) -> u8 { + for b in &header[..11] { + self.decode_byte(*b); + } + self.decode_byte(header[11]) + } + + #[allow(dead_code)] + pub fn encode_bytes(&mut self, byte: u8) -> u8 { + let key = self.key2 | 2; + self.update(byte); + byte ^ ((key.wrapping_mul(key ^ 1)) >> 8) as u8 + } + + pub fn decode_byte(&mut self, byte: u8) -> u8 { + let key = self.key2 | 2; + let byte = byte ^ ((key.wrapping_mul(key ^ 1)) >> 8) as u8; + self.update(byte); + byte + } +} + +pub struct WeakDecoder { + io: Io, + keys: Keys, +} + +impl WeakDecoder { + pub fn new(io: Io, keys: Keys) -> Self { + WeakDecoder { io, keys } + } +} + +impl Read for WeakDecoder { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + let bytes = self.io.read(buf)?; + for i in 0..bytes { + buf[i] = self.keys.decode_byte(buf[i]); + } + Ok(bytes) + } +} diff --git a/src/zip/error.rs b/src/zip/error.rs index 5573cf8..5508177 100644 --- a/src/zip/error.rs +++ b/src/zip/error.rs @@ -1,22 +1,25 @@ -use crate::{ArchiveError, ArchiveResult}; use std::error::Error; use std::fmt::Display; +use std::io::Error as IoError; -pub type ZipResult = ArchiveResult; +pub type ZipResult = Result; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug)] pub enum ZipError { + Io(IoError), + EocdrNotFound, InvalidEOCDR64Signature, InvalidFileHeaderSignature, InvalidCDRSignature, - InvalidCompressionMethod(u16), UnsupportedCompressionMethod(u16), UnsupportedEncryptionMethod, InvalidDate, InvalidTime, InvalidFileName, + InvalidExtraFields, + AesExtraFieldNotFound, InvalidFileComment, FileNotFound, @@ -26,18 +29,30 @@ pub enum ZipError { EncryptedDataIsUnseekable, } -impl From for ArchiveError { - fn from(value: ZipError) -> Self { - Self::Archivator { - module: "Zip", - error: value, +impl From for ZipError { + fn from(value: IoError) -> Self { + Self::Io(value) + } +} + +impl PartialEq for ZipError { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Io(l0), Self::Io(r0)) => l0.kind() == r0.kind(), + (Self::UnsupportedCompressionMethod(l0), Self::UnsupportedCompressionMethod(r0)) => { + l0 == r0 + } + _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +impl Eq for ZipError {} + impl Display for ZipError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Self::Io(error) => write!(f, "{}", error), Self::EocdrNotFound => write!(f, "End of central directory record not found"), Self::InvalidEOCDR64Signature => { write!( @@ -52,9 +67,6 @@ impl Display for ZipError { write!(f, "Invalid signature of central directory record") } - Self::InvalidCompressionMethod(id) => { - writeln!(f, "Invalid compression method {}", id) - } Self::UnsupportedCompressionMethod(id) => { writeln!(f, "Unsupported compression method {}", id) } @@ -64,6 +76,8 @@ impl Display for ZipError { Self::InvalidDate => write!(f, "Invalid date"), Self::InvalidTime => write!(f, "Invalid time"), Self::InvalidFileName => write!(f, "Invalid file name"), + Self::InvalidExtraFields => write!(f, "Invalid extra fields"), + Self::AesExtraFieldNotFound => write!(f, "Aes extra field not found"), Self::InvalidFileComment => write!(f, "Invalid file comment"), Self::FileNotFound => write!(f, "File not found"), diff --git a/src/zip/file/info.rs b/src/zip/file/info.rs index 599dcc3..38ea984 100644 --- a/src/zip/file/info.rs +++ b/src/zip/file/info.rs @@ -1,5 +1,5 @@ use crate::driver::ArchiveFileInfo; -use crate::zip::{ZipError, ZipResult}; +use crate::zip::datetime::DosDateTime; use chrono::{DateTime, Local}; #[derive(Debug, PartialEq, Eq, Clone)] @@ -14,42 +14,30 @@ pub enum CompressionMethod { } impl CompressionMethod { - pub(crate) fn from_struct_id(id: u16) -> ZipResult { - Ok(match id { + #[inline] + pub(crate) fn from_struct_id(id: u16) -> Self { + match id { 0 => Self::Store, 8 => Self::Deflate, 12 => Self::BZip2, 14 => Self::Lzma, 93 => Self::Zstd, 95 => Self::Xz, - 1..=7 | 9..=11 | 13 | 15..=20 | 94 | 96..=99 => Self::Unsupported(id), - 21..=92 | 100.. => return Err(ZipError::InvalidCompressionMethod(id).into()), - }) + _ => Self::Unsupported(id), + } } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum EncryptionMethod { None, - Weak(u8), + Weak, // ZipCrypto + Aes128, // WinZip encryption + Aes192, + Aes256, Unsupported, } -impl EncryptionMethod { - pub(crate) fn from_bif_flag(bit_flag: BitFlag, crc: u32, dos_time: u16) -> EncryptionMethod { - match (bit_flag.is_encrypted(), bit_flag.is_strong_encryption()) { - (false, false) => EncryptionMethod::None, - (true, false) => EncryptionMethod::Weak(if bit_flag.is_has_data_descriptor() { - (dos_time >> 8) as u8 // Info-ZIP modification - } else { - (crc >> 24) as u8 - }), - (true, true) => EncryptionMethod::Unsupported, - _ => panic!("impossible"), - } - } -} - #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct BitFlag { flag: u16, @@ -159,7 +147,7 @@ pub struct ZipFileInfo { } impl ZipFileInfo { - pub fn new( + pub(crate) fn new( compression_method: CompressionMethod, encryption_method: EncryptionMethod, bit_flag: BitFlag, @@ -189,6 +177,15 @@ impl ZipFileInfo { } } + #[inline] + pub(crate) fn password_check(&self) -> u8 { + if self.bit_flag.is_has_data_descriptor() { + (self.mtime.to_dos_time() >> 8) as u8 + } else { + (self.crc >> 24) as u8 + } + } + pub fn is_dir(&self) -> bool { self.name.ends_with("/") } diff --git a/src/zip/file/read.rs b/src/zip/file/read.rs index c26b304..80bdcfb 100644 --- a/src/zip/file/read.rs +++ b/src/zip/file/read.rs @@ -1,11 +1,16 @@ use crate::driver::FileDriver; use crate::utils::{IoCursor, ReadUtils}; -use crate::zip::encryption::WeakDecoder; +use crate::zip::encryption::{AesDecoder, Keys, WeakDecoder}; +use crate::zip::structs::FILE_HEADER_SIGNATURE; use crate::zip::{CompressionMethod, EncryptionMethod, ZipError, ZipFileInfo, ZipResult}; +use aes::cipher::KeyInit; +use aes::{Aes128, Aes192, Aes256}; use bzip2::read::BzDecoder; use flate2::read::DeflateDecoder; use liblzma::read::XzDecoder; use liblzma::stream::{Filters, LzmaOptions, Stream}; +use pbkdf2::pbkdf2_hmac_array; +use sha1::Sha1; use std::io::{ BufReader, Error as IoError, ErrorKind as IoErrorKind, Read, Result as IoResult, Seek, SeekFrom, }; @@ -14,34 +19,101 @@ use zstd::stream::Decoder as ZstdDecoder; enum Encryption { None(Io), Weak(WeakDecoder), + Aes128(AesDecoder), + Aes192(AesDecoder), + Aes256(AesDecoder), } impl Encryption { - pub fn new(io: Io, info: &ZipFileInfo, password: Option<&[u8]>) -> ZipResult { + #[inline] + pub fn new(mut io: Io, info: &ZipFileInfo, password: Option<&[u8]>) -> ZipResult { Ok(match info.encryption_method { EncryptionMethod::None => Self::None(io), - EncryptionMethod::Weak(check) => Self::Weak(WeakDecoder::new( - io, - check, - password.ok_or(ZipError::PasswordIsNotSpecified)?, - )?), - EncryptionMethod::Unsupported => { - return Err(ZipError::UnsupportedEncryptionMethod.into()) + EncryptionMethod::Weak => { + let mut keys = Keys::new(); + keys.set_password(password.ok_or(ZipError::PasswordIsNotSpecified)?); + + let check = keys.set_header(io.read_arr()?); + if check != info.password_check() { + return Err(ZipError::IncorrectPassword); + } + + Self::Weak(WeakDecoder::new(io, keys)) + } + EncryptionMethod::Aes128 => { + let header = io.read_arr::<10>()?; + let salt = &header[..8]; + let check = &header[8..]; + + let hash = pbkdf2_hmac_array::( + password.ok_or(ZipError::PasswordIsNotSpecified)?, + salt, + 1000, + ); + let key = &hash[..16]; + + if check != &hash[32..] { + return Err(ZipError::IncorrectPassword); + } + + Self::Aes128(AesDecoder::new(io, Aes128::new(key.into()))?) + } + EncryptionMethod::Aes192 => { + let header = io.read_arr::<14>()?; + let salt = &header[..12]; + let check = &header[12..]; + + let hash = pbkdf2_hmac_array::( + password.ok_or(ZipError::PasswordIsNotSpecified)?, + salt, + 1000, + ); + let key = &hash[..24]; + + if check != &hash[48..] { + return Err(ZipError::IncorrectPassword); + } + + Self::Aes192(AesDecoder::new(io, Aes192::new(key.into()))?) + } + EncryptionMethod::Aes256 => { + let header = io.read_arr::<18>()?; + let salt = &header[..16]; + let check = &header[16..]; + + let hash = pbkdf2_hmac_array::( + password.ok_or(ZipError::PasswordIsNotSpecified)?, + salt, + 1000, + ); + let key = &hash[..32]; + + if check != &hash[64..] { + return Err(ZipError::IncorrectPassword); + } + + Self::Aes256(AesDecoder::new(io, Aes256::new(key.into()))?) } + EncryptionMethod::Unsupported => return Err(ZipError::UnsupportedEncryptionMethod), }) } } impl Read for Encryption { + #[inline] fn read(&mut self, buf: &mut [u8]) -> IoResult { match self { Self::None(io) => io.read(buf), Self::Weak(io) => io.read(buf), + Self::Aes128(io) => io.read(buf), + Self::Aes192(io) => io.read(buf), + Self::Aes256(io) => io.read(buf), } } } impl Seek for Encryption { + #[inline] fn seek(&mut self, pos: SeekFrom) -> IoResult { match self { Self::None(io) => io.seek(pos), @@ -62,6 +134,7 @@ enum Compression { } impl Compression { + #[inline] pub fn new(mut io: Io, info: &ZipFileInfo) -> ZipResult { Ok(match info.compression_method { CompressionMethod::Store => Self::Store(io), @@ -88,13 +161,14 @@ impl Compression { CompressionMethod::Zstd => Self::Zstd(ZstdDecoder::new(io)?), CompressionMethod::Xz => Self::Xz(XzDecoder::new(io)), CompressionMethod::Unsupported(id) => { - return Err(ZipError::UnsupportedCompressionMethod(id).into()) + return Err(ZipError::UnsupportedCompressionMethod(id)) } }) } } impl Read for Compression { + #[inline] fn read(&mut self, buf: &mut [u8]) -> IoResult { match self { Compression::Store(io) => io.read(buf), @@ -107,6 +181,7 @@ impl Read for Compression { } impl Seek for Compression { + #[inline] fn seek(&mut self, pos: SeekFrom) -> IoResult { match self { Compression::Store(io) => io.seek(pos), @@ -137,8 +212,8 @@ impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> { io.seek(SeekFrom::Start(info.header_pointer))?; let buf = io.read_arr::<30>()?; - if u32::from_le_bytes(buf[..4].try_into().unwrap()) != 0x04034b50 { - return Err(ZipError::InvalidFileHeaderSignature.into()); + if buf[..4] != FILE_HEADER_SIGNATURE { + return Err(ZipError::InvalidFileHeaderSignature); } let data_pointer = info.header_pointer + 30 diff --git a/src/zip/mod.rs b/src/zip/mod.rs index fcc6161..488c06d 100644 --- a/src/zip/mod.rs +++ b/src/zip/mod.rs @@ -1,5 +1,6 @@ mod archive; mod cp437; +mod datetime; mod driver; mod encryption; mod error; diff --git a/src/zip/structs.rs b/src/zip/structs.rs index ebecae7..8b25400 100644 --- a/src/zip/structs.rs +++ b/src/zip/structs.rs @@ -1,6 +1,9 @@ use crate::structs::{ByteOrder, Settings, StructResult, VariantIndexType}; use serde::{Deserialize, Serialize}; +pub const FILE_HEADER_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x03, 0x04]; + +pub const EOCDR_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x05, 0x06]; #[derive(Serialize, Deserialize)] pub struct Eocdr { pub eocdr_disk: u16, @@ -12,6 +15,7 @@ pub struct Eocdr { pub comment_len: u16, } +pub const EOCDR64_LOCATOR_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x06, 0x07]; #[derive(Serialize, Deserialize)] pub struct Eocdr64Locator { pub eocdr64_disk: u32, @@ -19,6 +23,7 @@ pub struct Eocdr64Locator { pub disks: u32, } +pub const EOCDR64_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x06, 0x06]; #[derive(Serialize, Deserialize)] pub struct Eocdr64 { pub eocdr64_size: u64, @@ -32,6 +37,7 @@ pub struct Eocdr64 { pub cd_pointer: u64, } +pub const CDR_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x01, 0x02]; #[derive(Serialize, Deserialize)] pub struct Cdr { pub version: u16, @@ -58,6 +64,14 @@ pub struct ExtraHeader { pub size: u16, } +#[derive(Serialize, Deserialize)] +pub struct AesField { + pub version: u16, + pub id: u16, + pub strength: u8, + pub compression_method: u16, +} + #[inline] #[allow(dead_code)] pub fn serialize(object: &mut T) -> StructResult> { diff --git a/src/zip/tests.rs b/src/zip/tests.rs index e24cdfe..a8ac634 100644 --- a/src/zip/tests.rs +++ b/src/zip/tests.rs @@ -1,5 +1,7 @@ use crate::zip::cp437::{from_char, is_cp437, to_char, FromCp437}; +use crate::zip::datetime::DosDateTime; use crate::zip::{bit::DeflateMode, BitFlag}; +use chrono::{DateTime, Local, TimeZone}; #[test] fn test_bit_flag() { @@ -74,3 +76,20 @@ fn test_cp437() { "abcdefghijklmnopqrstuvwxyz" ); } + +#[test] +fn test_dos_datetime() { + let datetime = Local.with_ymd_and_hms(2001, 2, 3, 4, 5, 6).unwrap(); + assert_eq!( + DateTime::from_dos_date_time(datetime.to_dos_date(), datetime.to_dos_time(), Local) + .unwrap(), + datetime + ); + + let datetime = Local.with_ymd_and_hms(1999, 9, 9, 9, 9, 9).unwrap(); + assert_eq!( + DateTime::from_dos_date_time(datetime.to_dos_date(), datetime.to_dos_time(), Local) + .unwrap(), + Local.with_ymd_and_hms(1999, 9, 9, 9, 9, 8).unwrap() + ); // Dos format stores seconds with an accuracy of 2s +} -- cgit v1.2.3