aboutsummaryrefslogtreecommitdiff
path: root/src/zip/driver.rs
diff options
context:
space:
mode:
authorIgor Tolmachev <me@igorek.dev>2024-07-10 15:23:51 +0900
committerIgor Tolmachev <me@igorek.dev>2024-07-10 15:23:51 +0900
commitef91e79e35e9402855f1370c2a570bafdf3a58f1 (patch)
treeda0fd11f4f27914bdb8a386809c539c9f6cf89bc /src/zip/driver.rs
parent8a7106030cd6c0044820ebaa213984a5b842e497 (diff)
downloadarchivator-ef91e79e35e9402855f1370c2a570bafdf3a58f1.tar.gz
archivator-ef91e79e35e9402855f1370c2a570bafdf3a58f1.zip
Add mtime, atime, ctime
Diffstat (limited to 'src/zip/driver.rs')
-rw-r--r--src/zip/driver.rs146
1 files changed, 98 insertions, 48 deletions
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 @@
1use crate::driver::{ArchiveRead, ArchiveWrite, Driver}; 1use crate::driver::{ArchiveRead, ArchiveWrite, Driver};
2use crate::zip::structs::{deserialize, EOCDR64Locator, ExtraHeader, CDR, EOCDR, EOCDR64}; 2use crate::utils::ReadUtils;
3use crate::zip::structs::{deserialize, Cdr, Eocdr, Eocdr64, Eocdr64Locator, ExtraHeader};
3use crate::zip::{ 4use crate::zip::{
4 BitFlag, CompressionMethod, ZipError, ZipFileInfo, ZipFileReader, ZipFileWriter, ZipResult, 5 BitFlag, CompressionMethod, ZipError, ZipFileInfo, ZipFileReader, ZipFileWriter, ZipResult,
5}; 6};
6use chrono::{Local, NaiveDate, NaiveDateTime, NaiveTime}; 7use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime};
7use std::collections::HashMap as Map; 8use std::collections::HashMap as Map;
8use std::fs::File; 9use std::fs::File;
9use std::io::{Read, Seek, SeekFrom, Write}; 10use std::io::{Read, Seek, SeekFrom, Write};
10 11
12fn dos_to_local(date: u16, time: u16) -> ZipResult<DateTime<Local>> {
13 Ok(NaiveDateTime::new(
14 NaiveDate::from_ymd_opt(
15 (date as i32 >> 9 & 0x7F) + 1980,
16 date as u32 >> 5 & 0xF,
17 date as u32 & 0x1F,
18 )
19 .ok_or(ZipError::InvalidDate)?,
20 NaiveTime::from_hms_opt(
21 (time as u32 >> 11) & 0x1F,
22 (time as u32 >> 5) & 0x3F,
23 (time as u32 & 0x1F) * 2,
24 )
25 .ok_or(ZipError::InvalidTime)?,
26 )
27 .and_local_timezone(Local)
28 .unwrap())
29}
30
31fn ntfs_to_local(time: u64) -> ZipResult<DateTime<Local>> {
32 Ok(DateTime::from_timestamp(
33 (time / 10000000 - 11644473600) as i64,
34 (time % 10000000) as u32,
35 )
36 .ok_or(ZipError::InvalidTime)?
37 .with_timezone(&Local))
38}
39
40fn timestamp_to_local(time: i32) -> ZipResult<DateTime<Local>> {
41 Ok(DateTime::from_timestamp(time as i64, 0)
42 .ok_or(ZipError::InvalidTime)?
43 .with_timezone(&Local))
44}
45
11pub struct Zip<Io = File> { 46pub struct Zip<Io = File> {
12 io: Io, 47 io: Io,
13 48
@@ -29,23 +64,16 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
29 // Search eocdr 64 // Search eocdr
30 let limit = 65557.min(io.seek(SeekFrom::End(0))?) as i64; 65 let limit = 65557.min(io.seek(SeekFrom::End(0))?) as i64;
31 let start = io.seek(SeekFrom::End(-limit))?; 66 let start = io.seek(SeekFrom::End(-limit))?;
32 let pos = start + { 67 let pos = start
33 let mut buf = vec![0; limit as usize]; 68 + io.read_vec(limit as usize - 18)?
34 io.read(&mut buf)?;
35 buf[..buf.len() - 18]
36 .windows(4) 69 .windows(4)
37 .rposition(|v| u32::from_le_bytes(v.try_into().unwrap()) == 0x06054b50) 70 .rposition(|v| u32::from_le_bytes(v.try_into().unwrap()) == 0x06054b50)
38 .ok_or(ZipError::EOCDRNotFound)? as u64 71 .ok_or(ZipError::EOCDRNotFound)? as u64;
39 };
40 72
41 // Read eocdr 73 // Read eocdr
42 io.seek(SeekFrom::Start(pos + 4))?; 74 io.seek(SeekFrom::Start(pos + 4))?;
43 let buf = { 75 let buf = io.read_arr::<18>()?;
44 let mut buf = [0; 18]; 76 let eocdr: Eocdr = deserialize(&buf).unwrap();
45 io.read(&mut buf)?;
46 buf
47 };
48 let eocdr: EOCDR = deserialize(&buf).unwrap();
49 let comment = { 77 let comment = {
50 let mut buf: Vec<u8> = vec![0; eocdr.comment_len as usize]; 78 let mut buf: Vec<u8> = vec![0; eocdr.comment_len as usize];
51 io.read(&mut buf)?; 79 io.read(&mut buf)?;
@@ -54,25 +82,18 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
54 82
55 // Try to find eocdr64locator 83 // Try to find eocdr64locator
56 io.seek(SeekFrom::Start(pos - 20))?; 84 io.seek(SeekFrom::Start(pos - 20))?;
57 let buf = { 85 let buf = io.read_arr::<20>()?;
58 let mut buf = [0; 20];
59 io.read(&mut buf)?;
60 buf
61 };
62 let (cd_pointer, cd_size, cd_records) = 86 let (cd_pointer, cd_size, cd_records) =
87 // If locator found then read eocdr64
63 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) == 0x07064b50 { 88 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) == 0x07064b50 {
64 let eocdr64locator: EOCDR64Locator = deserialize(&buf[4..]).unwrap(); 89 let eocdr64locator: Eocdr64Locator = deserialize(&buf[4..]).unwrap();
65 90
66 io.seek(SeekFrom::Start(eocdr64locator.eocdr64_pointer))?; 91 io.seek(SeekFrom::Start(eocdr64locator.eocdr64_pointer))?;
67 let buf = { 92 let buf = io.read_arr::<56>()?;
68 let mut buf = [0; 56];
69 io.read(&mut buf)?;
70 buf
71 };
72 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != 0x06064b50 { 93 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != 0x06064b50 {
73 return Err(ZipError::InvalidEOCDR64Signature.into()); 94 return Err(ZipError::InvalidEOCDR64Signature.into());
74 } 95 }
75 let eocdr64: EOCDR64 = deserialize(&buf[4..]).unwrap(); 96 let eocdr64: Eocdr64 = deserialize(&buf[4..]).unwrap();
76 97
77 (eocdr64.cd_pointer, eocdr64.cd_size, eocdr64.cd_records) 98 (eocdr64.cd_pointer, eocdr64.cd_size, eocdr64.cd_records)
78 } else { 99 } else {
@@ -86,11 +107,7 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
86 // Read cd records 107 // Read cd records
87 let mut files = Map::with_capacity(cd_records as usize); 108 let mut files = Map::with_capacity(cd_records as usize);
88 io.seek(SeekFrom::Start(cd_pointer))?; 109 io.seek(SeekFrom::Start(cd_pointer))?;
89 let buf = { 110 let buf = io.read_vec(cd_size as usize)?;
90 let mut buf = vec![0; cd_size as usize];
91 io.read(&mut buf)?;
92 buf
93 };
94 111
95 let mut p: usize = 0; 112 let mut p: usize = 0;
96 for _ in 0..cd_records { 113 for _ in 0..cd_records {
@@ -98,7 +115,7 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
98 return Err(ZipError::InvalidCDRSignature.into()); 115 return Err(ZipError::InvalidCDRSignature.into());
99 } 116 }
100 p += 4; 117 p += 4;
101 let cdr: CDR = deserialize(&buf[p..p + 42]).unwrap(); 118 let cdr: Cdr = deserialize(&buf[p..p + 42]).unwrap();
102 p += 42; 119 p += 42;
103 let name = String::from_utf8(buf[p..p + cdr.name_len as usize].into()) 120 let name = String::from_utf8(buf[p..p + cdr.name_len as usize].into())
104 .map_err(|_| ZipError::InvalidFileName)?; 121 .map_err(|_| ZipError::InvalidFileName)?;
@@ -113,11 +130,17 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
113 let mut size = cdr.size as u64; 130 let mut size = cdr.size as u64;
114 let mut header_pointer = cdr.header_pointer as u64; 131 let mut header_pointer = cdr.header_pointer as u64;
115 132
133 let mut mtime = dos_to_local(cdr.dos_date, cdr.dos_time)?;
134 let mut atime = None;
135 let mut ctime = None;
136
137 // Parse extensible data fields
116 let mut ep: usize = 0; 138 let mut ep: usize = 0;
117 while ep < cdr.extra_field_len as usize { 139 while ep < cdr.extra_field_len as usize {
118 let header: ExtraHeader = deserialize(&extra_fields[ep..ep + 4]).unwrap(); 140 let header: ExtraHeader = deserialize(&extra_fields[ep..ep + 4]).unwrap();
119 ep += 4; 141 ep += 4;
120 match header.id { 142 match header.id {
143 // Zip64
121 0x0001 => { 144 0x0001 => {
122 if size == 0xFFFFFFFF { 145 if size == 0xFFFFFFFF {
123 compressed_size = 146 compressed_size =
@@ -137,6 +160,46 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
137 ep += 4 160 ep += 4
138 } 161 }
139 } 162 }
163 // NTFS
164 0x000a => {
165 let mut tp = ep + 4;
166 ep += header.size as usize;
167
168 while tp < ep {
169 let header: ExtraHeader =
170 deserialize(&extra_fields[tp..tp + 4]).unwrap();
171 tp += 4;
172 match header.id {
173 0x0001 => {
174 mtime = ntfs_to_local(u64::from_le_bytes(
175 extra_fields[tp..tp + 8].try_into().unwrap(),
176 ))?;
177 tp += 8;
178 atime = Some(ntfs_to_local(u64::from_le_bytes(
179 extra_fields[tp..tp + 8].try_into().unwrap(),
180 ))?);
181 tp += 8;
182 ctime = Some(ntfs_to_local(u64::from_le_bytes(
183 extra_fields[tp..tp + 8].try_into().unwrap(),
184 ))?);
185 tp += 8;
186 }
187 _ => {
188 tp += header.size as usize;
189 }
190 }
191 }
192 }
193 // Unix
194 0x000d => {
195 atime = Some(timestamp_to_local(i32::from_le_bytes(
196 extra_fields[ep..ep + 4].try_into().unwrap(),
197 ))?);
198 mtime = timestamp_to_local(i32::from_le_bytes(
199 extra_fields[ep + 4..ep + 8].try_into().unwrap(),
200 ))?;
201 ep += header.size as usize
202 }
140 _ => ep += header.size as usize, 203 _ => ep += header.size as usize,
141 } 204 }
142 } 205 }
@@ -146,22 +209,9 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
146 ZipFileInfo::new( 209 ZipFileInfo::new(
147 CompressionMethod::from_struct_id(cdr.compression_method)?, 210 CompressionMethod::from_struct_id(cdr.compression_method)?,
148 BitFlag::new(cdr.bit_flag), 211 BitFlag::new(cdr.bit_flag),
149 NaiveDateTime::new( 212 mtime,
150 NaiveDate::from_ymd_opt( 213 atime,
151 (cdr.dos_date as i32 >> 9 & 0x7F) + 1980, 214 ctime,
152 cdr.dos_date as u32 >> 5 & 0xF,
153 cdr.dos_date as u32 & 0x1F,
154 )
155 .ok_or(ZipError::InvalidDate)?,
156 NaiveTime::from_hms_opt(
157 (cdr.dos_time as u32 >> 11) & 0x1F,
158 (cdr.dos_time as u32 >> 5) & 0x3F,
159 (cdr.dos_time as u32 & 0x1F) * 2,
160 )
161 .ok_or(ZipError::InvalidTime)?,
162 )
163 .and_local_timezone(Local)
164 .unwrap(),
165 cdr.crc, 215 cdr.crc,
166 compressed_size, 216 compressed_size,
167 size, 217 size,