From ef91e79e35e9402855f1370c2a570bafdf3a58f1 Mon Sep 17 00:00:00 2001 From: Igor Tolmachev Date: Wed, 10 Jul 2024 15:23:51 +0900 Subject: Add mtime, atime, ctime --- .vscode/settings.json | 1 + src/lib.rs | 1 + src/utils.rs | 23 ++++++++ src/zip/driver.rs | 146 +++++++++++++++++++++++++++++++++----------------- src/zip/file/info.rs | 12 +++-- src/zip/file/read.rs | 17 ++---- src/zip/structs.rs | 8 +-- 7 files changed, 141 insertions(+), 67 deletions(-) create mode 100644 src/utils.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index b0a2099..fbdecba 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,7 @@ "flate", "lzma", "newtype", + "ntfs", "repr", "rposition", "seekable", diff --git a/src/lib.rs b/src/lib.rs index 26722c2..9ec8b34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ mod archive; mod error; mod file; +mod utils; pub mod driver; pub mod structs; diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..185758a --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,23 @@ +use std::io::{Read, Result as IOResult}; + +pub trait ReadUtils { + fn read_arr(&mut self) -> IOResult<[u8; S]>; + + fn read_vec(&mut self, size: usize) -> IOResult>; +} + +impl ReadUtils for R { + #[inline] + fn read_arr(&mut self) -> Result<[u8; S], std::io::Error> { + let mut arr = [0; S]; + self.read(&mut arr)?; + Ok(arr) + } + + #[inline] + fn read_vec(&mut self, size: usize) -> Result, std::io::Error> { + let mut vec = vec![0; size]; + self.read(&mut vec)?; + Ok(vec) + } +} diff --git a/src/zip/driver.rs b/src/zip/driver.rs index c76d071..3793e31 100644 --- a/src/zip/driver.rs +++ b/src/zip/driver.rs @@ -1,13 +1,48 @@ use crate::driver::{ArchiveRead, ArchiveWrite, Driver}; -use crate::zip::structs::{deserialize, EOCDR64Locator, ExtraHeader, CDR, EOCDR, EOCDR64}; +use crate::utils::ReadUtils; +use crate::zip::structs::{deserialize, Cdr, Eocdr, Eocdr64, Eocdr64Locator, ExtraHeader}; use crate::zip::{ BitFlag, CompressionMethod, ZipError, ZipFileInfo, ZipFileReader, ZipFileWriter, ZipResult, }; -use chrono::{Local, NaiveDate, NaiveDateTime, NaiveTime}; +use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime}; use std::collections::HashMap as Map; use std::fs::File; use std::io::{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()) +} + +fn ntfs_to_local(time: u64) -> ZipResult> { + Ok(DateTime::from_timestamp( + (time / 10000000 - 11644473600) as i64, + (time % 10000000) as u32, + ) + .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)) +} + pub struct Zip { io: Io, @@ -29,23 +64,16 @@ impl ArchiveRead for Zip { // Search eocdr let limit = 65557.min(io.seek(SeekFrom::End(0))?) as i64; let start = io.seek(SeekFrom::End(-limit))?; - let pos = start + { - let mut buf = vec![0; limit as usize]; - io.read(&mut buf)?; - buf[..buf.len() - 18] + let pos = start + + io.read_vec(limit as usize - 18)? .windows(4) .rposition(|v| u32::from_le_bytes(v.try_into().unwrap()) == 0x06054b50) - .ok_or(ZipError::EOCDRNotFound)? as u64 - }; + .ok_or(ZipError::EOCDRNotFound)? as u64; // Read eocdr io.seek(SeekFrom::Start(pos + 4))?; - let buf = { - let mut buf = [0; 18]; - io.read(&mut buf)?; - buf - }; - let eocdr: EOCDR = deserialize(&buf).unwrap(); + let buf = io.read_arr::<18>()?; + let eocdr: Eocdr = deserialize(&buf).unwrap(); let comment = { let mut buf: Vec = vec![0; eocdr.comment_len as usize]; io.read(&mut buf)?; @@ -54,25 +82,18 @@ impl ArchiveRead for Zip { // Try to find eocdr64locator io.seek(SeekFrom::Start(pos - 20))?; - let buf = { - let mut buf = [0; 20]; - io.read(&mut buf)?; - buf - }; + 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 { - let eocdr64locator: EOCDR64Locator = deserialize(&buf[4..]).unwrap(); + let eocdr64locator: Eocdr64Locator = deserialize(&buf[4..]).unwrap(); io.seek(SeekFrom::Start(eocdr64locator.eocdr64_pointer))?; - let buf = { - let mut buf = [0; 56]; - io.read(&mut buf)?; - buf - }; + let buf = io.read_arr::<56>()?; if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != 0x06064b50 { return Err(ZipError::InvalidEOCDR64Signature.into()); } - let eocdr64: EOCDR64 = deserialize(&buf[4..]).unwrap(); + let eocdr64: Eocdr64 = deserialize(&buf[4..]).unwrap(); (eocdr64.cd_pointer, eocdr64.cd_size, eocdr64.cd_records) } else { @@ -86,11 +107,7 @@ impl ArchiveRead for Zip { // Read cd records let mut files = Map::with_capacity(cd_records as usize); io.seek(SeekFrom::Start(cd_pointer))?; - let buf = { - let mut buf = vec![0; cd_size as usize]; - io.read(&mut buf)?; - buf - }; + let buf = io.read_vec(cd_size as usize)?; let mut p: usize = 0; for _ in 0..cd_records { @@ -98,7 +115,7 @@ impl ArchiveRead for Zip { return Err(ZipError::InvalidCDRSignature.into()); } p += 4; - let cdr: CDR = deserialize(&buf[p..p + 42]).unwrap(); + let cdr: Cdr = deserialize(&buf[p..p + 42]).unwrap(); p += 42; let name = String::from_utf8(buf[p..p + cdr.name_len as usize].into()) .map_err(|_| ZipError::InvalidFileName)?; @@ -113,11 +130,17 @@ impl ArchiveRead for Zip { 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 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 { + // Zip64 0x0001 => { if size == 0xFFFFFFFF { compressed_size = @@ -137,6 +160,46 @@ impl ArchiveRead for Zip { ep += 4 } } + // 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 { + 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; + } + } + } + } + // 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 + } _ => ep += header.size as usize, } } @@ -146,22 +209,9 @@ impl ArchiveRead for Zip { ZipFileInfo::new( CompressionMethod::from_struct_id(cdr.compression_method)?, BitFlag::new(cdr.bit_flag), - NaiveDateTime::new( - NaiveDate::from_ymd_opt( - (cdr.dos_date as i32 >> 9 & 0x7F) + 1980, - cdr.dos_date as u32 >> 5 & 0xF, - cdr.dos_date as u32 & 0x1F, - ) - .ok_or(ZipError::InvalidDate)?, - NaiveTime::from_hms_opt( - (cdr.dos_time as u32 >> 11) & 0x1F, - (cdr.dos_time as u32 >> 5) & 0x3F, - (cdr.dos_time as u32 & 0x1F) * 2, - ) - .ok_or(ZipError::InvalidTime)?, - ) - .and_local_timezone(Local) - .unwrap(), + mtime, + atime, + ctime, cdr.crc, compressed_size, size, diff --git a/src/zip/file/info.rs b/src/zip/file/info.rs index ff6e8d2..90d257d 100644 --- a/src/zip/file/info.rs +++ b/src/zip/file/info.rs @@ -123,7 +123,9 @@ impl BitFlag { pub struct ZipFileInfo { pub compression_method: CompressionMethod, pub bit_flag: BitFlag, - pub datetime: DateTime, + pub mtime: DateTime, + pub atime: Option>, + pub ctime: Option>, pub crc: u32, pub compressed_size: u64, pub size: u64, @@ -136,7 +138,9 @@ impl ZipFileInfo { pub fn new( compression_method: CompressionMethod, bit_flag: BitFlag, - datetime: DateTime, + mtime: DateTime, + atime: Option>, + ctime: Option>, crc: u32, compressed_size: u64, size: u64, @@ -147,7 +151,9 @@ impl ZipFileInfo { Self { compression_method, bit_flag, - datetime, + mtime, + atime, + ctime, crc, compressed_size, size, diff --git a/src/zip/file/read.rs b/src/zip/file/read.rs index 6ec7db7..7d683db 100644 --- a/src/zip/file/read.rs +++ b/src/zip/file/read.rs @@ -1,4 +1,5 @@ use crate::driver::FileDriver; +use crate::utils::ReadUtils; use crate::zip::{CompressionMethod, ZipError, ZipFileInfo, ZipResult}; use bzip2::read::BzDecoder; use flate2::read::DeflateDecoder; @@ -35,11 +36,7 @@ impl<'d, Io: Read> FileDriver for ZipFileReader<'d, Io> { impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> { pub fn new(io: &'d mut Io, info: &'d ZipFileInfo) -> ZipResult { io.seek(SeekFrom::Start(info.header_pointer))?; - let buf = { - let mut buf = [0; 30]; - io.read(&mut buf)?; - buf - }; + let buf = io.read_arr::<30>()?; if u32::from_le_bytes(buf[..4].try_into().unwrap()) != 0x04034b50 { return Err(ZipError::InvalidFileHeaderSignature.into()); @@ -56,12 +53,8 @@ impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> { CompressionMethod::Deflate => IoProxy::Deflate(DeflateDecoder::new(io)), CompressionMethod::BZip2 => IoProxy::BZip2(BzDecoder::new(io)), CompressionMethod::Lzma => { - let buf = { - let mut buf = [0; 9]; - io.read(&mut buf)?; - cursor += 9; - buf - }; + let buf = io.read_arr::<9>()?; + cursor += 9; IoProxy::Xz(XzDecoder::new_stream( io, Stream::new_raw_decoder( @@ -71,7 +64,7 @@ impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> { .literal_position_bits((buf[4] / 9 % 5) as u32) .position_bits((buf[4] / 45) as u32) .dict_size( - u32::from_le_bytes(buf[5..9].try_into().unwrap()).min(4096), + u32::from_le_bytes(buf[5..9].try_into().unwrap()).max(4096), ), ), ) diff --git a/src/zip/structs.rs b/src/zip/structs.rs index 3a93c1b..644b333 100644 --- a/src/zip/structs.rs +++ b/src/zip/structs.rs @@ -2,7 +2,7 @@ use crate::structs::{Settings, StructResult}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] -pub struct EOCDR { +pub struct Eocdr { pub eocdr_disk: u16, pub cd_disk: u16, pub cd_disk_records: u16, @@ -13,14 +13,14 @@ pub struct EOCDR { } #[derive(Serialize, Deserialize)] -pub struct EOCDR64Locator { +pub struct Eocdr64Locator { pub eocdr64_disk: u32, pub eocdr64_pointer: u64, pub disks: u32, } #[derive(Serialize, Deserialize)] -pub struct EOCDR64 { +pub struct Eocdr64 { pub eocdr64_size: u64, pub version: u16, pub version_needed: u16, @@ -33,7 +33,7 @@ pub struct EOCDR64 { } #[derive(Serialize, Deserialize)] -pub struct CDR { +pub struct Cdr { pub version: u16, pub version_needed: u16, pub bit_flag: u16, -- cgit v1.2.3