aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.vscode/settings.json1
-rw-r--r--src/lib.rs1
-rw-r--r--src/utils.rs23
-rw-r--r--src/zip/driver.rs146
-rw-r--r--src/zip/file/info.rs12
-rw-r--r--src/zip/file/read.rs17
-rw-r--r--src/zip/structs.rs8
7 files changed, 141 insertions, 67 deletions
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 @@
10 "flate", 10 "flate",
11 "lzma", 11 "lzma",
12 "newtype", 12 "newtype",
13 "ntfs",
13 "repr", 14 "repr",
14 "rposition", 15 "rposition",
15 "seekable", 16 "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 @@
1mod archive; 1mod archive;
2mod error; 2mod error;
3mod file; 3mod file;
4mod utils;
4 5
5pub mod driver; 6pub mod driver;
6pub mod structs; 7pub 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 @@
1use std::io::{Read, Result as IOResult};
2
3pub trait ReadUtils {
4 fn read_arr<const S: usize>(&mut self) -> IOResult<[u8; S]>;
5
6 fn read_vec(&mut self, size: usize) -> IOResult<Vec<u8>>;
7}
8
9impl<R: Read> ReadUtils for R {
10 #[inline]
11 fn read_arr<const S: usize>(&mut self) -> Result<[u8; S], std::io::Error> {
12 let mut arr = [0; S];
13 self.read(&mut arr)?;
14 Ok(arr)
15 }
16
17 #[inline]
18 fn read_vec(&mut self, size: usize) -> Result<Vec<u8>, std::io::Error> {
19 let mut vec = vec![0; size];
20 self.read(&mut vec)?;
21 Ok(vec)
22 }
23}
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,
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 {
123pub struct ZipFileInfo { 123pub struct ZipFileInfo {
124 pub compression_method: CompressionMethod, 124 pub compression_method: CompressionMethod,
125 pub bit_flag: BitFlag, 125 pub bit_flag: BitFlag,
126 pub datetime: DateTime<Local>, 126 pub mtime: DateTime<Local>,
127 pub atime: Option<DateTime<Local>>,
128 pub ctime: Option<DateTime<Local>>,
127 pub crc: u32, 129 pub crc: u32,
128 pub compressed_size: u64, 130 pub compressed_size: u64,
129 pub size: u64, 131 pub size: u64,
@@ -136,7 +138,9 @@ impl ZipFileInfo {
136 pub fn new( 138 pub fn new(
137 compression_method: CompressionMethod, 139 compression_method: CompressionMethod,
138 bit_flag: BitFlag, 140 bit_flag: BitFlag,
139 datetime: DateTime<Local>, 141 mtime: DateTime<Local>,
142 atime: Option<DateTime<Local>>,
143 ctime: Option<DateTime<Local>>,
140 crc: u32, 144 crc: u32,
141 compressed_size: u64, 145 compressed_size: u64,
142 size: u64, 146 size: u64,
@@ -147,7 +151,9 @@ impl ZipFileInfo {
147 Self { 151 Self {
148 compression_method, 152 compression_method,
149 bit_flag, 153 bit_flag,
150 datetime, 154 mtime,
155 atime,
156 ctime,
151 crc, 157 crc,
152 compressed_size, 158 compressed_size,
153 size, 159 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 @@
1use crate::driver::FileDriver; 1use crate::driver::FileDriver;
2use crate::utils::ReadUtils;
2use crate::zip::{CompressionMethod, ZipError, ZipFileInfo, ZipResult}; 3use crate::zip::{CompressionMethod, ZipError, ZipFileInfo, ZipResult};
3use bzip2::read::BzDecoder; 4use bzip2::read::BzDecoder;
4use flate2::read::DeflateDecoder; 5use flate2::read::DeflateDecoder;
@@ -35,11 +36,7 @@ impl<'d, Io: Read> FileDriver for ZipFileReader<'d, Io> {
35impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> { 36impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> {
36 pub fn new(io: &'d mut Io, info: &'d ZipFileInfo) -> ZipResult<Self> { 37 pub fn new(io: &'d mut Io, info: &'d ZipFileInfo) -> ZipResult<Self> {
37 io.seek(SeekFrom::Start(info.header_pointer))?; 38 io.seek(SeekFrom::Start(info.header_pointer))?;
38 let buf = { 39 let buf = io.read_arr::<30>()?;
39 let mut buf = [0; 30];
40 io.read(&mut buf)?;
41 buf
42 };
43 40
44 if u32::from_le_bytes(buf[..4].try_into().unwrap()) != 0x04034b50 { 41 if u32::from_le_bytes(buf[..4].try_into().unwrap()) != 0x04034b50 {
45 return Err(ZipError::InvalidFileHeaderSignature.into()); 42 return Err(ZipError::InvalidFileHeaderSignature.into());
@@ -56,12 +53,8 @@ impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> {
56 CompressionMethod::Deflate => IoProxy::Deflate(DeflateDecoder::new(io)), 53 CompressionMethod::Deflate => IoProxy::Deflate(DeflateDecoder::new(io)),
57 CompressionMethod::BZip2 => IoProxy::BZip2(BzDecoder::new(io)), 54 CompressionMethod::BZip2 => IoProxy::BZip2(BzDecoder::new(io)),
58 CompressionMethod::Lzma => { 55 CompressionMethod::Lzma => {
59 let buf = { 56 let buf = io.read_arr::<9>()?;
60 let mut buf = [0; 9]; 57 cursor += 9;
61 io.read(&mut buf)?;
62 cursor += 9;
63 buf
64 };
65 IoProxy::Xz(XzDecoder::new_stream( 58 IoProxy::Xz(XzDecoder::new_stream(
66 io, 59 io,
67 Stream::new_raw_decoder( 60 Stream::new_raw_decoder(
@@ -71,7 +64,7 @@ impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> {
71 .literal_position_bits((buf[4] / 9 % 5) as u32) 64 .literal_position_bits((buf[4] / 9 % 5) as u32)
72 .position_bits((buf[4] / 45) as u32) 65 .position_bits((buf[4] / 45) as u32)
73 .dict_size( 66 .dict_size(
74 u32::from_le_bytes(buf[5..9].try_into().unwrap()).min(4096), 67 u32::from_le_bytes(buf[5..9].try_into().unwrap()).max(4096),
75 ), 68 ),
76 ), 69 ),
77 ) 70 )
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};
2use serde::{Deserialize, Serialize}; 2use serde::{Deserialize, Serialize};
3 3
4#[derive(Serialize, Deserialize)] 4#[derive(Serialize, Deserialize)]
5pub struct EOCDR { 5pub struct Eocdr {
6 pub eocdr_disk: u16, 6 pub eocdr_disk: u16,
7 pub cd_disk: u16, 7 pub cd_disk: u16,
8 pub cd_disk_records: u16, 8 pub cd_disk_records: u16,
@@ -13,14 +13,14 @@ pub struct EOCDR {
13} 13}
14 14
15#[derive(Serialize, Deserialize)] 15#[derive(Serialize, Deserialize)]
16pub struct EOCDR64Locator { 16pub struct Eocdr64Locator {
17 pub eocdr64_disk: u32, 17 pub eocdr64_disk: u32,
18 pub eocdr64_pointer: u64, 18 pub eocdr64_pointer: u64,
19 pub disks: u32, 19 pub disks: u32,
20} 20}
21 21
22#[derive(Serialize, Deserialize)] 22#[derive(Serialize, Deserialize)]
23pub struct EOCDR64 { 23pub struct Eocdr64 {
24 pub eocdr64_size: u64, 24 pub eocdr64_size: u64,
25 pub version: u16, 25 pub version: u16,
26 pub version_needed: u16, 26 pub version_needed: u16,
@@ -33,7 +33,7 @@ pub struct EOCDR64 {
33} 33}
34 34
35#[derive(Serialize, Deserialize)] 35#[derive(Serialize, Deserialize)]
36pub struct CDR { 36pub struct Cdr {
37 pub version: u16, 37 pub version: u16,
38 pub version_needed: u16, 38 pub version_needed: u16,
39 pub bit_flag: u16, 39 pub bit_flag: u16,