aboutsummaryrefslogtreecommitdiff
path: root/src/zip
diff options
context:
space:
mode:
authorIgor Tolmachev <me@igorek.dev>2024-06-23 15:19:40 +0900
committerIgor Tolmachev <me@igorek.dev>2024-06-23 15:34:35 +0900
commita4e92ed9bec1f5879eb1c20dfe281c4d25ed5f89 (patch)
tree6acef99bfaf57c573b543f29836701a92c215a83 /src/zip
parent62aaae347d87c5c9411f1e9f8db525b7c2c603d2 (diff)
downloadarchivator-a4e92ed9bec1f5879eb1c20dfe281c4d25ed5f89.tar.gz
archivator-a4e92ed9bec1f5879eb1c20dfe281c4d25ed5f89.zip
Improve ZipFile
Diffstat (limited to 'src/zip')
-rw-r--r--src/zip/driver.rs113
-rw-r--r--src/zip/error.rs36
-rw-r--r--src/zip/file.rs69
-rw-r--r--src/zip/mod.rs2
-rw-r--r--src/zip/structs.rs8
5 files changed, 149 insertions, 79 deletions
diff --git a/src/zip/driver.rs b/src/zip/driver.rs
index e5aa58d..8e8c27c 100644
--- a/src/zip/driver.rs
+++ b/src/zip/driver.rs
@@ -1,7 +1,8 @@
1use crate::driver::{ArchiveRead, ArchiveWrite, Driver}; 1use crate::driver::{ArchiveRead, ArchiveWrite, Driver};
2use crate::zip::error::{ZipError, ZipResult}; 2use crate::zip::file::CompressionMethod;
3use crate::zip::structs::{deserialize, EOCDR64Locator, CDR, EOCDR, EOCDR64}; 3use crate::zip::structs::{deserialize, EOCDR64Locator, ExtraHeader, CDR, EOCDR, EOCDR64};
4use crate::zip::ZipFile; 4use crate::zip::{ZipError, ZipFile, ZipResult};
5use chrono::{Local, NaiveDate, NaiveDateTime, NaiveTime};
5use std::collections::HashMap as Map; 6use std::collections::HashMap as Map;
6use std::fs::File; 7use std::fs::File;
7use std::io::{Read, Seek, SeekFrom, Write}; 8use std::io::{Read, Seek, SeekFrom, Write};
@@ -41,7 +42,7 @@ impl<IO: Read + Seek> ArchiveRead for Zip<IO> {
41 io.read(&mut buf)?; 42 io.read(&mut buf)?;
42 buf 43 buf
43 }; 44 };
44 let eocdr: EOCDR = deserialize(&buf).map_err(|_| ZipError::InvalidEOCDR)?; 45 let eocdr: EOCDR = deserialize(&buf).unwrap();
45 let comment = { 46 let comment = {
46 let mut buf: Vec<u8> = vec![0; eocdr.comment_len as usize]; 47 let mut buf: Vec<u8> = vec![0; eocdr.comment_len as usize];
47 io.read(&mut buf)?; 48 io.read(&mut buf)?;
@@ -55,31 +56,29 @@ impl<IO: Read + Seek> ArchiveRead for Zip<IO> {
55 io.read(&mut buf)?; 56 io.read(&mut buf)?;
56 buf 57 buf
57 }; 58 };
58 let (cd_pointer, cd_size, cd_records) = if u32::from_le_bytes(buf[0..4].try_into().unwrap()) 59 let (cd_pointer, cd_size, cd_records) =
59 == 0x07064b50 60 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) == 0x07064b50 {
60 { 61 let eocdr64locator: EOCDR64Locator = deserialize(&buf[4..]).unwrap();
61 let eocdr64locator: EOCDR64Locator = 62
62 deserialize(&buf[4..]).map_err(|_| ZipError::InvalidEOCDR64Locator)?; 63 io.seek(SeekFrom::Start(eocdr64locator.eocdr64_pointer))?;
63 64 let buf = {
64 io.seek(SeekFrom::Start(eocdr64locator.eocdr64_pointer))?; 65 let mut buf = [0; 56];
65 let buf = { 66 io.read(&mut buf)?;
66 let mut buf = [0; 56]; 67 buf
67 io.read(&mut buf)?; 68 };
68 buf 69 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != 0x06064b50 {
70 return Err(ZipError::InvalidEOCDR64Signature.into());
71 }
72 let eocdr64: EOCDR64 = deserialize(&buf[4..]).unwrap();
73
74 (eocdr64.cd_pointer, eocdr64.cd_size, eocdr64.cd_records)
75 } else {
76 (
77 eocdr.cd_pointer as u64,
78 eocdr.cd_size as u64,
79 eocdr.cd_records as u64,
80 )
69 }; 81 };
70 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != 0x06064b50 {
71 return Err(ZipError::InvalidEOCDR64Signature.into());
72 }
73 let eocdr64: EOCDR64 = deserialize(&buf[4..]).map_err(|_| ZipError::InvalidEOCDR64)?;
74
75 (eocdr64.cd_pointer, eocdr64.cd_size, eocdr64.cd_records)
76 } else {
77 (
78 eocdr.cd_pointer as u64,
79 eocdr.cd_size as u64,
80 eocdr.cd_records as u64,
81 )
82 };
83 82
84 // Read cd records 83 // Read cd records
85 let mut files = Map::with_capacity(cd_records as usize); 84 let mut files = Map::with_capacity(cd_records as usize);
@@ -96,7 +95,7 @@ impl<IO: Read + Seek> ArchiveRead for Zip<IO> {
96 return Err(ZipError::InvalidCDRSignature.into()); 95 return Err(ZipError::InvalidCDRSignature.into());
97 } 96 }
98 p += 4; 97 p += 4;
99 let cdr: CDR = deserialize(&buf[p..p + 42]).map_err(|_| ZipError::InvalidCDR)?; 98 let cdr: CDR = deserialize(&buf[p..p + 42]).unwrap();
100 p += 42; 99 p += 42;
101 let name = String::from_utf8(buf[p..p + cdr.name_len as usize].into()).unwrap(); 100 let name = String::from_utf8(buf[p..p + cdr.name_len as usize].into()).unwrap();
102 p += cdr.name_len as usize; 101 p += cdr.name_len as usize;
@@ -105,15 +104,61 @@ impl<IO: Read + Seek> ArchiveRead for Zip<IO> {
105 let comment = String::from_utf8(buf[p..p + cdr.comment_len as usize].into()).unwrap(); 104 let comment = String::from_utf8(buf[p..p + cdr.comment_len as usize].into()).unwrap();
106 p += cdr.comment_len as usize; 105 p += cdr.comment_len as usize;
107 106
107 let mut compressed_size = cdr.compressed_size as u64;
108 let mut size = cdr.size as u64;
109 let mut header_pointer = cdr.header_pointer as u64;
110
111 let mut ep: usize = 0;
112 while ep < cdr.extra_field_len as usize {
113 let header: ExtraHeader = deserialize(&extra_fields[ep..ep + 4]).unwrap();
114 ep += 4;
115 match header.id {
116 0x0001 => {
117 if size == 0xFFFFFFFF {
118 compressed_size = deserialize(&extra_fields[ep..ep + 8]).unwrap();
119 ep += 8;
120 }
121 if compressed_size == 0xFFFFFFFF {
122 size = deserialize(&extra_fields[ep..ep + 8]).unwrap();
123 ep += 8;
124 }
125 if header_pointer == 0xFFFFFFFF {
126 header_pointer = deserialize(&extra_fields[ep..ep + 8]).unwrap();
127 ep += 8;
128 }
129 if cdr.disk == 0xFFFF {
130 ep += 4
131 }
132 }
133 _ => ep += header.size as usize,
134 }
135 }
136
108 files.insert( 137 files.insert(
109 name.clone(), 138 name.clone(),
110 ZipFile::new( 139 ZipFile::new(
140 CompressionMethod::from_struct_id(cdr.compression_method)?,
141 NaiveDateTime::new(
142 NaiveDate::from_ymd_opt(
143 (cdr.dos_date as i32 >> 9 & 0x7F) + 1980,
144 cdr.dos_date as u32 >> 5 & 0xF,
145 cdr.dos_date as u32 & 0x1F,
146 )
147 .ok_or(ZipError::InvalidDate)?,
148 NaiveTime::from_hms_opt(
149 (cdr.dos_time as u32 >> 11) & 0x1F,
150 (cdr.dos_time as u32 >> 5) & 0x3F,
151 (cdr.dos_time as u32 & 0x1F) * 2,
152 )
153 .ok_or(ZipError::InvalidTime)?,
154 )
155 .and_local_timezone(Local)
156 .unwrap(),
157 cdr.crc,
158 compressed_size,
159 size,
160 header_pointer,
111 name, 161 name,
112 cdr.dos_date,
113 cdr.dos_time,
114 cdr.compression_method,
115 cdr.compressed_size as u64,
116 cdr.size as u64,
117 comment, 162 comment,
118 ), 163 ),
119 ); 164 );
diff --git a/src/zip/error.rs b/src/zip/error.rs
index 18bbb22..1c5527c 100644
--- a/src/zip/error.rs
+++ b/src/zip/error.rs
@@ -7,15 +7,14 @@ pub type ZipResult<T> = ArchiveResult<T, ZipError>;
7#[derive(Debug)] 7#[derive(Debug)]
8pub enum ZipError { 8pub enum ZipError {
9 EOCDRNotFound, 9 EOCDRNotFound,
10 InvalidEOCDR,
11 InvalidArchiveComment,
12
13 InvalidEOCDR64Locator,
14 InvalidEOCDR64Signature, 10 InvalidEOCDR64Signature,
15 InvalidEOCDR64,
16
17 InvalidCDRSignature, 11 InvalidCDRSignature,
18 InvalidCDR, 12
13 InvalidArchiveComment,
14 InvalidCompressionMethod,
15 UnsupportedCompressionMethod,
16 InvalidDate,
17 InvalidTime,
19 InvalidFileName, 18 InvalidFileName,
20 InvalidFileComment, 19 InvalidFileComment,
21} 20}
@@ -32,25 +31,24 @@ impl From<ZipError> for ArchiveError<ZipError> {
32impl Display for ZipError { 31impl Display for ZipError {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self { 33 match self {
35 ZipError::EOCDRNotFound => write!(f, "End of central directory record not found"), 34 Self::EOCDRNotFound => write!(f, "End of central directory record not found"),
36 ZipError::InvalidEOCDR => write!(f, "Invalid end of central directory record"), 35 Self::InvalidEOCDR64Signature => {
37 ZipError::InvalidArchiveComment => write!(f, "Invalid archive comment"),
38 ZipError::InvalidEOCDR64Locator => {
39 write!(f, "Invalid zip64 end of central directory locator")
40 }
41 ZipError::InvalidEOCDR64Signature => {
42 write!( 36 write!(
43 f, 37 f,
44 "Invalid signature of zip64 end of central directory record" 38 "Invalid signature of zip64 end of central directory record"
45 ) 39 )
46 } 40 }
47 ZipError::InvalidEOCDR64 => write!(f, "Invalid zip64 end of central directory record"), 41 Self::InvalidCDRSignature => {
48 ZipError::InvalidCDRSignature => {
49 write!(f, "Invalid signature of central directory record") 42 write!(f, "Invalid signature of central directory record")
50 } 43 }
51 ZipError::InvalidCDR => write!(f, "Invalid central directory record"), 44
52 ZipError::InvalidFileName => write!(f, "Invalid file name"), 45 Self::InvalidArchiveComment => write!(f, "Invalid archive comment"),
53 ZipError::InvalidFileComment => write!(f, "Invalid file comment"), 46 Self::InvalidCompressionMethod => writeln!(f, "Invalid compression method"),
47 Self::UnsupportedCompressionMethod => writeln!(f, "Unsupported compression method"),
48 Self::InvalidDate => write!(f, "Invalid date"),
49 Self::InvalidTime => write!(f, "Invalid time"),
50 Self::InvalidFileName => write!(f, "Invalid file name"),
51 Self::InvalidFileComment => write!(f, "Invalid file comment"),
54 } 52 }
55 } 53 }
56} 54}
diff --git a/src/zip/file.rs b/src/zip/file.rs
index d5b3327..f735d65 100644
--- a/src/zip/file.rs
+++ b/src/zip/file.rs
@@ -1,45 +1,66 @@
1use crate::driver::ArchiveFile; 1use crate::driver::ArchiveFile;
2use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; 2use crate::zip::{ZipError, ZipResult};
3use chrono::{DateTime, Local};
4
5pub enum CompressionMethod {
6 Store,
7 Deflate,
8 BZIP2,
9 LZMA,
10 ZStd,
11 XZ,
12}
13
14impl CompressionMethod {
15 pub(crate) fn from_struct_id(id: u16) -> ZipResult<Self> {
16 match id {
17 0 => Ok(Self::Store),
18 8 => Ok(Self::Deflate),
19 12 => Ok(Self::BZIP2),
20 14 => Ok(Self::LZMA),
21 93 => Ok(Self::ZStd),
22 95 => Ok(Self::XZ),
23 1..=7 | 9..=11 | 13 | 15..=20 | 94 | 96..=99 => {
24 Err(ZipError::UnsupportedCompressionMethod.into())
25 }
26 21..=92 | 100.. => Err(ZipError::InvalidCompressionMethod.into()),
27 }
28 }
29}
3 30
4pub struct ZipFile { 31pub struct ZipFile {
5 pub name: String, 32 pub compression_method: CompressionMethod,
6 pub datetime: NaiveDateTime, 33 pub datetime: DateTime<Local>,
7 pub compression_method: u16, 34 pub crc: u32,
8 pub compressed_size: u64, 35 pub compressed_size: u64,
9 pub size: u64, 36 pub size: u64,
37 pub header_pointer: u64,
38 pub name: String,
10 pub comment: String, 39 pub comment: String,
11} 40}
12 41
13impl ArchiveFile for ZipFile {}
14
15impl ZipFile { 42impl ZipFile {
16 pub(crate) fn new( 43 pub fn new(
17 name: String, 44 compression_method: CompressionMethod,
18 dos_date: u16, 45 datetime: DateTime<Local>,
19 dos_time: u16, 46 crc: u32,
20 compression_method: u16,
21 compressed_size: u64, 47 compressed_size: u64,
22 size: u64, 48 size: u64,
49 header_pointer: u64,
50 name: String,
23 comment: String, 51 comment: String,
24 ) -> Self { 52 ) -> Self {
25 let year = (dos_date >> 9 & 0x7F) + 1980;
26 let month = dos_date >> 5 & 0xF;
27 let day = dos_date & 0x1F;
28
29 let hour = (dos_time >> 11) & 0x1F;
30 let minute = (dos_time >> 5) & 0x3F;
31 let seconds = (dos_time & 0x1F) * 2;
32
33 Self { 53 Self {
34 name,
35 datetime: NaiveDateTime::new(
36 NaiveDate::from_ymd_opt(year as i32, month as u32, day as u32).unwrap(),
37 NaiveTime::from_hms_opt(hour as u32, minute as u32, seconds as u32).unwrap(),
38 ),
39 compression_method, 54 compression_method,
55 datetime,
56 crc,
40 compressed_size, 57 compressed_size,
41 size, 58 size,
59 header_pointer,
60 name,
42 comment, 61 comment,
43 } 62 }
44 } 63 }
45} 64}
65
66impl ArchiveFile for ZipFile {}
diff --git a/src/zip/mod.rs b/src/zip/mod.rs
index 5aeca97..8601526 100644
--- a/src/zip/mod.rs
+++ b/src/zip/mod.rs
@@ -4,5 +4,5 @@ mod file;
4mod structs; 4mod structs;
5 5
6pub use driver::Zip; 6pub use driver::Zip;
7pub use error::ZipError; 7pub use error::{ZipError, ZipResult};
8pub use file::ZipFile; 8pub use file::ZipFile;
diff --git a/src/zip/structs.rs b/src/zip/structs.rs
index 0f9579e..3a93c1b 100644
--- a/src/zip/structs.rs
+++ b/src/zip/structs.rs
@@ -40,7 +40,7 @@ pub struct CDR {
40 pub compression_method: u16, 40 pub compression_method: u16,
41 pub dos_time: u16, 41 pub dos_time: u16,
42 pub dos_date: u16, 42 pub dos_date: u16,
43 pub crc32: u32, 43 pub crc: u32,
44 pub compressed_size: u32, 44 pub compressed_size: u32,
45 pub size: u32, 45 pub size: u32,
46 pub name_len: u16, 46 pub name_len: u16,
@@ -52,6 +52,12 @@ pub struct CDR {
52 pub header_pointer: u32, 52 pub header_pointer: u32,
53} 53}
54 54
55#[derive(Serialize, Deserialize)]
56pub struct ExtraHeader {
57 pub id: u16,
58 pub size: u16,
59}
60
55pub fn serialize<T: Serialize>(object: &mut T) -> StructResult<Vec<u8>> { 61pub fn serialize<T: Serialize>(object: &mut T) -> StructResult<Vec<u8>> {
56 Settings::default().serialize(object) 62 Settings::default().serialize(object)
57} 63}