aboutsummaryrefslogtreecommitdiff
path: root/src/zip
diff options
context:
space:
mode:
Diffstat (limited to 'src/zip')
-rw-r--r--src/zip/driver.rs88
-rw-r--r--src/zip/error.rs2
-rw-r--r--src/zip/file/info.rs (renamed from src/zip/file_info.rs)8
-rw-r--r--src/zip/file/mod.rs7
-rw-r--r--src/zip/file/read.rs125
-rw-r--r--src/zip/file/write.rs30
-rw-r--r--src/zip/file_driver.rs98
-rw-r--r--src/zip/mod.rs6
-rw-r--r--src/zip/tests.rs2
9 files changed, 220 insertions, 146 deletions
diff --git a/src/zip/driver.rs b/src/zip/driver.rs
index a280dc7..e276844 100644
--- a/src/zip/driver.rs
+++ b/src/zip/driver.rs
@@ -1,13 +1,17 @@
1use crate::driver::{ArchiveRead, ArchiveWrite, Driver}; 1use crate::driver::{ArchiveRead, ArchiveWrite, Driver};
2use crate::zip::structs::{deserialize, EOCDR64Locator, ExtraHeader, CDR, EOCDR, EOCDR64}; 2use crate::zip::structs::{deserialize, EOCDR64Locator, ExtraHeader, CDR, EOCDR, EOCDR64};
3use crate::zip::{BitFlag, CompressionMethod, ZipError, ZipFile, ZipFileInfo, ZipResult}; 3use crate::zip::{
4 BitFlag, CompressionMethod, ZipError, ZipFileInfo, ZipFileReader, ZipFileWriter, ZipResult,
5};
4use chrono::{Local, NaiveDate, NaiveDateTime, NaiveTime}; 6use chrono::{Local, NaiveDate, NaiveDateTime, NaiveTime};
7use std::collections::HashMap as Map;
8use std::fs::File;
5use std::io::{Read, Seek, SeekFrom, Write}; 9use std::io::{Read, Seek, SeekFrom, Write};
6 10
7pub struct Zip<Io> { 11pub struct Zip<Io = File> {
8 io: Io, 12 io: Io,
9 13
10 files: Vec<ZipFileInfo>, 14 files: Map<String, ZipFileInfo>,
11 comment: String, 15 comment: String,
12} 16}
13 17
@@ -15,11 +19,12 @@ impl<Io> Driver for Zip<Io> {
15 type Error = ZipError; 19 type Error = ZipError;
16 20
17 type Io = Io; 21 type Io = Io;
18 type FileDriver<'d> = ZipFile<'d, Self::Io> where Self::Io: 'd;
19 type FileInfo = ZipFileInfo; 22 type FileInfo = ZipFileInfo;
20} 23}
21 24
22impl<Io: Read + Seek> ArchiveRead for Zip<Io> { 25impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
26 type FileReader<'d> = ZipFileReader<'d, Io> where Io: 'd;
27
23 fn read(mut io: Self::Io) -> ZipResult<Self> { 28 fn read(mut io: Self::Io) -> ZipResult<Self> {
24 // Search eocdr 29 // Search eocdr
25 let limit = 65557.min(io.seek(SeekFrom::End(0))?) as i64; 30 let limit = 65557.min(io.seek(SeekFrom::End(0))?) as i64;
@@ -79,7 +84,7 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
79 }; 84 };
80 85
81 // Read cd records 86 // Read cd records
82 let mut files = Vec::with_capacity(cd_records as usize); 87 let mut files = Map::with_capacity(cd_records as usize);
83 io.seek(SeekFrom::Start(cd_pointer))?; 88 io.seek(SeekFrom::Start(cd_pointer))?;
84 let buf = { 89 let buf = {
85 let mut buf = vec![0; cd_size as usize]; 90 let mut buf = vec![0; cd_size as usize];
@@ -134,49 +139,54 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
134 } 139 }
135 } 140 }
136 141
137 files.push(ZipFileInfo::new( 142 files.insert(
138 CompressionMethod::from_struct_id(cdr.compression_method)?, 143 name.clone(),
139 BitFlag::new(cdr.bit_flag), 144 ZipFileInfo::new(
140 NaiveDateTime::new( 145 CompressionMethod::from_struct_id(cdr.compression_method)?,
141 NaiveDate::from_ymd_opt( 146 BitFlag::new(cdr.bit_flag),
142 (cdr.dos_date as i32 >> 9 & 0x7F) + 1980, 147 NaiveDateTime::new(
143 cdr.dos_date as u32 >> 5 & 0xF, 148 NaiveDate::from_ymd_opt(
144 cdr.dos_date as u32 & 0x1F, 149 (cdr.dos_date as i32 >> 9 & 0x7F) + 1980,
145 ) 150 cdr.dos_date as u32 >> 5 & 0xF,
146 .ok_or(ZipError::InvalidDate)?, 151 cdr.dos_date as u32 & 0x1F,
147 NaiveTime::from_hms_opt( 152 )
148 (cdr.dos_time as u32 >> 11) & 0x1F, 153 .ok_or(ZipError::InvalidDate)?,
149 (cdr.dos_time as u32 >> 5) & 0x3F, 154 NaiveTime::from_hms_opt(
150 (cdr.dos_time as u32 & 0x1F) * 2, 155 (cdr.dos_time as u32 >> 11) & 0x1F,
156 (cdr.dos_time as u32 >> 5) & 0x3F,
157 (cdr.dos_time as u32 & 0x1F) * 2,
158 )
159 .ok_or(ZipError::InvalidTime)?,
151 ) 160 )
152 .ok_or(ZipError::InvalidTime)?, 161 .and_local_timezone(Local)
153 ) 162 .unwrap(),
154 .and_local_timezone(Local) 163 cdr.crc,
155 .unwrap(), 164 compressed_size,
156 cdr.crc, 165 size,
157 compressed_size, 166 header_pointer,
158 size, 167 name,
159 header_pointer, 168 comment,
160 name, 169 ),
161 comment, 170 );
162 ));
163 } 171 }
164 172
165 Ok(Self { io, files, comment }) 173 Ok(Self { io, files, comment })
166 } 174 }
167 175
168 fn files(&self) -> &Vec<Self::FileInfo> { 176 fn files(&self) -> Vec<&Self::FileInfo> {
169 &self.files 177 let mut files: Vec<&Self::FileInfo> = self.files.values().collect();
178 files.sort_by_key(|f| &f.name);
179 files
170 } 180 }
171 181
172 fn get_file_info(&self, index: usize) -> ZipResult<&Self::FileInfo> { 182 fn get_file_info(&self, name: &str) -> ZipResult<&Self::FileInfo> {
173 self.files.get(index).ok_or(ZipError::FileNotFound.into()) 183 self.files.get(name).ok_or(ZipError::FileNotFound.into())
174 } 184 }
175 185
176 fn get_file_reader<'d>(&'d mut self, index: usize) -> ZipResult<Self::FileDriver<'d>> { 186 fn get_file_reader<'d>(&'d mut self, name: &str) -> ZipResult<Self::FileReader<'d>> {
177 Ok(ZipFile::new( 187 Ok(ZipFileReader::new(
178 &mut self.io, 188 &mut self.io,
179 self.files.get(index).ok_or(ZipError::FileNotFound)?, 189 self.files.get(name).ok_or(ZipError::FileNotFound)?,
180 )?) 190 )?)
181 } 191 }
182} 192}
@@ -187,6 +197,8 @@ impl<Io: Read + Seek> Zip<Io> {
187 } 197 }
188} 198}
189 199
190impl<Io: Read + Write + Seek> ArchiveWrite for Zip<Io> {} 200impl<Io: Read + Write + Seek> ArchiveWrite for Zip<Io> {
201 type FileWriter<'d> = ZipFileWriter<'d, Io> where Io: 'd;
202}
191 203
192impl<Io: Read + Write + Seek> Zip<Io> {} 204impl<Io: Read + Write + Seek> Zip<Io> {}
diff --git a/src/zip/error.rs b/src/zip/error.rs
index 3acbf8c..5659f93 100644
--- a/src/zip/error.rs
+++ b/src/zip/error.rs
@@ -21,6 +21,7 @@ pub enum ZipError {
21 21
22 NegativeFileOffset, 22 NegativeFileOffset,
23 FileNotFound, 23 FileNotFound,
24 CompressionDataIsUnseekable,
24} 25}
25 26
26impl From<ZipError> for ArchiveError<ZipError> { 27impl From<ZipError> for ArchiveError<ZipError> {
@@ -59,6 +60,7 @@ impl Display for ZipError {
59 60
60 Self::NegativeFileOffset => write!(f, "Negative file offset"), 61 Self::NegativeFileOffset => write!(f, "Negative file offset"),
61 Self::FileNotFound => write!(f, "File not found"), 62 Self::FileNotFound => write!(f, "File not found"),
63 Self::CompressionDataIsUnseekable => write!(f, "Compression data is unseekable"),
62 } 64 }
63 } 65 }
64} 66}
diff --git a/src/zip/file_info.rs b/src/zip/file/info.rs
index 92d52f7..2891b59 100644
--- a/src/zip/file_info.rs
+++ b/src/zip/file/info.rs
@@ -6,9 +6,8 @@ use chrono::{DateTime, Local};
6pub enum CompressionMethod { 6pub enum CompressionMethod {
7 Store, 7 Store,
8 Deflate, 8 Deflate,
9 BZIP2, 9 BZip2,
10 LZMA, 10 LZMA,
11 ZStd,
12 XZ, 11 XZ,
13} 12}
14 13
@@ -17,11 +16,10 @@ impl CompressionMethod {
17 match id { 16 match id {
18 0 => Ok(Self::Store), 17 0 => Ok(Self::Store),
19 8 => Ok(Self::Deflate), 18 8 => Ok(Self::Deflate),
20 12 => Ok(Self::BZIP2), 19 12 => Ok(Self::BZip2),
21 14 => Ok(Self::LZMA), 20 14 => Ok(Self::LZMA),
22 93 => Ok(Self::ZStd),
23 95 => Ok(Self::XZ), 21 95 => Ok(Self::XZ),
24 1..=7 | 9..=11 | 13 | 15..=20 | 94 | 96..=99 => { 22 1..=7 | 9..=11 | 13 | 15..=20 | 93..=94 | 96..=99 => {
25 Err(ZipError::UnsupportedCompressionMethod.into()) 23 Err(ZipError::UnsupportedCompressionMethod.into())
26 } 24 }
27 21..=92 | 100.. => Err(ZipError::InvalidCompressionMethod.into()), 25 21..=92 | 100.. => Err(ZipError::InvalidCompressionMethod.into()),
diff --git a/src/zip/file/mod.rs b/src/zip/file/mod.rs
new file mode 100644
index 0000000..43ccc04
--- /dev/null
+++ b/src/zip/file/mod.rs
@@ -0,0 +1,7 @@
1mod info;
2mod read;
3mod write;
4
5pub use info::{bit, BitFlag, CompressionMethod, ZipFileInfo};
6pub use read::ZipFileReader;
7pub use write::ZipFileWriter;
diff --git a/src/zip/file/read.rs b/src/zip/file/read.rs
new file mode 100644
index 0000000..56f42da
--- /dev/null
+++ b/src/zip/file/read.rs
@@ -0,0 +1,125 @@
1use crate::driver::FileDriver;
2use crate::zip::{CompressionMethod, ZipError, ZipFileInfo, ZipResult};
3use bzip2::read::BzDecoder;
4use flate2::read::DeflateDecoder;
5use std::io::{
6 Error as IoError, ErrorKind as IoErrorKind, Read, Result as IoResult, Seek, SeekFrom,
7};
8use xz2::read::XzDecoder;
9
10enum IoProxy<Io: Read> {
11 Store(Io),
12 Deflate(DeflateDecoder<Io>),
13 BZip2(BzDecoder<Io>),
14 XZ(XzDecoder<Io>),
15}
16
17pub struct ZipFileReader<'d, Io: Read> {
18 io: IoProxy<&'d mut Io>,
19 info: &'d ZipFileInfo,
20
21 bounds: (u64, u64),
22 cursor: u64,
23}
24
25impl<'d, Io: Read> FileDriver for ZipFileReader<'d, Io> {
26 type Io = Io;
27 type FileInfo = ZipFileInfo;
28
29 fn info(&self) -> &Self::FileInfo {
30 self.info
31 }
32}
33
34impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> {
35 pub fn new(io: &'d mut Io, info: &'d ZipFileInfo) -> ZipResult<Self> {
36 io.seek(SeekFrom::Start(info.header_pointer))?;
37 let buf = {
38 let mut buf = [0; 30];
39 io.read(&mut buf)?;
40 buf
41 };
42 if u32::from_le_bytes(buf[..4].try_into().unwrap()) != 0x04034b50 {
43 return Err(ZipError::InvalidFileHeaderSignature.into());
44 }
45 let data_pointer = info.header_pointer
46 + 30
47 + u16::from_le_bytes(buf[26..28].try_into().unwrap()) as u64
48 + u16::from_le_bytes(buf[28..30].try_into().unwrap()) as u64;
49 io.seek(SeekFrom::Start(data_pointer))?;
50
51 Ok(Self {
52 io: match info.compression_method {
53 CompressionMethod::Store => IoProxy::Store(io),
54 CompressionMethod::Deflate => IoProxy::Deflate(DeflateDecoder::new(io)),
55 CompressionMethod::BZip2 => IoProxy::BZip2(BzDecoder::new(io)),
56 CompressionMethod::LZMA => IoProxy::XZ(XzDecoder::new(io)),
57 CompressionMethod::XZ => IoProxy::XZ(XzDecoder::new(io)),
58 },
59 info,
60
61 bounds: (data_pointer, data_pointer + info.compressed_size),
62 cursor: data_pointer,
63 })
64 }
65
66 pub fn seekable(&self) -> bool {
67 match self.io {
68 IoProxy::Store(..) => true,
69 _ => false,
70 }
71 }
72}
73
74impl<'d, Io: Read> Read for ZipFileReader<'d, Io> {
75 fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
76 let upper = buf.len().min((self.bounds.1 - self.cursor) as usize);
77 let bytes = match &mut self.io {
78 IoProxy::Store(io) => io.read(&mut buf[..upper]),
79 IoProxy::Deflate(io) => io.read(&mut buf[..upper]),
80 IoProxy::BZip2(io) => io.read(&mut buf[..upper]),
81 IoProxy::XZ(io) => io.read(&mut buf[..upper]),
82 }?;
83 self.cursor += upper as u64;
84 Ok(bytes)
85 }
86}
87
88impl<'d, Io: Read + Seek> Seek for ZipFileReader<'d, Io> {
89 fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> {
90 match &mut self.io {
91 IoProxy::Store(io) => {
92 self.cursor = match pos {
93 SeekFrom::Start(offset) => self.bounds.0 + offset,
94 SeekFrom::End(offset) => {
95 let cursor = self.bounds.1.saturating_add_signed(offset);
96 if cursor < self.bounds.0 {
97 return Err(IoError::new(
98 IoErrorKind::InvalidInput,
99 ZipError::NegativeFileOffset,
100 ));
101 }
102 cursor
103 }
104 SeekFrom::Current(offset) => {
105 let cursor = self.cursor.saturating_add_signed(offset);
106 if cursor < self.bounds.0 {
107 return Err(IoError::new(
108 IoErrorKind::InvalidInput,
109 ZipError::NegativeFileOffset,
110 ));
111 }
112 cursor
113 }
114 }
115 .min(self.bounds.1);
116
117 Ok(io.seek(SeekFrom::Start(self.cursor))? - self.bounds.0)
118 }
119 _ => Err(IoError::new(
120 IoErrorKind::Unsupported,
121 ZipError::CompressionDataIsUnseekable,
122 )),
123 }
124 }
125}
diff --git a/src/zip/file/write.rs b/src/zip/file/write.rs
new file mode 100644
index 0000000..627db6d
--- /dev/null
+++ b/src/zip/file/write.rs
@@ -0,0 +1,30 @@
1use crate::driver::FileDriver;
2use crate::zip::ZipFileInfo;
3use bzip2::write::BzEncoder;
4use flate2::write::DeflateEncoder;
5use std::io::Write;
6use xz2::write::XzEncoder;
7
8enum IoProxy<Io: Write> {
9 Store(Io),
10 Deflate(DeflateEncoder<Io>),
11 BZip2(BzEncoder<Io>),
12 XZ(XzEncoder<Io>),
13}
14
15pub struct ZipFileWriter<'d, Io: Write> {
16 io: IoProxy<&'d mut Io>,
17 info: &'d ZipFileInfo,
18
19 bounds: (u64, u64),
20 cursor: u64,
21}
22
23impl<'d, Io: Write> FileDriver for ZipFileWriter<'d, Io> {
24 type Io = Io;
25 type FileInfo = ZipFileInfo;
26
27 fn info(&self) -> &Self::FileInfo {
28 self.info
29 }
30}
diff --git a/src/zip/file_driver.rs b/src/zip/file_driver.rs
deleted file mode 100644
index 51075e4..0000000
--- a/src/zip/file_driver.rs
+++ /dev/null
@@ -1,98 +0,0 @@
1use crate::driver::FileDriver;
2use crate::zip::{ZipError, ZipFileInfo, ZipResult};
3use std::io::{
4 Error as IoError, ErrorKind as IoErrorKind, Read, Result as IoResult, Seek, SeekFrom, Write,
5};
6
7pub struct ZipFile<'d, Io> {
8 io: &'d mut Io,
9 info: &'d ZipFileInfo,
10
11 bounds: (u64, u64),
12 cursor: u64,
13}
14
15impl<'d, Io> FileDriver for ZipFile<'d, Io> {
16 type Io = Io;
17 type FileInfo = ZipFileInfo;
18
19 fn info(&self) -> &Self::FileInfo {
20 self.info
21 }
22}
23
24impl<'d, Io: Read + Seek> ZipFile<'d, Io> {
25 pub fn new(io: &'d mut Io, info: &'d ZipFileInfo) -> ZipResult<Self> {
26 io.seek(SeekFrom::Start(info.header_pointer))?;
27 let buf = {
28 let mut buf = [0; 30];
29 io.read(&mut buf)?;
30 buf
31 };
32 if u32::from_le_bytes(buf[..4].try_into().unwrap()) != 0x04034b50 {
33 return Err(ZipError::InvalidFileHeaderSignature.into());
34 }
35 let data_pointer = info.header_pointer
36 + 30
37 + u16::from_le_bytes(buf[26..28].try_into().unwrap()) as u64
38 + u16::from_le_bytes(buf[28..30].try_into().unwrap()) as u64;
39 io.seek(SeekFrom::Start(data_pointer))?;
40
41 Ok(Self {
42 io,
43 info,
44
45 bounds: (data_pointer, data_pointer + info.compressed_size),
46 cursor: data_pointer,
47 })
48 }
49}
50
51impl<'d, Io: Read> Read for ZipFile<'d, Io> {
52 fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
53 let upper = buf.len().min((self.bounds.1 - self.cursor) as usize);
54 self.cursor += upper as u64;
55 self.io.read(&mut buf[..upper])
56 }
57}
58
59impl<'d, Io: Write> Write for ZipFile<'d, Io> {
60 fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
61 todo!()
62 }
63
64 fn flush(&mut self) -> IoResult<()> {
65 todo!()
66 }
67}
68
69impl<'d, Io: Seek> Seek for ZipFile<'d, Io> {
70 fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> {
71 self.cursor = match pos {
72 SeekFrom::Start(offset) => self.bounds.0 + offset,
73 SeekFrom::End(offset) => {
74 let cursor = self.bounds.1.saturating_add_signed(offset);
75 if cursor < self.bounds.0 {
76 return Err(IoError::new(
77 IoErrorKind::InvalidInput,
78 ZipError::NegativeFileOffset,
79 ));
80 }
81 cursor
82 }
83 SeekFrom::Current(offset) => {
84 let cursor = self.cursor.saturating_add_signed(offset);
85 if cursor < self.bounds.0 {
86 return Err(IoError::new(
87 IoErrorKind::InvalidInput,
88 ZipError::NegativeFileOffset,
89 ));
90 }
91 cursor
92 }
93 }
94 .min(self.bounds.1);
95
96 Ok(self.io.seek(SeekFrom::Start(self.cursor))? - self.bounds.0)
97 }
98}
diff --git a/src/zip/mod.rs b/src/zip/mod.rs
index 3fe8384..0f19824 100644
--- a/src/zip/mod.rs
+++ b/src/zip/mod.rs
@@ -1,14 +1,12 @@
1mod archive; 1mod archive;
2mod driver; 2mod driver;
3mod error; 3mod error;
4mod file_driver; 4mod file;
5mod file_info;
6mod structs; 5mod structs;
7 6
8pub use driver::Zip; 7pub use driver::Zip;
9pub use error::{ZipError, ZipResult}; 8pub use error::{ZipError, ZipResult};
10pub use file_driver::ZipFile; 9pub use file::{bit, BitFlag, CompressionMethod, ZipFileInfo, ZipFileReader, ZipFileWriter};
11pub use file_info::{bit, BitFlag, CompressionMethod, ZipFileInfo};
12 10
13#[cfg(test)] 11#[cfg(test)]
14mod tests; 12mod tests;
diff --git a/src/zip/tests.rs b/src/zip/tests.rs
index 05e076d..92a9c3f 100644
--- a/src/zip/tests.rs
+++ b/src/zip/tests.rs
@@ -1,4 +1,4 @@
1use crate::zip::file_info::{bit::DeflateMode, BitFlag}; 1use crate::zip::{bit::DeflateMode, BitFlag};
2 2
3#[test] 3#[test]
4fn test_bit_flag() { 4fn test_bit_flag() {