diff options
Diffstat (limited to 'src/zip')
| -rw-r--r-- | src/zip/datetime.rs | 45 | ||||
| -rw-r--r-- | src/zip/driver.rs | 179 | ||||
| -rw-r--r-- | src/zip/encryption/aes.rs | 46 | ||||
| -rw-r--r-- | src/zip/encryption/mod.rs | 5 | ||||
| -rw-r--r-- | src/zip/encryption/weak.rs (renamed from src/zip/encryption.rs) | 62 | ||||
| -rw-r--r-- | src/zip/error.rs | 38 | ||||
| -rw-r--r-- | src/zip/file/info.rs | 43 | ||||
| -rw-r--r-- | src/zip/file/read.rs | 99 | ||||
| -rw-r--r-- | src/zip/mod.rs | 1 | ||||
| -rw-r--r-- | src/zip/structs.rs | 14 | ||||
| -rw-r--r-- | src/zip/tests.rs | 19 |
11 files changed, 391 insertions, 160 deletions
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 @@ | |||
| 1 | use crate::zip::{ZipError, ZipResult}; | ||
| 2 | use chrono::{DateTime, Datelike, NaiveDate, TimeZone, Timelike}; | ||
| 3 | |||
| 4 | pub trait DosDateTime<Tz>: Sized { | ||
| 5 | fn from_dos_date_time(date: u16, time: u16, tz: Tz) -> ZipResult<Self>; | ||
| 6 | |||
| 7 | #[allow(dead_code)] | ||
| 8 | fn to_dos_date(&self) -> u16; | ||
| 9 | |||
| 10 | fn to_dos_time(&self) -> u16; | ||
| 11 | } | ||
| 12 | |||
| 13 | impl<Tz: TimeZone> DosDateTime<Tz> for DateTime<Tz> { | ||
| 14 | #[inline] | ||
| 15 | fn from_dos_date_time(date: u16, time: u16, tz: Tz) -> ZipResult<Self> { | ||
| 16 | Ok(NaiveDate::from_ymd_opt( | ||
| 17 | (date as i32 >> 9 & 0x7F) + 1980, | ||
| 18 | date as u32 >> 5 & 0xF, | ||
| 19 | date as u32 & 0x1F, | ||
| 20 | ) | ||
| 21 | .ok_or(ZipError::InvalidDate)? | ||
| 22 | .and_hms_opt( | ||
| 23 | (time as u32 >> 11) & 0x1F, | ||
| 24 | (time as u32 >> 5) & 0x3F, | ||
| 25 | (time as u32 & 0x1F) * 2, | ||
| 26 | ) | ||
| 27 | .ok_or(ZipError::InvalidTime)? | ||
| 28 | .and_local_timezone(tz) | ||
| 29 | .unwrap()) | ||
| 30 | } | ||
| 31 | |||
| 32 | #[inline] | ||
| 33 | fn to_dos_date(&self) -> u16 { | ||
| 34 | (self.year() as u16 - 1980 & 0x7F) << 9 | ||
| 35 | | (self.month() as u16 & 0xF) << 5 | ||
| 36 | | self.day() as u16 & 0x1F | ||
| 37 | } | ||
| 38 | |||
| 39 | #[inline] | ||
| 40 | fn to_dos_time(&self) -> u16 { | ||
| 41 | (self.hour() as u16 & 0x1F) << 11 | ||
| 42 | | (self.minute() as u16 & 0x3F) << 5 | ||
| 43 | | self.second() as u16 / 2 & 0x1F | ||
| 44 | } | ||
| 45 | } | ||
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 @@ | |||
| 1 | use crate::driver::{ArchiveRead, ArchiveWrite, Driver}; | 1 | use crate::driver::{ArchiveRead, ArchiveWrite, Driver}; |
| 2 | use crate::utils::ReadUtils; | 2 | use crate::utils::ReadUtils; |
| 3 | use crate::zip::cp437::FromCp437; | 3 | use crate::zip::cp437::FromCp437; |
| 4 | use crate::zip::structs::{deserialize, Cdr, Eocdr, Eocdr64, Eocdr64Locator, ExtraHeader}; | 4 | use crate::zip::datetime::DosDateTime; |
| 5 | use crate::zip::structs::{ | ||
| 6 | deserialize, AesField, Cdr, Eocdr, Eocdr64, Eocdr64Locator, ExtraHeader, CDR_SIGNATURE, | ||
| 7 | EOCDR64_LOCATOR_SIGNATURE, EOCDR64_SIGNATURE, EOCDR_SIGNATURE, | ||
| 8 | }; | ||
| 5 | use crate::zip::{ | 9 | use crate::zip::{ |
| 6 | BitFlag, CompressionMethod, EncryptionMethod, ZipError, ZipFileInfo, ZipFileReader, | 10 | BitFlag, CompressionMethod, EncryptionMethod, ZipError, ZipFileInfo, ZipFileReader, |
| 7 | ZipFileWriter, ZipResult, | 11 | ZipFileWriter, ZipResult, |
| 8 | }; | 12 | }; |
| 9 | use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime}; | 13 | use chrono::{DateTime, Local}; |
| 10 | use std::collections::HashMap as Map; | 14 | use std::collections::HashMap as Map; |
| 11 | use std::fs::File; | 15 | use std::fs::File; |
| 12 | use std::io::{BufReader, Read, Seek, SeekFrom, Write}; | 16 | use std::io::{BufReader, Read, Seek, SeekFrom, Write}; |
| 13 | 17 | ||
| 14 | fn dos_to_local(date: u16, time: u16) -> ZipResult<DateTime<Local>> { | 18 | #[inline] |
| 15 | Ok(NaiveDateTime::new( | 19 | fn split_fields(bytes: &[u8]) -> Option<Vec<(u16, &[u8])>> { |
| 16 | NaiveDate::from_ymd_opt( | 20 | let mut fields = Vec::new(); |
| 17 | (date as i32 >> 9 & 0x7F) + 1980, | 21 | |
| 18 | date as u32 >> 5 & 0xF, | 22 | let mut p = 0; |
| 19 | date as u32 & 0x1F, | 23 | while p < bytes.len() { |
| 20 | ) | 24 | let header: ExtraHeader = deserialize(bytes.get(p..p + 4)?).unwrap(); |
| 21 | .ok_or(ZipError::InvalidDate)?, | 25 | p += 4; |
| 22 | NaiveTime::from_hms_opt( | 26 | let data = bytes.get(p..p + header.size as usize)?; |
| 23 | (time as u32 >> 11) & 0x1F, | 27 | p += header.size as usize; |
| 24 | (time as u32 >> 5) & 0x3F, | 28 | |
| 25 | (time as u32 & 0x1F) * 2, | 29 | fields.push((header.id, data)); |
| 26 | ) | 30 | } |
| 27 | .ok_or(ZipError::InvalidTime)?, | 31 | |
| 28 | ) | 32 | Some(fields) |
| 29 | .and_local_timezone(Local) | ||
| 30 | .unwrap()) | ||
| 31 | } | 33 | } |
| 32 | 34 | ||
| 33 | fn ntfs_to_local(time: u64) -> ZipResult<DateTime<Local>> { | 35 | #[inline] |
| 34 | Ok(DateTime::from_timestamp( | 36 | fn ntfs_to_local(time: u64) -> Option<DateTime<Local>> { |
| 35 | (time / 10000000 - 11644473600) as i64, | 37 | Some( |
| 36 | (time % 10000000) as u32, | 38 | DateTime::from_timestamp( |
| 39 | (time / 10000000 - 11644473600) as i64, | ||
| 40 | (time % 10000000) as u32, | ||
| 41 | )? | ||
| 42 | .with_timezone(&Local), | ||
| 37 | ) | 43 | ) |
| 38 | .ok_or(ZipError::InvalidTime)? | ||
| 39 | .with_timezone(&Local)) | ||
| 40 | } | 44 | } |
| 41 | 45 | ||
| 42 | fn timestamp_to_local(time: i32) -> ZipResult<DateTime<Local>> { | 46 | #[inline] |
| 43 | Ok(DateTime::from_timestamp(time as i64, 0) | 47 | fn timestamp_to_local(time: i32) -> Option<DateTime<Local>> { |
| 44 | .ok_or(ZipError::InvalidTime)? | 48 | Some(DateTime::from_timestamp(time as i64, 0)?.with_timezone(&Local)) |
| 45 | .with_timezone(&Local)) | ||
| 46 | } | 49 | } |
| 47 | 50 | ||
| 48 | pub struct Zip<Io = File> { | 51 | pub struct Zip<Io = File> { |
| @@ -74,7 +77,7 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> { | |||
| 74 | .ok_or(ZipError::EocdrNotFound)?, | 77 | .ok_or(ZipError::EocdrNotFound)?, |
| 75 | )? | 78 | )? |
| 76 | .windows(4) | 79 | .windows(4) |
| 77 | .rposition(|v| u32::from_le_bytes(v.try_into().unwrap()) == 0x06054b50) | 80 | .rposition(|v| v == EOCDR_SIGNATURE) |
| 78 | .ok_or(ZipError::EocdrNotFound)? as u64; | 81 | .ok_or(ZipError::EocdrNotFound)? as u64; |
| 79 | 82 | ||
| 80 | // Read eocdr | 83 | // Read eocdr |
| @@ -88,13 +91,13 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> { | |||
| 88 | let buf = io.read_arr::<20>()?; | 91 | let buf = io.read_arr::<20>()?; |
| 89 | let (cd_pointer, cd_size, cd_records) = | 92 | let (cd_pointer, cd_size, cd_records) = |
| 90 | // If locator found then read eocdr64 | 93 | // If locator found then read eocdr64 |
| 91 | if u32::from_le_bytes(buf[0..4].try_into().unwrap()) == 0x07064b50 { | 94 | if buf[0..4] == EOCDR64_LOCATOR_SIGNATURE { |
| 92 | let eocdr64locator: Eocdr64Locator = deserialize(&buf[4..]).unwrap(); | 95 | let eocdr64locator: Eocdr64Locator = deserialize(&buf[4..]).unwrap(); |
| 93 | 96 | ||
| 94 | io.seek(SeekFrom::Start(eocdr64locator.eocdr64_pointer))?; | 97 | io.seek(SeekFrom::Start(eocdr64locator.eocdr64_pointer))?; |
| 95 | let buf = io.read_arr::<56>()?; | 98 | let buf = io.read_arr::<56>()?; |
| 96 | if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != 0x06064b50 { | 99 | if buf[0..4] != EOCDR64_SIGNATURE { |
| 97 | return Err(ZipError::InvalidEOCDR64Signature.into()); | 100 | return Err(ZipError::InvalidEOCDR64Signature); |
| 98 | } | 101 | } |
| 99 | let eocdr64: Eocdr64 = deserialize(&buf[4..]).unwrap(); | 102 | let eocdr64: Eocdr64 = deserialize(&buf[4..]).unwrap(); |
| 100 | 103 | ||
| @@ -116,8 +119,8 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> { | |||
| 116 | for i in 0..cd_records as usize { | 119 | for i in 0..cd_records as usize { |
| 117 | let buf = buf_reader.read_arr::<46>()?; | 120 | let buf = buf_reader.read_arr::<46>()?; |
| 118 | 121 | ||
| 119 | if u32::from_le_bytes(buf[..4].try_into().unwrap()) != 0x02014b50 { | 122 | if buf[..4] != CDR_SIGNATURE { |
| 120 | return Err(ZipError::InvalidCDRSignature.into()); | 123 | return Err(ZipError::InvalidCDRSignature); |
| 121 | } | 124 | } |
| 122 | let cdr: Cdr = deserialize(&buf[4..46]).unwrap(); | 125 | let cdr: Cdr = deserialize(&buf[4..46]).unwrap(); |
| 123 | let bit_flag = BitFlag::new(cdr.bit_flag); | 126 | let bit_flag = BitFlag::new(cdr.bit_flag); |
| @@ -138,91 +141,87 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> { | |||
| 138 | String::from_cp437(comment) | 141 | String::from_cp437(comment) |
| 139 | }; | 142 | }; |
| 140 | 143 | ||
| 144 | let mut compression_method = cdr.compression_method; | ||
| 145 | let mut encryption_method = if !bit_flag.is_encrypted() { | ||
| 146 | EncryptionMethod::None | ||
| 147 | } else if !bit_flag.is_strong_encryption() { | ||
| 148 | EncryptionMethod::Weak | ||
| 149 | } else { | ||
| 150 | EncryptionMethod::Unsupported | ||
| 151 | }; | ||
| 152 | |||
| 141 | let mut compressed_size = cdr.compressed_size as u64; | 153 | let mut compressed_size = cdr.compressed_size as u64; |
| 142 | let mut size = cdr.size as u64; | 154 | let mut size = cdr.size as u64; |
| 143 | let mut header_pointer = cdr.header_pointer as u64; | 155 | let mut header_pointer = cdr.header_pointer as u64; |
| 144 | 156 | ||
| 145 | let mut mtime = dos_to_local(cdr.dos_date, cdr.dos_time)?; | 157 | let mut mtime = DateTime::from_dos_date_time(cdr.dos_date, cdr.dos_time, Local)?; |
| 146 | let mut atime = None; | 158 | let mut atime = None; |
| 147 | let mut ctime = None; | 159 | let mut ctime = None; |
| 148 | 160 | ||
| 149 | // Parse extensible data fields | 161 | // Parse extensible data fields |
| 150 | let mut ep: usize = 0; | 162 | for (id, mut data) in split_fields(&extra_fields).ok_or(ZipError::InvalidExtraFields)? { |
| 151 | while ep < cdr.extra_field_len as usize { | 163 | match id { |
| 152 | let header: ExtraHeader = deserialize(&extra_fields[ep..ep + 4]).unwrap(); | ||
| 153 | ep += 4; | ||
| 154 | |||
| 155 | match header.id { | ||
| 156 | // Zip64 | 164 | // Zip64 |
| 157 | 0x0001 => { | 165 | 0x0001 => { |
| 158 | if size == 0xFFFFFFFF { | 166 | if size == 0xFFFFFFFF { |
| 159 | compressed_size = | 167 | size = u64::from_le_bytes(data.read_arr()?); |
| 160 | u64::from_le_bytes(extra_fields[ep..ep + 8].try_into().unwrap()); | ||
| 161 | ep += 8; | ||
| 162 | } | 168 | } |
| 163 | if compressed_size == 0xFFFFFFFF { | 169 | if compressed_size == 0xFFFFFFFF { |
| 164 | size = u64::from_le_bytes(extra_fields[ep..ep + 8].try_into().unwrap()); | 170 | compressed_size = u64::from_le_bytes(data.read_arr()?); |
| 165 | ep += 8; | ||
| 166 | } | 171 | } |
| 167 | if header_pointer == 0xFFFFFFFF { | 172 | if header_pointer == 0xFFFFFFFF { |
| 168 | header_pointer = | 173 | header_pointer = u64::from_le_bytes(data.read_arr()?); |
| 169 | u64::from_le_bytes(extra_fields[ep..ep + 8].try_into().unwrap()); | ||
| 170 | ep += 8; | ||
| 171 | } | ||
| 172 | if cdr.disk == 0xFFFF { | ||
| 173 | ep += 4 | ||
| 174 | } | 174 | } |
| 175 | } | 175 | } |
| 176 | // NTFS | 176 | // NTFS |
| 177 | 0x000a => { | 177 | 0x000a => { |
| 178 | let mut tp = ep + 4; | 178 | for (id, mut data) in |
| 179 | ep += header.size as usize; | 179 | split_fields(&data).ok_or(ZipError::InvalidExtraFields)? |
| 180 | 180 | { | |
| 181 | while tp < ep { | 181 | match id { |
| 182 | let header: ExtraHeader = | ||
| 183 | deserialize(&extra_fields[tp..tp + 4]).unwrap(); | ||
| 184 | tp += 4; | ||
| 185 | |||
| 186 | match header.id { | ||
| 187 | 0x0001 => { | 182 | 0x0001 => { |
| 188 | mtime = ntfs_to_local(u64::from_le_bytes( | 183 | mtime = ntfs_to_local(u64::from_le_bytes(data.read_arr()?)) |
| 189 | extra_fields[tp..tp + 8].try_into().unwrap(), | 184 | .unwrap_or(mtime); |
| 190 | ))?; | 185 | atime = ntfs_to_local(u64::from_le_bytes(data.read_arr()?)); |
| 191 | tp += 8; | 186 | ctime = ntfs_to_local(u64::from_le_bytes(data.read_arr()?)); |
| 192 | atime = Some(ntfs_to_local(u64::from_le_bytes( | ||
| 193 | extra_fields[tp..tp + 8].try_into().unwrap(), | ||
| 194 | ))?); | ||
| 195 | tp += 8; | ||
| 196 | ctime = Some(ntfs_to_local(u64::from_le_bytes( | ||
| 197 | extra_fields[tp..tp + 8].try_into().unwrap(), | ||
| 198 | ))?); | ||
| 199 | tp += 8; | ||
| 200 | } | ||
| 201 | _ => { | ||
| 202 | tp += header.size as usize; | ||
| 203 | } | 187 | } |
| 188 | _ => {} | ||
| 204 | } | 189 | } |
| 205 | } | 190 | } |
| 206 | } | 191 | } |
| 207 | // Unix | 192 | // Unix |
| 208 | 0x000d => { | 193 | 0x000d => { |
| 209 | atime = Some(timestamp_to_local(i32::from_le_bytes( | 194 | atime = timestamp_to_local(i32::from_le_bytes(data.read_arr()?)); |
| 210 | extra_fields[ep..ep + 4].try_into().unwrap(), | 195 | mtime = timestamp_to_local(i32::from_le_bytes(data.read_arr()?)) |
| 211 | ))?); | 196 | .unwrap_or(mtime); |
| 212 | mtime = timestamp_to_local(i32::from_le_bytes( | 197 | } |
| 213 | extra_fields[ep + 4..ep + 8].try_into().unwrap(), | 198 | // AES |
| 214 | ))?; | 199 | 0x9901 => { |
| 215 | ep += header.size as usize | 200 | let aes: AesField = deserialize(&data.read_arr::<7>()?).unwrap(); |
| 201 | if aes.id != 0x4541 { | ||
| 202 | return Err(ZipError::InvalidExtraFields); | ||
| 203 | } | ||
| 204 | encryption_method = match aes.strength { | ||
| 205 | 0x01 => EncryptionMethod::Aes128, | ||
| 206 | 0x02 => EncryptionMethod::Aes192, | ||
| 207 | 0x03 => EncryptionMethod::Aes256, | ||
| 208 | _ => EncryptionMethod::Unsupported, | ||
| 209 | }; | ||
| 210 | compression_method = aes.compression_method; | ||
| 216 | } | 211 | } |
| 217 | // Skip unrecognized header | 212 | // Skip unrecognized header |
| 218 | _ => ep += header.size as usize, | 213 | _ => {} |
| 219 | } | 214 | } |
| 220 | } | 215 | } |
| 221 | 216 | ||
| 217 | if compression_method == 99 { | ||
| 218 | return Err(ZipError::AesExtraFieldNotFound); | ||
| 219 | } | ||
| 220 | |||
| 222 | indexes.insert(name.clone(), i); | 221 | indexes.insert(name.clone(), i); |
| 223 | files.push(ZipFileInfo::new( | 222 | files.push(ZipFileInfo::new( |
| 224 | CompressionMethod::from_struct_id(cdr.compression_method)?, | 223 | CompressionMethod::from_struct_id(compression_method), |
| 225 | EncryptionMethod::from_bif_flag(bit_flag, cdr.crc, cdr.dos_time), | 224 | encryption_method, |
| 226 | bit_flag, | 225 | bit_flag, |
| 227 | mtime, | 226 | mtime, |
| 228 | atime, | 227 | atime, |
| @@ -248,15 +247,15 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> { | |||
| 248 | &self.files | 247 | &self.files |
| 249 | } | 248 | } |
| 250 | 249 | ||
| 251 | fn get_file_index(&self, name: &str) -> crate::ArchiveResult<usize, Self::Error> { | 250 | fn get_file_index(&self, name: &str) -> ZipResult<usize> { |
| 252 | self.indexes | 251 | self.indexes |
| 253 | .get(name) | 252 | .get(name) |
| 254 | .ok_or(ZipError::FileNotFound.into()) | 253 | .ok_or(ZipError::FileNotFound) |
| 255 | .copied() | 254 | .copied() |
| 256 | } | 255 | } |
| 257 | 256 | ||
| 258 | fn get_file_info(&self, index: usize) -> ZipResult<&Self::FileInfo> { | 257 | fn get_file_info(&self, index: usize) -> ZipResult<&Self::FileInfo> { |
| 259 | self.files.get(index).ok_or(ZipError::FileNotFound.into()) | 258 | self.files.get(index).ok_or(ZipError::FileNotFound) |
| 260 | } | 259 | } |
| 261 | 260 | ||
| 262 | #[inline] | 261 | #[inline] |
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 @@ | |||
| 1 | use crate::utils::ReadUtils; | ||
| 2 | use aes::cipher::generic_array::GenericArray; | ||
| 3 | use aes::cipher::BlockEncrypt; | ||
| 4 | use std::io::{Read, Result as IoResult}; | ||
| 5 | |||
| 6 | #[allow(dead_code)] | ||
| 7 | pub struct AesDecoder<Io: Read, Aes: BlockEncrypt> { | ||
| 8 | io: Io, | ||
| 9 | aes: Aes, | ||
| 10 | |||
| 11 | counter: u128, | ||
| 12 | block: [u8; 16], | ||
| 13 | cursor: usize, | ||
| 14 | } | ||
| 15 | |||
| 16 | impl<Io: Read, Aes: BlockEncrypt> AesDecoder<Io, Aes> { | ||
| 17 | pub fn new(mut io: Io, aes: Aes) -> IoResult<Self> { | ||
| 18 | let block = io.read_arr::<16>()?; | ||
| 19 | let mut decoder = Self { | ||
| 20 | io, | ||
| 21 | aes, | ||
| 22 | counter: 1, | ||
| 23 | block, | ||
| 24 | cursor: 0, | ||
| 25 | }; | ||
| 26 | decoder.decrypt_block(); | ||
| 27 | Ok(decoder) | ||
| 28 | } | ||
| 29 | |||
| 30 | #[inline] | ||
| 31 | fn decrypt_block(&mut self) { | ||
| 32 | let mut mask = self.counter.to_le_bytes(); | ||
| 33 | self.aes | ||
| 34 | .encrypt_block(GenericArray::from_mut_slice(&mut mask)); | ||
| 35 | for (b, m) in self.block.iter_mut().zip(mask) { | ||
| 36 | *b ^= m | ||
| 37 | } | ||
| 38 | self.counter += 1; | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | impl<Io: Read, Aes: BlockEncrypt> Read for AesDecoder<Io, Aes> { | ||
| 43 | fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> { | ||
| 44 | todo!() | ||
| 45 | } | ||
| 46 | } | ||
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 @@ | |||
| 1 | mod aes; | ||
| 2 | mod weak; | ||
| 3 | |||
| 4 | pub use aes::AesDecoder; | ||
| 5 | pub use weak::{Keys, WeakDecoder}; | ||
diff --git a/src/zip/encryption.rs b/src/zip/encryption/weak.rs index f317245..144cd53 100644 --- a/src/zip/encryption.rs +++ b/src/zip/encryption/weak.rs | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | use crate::utils::ReadUtils; | ||
| 2 | use crate::zip::{ZipError, ZipResult}; | ||
| 3 | use std::io::{Read, Result as IoResult}; | 1 | use std::io::{Read, Result as IoResult}; |
| 4 | 2 | ||
| 5 | const TABLE: [u32; 256] = generate_table(); | 3 | const TABLE: [u32; 256] = generate_table(); |
| @@ -32,35 +30,22 @@ fn crc32(byte: u8, crc: u32) -> u32 { | |||
| 32 | (crc >> 8) ^ TABLE[((crc & 0xFF) as u8 ^ byte) as usize] | 30 | (crc >> 8) ^ TABLE[((crc & 0xFF) as u8 ^ byte) as usize] |
| 33 | } | 31 | } |
| 34 | 32 | ||
| 35 | pub struct WeakDecoder<Io: Read> { | 33 | pub struct Keys { |
| 36 | key0: u32, | 34 | key0: u32, |
| 37 | key1: u32, | 35 | key1: u32, |
| 38 | key2: u32, | 36 | key2: u32, |
| 39 | io: Io, | ||
| 40 | } | 37 | } |
| 41 | 38 | ||
| 42 | impl<Io: Read> WeakDecoder<Io> { | 39 | impl Keys { |
| 43 | pub fn new(io: Io, check: u8, password: &[u8]) -> ZipResult<Self> { | 40 | pub fn new() -> Self { |
| 44 | let mut decoder = Self { | 41 | Self { |
| 45 | key0: 305419896, | 42 | key0: 305419896, |
| 46 | key1: 591751049, | 43 | key1: 591751049, |
| 47 | key2: 878082192, | 44 | key2: 878082192, |
| 48 | io, | ||
| 49 | }; | ||
| 50 | |||
| 51 | for c in password { | ||
| 52 | decoder.update_keys(*c) | ||
| 53 | } | ||
| 54 | |||
| 55 | let buf = decoder.read_arr::<12>()?; | ||
| 56 | if check != buf[11] { | ||
| 57 | return Err(ZipError::IncorrectPassword.into()); | ||
| 58 | } | 45 | } |
| 59 | |||
| 60 | Ok(decoder) | ||
| 61 | } | 46 | } |
| 62 | 47 | ||
| 63 | fn update_keys(&mut self, byte: u8) { | 48 | fn update(&mut self, byte: u8) { |
| 64 | self.key0 = crc32(byte, self.key0); | 49 | self.key0 = crc32(byte, self.key0); |
| 65 | self.key1 = self | 50 | self.key1 = self |
| 66 | .key1 | 51 | .key1 |
| @@ -70,19 +55,50 @@ impl<Io: Read> WeakDecoder<Io> { | |||
| 70 | self.key2 = crc32((self.key1 >> 24) as u8, self.key2); | 55 | self.key2 = crc32((self.key1 >> 24) as u8, self.key2); |
| 71 | } | 56 | } |
| 72 | 57 | ||
| 73 | fn decode_byte(&mut self, byte: u8) -> u8 { | 58 | pub fn set_password(&mut self, passwd: &[u8]) { |
| 59 | for b in passwd { | ||
| 60 | self.update(*b) | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | pub fn set_header(&mut self, header: [u8; 12]) -> u8 { | ||
| 65 | for b in &header[..11] { | ||
| 66 | self.decode_byte(*b); | ||
| 67 | } | ||
| 68 | self.decode_byte(header[11]) | ||
| 69 | } | ||
| 70 | |||
| 71 | #[allow(dead_code)] | ||
| 72 | pub fn encode_bytes(&mut self, byte: u8) -> u8 { | ||
| 73 | let key = self.key2 | 2; | ||
| 74 | self.update(byte); | ||
| 75 | byte ^ ((key.wrapping_mul(key ^ 1)) >> 8) as u8 | ||
| 76 | } | ||
| 77 | |||
| 78 | pub fn decode_byte(&mut self, byte: u8) -> u8 { | ||
| 74 | let key = self.key2 | 2; | 79 | let key = self.key2 | 2; |
| 75 | let byte = byte ^ ((key.wrapping_mul(key ^ 1)) >> 8) as u8; | 80 | let byte = byte ^ ((key.wrapping_mul(key ^ 1)) >> 8) as u8; |
| 76 | self.update_keys(byte); | 81 | self.update(byte); |
| 77 | byte | 82 | byte |
| 78 | } | 83 | } |
| 79 | } | 84 | } |
| 80 | 85 | ||
| 86 | pub struct WeakDecoder<Io: Read> { | ||
| 87 | io: Io, | ||
| 88 | keys: Keys, | ||
| 89 | } | ||
| 90 | |||
| 91 | impl<Io: Read> WeakDecoder<Io> { | ||
| 92 | pub fn new(io: Io, keys: Keys) -> Self { | ||
| 93 | WeakDecoder { io, keys } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 81 | impl<Io: Read> Read for WeakDecoder<Io> { | 97 | impl<Io: Read> Read for WeakDecoder<Io> { |
| 82 | fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> { | 98 | fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> { |
| 83 | let bytes = self.io.read(buf)?; | 99 | let bytes = self.io.read(buf)?; |
| 84 | for i in 0..bytes { | 100 | for i in 0..bytes { |
| 85 | buf[i] = self.decode_byte(buf[i]); | 101 | buf[i] = self.keys.decode_byte(buf[i]); |
| 86 | } | 102 | } |
| 87 | Ok(bytes) | 103 | Ok(bytes) |
| 88 | } | 104 | } |
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 @@ | |||
| 1 | use crate::{ArchiveError, ArchiveResult}; | ||
| 2 | use std::error::Error; | 1 | use std::error::Error; |
| 3 | use std::fmt::Display; | 2 | use std::fmt::Display; |
| 3 | use std::io::Error as IoError; | ||
| 4 | 4 | ||
| 5 | pub type ZipResult<T> = ArchiveResult<T, ZipError>; | 5 | pub type ZipResult<T> = Result<T, ZipError>; |
| 6 | 6 | ||
| 7 | #[derive(Debug, PartialEq, Eq)] | 7 | #[derive(Debug)] |
| 8 | pub enum ZipError { | 8 | pub enum ZipError { |
| 9 | Io(IoError), | ||
| 10 | |||
| 9 | EocdrNotFound, | 11 | EocdrNotFound, |
| 10 | InvalidEOCDR64Signature, | 12 | InvalidEOCDR64Signature, |
| 11 | InvalidFileHeaderSignature, | 13 | InvalidFileHeaderSignature, |
| 12 | InvalidCDRSignature, | 14 | InvalidCDRSignature, |
| 13 | 15 | ||
| 14 | InvalidCompressionMethod(u16), | ||
| 15 | UnsupportedCompressionMethod(u16), | 16 | UnsupportedCompressionMethod(u16), |
| 16 | UnsupportedEncryptionMethod, | 17 | UnsupportedEncryptionMethod, |
| 17 | InvalidDate, | 18 | InvalidDate, |
| 18 | InvalidTime, | 19 | InvalidTime, |
| 19 | InvalidFileName, | 20 | InvalidFileName, |
| 21 | InvalidExtraFields, | ||
| 22 | AesExtraFieldNotFound, | ||
| 20 | InvalidFileComment, | 23 | InvalidFileComment, |
| 21 | 24 | ||
| 22 | FileNotFound, | 25 | FileNotFound, |
| @@ -26,18 +29,30 @@ pub enum ZipError { | |||
| 26 | EncryptedDataIsUnseekable, | 29 | EncryptedDataIsUnseekable, |
| 27 | } | 30 | } |
| 28 | 31 | ||
| 29 | impl From<ZipError> for ArchiveError<ZipError> { | 32 | impl From<IoError> for ZipError { |
| 30 | fn from(value: ZipError) -> Self { | 33 | fn from(value: IoError) -> Self { |
| 31 | Self::Archivator { | 34 | Self::Io(value) |
| 32 | module: "Zip", | 35 | } |
| 33 | error: value, | 36 | } |
| 37 | |||
| 38 | impl PartialEq for ZipError { | ||
| 39 | fn eq(&self, other: &Self) -> bool { | ||
| 40 | match (self, other) { | ||
| 41 | (Self::Io(l0), Self::Io(r0)) => l0.kind() == r0.kind(), | ||
| 42 | (Self::UnsupportedCompressionMethod(l0), Self::UnsupportedCompressionMethod(r0)) => { | ||
| 43 | l0 == r0 | ||
| 44 | } | ||
| 45 | _ => core::mem::discriminant(self) == core::mem::discriminant(other), | ||
| 34 | } | 46 | } |
| 35 | } | 47 | } |
| 36 | } | 48 | } |
| 37 | 49 | ||
| 50 | impl Eq for ZipError {} | ||
| 51 | |||
| 38 | impl Display for ZipError { | 52 | impl Display for ZipError { |
| 39 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 53 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 40 | match self { | 54 | match self { |
| 55 | Self::Io(error) => write!(f, "{}", error), | ||
| 41 | Self::EocdrNotFound => write!(f, "End of central directory record not found"), | 56 | Self::EocdrNotFound => write!(f, "End of central directory record not found"), |
| 42 | Self::InvalidEOCDR64Signature => { | 57 | Self::InvalidEOCDR64Signature => { |
| 43 | write!( | 58 | write!( |
| @@ -52,9 +67,6 @@ impl Display for ZipError { | |||
| 52 | write!(f, "Invalid signature of central directory record") | 67 | write!(f, "Invalid signature of central directory record") |
| 53 | } | 68 | } |
| 54 | 69 | ||
| 55 | Self::InvalidCompressionMethod(id) => { | ||
| 56 | writeln!(f, "Invalid compression method {}", id) | ||
| 57 | } | ||
| 58 | Self::UnsupportedCompressionMethod(id) => { | 70 | Self::UnsupportedCompressionMethod(id) => { |
| 59 | writeln!(f, "Unsupported compression method {}", id) | 71 | writeln!(f, "Unsupported compression method {}", id) |
| 60 | } | 72 | } |
| @@ -64,6 +76,8 @@ impl Display for ZipError { | |||
| 64 | Self::InvalidDate => write!(f, "Invalid date"), | 76 | Self::InvalidDate => write!(f, "Invalid date"), |
| 65 | Self::InvalidTime => write!(f, "Invalid time"), | 77 | Self::InvalidTime => write!(f, "Invalid time"), |
| 66 | Self::InvalidFileName => write!(f, "Invalid file name"), | 78 | Self::InvalidFileName => write!(f, "Invalid file name"), |
| 79 | Self::InvalidExtraFields => write!(f, "Invalid extra fields"), | ||
| 80 | Self::AesExtraFieldNotFound => write!(f, "Aes extra field not found"), | ||
| 67 | Self::InvalidFileComment => write!(f, "Invalid file comment"), | 81 | Self::InvalidFileComment => write!(f, "Invalid file comment"), |
| 68 | 82 | ||
| 69 | Self::FileNotFound => write!(f, "File not found"), | 83 | 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 @@ | |||
| 1 | use crate::driver::ArchiveFileInfo; | 1 | use crate::driver::ArchiveFileInfo; |
| 2 | use crate::zip::{ZipError, ZipResult}; | 2 | use crate::zip::datetime::DosDateTime; |
| 3 | use chrono::{DateTime, Local}; | 3 | use chrono::{DateTime, Local}; |
| 4 | 4 | ||
| 5 | #[derive(Debug, PartialEq, Eq, Clone)] | 5 | #[derive(Debug, PartialEq, Eq, Clone)] |
| @@ -14,42 +14,30 @@ pub enum CompressionMethod { | |||
| 14 | } | 14 | } |
| 15 | 15 | ||
| 16 | impl CompressionMethod { | 16 | impl CompressionMethod { |
| 17 | pub(crate) fn from_struct_id(id: u16) -> ZipResult<Self> { | 17 | #[inline] |
| 18 | Ok(match id { | 18 | pub(crate) fn from_struct_id(id: u16) -> Self { |
| 19 | match id { | ||
| 19 | 0 => Self::Store, | 20 | 0 => Self::Store, |
| 20 | 8 => Self::Deflate, | 21 | 8 => Self::Deflate, |
| 21 | 12 => Self::BZip2, | 22 | 12 => Self::BZip2, |
| 22 | 14 => Self::Lzma, | 23 | 14 => Self::Lzma, |
| 23 | 93 => Self::Zstd, | 24 | 93 => Self::Zstd, |
| 24 | 95 => Self::Xz, | 25 | 95 => Self::Xz, |
| 25 | 1..=7 | 9..=11 | 13 | 15..=20 | 94 | 96..=99 => Self::Unsupported(id), | 26 | _ => Self::Unsupported(id), |
| 26 | 21..=92 | 100.. => return Err(ZipError::InvalidCompressionMethod(id).into()), | 27 | } |
| 27 | }) | ||
| 28 | } | 28 | } |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | #[derive(Debug, PartialEq, Eq, Clone)] | 31 | #[derive(Debug, PartialEq, Eq, Clone)] |
| 32 | pub enum EncryptionMethod { | 32 | pub enum EncryptionMethod { |
| 33 | None, | 33 | None, |
| 34 | Weak(u8), | 34 | Weak, // ZipCrypto |
| 35 | Aes128, // WinZip encryption | ||
| 36 | Aes192, | ||
| 37 | Aes256, | ||
| 35 | Unsupported, | 38 | Unsupported, |
| 36 | } | 39 | } |
| 37 | 40 | ||
| 38 | impl EncryptionMethod { | ||
| 39 | pub(crate) fn from_bif_flag(bit_flag: BitFlag, crc: u32, dos_time: u16) -> EncryptionMethod { | ||
| 40 | match (bit_flag.is_encrypted(), bit_flag.is_strong_encryption()) { | ||
| 41 | (false, false) => EncryptionMethod::None, | ||
| 42 | (true, false) => EncryptionMethod::Weak(if bit_flag.is_has_data_descriptor() { | ||
| 43 | (dos_time >> 8) as u8 // Info-ZIP modification | ||
| 44 | } else { | ||
| 45 | (crc >> 24) as u8 | ||
| 46 | }), | ||
| 47 | (true, true) => EncryptionMethod::Unsupported, | ||
| 48 | _ => panic!("impossible"), | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | 41 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] |
| 54 | pub struct BitFlag { | 42 | pub struct BitFlag { |
| 55 | flag: u16, | 43 | flag: u16, |
| @@ -159,7 +147,7 @@ pub struct ZipFileInfo { | |||
| 159 | } | 147 | } |
| 160 | 148 | ||
| 161 | impl ZipFileInfo { | 149 | impl ZipFileInfo { |
| 162 | pub fn new( | 150 | pub(crate) fn new( |
| 163 | compression_method: CompressionMethod, | 151 | compression_method: CompressionMethod, |
| 164 | encryption_method: EncryptionMethod, | 152 | encryption_method: EncryptionMethod, |
| 165 | bit_flag: BitFlag, | 153 | bit_flag: BitFlag, |
| @@ -189,6 +177,15 @@ impl ZipFileInfo { | |||
| 189 | } | 177 | } |
| 190 | } | 178 | } |
| 191 | 179 | ||
| 180 | #[inline] | ||
| 181 | pub(crate) fn password_check(&self) -> u8 { | ||
| 182 | if self.bit_flag.is_has_data_descriptor() { | ||
| 183 | (self.mtime.to_dos_time() >> 8) as u8 | ||
| 184 | } else { | ||
| 185 | (self.crc >> 24) as u8 | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 192 | pub fn is_dir(&self) -> bool { | 189 | pub fn is_dir(&self) -> bool { |
| 193 | self.name.ends_with("/") | 190 | self.name.ends_with("/") |
| 194 | } | 191 | } |
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 @@ | |||
| 1 | use crate::driver::FileDriver; | 1 | use crate::driver::FileDriver; |
| 2 | use crate::utils::{IoCursor, ReadUtils}; | 2 | use crate::utils::{IoCursor, ReadUtils}; |
| 3 | use crate::zip::encryption::WeakDecoder; | 3 | use crate::zip::encryption::{AesDecoder, Keys, WeakDecoder}; |
| 4 | use crate::zip::structs::FILE_HEADER_SIGNATURE; | ||
| 4 | use crate::zip::{CompressionMethod, EncryptionMethod, ZipError, ZipFileInfo, ZipResult}; | 5 | use crate::zip::{CompressionMethod, EncryptionMethod, ZipError, ZipFileInfo, ZipResult}; |
| 6 | use aes::cipher::KeyInit; | ||
| 7 | use aes::{Aes128, Aes192, Aes256}; | ||
| 5 | use bzip2::read::BzDecoder; | 8 | use bzip2::read::BzDecoder; |
| 6 | use flate2::read::DeflateDecoder; | 9 | use flate2::read::DeflateDecoder; |
| 7 | use liblzma::read::XzDecoder; | 10 | use liblzma::read::XzDecoder; |
| 8 | use liblzma::stream::{Filters, LzmaOptions, Stream}; | 11 | use liblzma::stream::{Filters, LzmaOptions, Stream}; |
| 12 | use pbkdf2::pbkdf2_hmac_array; | ||
| 13 | use sha1::Sha1; | ||
| 9 | use std::io::{ | 14 | use std::io::{ |
| 10 | BufReader, Error as IoError, ErrorKind as IoErrorKind, Read, Result as IoResult, Seek, SeekFrom, | 15 | BufReader, Error as IoError, ErrorKind as IoErrorKind, Read, Result as IoResult, Seek, SeekFrom, |
| 11 | }; | 16 | }; |
| @@ -14,34 +19,101 @@ use zstd::stream::Decoder as ZstdDecoder; | |||
| 14 | enum Encryption<Io: Read> { | 19 | enum Encryption<Io: Read> { |
| 15 | None(Io), | 20 | None(Io), |
| 16 | Weak(WeakDecoder<Io>), | 21 | Weak(WeakDecoder<Io>), |
| 22 | Aes128(AesDecoder<Io, Aes128>), | ||
| 23 | Aes192(AesDecoder<Io, Aes192>), | ||
| 24 | Aes256(AesDecoder<Io, Aes256>), | ||
| 17 | } | 25 | } |
| 18 | 26 | ||
| 19 | impl<Io: Read> Encryption<Io> { | 27 | impl<Io: Read> Encryption<Io> { |
| 20 | pub fn new(io: Io, info: &ZipFileInfo, password: Option<&[u8]>) -> ZipResult<Self> { | 28 | #[inline] |
| 29 | pub fn new(mut io: Io, info: &ZipFileInfo, password: Option<&[u8]>) -> ZipResult<Self> { | ||
| 21 | Ok(match info.encryption_method { | 30 | Ok(match info.encryption_method { |
| 22 | EncryptionMethod::None => Self::None(io), | 31 | EncryptionMethod::None => Self::None(io), |
| 23 | EncryptionMethod::Weak(check) => Self::Weak(WeakDecoder::new( | 32 | EncryptionMethod::Weak => { |
| 24 | io, | 33 | let mut keys = Keys::new(); |
| 25 | check, | 34 | keys.set_password(password.ok_or(ZipError::PasswordIsNotSpecified)?); |
| 26 | password.ok_or(ZipError::PasswordIsNotSpecified)?, | 35 | |
| 27 | )?), | 36 | let check = keys.set_header(io.read_arr()?); |
| 28 | EncryptionMethod::Unsupported => { | 37 | if check != info.password_check() { |
| 29 | return Err(ZipError::UnsupportedEncryptionMethod.into()) | 38 | return Err(ZipError::IncorrectPassword); |
| 39 | } | ||
| 40 | |||
| 41 | Self::Weak(WeakDecoder::new(io, keys)) | ||
| 42 | } | ||
| 43 | EncryptionMethod::Aes128 => { | ||
| 44 | let header = io.read_arr::<10>()?; | ||
| 45 | let salt = &header[..8]; | ||
| 46 | let check = &header[8..]; | ||
| 47 | |||
| 48 | let hash = pbkdf2_hmac_array::<Sha1, 34>( | ||
| 49 | password.ok_or(ZipError::PasswordIsNotSpecified)?, | ||
| 50 | salt, | ||
| 51 | 1000, | ||
| 52 | ); | ||
| 53 | let key = &hash[..16]; | ||
| 54 | |||
| 55 | if check != &hash[32..] { | ||
| 56 | return Err(ZipError::IncorrectPassword); | ||
| 57 | } | ||
| 58 | |||
| 59 | Self::Aes128(AesDecoder::new(io, Aes128::new(key.into()))?) | ||
| 60 | } | ||
| 61 | EncryptionMethod::Aes192 => { | ||
| 62 | let header = io.read_arr::<14>()?; | ||
| 63 | let salt = &header[..12]; | ||
| 64 | let check = &header[12..]; | ||
| 65 | |||
| 66 | let hash = pbkdf2_hmac_array::<Sha1, 50>( | ||
| 67 | password.ok_or(ZipError::PasswordIsNotSpecified)?, | ||
| 68 | salt, | ||
| 69 | 1000, | ||
| 70 | ); | ||
| 71 | let key = &hash[..24]; | ||
| 72 | |||
| 73 | if check != &hash[48..] { | ||
| 74 | return Err(ZipError::IncorrectPassword); | ||
| 75 | } | ||
| 76 | |||
| 77 | Self::Aes192(AesDecoder::new(io, Aes192::new(key.into()))?) | ||
| 78 | } | ||
| 79 | EncryptionMethod::Aes256 => { | ||
| 80 | let header = io.read_arr::<18>()?; | ||
| 81 | let salt = &header[..16]; | ||
| 82 | let check = &header[16..]; | ||
| 83 | |||
| 84 | let hash = pbkdf2_hmac_array::<Sha1, 66>( | ||
| 85 | password.ok_or(ZipError::PasswordIsNotSpecified)?, | ||
| 86 | salt, | ||
| 87 | 1000, | ||
| 88 | ); | ||
| 89 | let key = &hash[..32]; | ||
| 90 | |||
| 91 | if check != &hash[64..] { | ||
| 92 | return Err(ZipError::IncorrectPassword); | ||
| 93 | } | ||
| 94 | |||
| 95 | Self::Aes256(AesDecoder::new(io, Aes256::new(key.into()))?) | ||
| 30 | } | 96 | } |
| 97 | EncryptionMethod::Unsupported => return Err(ZipError::UnsupportedEncryptionMethod), | ||
| 31 | }) | 98 | }) |
| 32 | } | 99 | } |
| 33 | } | 100 | } |
| 34 | 101 | ||
| 35 | impl<Io: Read> Read for Encryption<Io> { | 102 | impl<Io: Read> Read for Encryption<Io> { |
| 103 | #[inline] | ||
| 36 | fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> { | 104 | fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> { |
| 37 | match self { | 105 | match self { |
| 38 | Self::None(io) => io.read(buf), | 106 | Self::None(io) => io.read(buf), |
| 39 | Self::Weak(io) => io.read(buf), | 107 | Self::Weak(io) => io.read(buf), |
| 108 | Self::Aes128(io) => io.read(buf), | ||
| 109 | Self::Aes192(io) => io.read(buf), | ||
| 110 | Self::Aes256(io) => io.read(buf), | ||
| 40 | } | 111 | } |
| 41 | } | 112 | } |
| 42 | } | 113 | } |
| 43 | 114 | ||
| 44 | impl<Io: Read + Seek> Seek for Encryption<Io> { | 115 | impl<Io: Read + Seek> Seek for Encryption<Io> { |
| 116 | #[inline] | ||
| 45 | fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> { | 117 | fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> { |
| 46 | match self { | 118 | match self { |
| 47 | Self::None(io) => io.seek(pos), | 119 | Self::None(io) => io.seek(pos), |
| @@ -62,6 +134,7 @@ enum Compression<Io: Read> { | |||
| 62 | } | 134 | } |
| 63 | 135 | ||
| 64 | impl<Io: Read + Seek> Compression<Io> { | 136 | impl<Io: Read + Seek> Compression<Io> { |
| 137 | #[inline] | ||
| 65 | pub fn new(mut io: Io, info: &ZipFileInfo) -> ZipResult<Self> { | 138 | pub fn new(mut io: Io, info: &ZipFileInfo) -> ZipResult<Self> { |
| 66 | Ok(match info.compression_method { | 139 | Ok(match info.compression_method { |
| 67 | CompressionMethod::Store => Self::Store(io), | 140 | CompressionMethod::Store => Self::Store(io), |
| @@ -88,13 +161,14 @@ impl<Io: Read + Seek> Compression<Io> { | |||
| 88 | CompressionMethod::Zstd => Self::Zstd(ZstdDecoder::new(io)?), | 161 | CompressionMethod::Zstd => Self::Zstd(ZstdDecoder::new(io)?), |
| 89 | CompressionMethod::Xz => Self::Xz(XzDecoder::new(io)), | 162 | CompressionMethod::Xz => Self::Xz(XzDecoder::new(io)), |
| 90 | CompressionMethod::Unsupported(id) => { | 163 | CompressionMethod::Unsupported(id) => { |
| 91 | return Err(ZipError::UnsupportedCompressionMethod(id).into()) | 164 | return Err(ZipError::UnsupportedCompressionMethod(id)) |
| 92 | } | 165 | } |
| 93 | }) | 166 | }) |
| 94 | } | 167 | } |
| 95 | } | 168 | } |
| 96 | 169 | ||
| 97 | impl<Io: Read> Read for Compression<Io> { | 170 | impl<Io: Read> Read for Compression<Io> { |
| 171 | #[inline] | ||
| 98 | fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> { | 172 | fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> { |
| 99 | match self { | 173 | match self { |
| 100 | Compression::Store(io) => io.read(buf), | 174 | Compression::Store(io) => io.read(buf), |
| @@ -107,6 +181,7 @@ impl<Io: Read> Read for Compression<Io> { | |||
| 107 | } | 181 | } |
| 108 | 182 | ||
| 109 | impl<Io: Read + Seek> Seek for Compression<Io> { | 183 | impl<Io: Read + Seek> Seek for Compression<Io> { |
| 184 | #[inline] | ||
| 110 | fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> { | 185 | fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> { |
| 111 | match self { | 186 | match self { |
| 112 | Compression::Store(io) => io.seek(pos), | 187 | Compression::Store(io) => io.seek(pos), |
| @@ -137,8 +212,8 @@ impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> { | |||
| 137 | io.seek(SeekFrom::Start(info.header_pointer))?; | 212 | io.seek(SeekFrom::Start(info.header_pointer))?; |
| 138 | 213 | ||
| 139 | let buf = io.read_arr::<30>()?; | 214 | let buf = io.read_arr::<30>()?; |
| 140 | if u32::from_le_bytes(buf[..4].try_into().unwrap()) != 0x04034b50 { | 215 | if buf[..4] != FILE_HEADER_SIGNATURE { |
| 141 | return Err(ZipError::InvalidFileHeaderSignature.into()); | 216 | return Err(ZipError::InvalidFileHeaderSignature); |
| 142 | } | 217 | } |
| 143 | let data_pointer = info.header_pointer | 218 | let data_pointer = info.header_pointer |
| 144 | + 30 | 219 | + 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 @@ | |||
| 1 | mod archive; | 1 | mod archive; |
| 2 | mod cp437; | 2 | mod cp437; |
| 3 | mod datetime; | ||
| 3 | mod driver; | 4 | mod driver; |
| 4 | mod encryption; | 5 | mod encryption; |
| 5 | mod error; | 6 | 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 @@ | |||
| 1 | use crate::structs::{ByteOrder, Settings, StructResult, VariantIndexType}; | 1 | use crate::structs::{ByteOrder, Settings, StructResult, VariantIndexType}; |
| 2 | use serde::{Deserialize, Serialize}; | 2 | use serde::{Deserialize, Serialize}; |
| 3 | 3 | ||
| 4 | pub const FILE_HEADER_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x03, 0x04]; | ||
| 5 | |||
| 6 | pub const EOCDR_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x05, 0x06]; | ||
| 4 | #[derive(Serialize, Deserialize)] | 7 | #[derive(Serialize, Deserialize)] |
| 5 | pub struct Eocdr { | 8 | pub struct Eocdr { |
| 6 | pub eocdr_disk: u16, | 9 | pub eocdr_disk: u16, |
| @@ -12,6 +15,7 @@ pub struct Eocdr { | |||
| 12 | pub comment_len: u16, | 15 | pub comment_len: u16, |
| 13 | } | 16 | } |
| 14 | 17 | ||
| 18 | pub const EOCDR64_LOCATOR_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x06, 0x07]; | ||
| 15 | #[derive(Serialize, Deserialize)] | 19 | #[derive(Serialize, Deserialize)] |
| 16 | pub struct Eocdr64Locator { | 20 | pub struct Eocdr64Locator { |
| 17 | pub eocdr64_disk: u32, | 21 | pub eocdr64_disk: u32, |
| @@ -19,6 +23,7 @@ pub struct Eocdr64Locator { | |||
| 19 | pub disks: u32, | 23 | pub disks: u32, |
| 20 | } | 24 | } |
| 21 | 25 | ||
| 26 | pub const EOCDR64_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x06, 0x06]; | ||
| 22 | #[derive(Serialize, Deserialize)] | 27 | #[derive(Serialize, Deserialize)] |
| 23 | pub struct Eocdr64 { | 28 | pub struct Eocdr64 { |
| 24 | pub eocdr64_size: u64, | 29 | pub eocdr64_size: u64, |
| @@ -32,6 +37,7 @@ pub struct Eocdr64 { | |||
| 32 | pub cd_pointer: u64, | 37 | pub cd_pointer: u64, |
| 33 | } | 38 | } |
| 34 | 39 | ||
| 40 | pub const CDR_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x01, 0x02]; | ||
| 35 | #[derive(Serialize, Deserialize)] | 41 | #[derive(Serialize, Deserialize)] |
| 36 | pub struct Cdr { | 42 | pub struct Cdr { |
| 37 | pub version: u16, | 43 | pub version: u16, |
| @@ -58,6 +64,14 @@ pub struct ExtraHeader { | |||
| 58 | pub size: u16, | 64 | pub size: u16, |
| 59 | } | 65 | } |
| 60 | 66 | ||
| 67 | #[derive(Serialize, Deserialize)] | ||
| 68 | pub struct AesField { | ||
| 69 | pub version: u16, | ||
| 70 | pub id: u16, | ||
| 71 | pub strength: u8, | ||
| 72 | pub compression_method: u16, | ||
| 73 | } | ||
| 74 | |||
| 61 | #[inline] | 75 | #[inline] |
| 62 | #[allow(dead_code)] | 76 | #[allow(dead_code)] |
| 63 | pub fn serialize<T: Serialize>(object: &mut T) -> StructResult<Vec<u8>> { | 77 | pub fn serialize<T: Serialize>(object: &mut T) -> StructResult<Vec<u8>> { |
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 @@ | |||
| 1 | use crate::zip::cp437::{from_char, is_cp437, to_char, FromCp437}; | 1 | use crate::zip::cp437::{from_char, is_cp437, to_char, FromCp437}; |
| 2 | use crate::zip::datetime::DosDateTime; | ||
| 2 | use crate::zip::{bit::DeflateMode, BitFlag}; | 3 | use crate::zip::{bit::DeflateMode, BitFlag}; |
| 4 | use chrono::{DateTime, Local, TimeZone}; | ||
| 3 | 5 | ||
| 4 | #[test] | 6 | #[test] |
| 5 | fn test_bit_flag() { | 7 | fn test_bit_flag() { |
| @@ -74,3 +76,20 @@ fn test_cp437() { | |||
| 74 | "abcdefghijklmnopqrstuvwxyz" | 76 | "abcdefghijklmnopqrstuvwxyz" |
| 75 | ); | 77 | ); |
| 76 | } | 78 | } |
| 79 | |||
| 80 | #[test] | ||
| 81 | fn test_dos_datetime() { | ||
| 82 | let datetime = Local.with_ymd_and_hms(2001, 2, 3, 4, 5, 6).unwrap(); | ||
| 83 | assert_eq!( | ||
| 84 | DateTime::from_dos_date_time(datetime.to_dos_date(), datetime.to_dos_time(), Local) | ||
| 85 | .unwrap(), | ||
| 86 | datetime | ||
| 87 | ); | ||
| 88 | |||
| 89 | let datetime = Local.with_ymd_and_hms(1999, 9, 9, 9, 9, 9).unwrap(); | ||
| 90 | assert_eq!( | ||
| 91 | DateTime::from_dos_date_time(datetime.to_dos_date(), datetime.to_dos_time(), Local) | ||
| 92 | .unwrap(), | ||
| 93 | Local.with_ymd_and_hms(1999, 9, 9, 9, 9, 8).unwrap() | ||
| 94 | ); // Dos format stores seconds with an accuracy of 2s | ||
| 95 | } | ||
